diff --git a/.gitignore b/.gitignore index 06a91da..a60367c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ vendor .php-cs-fixer.cache node_modules package-lock.json +.phpunit.result.cache diff --git a/composer.json b/composer.json index bef1942..8a74f1a 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,7 @@ "gedmo/doctrine-extensions": "^3.0", "hubspot/hubspot-php": "^5.0", "knplabs/doctrine-behaviors": "^2.0.6", + "knplabs/knp-paginator-bundle": "^5.8", "runroom-packages/form-handler-bundle": "^0.16", "runroom-packages/render-event-bundle": "^0.16", "runroom-packages/sortable-behavior-bundle": "^0.16", diff --git a/src/BasicEntities/Controller/BookController.php b/src/BasicEntities/Controller/BookController.php index 2843050..733a795 100644 --- a/src/BasicEntities/Controller/BookController.php +++ b/src/BasicEntities/Controller/BookController.php @@ -26,10 +26,10 @@ public function __construct(BookService $service) $this->service = $service; } - public function books(): Response + public function books(int $page): Response { return $this->render('@RunroomSamples/BasicEntities/books.html.twig', [ - 'model' => $this->service->getBooksViewModel(), + 'model' => $this->service->getBooksViewModel($page), ]); } diff --git a/src/BasicEntities/Repository/BookRepository.php b/src/BasicEntities/Repository/BookRepository.php index 965aaf5..678ffc5 100644 --- a/src/BasicEntities/Repository/BookRepository.php +++ b/src/BasicEntities/Repository/BookRepository.php @@ -16,6 +16,8 @@ use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\ORM\Query\Expr\Join; use Doctrine\Persistence\ManagerRegistry; +use Knp\Bundle\PaginatorBundle\Pagination\SlidingPaginationInterface; +use Knp\Component\Pager\PaginatorInterface; use Runroom\SamplesBundle\BasicEntities\Entity\Book; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; @@ -30,12 +32,17 @@ class BookRepository extends ServiceEntityRepository { private RequestStack $requestStack; + private PaginatorInterface $paginator; - public function __construct(ManagerRegistry $registry, RequestStack $requestStack) - { + public function __construct( + ManagerRegistry $registry, + RequestStack $requestStack, + PaginatorInterface $paginator + ) { parent::__construct($registry, Book::class); $this->requestStack = $requestStack; + $this->paginator = $paginator; } public function findBySlug(string $slug): Book @@ -55,4 +62,24 @@ public function findBySlug(string $slug): Book return $book; } + + /** + * @phpstan-return SlidingPaginationInterface + * + * @psalm-return SlidingPaginationInterface + */ + public function getPaginatedBooks(int $page, int $limitPerPage): SlidingPaginationInterface + { + $request = $this->requestStack->getCurrentRequest() ?? new Request(); + + $queryBuilder = $this->createQueryBuilder('books') + ->where('books.publish = true') + ->leftJoin('books.translations', 'translations', Join::WITH, 'translations.locale = :locale') + ->setParameter('locale', $request->getLocale()); + + $pagination = $this->paginator->paginate($queryBuilder, $page, $limitPerPage); + \assert($pagination instanceof SlidingPaginationInterface); + + return $pagination; + } } diff --git a/src/BasicEntities/Service/BookService.php b/src/BasicEntities/Service/BookService.php index f029ecd..a1764f1 100644 --- a/src/BasicEntities/Service/BookService.php +++ b/src/BasicEntities/Service/BookService.php @@ -19,6 +19,8 @@ class BookService { + public const LIMIT_PER_PAGE = 6; + private BookRepository $repository; public function __construct(BookRepository $repository) @@ -26,9 +28,11 @@ public function __construct(BookRepository $repository) $this->repository = $repository; } - public function getBooksViewModel(): BooksViewModel + public function getBooksViewModel(int $page): BooksViewModel { - return new BooksViewModel($this->repository->findBy(['publish' => true], ['position' => 'ASC'])); + $pagination = $this->repository->getPaginatedBooks($page, self::LIMIT_PER_PAGE); + + return new BooksViewModel($pagination); } public function getBookViewModel(string $slug): BookViewModel diff --git a/src/BasicEntities/ViewModel/BooksViewModel.php b/src/BasicEntities/ViewModel/BooksViewModel.php index b232f99..1c81031 100644 --- a/src/BasicEntities/ViewModel/BooksViewModel.php +++ b/src/BasicEntities/ViewModel/BooksViewModel.php @@ -13,28 +13,35 @@ namespace Runroom\SamplesBundle\BasicEntities\ViewModel; +use Knp\Bundle\PaginatorBundle\Pagination\SlidingPaginationInterface; use Runroom\SamplesBundle\BasicEntities\Entity\Book; class BooksViewModel { /** - * @var Book[] + * @phpstan-var SlidingPaginationInterface + * + * @psalm-var SlidingPaginationInterface */ - private array $books; + protected $pagination; /** - * @param Book[] $books + * @phpstan-param SlidingPaginationInterface $pagination + * + * @psalm-param SlidingPaginationInterface $pagination */ - public function __construct(array $books) + public function __construct(SlidingPaginationInterface $pagination) { - $this->books = $books; + $this->pagination = $pagination; } /** - * @return Book[] + * @phpstan-return SlidingPaginationInterface + * + * @psalm-return SlidingPaginationInterface */ - public function getBooks(): array + public function getPagination(): SlidingPaginationInterface { - return $this->books; + return $this->pagination; } } diff --git a/src/Resources/config/routing.yaml b/src/Resources/config/routing.yaml index 8d076bd..fd670af 100644 --- a/src/Resources/config/routing.yaml +++ b/src/Resources/config/routing.yaml @@ -6,8 +6,12 @@ runroom_samples.home: # BasicEntities runroom_samples.basic_entities.books: - path: /books + path: /books/{page} controller: Runroom\SamplesBundle\BasicEntities\Controller\BookController::books + defaults: + page: 1 + requirements: + page: '\d+' runroom_samples.basic_entities.book: path: /book/{slug} diff --git a/src/Resources/views/BasicEntities/books.html.twig b/src/Resources/views/BasicEntities/books.html.twig index 23a0a0e..6e0115c 100644 --- a/src/Resources/views/BasicEntities/books.html.twig +++ b/src/Resources/views/BasicEntities/books.html.twig @@ -1,10 +1,33 @@ {% extends '@RunroomSamples/base.html.twig' %} +{% block contentClass %}books-list{% endblock %} + {% block content %} - {% for book in model.books %} - -

