Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blog #611

Merged
merged 4 commits into from
Feb 23, 2024
Merged

Blog #611

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions blog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,41 @@ It's intended to show and test all Yii features.
You'll need at least PHP 8.1.

1. Clone this repository.
2. Run `composer install` in your project root directory.
2. Run command in your project root directory.
```bash
composer install
```
3. Run `./yii serve` (on Windows `yii serve`). The application will be started on http://localhost:8080/.
```bash
./yii serve
```
4. Go to the index page. Cycle ORM will create tables, indexes and relations automatically in the configured DB for you.
If you want to disable this behavior then comment out the line with the `Generator\SyncTables::class` in the `config/packges/yiisoft/yii-cycle/params.php`.
In this case you should create migrations to sync changes that you have made to entities with the DB.
5. Run `./yii fixture/add 20` to create some random data.

5. Run command to create some random data.
```bash
./yii fixture/add 20
```
## Console

Console works out of the box and could be executed with `./yii`.

Some commands:

```bash
user/create <login> <password>
fixture/add [count]
./yii user/create login password
./yii fixture/add 10
```

In order to register your own commands, add them to `console/params.php`, `console` → `commands` section.

## Web application

In order to run the web application, you can either use the built-in web server by running `./yii serve` or you could use a
real web server by pointing it to `/public/index.php`.
In order to run the web application, you can either use the built-in web server by running
```bash
./yii serve
```
or you could use a real web server by pointing it to `/public/index.php`.

More routes could be added by editing `src/Factory/AppRouterFactory`.

