rekalogika/api-lite
A set of tools to simplify working with API
diff --git a/api-lite/abstractstate.html b/api-lite/abstractstate.html
index 1a976fd3..9c1eeed9 100644
--- a/api-lite/abstractstate.html
+++ b/api-lite/abstractstate.html
@@ -4,8 +4,8 @@
Base class for our providers and processors. The properties in the input DTO are uninitialized. This is important for PATCH The properties in the input DTO are uninitialized. This is important for PATCH
diff --git a/api-lite/basic-endpoints/post-create.html b/api-lite/basic-endpoints/post-create.html
index 4a01b68f..8901db5d 100644
--- a/api-lite/basic-endpoints/post-create.html
+++ b/api-lite/basic-endpoints/post-create.html
@@ -4,8 +4,8 @@
These are the components involved in building an API Platform-based project
diff --git a/api-lite/design.html b/api-lite/design.html
index 810c87d7..a14e9652 100644
--- a/api-lite/design.html
+++ b/api-lite/design.html
@@ -4,8 +4,8 @@
Some of our design considerations and decisions based on our experience. These
diff --git a/api-lite/filtering.html b/api-lite/filtering.html
index b659da7f..eb7f45dd 100644
--- a/api-lite/filtering.html
+++ b/api-lite/filtering.html
@@ -4,8 +4,8 @@
Filtering support is planned, but it is going to take some time. A set of tools to simplify working with API
diff --git a/api-lite/mapping.html b/api-lite/mapping.html
index 2e3e1191..933d5bcc 100644
--- a/api-lite/mapping.html
+++ b/api-lite/mapping.html
@@ -4,8 +4,8 @@
By separating the entity and the The If you don't want your state providers and processors to extend Your Docusaurus site did not load properly. A very common reason is a wrong site baseUrl configuration. Current configured baseUrl = ${e} ${"/"===e?" (default value)":""} We suggest trying baseUrl = AbstractState
Use Cases: Basic Endpoints
📄️ Objects Used in the Examples
📄️ GET Collection Endpoint
📄️ GET Endpoint
📄️ POST Endpoint for Entity Creation
📄️ PATCH and PUT Endpoint for Entity Update
📄️ DELETE Endpoint
📄️ POST Endpoint for an Action Without Input
DELETE Endpoint
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Patch;
use App\ApiInput\BookInputDto;
use App\ApiState\Admin\Book\BookRemoveProcessor;
#[ApiResource(
shortName: 'Admin/Book',
routePrefix: '/admin',
operations: [
// ...
new Delete(
uriTemplate: '/books/{id}',
processor: BookRemoveProcessor::class
),
// ...
]
)]
class BookDto
{
// ...
}namespace App\ApiState\Admin\Book;
use ApiPlatform\Metadata\Operation;
use Doctrine\ORM\EntityManagerInterface;
use App\Repository\BookRepository;
use Rekalogika\ApiLite\Exception\NotFoundException;
use Rekalogika\ApiLite\State\AbstractProcessor;
/**
* @extends AbstractProcessor<void,void>
*/
class BookRemoveProcessor extends AbstractProcessor
{
public function __construct(
private BookRepository $bookRepository,
private EntityManagerInterface $entityManager
) {
}
public function process(
mixed $data,
Operation $operation,
array $uriVariables = [],
array $context = []
) {
// Gets the book from the database
$book = $this->bookRepository
->find($uriVariables['id'] ?? null)
?? throw new NotFoundException('Book not found');
// Check for authorization
$this->denyAccessUnlessGranted('remove', $book);
// Remove the book
$this->entityManager->remove($book);
// Flush the change to the database
$this->entityManager->flush();
}
}GET Collection Endpoint
namespace App\ApiResource\Admin;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use App\ApiState\Admin\Book\BookCollectionProvider;
#[ApiResource(
shortName: 'Admin/Book',
routePrefix: '/admin',
// // Uncomment the following to enable keyset-pagination:
// extraProperties: [
// 'api_lite_rekapager' => true
// ]
operations: [
// ...
new GetCollection(
uriTemplate: '/books',
provider: BookCollectionProvider::class,
),
// ...
]
)]
class BookDto
{
// ...
}namespace App\ApiState\Admin\Book;
use ApiPlatform\Metadata\Operation;
use App\ApiResource\Admin\BookDto;
use App\Repository\BookRepository;
use Rekalogika\ApiLite\State\AbstractProvider;
/**
* @extends AbstractProvider<BookDto>
*/
class BookCollectionProvider extends AbstractProvider
{
public function __construct(
private BookRepository $bookRepository
) {
}
public function provide(
Operation $operation,
array $uriVariables = [],
array $context = []
): object|array|null {
// Check for authorization
$this->denyAccessUnlessGranted('view', $this->bookRepository);
// A Doctrine repository implements Selectable, and our PaginatorApplier
// supports Selectable, so we can convieniently use it as a collection
// of entities to map. Here we map the Books to BookDtos, and return
// them.
return $this->mapCollection(
collection: $this->bookRepository,
target: BookDto::class,
operation: $operation,
context: $context
);
}
}GET Endpoint
namespace App\ApiResource\Admin;
use App\ApiState\Admin\Book\BookProvider;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
#[ApiResource(
shortName: 'Admin/Book',
routePrefix: '/admin',
operations: [
// ...
new Get(
uriTemplate: '/books/{id}',
provider: BookProvider::class,
),
// ...
]
)]
class BookDto
{
// ...
}namespace App\ApiState\Admin\Book;
use ApiPlatform\Metadata\Operation;
use App\ApiResource\Admin\BookDto;
use App\Repository\BookRepository;
use Rekalogika\ApiLite\Exception\NotFoundException;
use Rekalogika\ApiLite\State\AbstractProvider;
/**
* @extends AbstractProvider<BookDto>
*/
class BookProvider extends AbstractProvider
{
public function __construct(
private BookRepository $bookRepository
) {
}
public function provide(
Operation $operation,
array $uriVariables = [],
array $context = []
): object|array|null {
// Get the book from the database
$book = $this->bookRepository
->find($uriVariables['id'] ?? null)
?? throw new NotFoundException('Book not found');
// Check for authorization
$this->denyAccessUnlessGranted('view', $book);
// Map the Book to the output DTO, and return it.
return $this->map($book, BookDto::class);
}
}Objects Used in the Examples
namespace App\Entity;
use App\Repository\BookRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;
#[ORM\Entity(repositoryClass: BookRepository::class)]
class Book extends \stdClass
{
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true, nullable: false)]
private Uuid $id;
#[ORM\Column]
private ?string $title = null;
#[ORM\Column(type: Types::TEXT)]
private ?string $description = null;
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)]
private ?\DateTimeInterface $lastChecked = null;
/**
* @var Collection<array-key,Review>
*/
#[ORM\OneToMany(
targetEntity: Review::class,
mappedBy: 'book',
cascade: ['persist', 'remove'],
orphanRemoval: true,
fetch: 'EXTRA_LAZY',
indexBy: 'id',
)]
private Collection $reviews;
public function __construct()
{
$this->id = Uuid::v7();
$this->reviews = new ArrayCollection();
}
/**
* We want to check our books' conditions every now and then.
*/
public function check(): void
{
$this->lastChecked = new \DateTimeImmutable();
}
public function getId(): Uuid
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(?string $title): self
{
$this->title = $title;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): self
{
$this->description = $description;
return $this;
}
/**
* @return Collection<array-key,Review>
*/
public function getReviews(): Collection
{
return $this->reviews;
}
public function addReview(Review $review): self
{
if (!$this->reviews->contains($review)) {
$this->reviews[] = $review;
$review->setBook($this);
}
return $this;
}
public function removeReview(Review $review): self
{
if ($this->reviews->removeElement($review)) {
// set the owning side to null (unless already changed)
if ($review->getBook() === $this) {
$review->setBook(null);
}
}
return $this;
}
public function getLastChecked(): ?\DateTimeInterface
{
return $this->lastChecked;
}
}namespace App\Entity;
use App\Repository\ReviewRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;
#[ORM\Entity(repositoryClass: ReviewRepository::class)]
class Review
{
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true, nullable: false)]
private Uuid $id;
/**
* @var int<1,5>
*/
#[ORM\Column]
private int $rating = 3;
#[ORM\Column]
private ?string $body = null;
#[ORM\ManyToOne(
targetEntity: Book::class,
inversedBy: 'reviews',
)]
private ?Book $book = null;
public function __construct()
{
$this->id = Uuid::v7();
}
public function getId(): Uuid
{
return $this->id;
}
public function getBook(): ?Book
{
return $this->book;
}
public function setBook(?Book $book): self
{
$this->book = $book;
return $this;
}
public function getBody(): ?string
{
return $this->body;
}
public function setBody(?string $body): self
{
$this->body = $body;
return $this;
}
/**
* @return int<1,5>
*/
public function getRating(): int
{
return $this->rating;
}
/**
* @param int<1,5> $rating
*/
public function setRating(int $rating): self
{
$this->rating = $rating;
return $this;
}
}namespace App\ApiResource\Admin;
use Rekalogika\Mapper\CollectionInterface;
use Symfony\Component\Uid\Uuid;
class BookDto
{
public ?Uuid $id = null;
public ?string $title = null;
public ?string $description = null;
public ?\DateTimeInterface $lastChecked = null;
/**
* @var ?CollectionInterface<int,ReviewDto>
*/
public ?CollectionInterface $reviews = null;
}namespace App\ApiResource\Admin;
use Symfony\Component\Uid\Uuid;
class ReviewDto
{
public ?Uuid $id = null;
public ?string $body = null;
/**
* @var int<1,5>|null
*/
public ?int $rating = null;
public ?BookDto $book = null;
}PATCH and PUT Endpoint for Entity Update
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Patch;
use App\ApiInput\BookInputDto;
use App\ApiState\Admin\Book\BookProvider;
use App\ApiState\Admin\Book\BookUpdateProcessor;
#[ApiResource(
shortName: 'Admin/Book',
routePrefix: '/admin',
operations: [
// ...
new Patch(
uriTemplate: '/books/{id}',
input: BookInputDto::class,
processor: BookUpdateProcessor::class,
read: false,
),
new Put(
uriTemplate: '/books/{id}',
input: BookInputDto::class,
processor: BookUpdateProcessor::class,
read: false,
),
// ...
]
)]
class BookDto
{
// ...
}namespace App\ApiInput;
class BookInputDto
{
public string $title;
public string $description;
}POST Endpoint for Entity Creation
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use App\ApiInput\BookInputDto;
use App\ApiState\Admin\Book\BookCreateProcessor;
#[ApiResource(
shortName: 'Admin/Book',
routePrefix: '/admin',
operations: [
// ...
new Post(
uriTemplate: '/books',
input: BookInputDto::class,
processor: BookCreateProcessor::class
),
// ...
]
)]
class BookDto
{
// ...
}namespace App\ApiInput;
class BookInputDto
{
public string $title;
public string $description;
}use ApiPlatform\Metadata\Operation;
use Doctrine\ORM\EntityManagerInterface;
use App\ApiInput\BookInputDto;
use App\ApiResource\BookDto;
use App\Entity\Book;
use App\Repository\BookRepository;
use Rekalogika\ApiLite\State\AbstractProcessor;
/**
* @extends AbstractProcessor<BookInputDto,BookDto>
*/
class BookCreateProcessor extends AbstractProcessor
{
public function __construct(
private EntityManagerInterface $entityManager,
private BookRepository $bookRepository
) {
}
public function process(
mixed $data,
Operation $operation,
array $uriVariables = [],
array $context = []
) {
// Check for authorization
$this->denyAccessUnlessGranted('add', $this->bookRepository);
// Map the input DTO to the Book entity. In more complex scenarios,
// using the mapper can be impractical, and you need to do it according
// to the requirement of your domain logic.
$book = $this->map($data, Book::class);
// Persist the entity and flush the changes
$this->entityManager->persist($book);
$this->entityManager->flush();
// Map the resulting Book to the output DTO, and return it.
return $this->map($book, BookDto::class);
}
}POST Endpoint for an Action Without Input
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\OpenApi\Model\Operation;
use ApiPlatform\OpenApi\Model\RequestBody;
use App\ApiInput\BookInputDto;
use App\ApiState\Admin\Book\BookCheckProcessor;
#[ApiResource(
shortName: 'Admin/Book',
routePrefix: '/admin',
operations: [
// ...
new Post(
uriTemplate: '/books/{id}/check',
processor: BookCheckProcessor::class,
input: false,
openapi: new Operation(
summary: 'Check the book\'s condition',
description: 'Tells us that the book condition has been checked.',
)
),
// ...
]
)]
class BookDto
{
// ...
}namespace App\ApiState\Admin\Book;
use ApiPlatform\Metadata\Operation;
use App\ApiResource\Admin\BookDto;
use App\Repository\BookRepository;
use Doctrine\ORM\EntityManagerInterface;
use Rekalogika\ApiLite\Exception\NotFoundException;
use Rekalogika\ApiLite\State\AbstractProcessor;
/**
* @extends AbstractProcessor<void,BookDto>
*/
class BookCheckProcessor extends AbstractProcessor
{
public function __construct(
private EntityManagerInterface $entityManager,
private BookRepository $bookRepository
) {
}
public function process(
mixed $data,
Operation $operation,
array $uriVariables = [],
array $context = []
) {
// Gets the book from the database
$book = $this->bookRepository
->find($uriVariables['id'] ?? null)
?? throw new NotFoundException('Book not found');
// Check for authorization
$this->denyAccessUnlessGranted('check', $book);
// Execute the action
$book->check();
// Flush any changes to the database
$this->entityManager->flush();
// Map the Book to the output DTO, and return it.
return $this->map($book, BookDto::class);
}
}Components Overview
Design Considerations & Decisions
Filtering
Introduction
Mapping
ApiResource
DTO, mapping between the two
diff --git a/api-lite/pagination.html b/api-lite/pagination.html
index a05a32e1..0f0517c3 100644
--- a/api-lite/pagination.html
+++ b/api-lite/pagination.html
@@ -4,8 +4,8 @@
Pagination
mapCollection()
method automates the task of handling collection results,
diff --git a/api-lite/subresource.html b/api-lite/subresource.html
index 71c868e1..d9528eec 100644
--- a/api-lite/subresource.html
+++ b/api-lite/subresource.html
@@ -4,8 +4,8 @@
GET Collection Endpoint for Subresources
namespace App\ApiResource\User;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Link;
use App\ApiState\User\Review\BookReviewCollectionProvider;
#[ApiResource(
shortName: 'User/Review',
routePrefix: '/user',
operations: [
// ...
new GetCollection(
uriTemplate: '/books/{bookId}/reviews',
uriVariables: [
'bookId' => new Link(
fromClass: BookDto::class,
),
],
provider: BookReviewCollectionProvider::class,
paginationItemsPerPage: 10
),
// ...
]
)]
class ReviewDto
{
// ...
}namespace App\ApiState\User\Review;
use ApiPlatform\Metadata\Operation;
use App\ApiResource\User\ReviewDto;
use App\Repository\BookRepository;
use Rekalogika\ApiLite\Exception\NotFoundException;
use Rekalogika\ApiLite\State\AbstractProvider;
/**
* @extends AbstractProvider<ReviewDto>
*/
class BookReviewCollectionProvider extends AbstractProvider
{
public function __construct(
private BookRepository $bookRepository
) {
}
public function provide(
Operation $operation,
array $uriVariables = [],
array $context = []
): object|array|null {
// Gets the book from the database
$book = $this->bookRepository
->find($uriVariables['bookId'] ?? null)
?? throw new NotFoundException('Book not found');
// Check for authorization
$this->denyAccessUnlessGranted('getReviews', $book);
// Get the collection of reviews we want to show. This will get us a
// Doctrine collection of Review entities.
$reviews = $book->getReviews();
// Map the collection of reviews to a collection of output DTO, and
// return them.
return $this->mapCollection(
collection: $reviews,
target: ReviewDto::class,
operation: $operation,
context: $context
);
}
}GET Endpoint for a Subresource
namespace App\ApiResource\User;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Link;
use App\ApiState\User\Review\BookReviewCollectionProvider;
use App\ApiState\User\Review\BookReviewProvider;
#[ApiResource(
shortName: 'User/Review',
routePrefix: '/user',
operations: [
// ...
new Get(
uriTemplate: '/books/{bookId}/reviews/{id}',
provider: BookReviewProvider::class,
uriVariables: [
'bookId' => new Link(
fromClass: BookDto::class,
)
],
),
// ...
]
)]
class ReviewDto
{
// ...
}namespace App\ApiState\User\Review;
use ApiPlatform\Metadata\Operation;
use App\ApiResource\User\ReviewDto;
use App\Repository\BookRepository;
use Rekalogika\ApiLite\Exception\NotFoundException;
use Rekalogika\ApiLite\State\AbstractProvider;
use Symfony\Component\Uid\Uuid;
/**
* @extends AbstractProvider<ReviewDto>
*/
class BookReviewProvider extends AbstractProvider
{
public function __construct(
private BookRepository $bookRepository
) {
}
public function provide(
Operation $operation,
array $uriVariables = [],
array $context = []
): object|array|null {
// Gets the book from the database
$book = $this->bookRepository
->find($uriVariables['bookId'] ?? null)
?? throw new NotFoundException('Book not found');
// Gets the review ID from the URI variables
$reviewId = $uriVariables['id']
?? throw new NotFoundException('Review not found');
// Check the type of the review ID because the next step demands that
// the ID is in UUID format. You don't need this check if you are using
// a plain integer or string ID.
if (!$reviewId instanceof Uuid) {
throw new \InvalidArgumentException('Invalid reviewId');
}
// Get the review from the book's collection of reviews, using the
// review ID.
$review = $book->getReviews()->get($reviewId->toBinary())
?? throw new NotFoundException('Review not found');
// Check for authorization
$this->denyAccessUnlessGranted('view', $review);
// Map the Review to the output DTO, and return it.
return $this->map($review, ReviewDto::class);
}
}Referencing a Collection of Subresources
Using a Collection of IRIs
diff --git a/api-lite/subresource/referencing-subresource.html b/api-lite/subresource/referencing-subresource.html
index 086c8891..c0730989 100644
--- a/api-lite/subresource/referencing-subresource.html
+++ b/api-lite/subresource/referencing-subresource.html
@@ -4,8 +4,8 @@
Referencing a Subresource
Using the IRI of the resource
diff --git a/api-lite/without-abstractstate.html b/api-lite/without-abstractstate.html
index c5f28ae4..23ac6acd 100644
--- a/api-lite/without-abstractstate.html
+++ b/api-lite/without-abstractstate.html
@@ -4,8 +4,8 @@
Usage Without AbstractState
AbstractState
,
diff --git a/assets/js/37355be4.5e13391e.js b/assets/js/37355be4.5e13391e.js
new file mode 100644
index 00000000..509a08ff
--- /dev/null
+++ b/assets/js/37355be4.5e13391e.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkrekalogika_docs=self.webpackChunkrekalogika_docs||[]).push([[6115],{807:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>l,contentTitle:()=>i,default:()=>d,frontMatter:()=>o,metadata:()=>s,toc:()=>c});var a=r(4848),t=r(8453);const o={title:"API Platform Integration"},i=void 0,s={id:"rekapager/framework-integration/api-platform",title:"API Platform Integration",description:"API Platform integration is provided by the package",source:"@site/docs/rekapager/05-framework-integration/02-api-platform.md",sourceDirName:"rekapager/05-framework-integration",slug:"/rekapager/framework-integration/api-platform",permalink:"/rekapager/framework-integration/api-platform",draft:!1,unlisted:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/rekapager/05-framework-integration/02-api-platform.md",tags:[],version:"current",sidebarPosition:2,frontMatter:{title:"API Platform Integration"},sidebar:"docs",previous:{title:"Symfony Integration",permalink:"/rekapager/framework-integration/symfony"},next:{title:"Doctrine Collections Integration",permalink:"/rekapager/framework-integration/doctrine"}},l={},c=[{value:"Installation",id:"installation",level:2},{value:"API Changes",id:"api-changes",level:2},{value:"Provided Components",id:"provided-components",level:2},{value:"Usage in a State Provider or Processor",id:"usage-in-a-state-provider-or-processor",level:2},{value:"Doctrine ORM Support",id:"doctrine-orm-support",level:2}];function p(e){const n={a:"a",code:"code",h2:"h2",li:"li",p:"p",pre:"pre",ul:"ul",...(0,t.R)(),...e.components};return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsxs)(n.p,{children:["API Platform integration is provided by the package\n",(0,a.jsx)(n.code,{children:"rekalogika/rekapager-api-platform"}),"."]}),"\n",(0,a.jsx)(n.h2,{id:"installation",children:"Installation"}),"\n",(0,a.jsx)(n.p,{children:"Preinstallation checklists:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:["Make sure Composer is installed globally, as explained in the ",(0,a.jsx)(n.a,{href:"https://getcomposer.org/doc/00-intro.md",children:"installation\nchapter"})," of the Composer\ndocumentation. Run ",(0,a.jsx)(n.code,{children:"composer about"})," to verify."]}),"\n",(0,a.jsxs)(n.li,{children:["Make sure your project has Symfony Flex installed and enabled (it is enabled\nby default). Run ",(0,a.jsx)(n.code,{children:"composer why symfony/flex"})," to verify."]}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"Open a command console, enter your project directory, and execute:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"composer require rekalogika/rekapager-api-platform\n"})}),"\n",(0,a.jsx)(n.h2,{id:"api-changes",children:"API Changes"}),"\n",(0,a.jsx)(n.p,{children:"This package aims to implement keyset pagination by changing the type of the\nexisting 'page' query parameter from integer to string."}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-diff-json",children:'{\n "@context": "/api/contexts/Post",\n "@id": "/api/posts",\n "@type": "hydra:Collection",\n "hydra:member": [\n ...\n ],\n "hydra:view": {\n "@type": "hydra:PartialCollectionView",\n "@id": "/api/posts",\n- "hydra:last": "/api/posts?page=21",\n- "hydra:next": "/api/posts?page=2"\n+ "hydra:last": "/api/posts?page=q1YqU7KKjtVRKlCy0jXUUcpRssorzcnRUcpXsjLQUSpRslIqVaoFAA",\n+ "hydra:next": "/api/posts?page=q1YqU7KqVsrXy0xRsjI2qNVRKlCyMtJRylGyyivNydFRyleyMtBRKlGyAgrVAgA"\n }\n}\n'})}),"\n",(0,a.jsxs)(n.p,{children:["The change should be transparent to the consumers of the API, provided that they\ntraverse the set by using the URIs as they are returned by the API. But if the\nconsumer increments the page number manually, they need to change how they go to\nthe next page by using the URI provided by the API (",(0,a.jsx)(n.code,{children:"hydra:next"})," and others)."]}),"\n",(0,a.jsx)(n.p,{children:"The change is opt-in and can be enabled per operation or globally."}),"\n",(0,a.jsx)(n.h2,{id:"provided-components",children:"Provided Components"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:["A decorator for ",(0,a.jsx)(n.code,{children:"OpenApiFactoryInterface"})," that changes the type of every\n'page' query parameter from integer to string. It should still be compatible\nwith API Platform's standard pagination system."]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"PagerNormalizer"}),": a normalizer for ",(0,a.jsx)(n.code,{children:"PagerInterface"}),"."]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"RekapagerExtension"}),": an extension for API Platform's Doctrine ORM integration\nto use Rekapager."]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"PagerFactory"}),": creates a ",(0,a.jsx)(n.code,{children:"PagerInterface"})," object from a ",(0,a.jsx)(n.code,{children:"PageableInterface"}),",\nthe current operation, and the context. Useful in a state provider or\nprocessor."]}),"\n"]}),"\n",(0,a.jsx)(n.h2,{id:"usage-in-a-state-provider-or-processor",children:"Usage in a State Provider or Processor"}),"\n",(0,a.jsxs)(n.p,{children:["In a state provider, you can use ",(0,a.jsx)(n.code,{children:"PagerFactoryInterface"})," to transform any\n",(0,a.jsx)(n.code,{children:"PageableInterface"})," into a ",(0,a.jsx)(n.code,{children:"PagerInterface"}),". Then, you can simply return the\npager instance and our ",(0,a.jsx)(n.code,{children:"PagerNormalizer"})," will output it correctly."]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-php",children:"use ApiPlatform\\Metadata\\Operation;\nuse ApiPlatform\\State\\ProviderInterface;\nuse Rekalogika\\Rekapager\\ApiPlatform\\PagerFactoryInterface;\nuse Rekalogika\\Rekapager\\Doctrine\\Collections\\SelectableAdapter;\nuse Rekalogika\\Rekapager\\Keyset\\KeysetPageable;\n\n/**\n * @implements ProviderInterface/g,(function(){return n})).replace(/*\/?)?>/.source),e.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/,e.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s'">]+)/,e.languages.jsx.tag.inside.tag.inside["class-name"]=/^[A-Z]\w*(?:\.[A-Z]\w*)*$/,e.languages.jsx.tag.inside.comment=t.comment,e.languages.insertBefore("inside","attr-name",{spread:{pattern:o(/