Skip to content
Merged
Changes from 4 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
89 changes: 68 additions & 21 deletions docs/book/v6/tutorials/create-book-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ use Api\App\Collection\ResourceCollection;
class BookCollection extends ResourceCollection
{
}

```

* `src/Core/src/Book/src/Entity/Book.php`
Expand Down Expand Up @@ -180,17 +181,18 @@ 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']);
}
}

```

* `src/Book/src/Service/BookServiceInterface.php`
Expand All @@ -203,14 +205,18 @@ 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;
public function getBooks(array $params): QueryBuilder;
}

```

* `src/Book/src/Service/BookService.php`
Expand All @@ -230,11 +236,19 @@ use Doctrine\ORM\QueryBuilder;
use Dot\DependencyInjection\Attribute\Inject;
use Exception;

use function in_array;

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;
}

/**
Expand All @@ -253,13 +267,26 @@ 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);
}
}

```

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
Expand Down Expand Up @@ -297,6 +324,7 @@ class AuthorInput extends Input
], true);
}
}

```

* `src/Book/src/InputFilter/Input/NameInput.php`
Expand Down Expand Up @@ -332,6 +360,7 @@ class NameInput extends Input
], true);
}
}

```

* `src/Book/src/InputFilter/Input/ReleaseDateInput.php`
Expand Down Expand Up @@ -367,6 +396,7 @@ class ReleaseDateInput extends Input
], true);
}
}

```

Now we add all the inputs together in a parent input filter.
Expand All @@ -383,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
{
Expand All @@ -394,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:
Expand Down Expand Up @@ -478,13 +509,16 @@ class GetBookCollectionHandler extends AbstractHandler
);
}
}

```

* `src/Book/src/Handler/GetBookResourceHandler.php`

```php
<?php

declare(strict_types=1);

namespace Api\Book\Handler;

use Api\App\Attribute\Resource;
Expand All @@ -504,6 +538,7 @@ class GetBookResourceHandler extends AbstractHandler
);
}
}

```

* `src/Book/src/Handler/PostBookResourceHandler.php`
Expand All @@ -523,9 +558,8 @@ use Core\App\Message;
use Dot\DependencyInjection\Attribute\Inject;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

class PostBookResourceHandler extends AbstractHandler implements RequestHandlerInterface
class PostBookResourceHandler extends AbstractHandler
{
#[Inject(
CreateBookInputFilter::class,
Expand All @@ -537,6 +571,9 @@ class PostBookResourceHandler extends AbstractHandler implements RequestHandlerI
) {
}

/**
* @throws BadRequestException
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
$this->inputFilter->setData((array) $request->getParsedBody());
Expand All @@ -553,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`.
Expand Down Expand Up @@ -596,13 +634,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,
],
Expand All @@ -620,6 +658,7 @@ class ConfigProvider
];
}
}

```

* `src/Book/src/RoutesDelegator.php`
Expand Down Expand Up @@ -661,6 +700,7 @@ class RoutesDelegator
return $callback();
}
}

```

In `src/Core/src/Book/src` we will create `ConfigProvider.php` where we configure Doctrine ORM.
Expand All @@ -683,8 +723,8 @@ class ConfigProvider
public function __invoke(): array
{
return [
'dependencies' => $this->getDependencies(),
'doctrine' => $this->getDoctrineConfig(),
'dependencies' => $this->getDependencies(),
'doctrine' => $this->getDoctrineConfig(),
];
}

Expand Down Expand Up @@ -715,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
Expand All @@ -732,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`

Expand Down Expand Up @@ -764,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
Expand All @@ -778,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}
Expand Down