From a3a8a37dd6f520e19e625d6c0d0dbf2ecfa2694a Mon Sep 17 00:00:00 2001 From: horea Date: Mon, 19 May 2025 17:18:02 +0300 Subject: [PATCH 1/8] Issue #110: update book tutorial Signed-off-by: horea --- docs/book/v6/tutorials/create-book-module.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/book/v6/tutorials/create-book-module.md b/docs/book/v6/tutorials/create-book-module.md index 76d62eb5..8b7711a9 100644 --- a/docs/book/v6/tutorials/create-book-module.md +++ b/docs/book/v6/tutorials/create-book-module.md @@ -203,10 +203,13 @@ declare(strict_types=1); namespace Api\Book\Service; use Core\Book\Entity\Book; +use Core\Book\Repository\BookRepository; use Doctrine\ORM\QueryBuilder; interface BookServiceInterface { + public function getBookRepository(): BookRepository; + public function saveBook(array $data): Book; public function getBooks(array $filters = []): QueryBuilder; @@ -233,8 +236,14 @@ use Exception; class BookService implements BookServiceInterface { #[Inject(BookRepository::class)] - public function __construct(protected BookRepository $bookRepository) + public function __construct( + protected BookRepository $bookRepository + ) { + } + + public function getBookRepository(): BookRepository { + return $this->bookRepository; } /** From 94f9083110f579ff131e660f413cdc8bc25e6308 Mon Sep 17 00:00:00 2001 From: horea Date: Tue, 20 May 2025 20:14:31 +0300 Subject: [PATCH 2/8] Issue #110: update book tutorial Signed-off-by: horea --- docs/book/v6/tutorials/create-book-module.md | 34 ++++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/docs/book/v6/tutorials/create-book-module.md b/docs/book/v6/tutorials/create-book-module.md index 8b7711a9..82b46f4c 100644 --- a/docs/book/v6/tutorials/create-book-module.md +++ b/docs/book/v6/tutorials/create-book-module.md @@ -180,13 +180,13 @@ use Dot\DependencyInjection\Attribute\Entity; #[Entity(name: Book::class)] class BookRepository extends AbstractRepository { - public function getBooks(array $params = [], array $filters = []): QueryBuilder + public function getBooks(array $params = []): QueryBuilder { return $this ->getQueryBuilder() ->select('book') ->from(Book::class, 'book') - ->orderBy($filters['order'] ?? 'book.created', $filters['dir'] ?? 'desc') + ->orderBy($params['sort'], $params['dir']) ->setFirstResult($params['offset']) ->setMaxResults($params['limit']); } @@ -212,7 +212,7 @@ interface BookServiceInterface public function saveBook(array $data): Book; - public function getBooks(array $filters = []): QueryBuilder; + public function getBooks(array $params): QueryBuilder; } ``` @@ -233,6 +233,8 @@ use Doctrine\ORM\QueryBuilder; use Dot\DependencyInjection\Attribute\Inject; use Exception; +use function in_array; + class BookService implements BookServiceInterface { #[Inject(BookRepository::class)] @@ -262,11 +264,23 @@ class BookService implements BookServiceInterface return $book; } - public function getBooks(array $filters = []): QueryBuilder + public function getBooks(array $params = []): QueryBuilder { - $params = Paginator::getParams($filters, 'book.created'); + $filters = $params['filters'] ?? []; + $params = Paginator::getParams($filters, 'book.created'); + + $sortableColumns = [ + 'book.name', + 'book.author', + 'book.releaseDate', + 'book.created', + ]; + + if (! in_array($params['sort'], $sortableColumns, true)) { + $params['sort'] = 'book.created'; + } - return $this->bookRepository->getBooks($params, $filters); + return $this->bookRepository->getBooks($params); } } ``` @@ -605,13 +619,13 @@ class ConfigProvider return [ 'delegators' => [ Application::class => [RoutesDelegator::class], - PostBookResourceHandler::class => [HandlerDelegatorFactory::class], - GetBookResourceHandler::class => [HandlerDelegatorFactory::class], + PostBookResourceHandler::class => [HandlerDelegatorFactory::class], + GetBookResourceHandler::class => [HandlerDelegatorFactory::class], GetBookCollectionHandler::class => [HandlerDelegatorFactory::class], ], 'factories' => [ - PostBookResourceHandler::class => AttributedServiceFactory::class, - GetBookResourceHandler::class => AttributedServiceFactory::class, + PostBookResourceHandler::class => AttributedServiceFactory::class, + GetBookResourceHandler::class => AttributedServiceFactory::class, GetBookCollectionHandler::class => AttributedServiceFactory::class, BookService::class => AttributedServiceFactory::class, ], From e31e3481056e077199fa898a60c55f5ac8902fb7 Mon Sep 17 00:00:00 2001 From: horea Date: Wed, 21 May 2025 11:30:34 +0300 Subject: [PATCH 3/8] Issue #110: update book tutorial Signed-off-by: horea --- docs/book/v6/tutorials/create-book-module.md | 40 ++++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/docs/book/v6/tutorials/create-book-module.md b/docs/book/v6/tutorials/create-book-module.md index 82b46f4c..fc19a948 100644 --- a/docs/book/v6/tutorials/create-book-module.md +++ b/docs/book/v6/tutorials/create-book-module.md @@ -70,6 +70,7 @@ use Api\App\Collection\ResourceCollection; class BookCollection extends ResourceCollection { } + ``` * `src/Core/src/Book/src/Entity/Book.php` @@ -191,6 +192,7 @@ class BookRepository extends AbstractRepository ->setMaxResults($params['limit']); } } + ``` * `src/Book/src/Service/BookServiceInterface.php` @@ -214,6 +216,7 @@ interface BookServiceInterface public function getBooks(array $params): QueryBuilder; } + ``` * `src/Book/src/Service/BookService.php` @@ -283,6 +286,7 @@ class BookService implements BookServiceInterface return $this->bookRepository->getBooks($params); } } + ``` When creating or updating a book, we will need some validators, so we will create input filters that will be used to validate the data received in the request @@ -320,6 +324,7 @@ class AuthorInput extends Input ], true); } } + ``` * `src/Book/src/InputFilter/Input/NameInput.php` @@ -355,6 +360,7 @@ class NameInput extends Input ], true); } } + ``` * `src/Book/src/InputFilter/Input/ReleaseDateInput.php` @@ -390,6 +396,7 @@ class ReleaseDateInput extends Input ], true); } } + ``` Now we add all the inputs together in a parent input filter. @@ -406,7 +413,7 @@ namespace Api\Book\InputFilter; use Api\Book\InputFilter\Input\AuthorInput; use Api\Book\InputFilter\Input\NameInput; use Api\Book\InputFilter\Input\ReleaseDateInput; -use Core\Book\InputFilter\AbstractInputFilter; +use Core\App\InputFilter\AbstractInputFilter; class CreateBookInputFilter extends AbstractInputFilter { @@ -417,6 +424,7 @@ class CreateBookInputFilter extends AbstractInputFilter $this->add(new ReleaseDateInput('releaseDate')); } } + ``` We create separate `Input` files to demonstrate their reusability and obtain a clean `CreateBookInputFilter` but you could have all the inputs created directly in the `CreateBookInputFilter` like this: @@ -501,6 +509,7 @@ class GetBookCollectionHandler extends AbstractHandler ); } } + ``` * `src/Book/src/Handler/GetBookResourceHandler.php` @@ -508,6 +517,8 @@ class GetBookCollectionHandler extends AbstractHandler ```php inputFilter->setData((array) $request->getParsedBody()); @@ -576,6 +590,7 @@ class PostBookResourceHandler extends AbstractHandler implements RequestHandlerI return $this->createdResponse($request, $this->bookService->saveBook($data)); } } + ``` In `src/Book/src` we now create the 2 PHP files: `RoutesDelegator.php` and `ConfigProvider.php`. @@ -643,6 +658,7 @@ class ConfigProvider ]; } } + ``` * `src/Book/src/RoutesDelegator.php` @@ -684,6 +700,7 @@ class RoutesDelegator return $callback(); } } + ``` In `src/Core/src/Book/src` we will create `ConfigProvider.php` where we configure Doctrine ORM. @@ -738,12 +755,13 @@ class ConfigProvider ]; } } + ``` ### Registering the module -* register the module config by adding `Api\Book\ConfigProvider::class` and `Core\Book\ConfigProvider::class` in `config/config.php` under the `Api\User\ConfigProvider::class` -* register the namespace by adding this line `"Api\\Book\\": "src/Book/src/"` and `"Core\\Book\\": "src/Core/src/Book/src/"`, in composer.json under the autoload.psr-4 key +* register the module config by adding `Api\Book\ConfigProvider::class,` and `Core\Book\ConfigProvider::class,` in `config/config.php` under the `Api\User\ConfigProvider::class,` +* register the namespace by adding this line `"Api\\Book\\": "src/Book/src/"` and `"Core\\Book\\": "src/Core/src/Book/src/"`, in `composer.json` under the `autoload`.`psr-4` key * update Composer autoloader by running the command: ```shell @@ -755,7 +773,7 @@ That's it. The module is now registered. We need to configure access to the newly created endpoints. Open `config/autoload/authorization.global.php` and append the below route names to the `UserRoleEnum::Guest->value` key: -* `books::list-books` +* `book::list-books` * `book::view-book` * `book::create-book` @@ -787,7 +805,13 @@ php ./vendor/bin/doctrine-migrations migrate ## Checking endpoints -If we did everything as planned, we can call the `http://0.0.0.0:8080/book` endpoint and create a new book: +First, we start a local server by executing: + +```shell +composer serve +``` + +If we did everything as planned, we should be able to create a new book by executing the below command: ```shell curl -X POST http://0.0.0.0:8080/book @@ -801,7 +825,7 @@ To list the books use: curl http://0.0.0.0:8080/books ``` -To retrieve a book use: +To retrieve a book, `curl` one of the links found in the output of the **list books** command, under `_embedded` . `books` . * . `_links` . `self` . `href` ```shell curl http://0.0.0.0:8080/book/{uuid} From 32bcb2fd1ae77c2f949d36775e1c196ecf9c6b07 Mon Sep 17 00:00:00 2001 From: horea Date: Wed, 21 May 2025 12:02:41 +0300 Subject: [PATCH 4/8] Issue #110: update book tutorial Signed-off-by: horea --- docs/book/v6/tutorials/create-book-module.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/v6/tutorials/create-book-module.md b/docs/book/v6/tutorials/create-book-module.md index fc19a948..cd31d233 100644 --- a/docs/book/v6/tutorials/create-book-module.md +++ b/docs/book/v6/tutorials/create-book-module.md @@ -723,8 +723,8 @@ class ConfigProvider public function __invoke(): array { return [ - 'dependencies' => $this->getDependencies(), - 'doctrine' => $this->getDoctrineConfig(), + 'dependencies' => $this->getDependencies(), + 'doctrine' => $this->getDoctrineConfig(), ]; } From 5513de2ef3fd5d7108b46973f5fea1ae3b880634 Mon Sep 17 00:00:00 2001 From: horea Date: Wed, 21 May 2025 13:03:22 +0300 Subject: [PATCH 5/8] Issue #110: update book tutorial Signed-off-by: horea --- docs/book/v6/tutorials/create-book-module.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/book/v6/tutorials/create-book-module.md b/docs/book/v6/tutorials/create-book-module.md index cd31d233..68b77389 100644 --- a/docs/book/v6/tutorials/create-book-module.md +++ b/docs/book/v6/tutorials/create-book-module.md @@ -154,9 +154,9 @@ class Book extends AbstractEntity public function getArrayCopy(): array { return [ - 'uuid' => $this->getUuid()->toString(), - 'name' => $this->getName(), - 'author' => $this->getAuthor(), + 'uuid' => $this->getUuid()->toString(), + 'name' => $this->getName(), + 'author' => $this->getAuthor(), 'releaseDate' => $this->getReleaseDate(), ]; } @@ -181,7 +181,7 @@ use Dot\DependencyInjection\Attribute\Entity; #[Entity(name: Book::class)] class BookRepository extends AbstractRepository { - public function getBooks(array $params = []): QueryBuilder + public function getBooks(array $params): QueryBuilder { return $this ->getQueryBuilder() @@ -825,7 +825,9 @@ To list the books use: curl http://0.0.0.0:8080/books ``` -To retrieve a book, `curl` one of the links found in the output of the **list books** command, under `_embedded` . `books` . * . `_links` . `self` . `href` +To fetch a book, `curl` one of the links found in the output of the **list books** command, under `_embedded` . `books` . * . `_links` . `self` . `href`. + +The link should have the following format: ```shell curl http://0.0.0.0:8080/book/{uuid} From a8906f0a16a72cdd4b1ffd4ba9fa7527835da2ff Mon Sep 17 00:00:00 2001 From: Alex Karajos Date: Thu, 22 May 2025 07:54:38 +0300 Subject: [PATCH 6/8] Update docs/book/v6/tutorials/create-book-module.md Signed-off-by: Alex Karajos --- docs/book/v6/tutorials/create-book-module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/v6/tutorials/create-book-module.md b/docs/book/v6/tutorials/create-book-module.md index 68b77389..d5daeb5d 100644 --- a/docs/book/v6/tutorials/create-book-module.md +++ b/docs/book/v6/tutorials/create-book-module.md @@ -283,7 +283,7 @@ class BookService implements BookServiceInterface $params['sort'] = 'book.created'; } - return $this->bookRepository->getBooks($params); + return $this->bookRepository->getBooks($params, $filters); } } From b4eb11fafe4baff0c44714027c46b7cb6b2568ba Mon Sep 17 00:00:00 2001 From: Alex Karajos Date: Thu, 22 May 2025 07:54:55 +0300 Subject: [PATCH 7/8] Update docs/book/v6/tutorials/create-book-module.md Signed-off-by: Alex Karajos --- docs/book/v6/tutorials/create-book-module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/v6/tutorials/create-book-module.md b/docs/book/v6/tutorials/create-book-module.md index d5daeb5d..35f58cc9 100644 --- a/docs/book/v6/tutorials/create-book-module.md +++ b/docs/book/v6/tutorials/create-book-module.md @@ -181,7 +181,7 @@ use Dot\DependencyInjection\Attribute\Entity; #[Entity(name: Book::class)] class BookRepository extends AbstractRepository { - public function getBooks(array $params): QueryBuilder + public function getBooks(array $params, array $filters = []): QueryBuilder { return $this ->getQueryBuilder() From 32eacc74c81b7c23d7f1eff4d793d29eb91d7d2e Mon Sep 17 00:00:00 2001 From: Alex Karajos Date: Thu, 22 May 2025 07:55:05 +0300 Subject: [PATCH 8/8] Update docs/book/v6/tutorials/create-book-module.md Signed-off-by: Alex Karajos --- docs/book/v6/tutorials/create-book-module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/v6/tutorials/create-book-module.md b/docs/book/v6/tutorials/create-book-module.md index 35f58cc9..bca00671 100644 --- a/docs/book/v6/tutorials/create-book-module.md +++ b/docs/book/v6/tutorials/create-book-module.md @@ -214,7 +214,7 @@ interface BookServiceInterface public function saveBook(array $data): Book; - public function getBooks(array $params): QueryBuilder; + public function getBooks(array $params = []): QueryBuilder; } ```