Skip to content

Commit

Permalink
Custom Paginator in Provider with QueryBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
aratinau committed Aug 2, 2021
1 parent 306ba97 commit 9a0989e
Show file tree
Hide file tree
Showing 14 changed files with 600 additions and 6 deletions.
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Inspired by https://github.com/api-platform/demo
- [Fifth example use JobCollectionDataProvider (paginationExtension)](#fifth-example-use-jobcollectiondataprovider-paginationextension)
- [Sixth example use FurnitureDataProvider (collectionDataProvider)](#sixth-example-use-furnituredataprovider-collectiondataprovider)
- [Seventh example use QueryBuilder in subresource](#seventh-example--simple-dataprovider-using-subresourcedataprovider)
- [Eight example use QueryBuilder in subresource](#eight-example-use-querybuilder-in-subresource)
- [Eighth example use QueryBuilder in subresource](#eight-example-use-querybuilder-in-subresource)
- [Ninth use custom subresource with provider (without subresourceDataProvider)](#ninth-example---custom-subresource-with-provider-without-subresourcedataprovider)


Expand Down Expand Up @@ -105,7 +105,7 @@ CommentSubresourceDataProvider show how use the standard behaviour

`api/movies/{id}/comments`

## Eight example use QueryBuilder in subresource
## Eighth example use QueryBuilder in subresource

DiscussionSubresourceDataProvider show how use QueryBuilder and order by DESC the result

Expand All @@ -121,7 +121,19 @@ With JobDataProvider and path `jobs/{id}/employees` return employees from id's j

### Usage

`api/jobs/{id}/employees`
`api/jobs/{id}/employees/{arg1}`

## Tenth example - Custom Paginator in Provider with QueryBuilder

PostCollectionDataProvider call the method findLatest from PostRepository and PostRepository call PostPaginator

### Usage

`/api/posts?page=2`

## Notes

`Album` and `Artist` are ready to be used

## Install

Expand Down
2 changes: 1 addition & 1 deletion config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@ services:
bind:
$collectionDataProvider: '@api_platform.doctrine.orm.default.collection_data_provider'

App\DataProvider\JobDataProvider:
App\DataProvider\JobEmployeeDataProvider:
bind:
$itemDataProvider: '@api_platform.doctrine.orm.default.item_data_provider'
5 changes: 5 additions & 0 deletions fixtures/album.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
App\Entity\Album:
album_{1..1000}:
title: <word()>
year: '<randomElement( [1999, 2000, 2004, 2011, 2016] )>'
artist: '@artist*'
3 changes: 3 additions & 0 deletions fixtures/artist.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
App\Entity\Artist:
artist_{1..10}:
author: <name()>
7 changes: 7 additions & 0 deletions fixtures/post.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
App\Entity\Post:
post_{1..100}:
title: <word(10, 700)>
slug: <word(10, 700)>
summary: <word(10, 700)>
content: <word(10, 700)>
publishedAt: '<dateTimeBetween("-100 days", "100 days")>'
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,19 @@

use ApiPlatform\Core\DataProvider\DenormalizedIdentifiersAwareItemDataProviderInterface;
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
use ApiPlatform\Core\DataProvider\Pagination;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use App\Entity\Job;

final class JobDataProvider implements RestrictedDataProviderInterface, DenormalizedIdentifiersAwareItemDataProviderInterface
final class JobEmployeeDataProvider implements RestrictedDataProviderInterface, DenormalizedIdentifiersAwareItemDataProviderInterface
{
private $itemDataProvider;
private $pagination;

public function __construct(ItemDataProviderInterface $itemDataProvider)
public function __construct(ItemDataProviderInterface $itemDataProvider, Pagination $pagination)
{
$this->itemDataProvider = $itemDataProvider;
$this->pagination = $pagination;
}

public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
Expand All @@ -30,6 +33,8 @@ public function getItem(string $resourceClass, $id, string $operationName = null
{
$itemDataProvider = $this->itemDataProvider->getItem($resourceClass, $id, $operationName, $context);

[$page, $offset, $itemPerPage] = $this->pagination->getPagination($resourceClass, $operationName, $context);

return $itemDataProvider->getEmployees()->getValues();
}
}
79 changes: 79 additions & 0 deletions src/DataProvider/Pagination/PostPaginator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

namespace App\DataProvider\Pagination;

use ApiPlatform\Core\DataProvider\PaginatorInterface;
use Doctrine\ORM\QueryBuilder as DoctrineQueryBuilder;

class PostPaginator implements PaginatorInterface, \IteratorAggregate
{
public const PAGE_SIZE = 3;

private $postsIterator;
private $currentPage;
private $maxResults;
private $queryBuilder;
private $totalResult;
private $results;

public function __construct(DoctrineQueryBuilder $queryBuilder,
int $currentPage,
$totalResult,
int $maxResults = self::PAGE_SIZE)
{
$this->currentPage = $currentPage;
$this->maxResults = $maxResults;
$this->queryBuilder = $queryBuilder;
$this->totalResult = $totalResult;
}

public function getLastPage(): float
{
return ceil($this->getTotalItems() / $this->getItemsPerPage()) ?: 1.;
}

public function getTotalItems(): float
{
return $this->totalResult;
}

public function getCurrentPage(): float
{
return $this->currentPage;
}

public function getItemsPerPage(): float
{
return $this->maxResults;
}

public function count()
{
return iterator_count($this->getIterator());
}

public function getIterator()
{
if ($this->postsIterator === null) {
$offset = ($this->currentPage - 1) * $this->maxResults;

$query = $this->queryBuilder
->setFirstResult($offset)
->setMaxResults($this->maxResults)
->getQuery()
;
$this->results = $query->getResult();

$this->postsIterator = new \ArrayIterator(
$this->results
);
}

return $this->postsIterator;
}

public function getResults(): \Traversable
{
return $this->results;
}
}
34 changes: 34 additions & 0 deletions src/DataProvider/PostCollectionDataProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace App\DataProvider;

use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface;
use ApiPlatform\Core\DataProvider\Pagination;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use App\Entity\Post;
use App\Repository\PostRepository;

class PostCollectionDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface
{
private $postRepository;
private $pagination;

public function __construct(PostRepository $postRepository,
Pagination $pagination)
{
$this->postRepository = $postRepository;
$this->pagination = $pagination;
}

public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
{
return $resourceClass === Post::class;
}

public function getCollection(string $resourceClass, string $operationName = null, array $context = [])
{
[$page] = $this->pagination->getPagination($resourceClass, $operationName, $context);

return $this->postRepository->findLatest($page);
}
}
81 changes: 81 additions & 0 deletions src/Entity/Album.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use App\Repository\AlbumRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;

/**
* @ApiResource()
* @ORM\Entity(repositoryClass=AlbumRepository::class)
*/
class Album
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Groups({"normalization-albums-by-artist"})
*/
private $id;

/**
* @ORM\Column(type="string", length=255)
* @Groups({"normalization-albums-by-artist"})
*/
private $title;

/**
* @ORM\Column(type="integer")
* @Groups({"normalization-albums-by-artist"})
*/
private $year;

/**
* @ORM\ManyToOne(targetEntity=Artist::class, inversedBy="albums")
*/
private $artist;

public function getId(): ?int
{
return $this->id;
}

public function getTitle(): ?string
{
return $this->title;
}

public function setTitle(string $title): self
{
$this->title = $title;

return $this;
}

public function getYear(): ?int
{
return $this->year;
}

public function setYear(int $year): self
{
$this->year = $year;

return $this;
}

public function getArtist(): ?Artist
{
return $this->artist;
}

public function setArtist(?Artist $artist): self
{
$this->artist = $artist;

return $this;
}
}
99 changes: 99 additions & 0 deletions src/Entity/Artist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use App\Repository\ArtistRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;

/**
* @ApiResource(
* collectionOperations={
* "get",
* "post",
* "subresource-albums-by-artist"={
* "method"="GET",
* "deserialize"=false,
* "path"="/artists/{id}/album/{year}",
* "normalization_context"={"groups" = "normalization-albums-by-artist"}
* }
* }
* )
* @ORM\Entity(repositoryClass=ArtistRepository::class)
*/
class Artist
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Groups({"normalization-albums-by-artist"})
*/
private $id;

/**
* @ORM\Column(type="string", length=255)
* @Groups({"normalization-albums-by-artist"})
*/
private $author;

/**
* @ORM\OneToMany(targetEntity=Album::class, mappedBy="artist")
*/
private $albums;

public function __construct()
{
$this->albums = new ArrayCollection();
}

public function getId(): ?int
{
return $this->id;
}

public function getAuthor(): ?string
{
return $this->author;
}

public function setAuthor(string $author): self
{
$this->author = $author;

return $this;
}

/**
* @return Collection|Album[]
*/
public function getAlbums(): Collection
{
return $this->albums;
}

public function addAlbum(Album $album): self
{
if (!$this->albums->contains($album)) {
$this->albums[] = $album;
$album->setArtist($this);
}

return $this;
}

public function removeAlbum(Album $album): self
{
if ($this->albums->removeElement($album)) {
// set the owning side to null (unless already changed)
if ($album->getArtist() === $this) {
$album->setArtist(null);
}
}

return $this;
}
}
Loading

0 comments on commit 9a0989e

Please sign in to comment.