diff --git a/blog/README.md b/blog/README.md index 3475e92a..e646d997 100644 --- a/blog/README.md +++ b/blog/README.md @@ -22,13 +22,21 @@ 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`. @@ -36,16 +44,19 @@ Console works out of the box and could be executed with `./yii`. Some commands: ```bash -user/create -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`. diff --git a/blog/resources/views/blog/comments/_comments.php b/blog/resources/views/blog/comments/_comments.php index c9db5fcf..336e2e47 100644 --- a/blog/resources/views/blog/comments/_comments.php +++ b/blog/resources/views/blog/comments/_comments.php @@ -33,7 +33,7 @@
diff --git a/blog/src/Auth/Controller/AuthController.php b/blog/src/Auth/Controller/AuthController.php index 446f81b5..c23af354 100644 --- a/blog/src/Auth/Controller/AuthController.php +++ b/blog/src/Auth/Controller/AuthController.php @@ -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'); } @@ -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')) { @@ -56,6 +51,7 @@ public function login( return $this->viewRenderer->render('login', ['formModel' => $loginForm]); } + public function logout(): ResponseInterface { $this->authService->logout(); diff --git a/blog/src/Auth/Controller/SignupController.php b/blog/src/Auth/Controller/SignupController.php index e490def9..2b975488 100644 --- a/blog/src/Auth/Controller/SignupController.php +++ b/blog/src/Auth/Controller/SignupController.php @@ -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(); } diff --git a/blog/src/Auth/Form/SignupForm.php b/blog/src/Auth/Form/SignupForm.php index d8e53c5f..1d4b9aed 100644 --- a/blog/src/Auth/Form/SignupForm.php +++ b/blog/src/Auth/Form/SignupForm.php @@ -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, ) { } @@ -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 diff --git a/blog/src/Blog/BlogController.php b/blog/src/Blog/BlogController.php index 3030aae8..cdd5b092 100644 --- a/blog/src/Blog/BlogController.php +++ b/blog/src/Blog/BlogController.php @@ -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, diff --git a/blog/src/Blog/Comment/CommentRepository.php b/blog/src/Blog/Comment/CommentRepository.php index 6318fdd2..f7a817ca 100644 --- a/blog/src/Blog/Comment/CommentRepository.php +++ b/blog/src/Blog/Comment/CommentRepository.php @@ -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 */ @@ -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)); + } } diff --git a/blog/src/Blog/CommentController.php b/blog/src/Blog/CommentController.php index c4de61c7..431027e3 100644 --- a/blog/src/Blog/CommentController.php +++ b/blog/src/Blog/CommentController.php @@ -4,14 +4,19 @@ 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) @@ -19,12 +24,23 @@ 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]); diff --git a/blog/src/Blog/Post/PostController.php b/blog/src/Blog/Post/PostController.php index c711b44f..adeed9bf 100644 --- a/blog/src/Blog/Post/PostController.php +++ b/blog/src/Blog/Post/PostController.php @@ -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'); @@ -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); @@ -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); diff --git a/blog/src/User/Console/CreateCommand.php b/blog/src/User/Console/CreateCommand.php index f712327d..8e2bfd65 100644 --- a/blog/src/User/Console/CreateCommand.php +++ b/blog/src/User/Console/CreateCommand.php @@ -12,6 +12,7 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Throwable; +use Yiisoft\FormModel\FormHydrator; use Yiisoft\Rbac\Manager; use Yiisoft\Yii\Console\ExitCode; @@ -19,7 +20,11 @@ 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(); } @@ -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) { - $errors = $this->signupForm->getFormErrors()->getFirstErrors(); - array_walk($errors, fn ($error, $attribute) => $io->error("$attribute: $error")); - + $errors = $this->signupForm->getValidationResult()?->getErrorMessagesIndexedByAttribute(); + array_walk($errors, fn($error, $attribute) => $io->error("$attribute: " . implode("\n", (array) $error))); return ExitCode::DATAERR; } diff --git a/blog/tests/Cli/ConsoleCest.php b/blog/tests/Cli/ConsoleCest.php index 2949fea7..1bd43120 100644 --- a/blog/tests/Cli/ConsoleCest.php +++ b/blog/tests/Cli/ConsoleCest.php @@ -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,