Expand Down
2 changes: 1 addition & 1 deletion blog/resources/views/blog/comments/_comments.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<div class="row load-more-comment-container">
<div class="col-sm-12">
<a class="load-more-comment btn btn-primary btn-lg btn-block"
href="<?= $urlGenerator->generate('blog/comment/index', ['next' => $data->getNextPageToken()]) ?>">
href="<?= $urlGenerator->generate('blog/comment/index', ['next' => $data->getToken()->value]) ?>">
<?= $translator->translate('layout.show-more') ?>
</a>
</div>
Expand Down
14 changes: 5 additions & 9 deletions blog/src/Auth/Controller/AuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
final class AuthController
{
public function __construct(
private AuthService $authService,
private WebControllerService $webService,
private ViewRenderer $viewRenderer,
private readonly AuthService $authService,
private readonly WebControllerService $webService,
private ViewRenderer $viewRenderer,
) {
$this->viewRenderer = $viewRenderer->withControllerName('auth');
}
Expand All @@ -36,14 +36,9 @@ public function login(
return $this->redirectToMain();
}

$body = $request->getParsedBody();
$loginForm = new LoginForm($this->authService, $translator);

if (
$request->getMethod() === Method::POST
&& $formHydrator->populate($loginForm, $body)
&& $loginForm->isValid()
) {
if ($formHydrator->populateFromPostAndValidate($loginForm, $request)) {
$identity = $this->authService->getIdentity();

if ($identity instanceof CookieLoginIdentityInterface && $loginForm->getPropertyValue('rememberMe')) {
Expand All @@ -56,6 +51,7 @@ public function login(
return $this->viewRenderer->render('login', ['formModel' => $loginForm]);
}


public function logout(): ResponseInterface
{
$this->authService->logout();
Expand Down
6 changes: 2 additions & 4 deletions blog/src/Auth/Controller/SignupController.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,8 @@ public function signup(
return $this->redirectToMain();
}

if ($request->getMethod() === Method::POST
&& $formHydrator->populate($signupForm, $request->getParsedBody())
&& $signupForm->signup()
) {
if ($formHydrator->populateFromPostAndValidate($signupForm, $request)) {
$signupForm->signup();
return $this->redirectToMain();
}

Expand Down
16 changes: 6 additions & 10 deletions blog/src/Auth/Form/SignupForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ final class SignupForm extends FormModel implements RulesProviderInterface
private string $passwordVerify = '';

public function __construct(
private TranslatorInterface $translator,
private UserRepository $userRepository,
private readonly TranslatorInterface $translator,
private readonly UserRepository $userRepository,
) {
}

Expand Down Expand Up @@ -50,16 +50,12 @@ public function getPassword(): string
return $this->password;
}

public function signup(): false|User
public function signup(): User
{
if ($this->isValid()) {
$user = new User($this->getLogin(), $this->getPassword());
$this->userRepository->save($user);
$user = new User($this->getLogin(), $this->getPassword());
$this->userRepository->save($user);
return $user;

return $user;
}

return false;
}

public function getRules(): array
Expand Down
2 changes: 1 addition & 1 deletion blog/src/Blog/BlogController.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function index(
$dataReader = $postRepository->findAllPreloaded();
$paginator = (new OffsetPaginator($dataReader))
->withPageSize(self::POSTS_PER_PAGE)
->withCurrentPage($pageNum);
->withCurrentPage((int)$pageNum);

$data = [
'paginator' => $paginator,
Expand Down
15 changes: 14 additions & 1 deletion blog/src/Blog/Comment/CommentRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@
use Yiisoft\Data\Reader\DataReaderInterface;
use Yiisoft\Data\Reader\Sort;
use Yiisoft\Yii\Cycle\Data\Reader\EntityReader;
use Yiisoft\Yii\Cycle\Data\Writer\EntityWriter;

final class CommentRepository extends Select\Repository
{
public function __construct( Select $select)
{
parent::__construct($select);
}
/**
* @psalm-return DataReaderInterface<int, Comment>
*/
Expand All @@ -21,8 +26,16 @@ public function getReader(): DataReaderInterface
->withSort($this->getSort());
}

private function getSort(): Sort
public function getSort(): Sort
{
return Sort::only(['id', 'public', 'created_at', 'post_id', 'user_id'])->withOrder(['id' => 'asc']);
}

public function findAll(array $scope = [], array $orderBy = []): DataReaderInterface
{
return new EntityReader($this
->select()
->where($scope)
->orderBy($orderBy));
}
}
24 changes: 20 additions & 4 deletions blog/src/Blog/CommentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,43 @@

namespace App\Blog;

use App\Blog\Comment\CommentRepository;
use App\Blog\Comment\CommentService;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Yiisoft\Data\Paginator\OffsetPaginator;
use Yiisoft\Data\Paginator\PageToken;
use Yiisoft\Input\Http\Attribute\Parameter\Query;
use Yiisoft\Router\HydratorAttribute\RouteArgument;
use Yiisoft\Yii\View\ViewRenderer;

final class CommentController
{
private const COMMENTS_FEED_PER_PAGE = 10;
private ViewRenderer $viewRenderer;

public function __construct(ViewRenderer $viewRenderer)
{
$this->viewRenderer = $viewRenderer->withControllerName('blog/comments');
}

public function index(Request $request, CommentService $service, #[RouteArgument('next')] ?string $next): Response
public function index(Request $request, CommentRepository $repository,
#[Query('sort')] ?string $sortOrder = null,
#[RouteArgument('page')] int $page = 1,
#[RouteArgument('pagesize')] int $pageSize = null,
): Response
{
$paginator = $service->getFeedPaginator();
if ($next !== null) {
$paginator = $paginator->withNextPageToken($next);
$dataReader = $repository
->findAll()
->withSort($repository->getSort()
->withOrderString($sortOrder ?? 'id'));

if ($pageSize === null) {
$pageSize = (int) ($body['pageSize'] ?? self::COMMENTS_FEED_PER_PAGE);
}
$paginator = (new OffsetPaginator($dataReader));
$paginator = $paginator->withToken(PageToken::next((string) $page))->withPageSize($pageSize);


if ($this->isAjaxRequest($request)) {
return $this->viewRenderer->renderPartial('_comments', ['data' => $paginator]);
Expand Down
12 changes: 5 additions & 7 deletions blog/src/Blog/Post/PostController.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public function add(Request $request, FormHydrator $formHydrator): Response

if ($request->getMethod() === Method::POST) {
$form = new PostForm();
if ($formHydrator->populate($form, $parameters['body']) && $form->isValid()) {
if ($formHydrator->populateAndValidate($form, $parameters['body'])) {
$this->postService->savePost($this->userService->getUser(), new Post(), $form);

return $this->webService->getRedirectResponse('blog/index');
Expand All @@ -65,7 +65,8 @@ public function edit(
Request $request,
PostRepository $postRepository,
ValidatorInterface $validator,
CurrentRoute $currentRoute
CurrentRoute $currentRoute,
FormHydrator $formHydrator
): Response {
$slug = $currentRoute->getArgument('slug');
$post = $postRepository->fullPostPage($slug);
Expand All @@ -87,16 +88,13 @@ public function edit(
if ($request->getMethod() === Method::POST) {
$form = new PostForm();
$body = $request->getParsedBody();
if ($form->load($body) && $validator
->validate($form)
->isValid()) {
if ($formHydrator->populateAndValidate($form,$body)) {
$this->postService->savePost($this->userService->getUser(), $post, $form);

return $this->webService->getRedirectResponse('blog/index');
}

$parameters['body'] = $body;
$parameters['errors'] = $form->getFormErrors();
$parameters['errors'] = $form->getValidationResult()->getErrorMessagesIndexedByAttribute();
}

return $this->viewRenderer->render('__form', $parameters);
Expand Down
25 changes: 13 additions & 12 deletions blog/src/User/Console/CreateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Throwable;
use Yiisoft\FormModel\FormHydrator;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Psalm Level 1 Testing Recommendation: Add the following to support recommendation below:

use App\User\User;

use Yiisoft\Rbac\Manager;
use Yiisoft\Yii\Console\ExitCode;

final class CreateCommand extends Command
{
protected static $defaultName = 'user/create';

public function __construct(private SignupForm $signupForm, private Manager $manager)
public function __construct(
private readonly SignupForm $signupForm,
private readonly Manager $manager,
private readonly FormHydrator $formHydrator
)
{
parent::__construct();
}
Expand All @@ -41,25 +46,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$login = (string) $input->getArgument('login');
$password = (string) $input->getArgument('password');
$isAdmin = (bool) $input->getArgument('isAdmin');

$this->signupForm->load([
'login' => $login,
'password' => $password,
'passwordVerify' => $password,
], '');

try {
$this->formHydrator->populate(model: $this->signupForm, data: [
'login' => $login,
'password' => $password,
'passwordVerify' => $password,
], scope: '');
$user = $this->signupForm->signup();
} catch (Throwable $t) {
$io->error($t->getMessage() . ' ' . $t->getFile() . ' ' . $t->getLine());

return $t->getCode() ?: ExitCode::UNSPECIFIED_ERROR;
}

if ($user === false) {
Copy link
Contributor

@rossaddison rossaddison Apr 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Psalm Level 1: Testing
ERROR: TypeDoesNotContainType - src/User/Console/CreateCommand.php:78:13 - Type App\User\User for $user is never falsy (see https://psalm.dev/056)
if ($user === false) {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommended: if (!$user instanceof User) {

$errors = $this->signupForm->getFormErrors()->getFirstErrors();
array_walk($errors, fn ($error, $attribute) => $io->error("$attribute: $error"));

$errors = $this->signupForm->getValidationResult()?->getErrorMessagesIndexedByAttribute();
Copy link
Contributor

@rossaddison rossaddison Apr 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Psalm Level 1 Testing:
ERROR: TypeDoesNotContainNull - src/User/Console/CreateCommand.php:62:23 - Yiisoft\Validator\Result does not contain null (see https://psalm.dev/090)
$errors = $this->signupForm->getValidationResult()?->getErrorMessagesIndexedByAttribute();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommended: Remove the question mark in getValidationResult?

array_walk($errors, fn($error, $attribute) => $io->error("$attribute: " . implode("\n", (array) $error)));
return ExitCode::DATAERR;
}

Expand Down
6 changes: 6 additions & 0 deletions blog/tests/Cli/ConsoleCest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ public function testCommandListCommand(CliTester $I): void
$I->seeResultCodeIs(ExitCode::OK);
}

public function testCommandUserCreateSuccessCommand(CliTester $I): void
{
$command = dirname(__DIR__, 2) . '/yii';
$I->runShellCommand($command . ' user/create user create123456');
$I->seeResultCodeIs(ExitCode::OK);
}
/**
* Clear all data created with testCommandFixtureAdd().
* Clearing database prevents from getting errors during multiple continuous testing with other test,
Expand Down
Loading