{{ book.title }}

-
{{ book.description|raw }}
-
- {% endfor %} +
+
    + {% for book in model.pagination.items %} +
  • +
    +
    +
    +
    {{ book.title }}
    +

    + {{ book.description|raw }} +

    +
    +
  • + {#
    +
  • + {{book.title}} +
  • +
    #} + {% endfor %} +
+ +
+ {% include '@RunroomSamples/pagination.html.twig' with { pagination: model.pagination } %} +
+ +
{% endblock %} diff --git a/src/Resources/views/pagination.html.twig b/src/Resources/views/pagination.html.twig new file mode 100644 index 0000000..d58fde7 --- /dev/null +++ b/src/Resources/views/pagination.html.twig @@ -0,0 +1,41 @@ +{% set paginationData = pagination.paginationData %} +{% set route = app.request.attributes.get('_route') %} +{% set routeParams = app.request.attributes.get('_route_params')|merge(app.request.query.all) %} + +{% if paginationData.pageCount > 1 %} +
+ {% if paginationData.first is defined and paginationData.current != paginationData.first %} + + << + + {% endif %} + + {% if paginationData.previous is defined %} + + + + {% endif %} + + {% for page in paginationData.pagesInRange %} + {% if page != paginationData.current %} + + {{ page }} + + {% else %} + {{ page }} + {% endif %} + {% endfor %} + + {% if paginationData.next is defined %} + + + + {% endif %} + + {% if paginationData.last is defined and paginationData.current != paginationData.last %} + + >> + + {% endif %} +
+{% endif %} diff --git a/tests/App/Kernel.php b/tests/App/Kernel.php index 32f9a0b..ee2bb1f 100644 --- a/tests/App/Kernel.php +++ b/tests/App/Kernel.php @@ -18,6 +18,7 @@ use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; use FOS\CKEditorBundle\FOSCKEditorBundle; use Knp\Bundle\MenuBundle\KnpMenuBundle; +use Knp\Bundle\PaginatorBundle\KnpPaginatorBundle; use Knp\DoctrineBehaviors\DoctrineBehaviorsBundle; use Runroom\FormHandlerBundle\RunroomFormHandlerBundle; use Runroom\RenderEventBundle\RunroomRenderEventBundle; @@ -54,6 +55,7 @@ public function registerBundles(): iterable new FOSCKEditorBundle(), new FrameworkBundle(), new KnpMenuBundle(), + new KnpPaginatorBundle(), new SecurityBundle(), new TwigBundle(), new SonataMediaBundle(), diff --git a/tests/BasicEntities/Unit/BookControllerTest.php b/tests/BasicEntities/Unit/BookControllerTest.php index 9157df1..adc4a93 100644 --- a/tests/BasicEntities/Unit/BookControllerTest.php +++ b/tests/BasicEntities/Unit/BookControllerTest.php @@ -13,6 +13,7 @@ namespace Runroom\SamplesBundle\Tests\BasicEntities\Unit; +use Knp\Bundle\PaginatorBundle\Pagination\SlidingPaginationInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Runroom\SamplesBundle\BasicEntities\Controller\BookController; @@ -54,11 +55,12 @@ protected function setUp(): void */ public function itRenderBooks(): void { - $model = new BooksViewModel([]); + $model = new BooksViewModel($this->createStub(SlidingPaginationInterface::class)); + $page = 1; - $this->service->method('getBooksViewModel')->willReturn($model); + $this->service->method('getBooksViewModel')->with($page)->willReturn($model); - $response = $this->controller->books(); + $response = $this->controller->books($page); static::assertSame(200, $response->getStatusCode()); } diff --git a/tests/BasicEntities/Unit/BookServiceTest.php b/tests/BasicEntities/Unit/BookServiceTest.php index 7445340..d122895 100644 --- a/tests/BasicEntities/Unit/BookServiceTest.php +++ b/tests/BasicEntities/Unit/BookServiceTest.php @@ -13,6 +13,7 @@ namespace Runroom\SamplesBundle\Tests\BasicEntities\Unit; +use Knp\Bundle\PaginatorBundle\Pagination\SlidingPaginationInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Runroom\SamplesBundle\BasicEntities\Factory\BookFactory; @@ -34,7 +35,6 @@ class BookServiceTest extends TestCase protected function setUp(): void { $this->repository = $this->createMock(BookRepository::class); - $this->service = new BookService($this->repository); } @@ -43,13 +43,14 @@ protected function setUp(): void */ public function itBuildsBooksViewModel(): void { - $expectedBooks = [BookFactory::createOne()->object()]; + $pagination = $this->createStub(SlidingPaginationInterface::class); + $page = 1; - $this->repository->method('findBy')->with(['publish' => true], ['position' => 'ASC'])->willReturn($expectedBooks); + $this->repository->method('getPaginatedBooks')->willReturn($pagination); - $model = $this->service->getBooksViewModel(); + $model = $this->service->getBooksViewModel($page); - static::assertSame($model->getBooks(), $expectedBooks); + static::assertSame($model->getPagination(), $pagination); } /**