Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Add Docs
API Platform Core
https://github.com/api-platform/core
Admin
API Platform Core is a system for creating hypermedia-driven REST and GraphQL APIs with support for
...
Tokens:
28,074
Snippets:
182
Trust Score:
8.3
Update:
5 months ago
Context
Skills
Chat
Benchmark
52.4
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# API Platform Core ## Introduction API Platform Core is a powerful PHP framework for building hypermedia-driven REST and GraphQL APIs. It integrates seamlessly with Symfony and Doctrine ORM, providing a declarative approach to API development using PHP attributes. The framework automatically generates fully-featured APIs with support for JSON-LD, Hydra, OpenAPI, JSON:API, HAL, and other popular formats, eliminating boilerplate code while maintaining flexibility for customization. The framework follows a state-based architecture where resources are declared using attributes, and data flow is controlled through Providers (for reading) and Processors (for writing). It includes built-in support for validation, filtering, pagination, authentication, caching, and comprehensive documentation generation. API Platform emphasizes standards compliance and hypermedia principles, making APIs self-documenting and discoverable while offering extension points at every layer. ## API Resources and Operations ### Declaring a Basic API Resource Define a resource with CRUD operations using the `#[ApiResource]` attribute. ```php <?php namespace App\ApiResource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; #[ApiResource( operations: [ new Get(uriTemplate: '/books/{id}'), new GetCollection(uriTemplate: '/books'), new Post(uriTemplate: '/books'), new Patch(uriTemplate: '/books/{id}'), new Delete(uriTemplate: '/books/{id}'), ] )] class Book { public string $id; public string $title; } // GET /books - Returns collection of books // GET /books/1 - Returns single book // POST /books - Creates new book // PATCH /books/1 - Updates book // DELETE /books/1 - Deletes book ``` ### Doctrine ORM Entity as API Resource Convert Doctrine entities to API resources with automatic persistence. ```php <?php namespace App\Entity; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiResource; use Doctrine\ORM\Mapping as ORM; #[ApiResource] #[ApiFilter(OrderFilter::class)] #[ORM\Entity] class Book { #[ORM\Id, ORM\Column, ORM\GeneratedValue] private ?int $id = null; #[ORM\Column] public ?string $title = null; public function getId(): ?int { return $this->id; } } // Test with curl: // curl -X GET http://localhost/books.jsonld?order[id]=desc // curl -X POST -H "Content-Type: application/ld+json" \ // -d '{"title": "API Platform rocks"}' \ // http://localhost/books ``` ## State Management ### Custom Provider for Reading Data Implement a Provider to control how resources are retrieved. ```php <?php namespace App\ApiResource; use ApiPlatform\Metadata\ApiResource; use App\State\BookProvider; #[ApiResource(provider: BookProvider::class)] class Book { public string $id; public string $title; } namespace App\State; use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; use App\ApiResource\Book; final class BookProvider implements ProviderInterface { public function provide(Operation $operation, array $uriVariables = [], array $context = []): iterable|object|null { // Handle collection requests if ($operation instanceof CollectionOperationInterface) { $book1 = new Book(); $book1->id = '1'; $book1->title = 'First Book'; $book2 = new Book(); $book2->id = '2'; $book2->title = 'Second Book'; return [$book1, $book2]; } // Handle single item requests - use URI variables $book = new Book(); $book->id = $uriVariables['id']; $book->title = 'Book ' . $uriVariables['id']; return $book; } } // GET /books.jsonld returns: // { // "@context": "/contexts/Book", // "@type": "hydra:Collection", // "member": [ // {"@id": "/books/1", "@type": "Book", "id": "1", "title": "First Book"}, // {"@id": "/books/2", "@type": "Book", "id": "2", "title": "Second Book"} // ] // } ``` ### Custom Processor for Writing Data Implement a Processor to control how resources are persisted. ```php <?php namespace App\ApiResource; use ApiPlatform\Metadata\ApiResource; use App\State\BookProcessor; use App\State\BookProvider; #[ApiResource( processor: BookProcessor::class, provider: BookProvider::class )] class Book { public function __construct( public string $id, public string $title ) {} } namespace App\State; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProcessorInterface; use ApiPlatform\State\ProviderInterface; use App\ApiResource\Book; final class BookProcessor implements ProcessorInterface { public function process($data, Operation $operation, array $uriVariables = [], array $context = []): Book { $id = $uriVariables['id'] ?? $data->id; // Persist to JSON file file_put_contents( sprintf('book-%s.json', $id), json_encode($data) ); return $data; } } final class BookProvider implements ProviderInterface { public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?Book { $file = sprintf('book-%s.json', $uriVariables['id']); if (!file_exists($file)) { return null; } $data = json_decode(file_get_contents($file)); return new Book($data->id, $data->title); } } // curl -X POST -H "Content-Type: application/ld+json" \ // -d '{"id": "1", "title": "API Platform rocks"}' \ // http://localhost/books ``` ## Filtering and Search ### Doctrine Search Filter Enable filtering on resource properties with configurable strategies. ```php <?php namespace App\Entity; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\QueryParameter; use Doctrine\ORM\Mapping as ORM; #[GetCollection( uriTemplate: 'books{._format}', parameters: [ ':property' => new QueryParameter(filter: 'app.search_filter'), ] )] #[ORM\Entity] class Book { #[ORM\Id, ORM\Column, ORM\GeneratedValue] public ?int $id = null; #[ORM\Column] public ?string $title = null; #[ORM\Column] public ?string $author = null; } namespace App\DependencyInjection; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; function configure(ContainerConfigurator $configurator): void { $services = $configurator->services(); $services->set('app.search_filter') ->parent('api_platform.doctrine.orm.search_filter') ->args([['author' => 'partial', 'title' => 'partial']]) ->tag('api_platform.filter'); } // curl http://localhost/books.jsonld?author=tolkien // curl http://localhost/books.jsonld?title=lord&author=tolkien // Available strategies: exact, partial, start, end, word_start ``` ### Custom Pagination Implement custom pagination logic with API Platform's Paginator wrapper. ```php <?php namespace App\Entity; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GetCollection; use App\State\BooksListProvider; use Doctrine\ORM\Mapping as ORM; #[ApiResource( operations: [ new GetCollection(provider: BooksListProvider::class), ] )] #[ORM\Entity(repositoryClass: BookRepository::class)] class Book { #[ORM\Id, ORM\Column, ORM\GeneratedValue] public ?int $id = null; #[ORM\Column] public ?string $title = null; #[ORM\Column(name: 'is_published', type: 'boolean')] public ?bool $published = null; } namespace App\Repository; use App\Entity\Book; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator; use Doctrine\Persistence\ManagerRegistry; class BookRepository extends ServiceEntityRepository { public function __construct(ManagerRegistry $registry) { parent::__construct($registry, Book::class); } public function getPublishedBooks(int $page = 1, int $itemsPerPage = 30): DoctrinePaginator { return new DoctrinePaginator( $this->createQueryBuilder('b') ->where('b.published = :isPublished') ->setParameter('isPublished', true) ->addCriteria( Criteria::create() ->setFirstResult(($page - 1) * $itemsPerPage) ->setMaxResults($itemsPerPage) ) ); } } namespace App\State; use ApiPlatform\Doctrine\Orm\Paginator; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\Pagination\Pagination; use ApiPlatform\State\ProviderInterface; use App\Repository\BookRepository; class BooksListProvider implements ProviderInterface { public function __construct( private readonly BookRepository $bookRepository, private readonly Pagination $pagination ) {} public function provide(Operation $operation, array $uriVariables = [], array $context = []): Paginator { [$page, , $limit] = $this->pagination->getPagination($operation, $context); return new Paginator($this->bookRepository->getPublishedBooks($page, $limit)); } } // curl http://localhost/books.jsonld // Returns paginated response with view information: // { // "@context": "/contexts/Book", // "@type": "hydra:Collection", // "totalItems": 35, // "member": [...], // "view": { // "@id": "/books.jsonld?page=1", // "@type": "PartialCollectionView", // "first": "/books.jsonld?page=1", // "last": "/books.jsonld?page=2", // "next": "/books.jsonld?page=2" // } // } ``` ## Validation ### Input Validation with Symfony Validator Validate incoming data using Symfony constraints and custom validators. ```php <?php namespace App\Entity; use ApiPlatform\Metadata\ApiResource; use App\Validator\Constraints\MinimalProperties; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; #[ORM\Entity] #[ApiResource] class Product { #[ORM\Id, ORM\Column, ORM\GeneratedValue] private ?int $id = null; #[ORM\Column] #[Assert\NotBlank] public string $name; #[MinimalProperties] #[ORM\Column(type: 'json')] public array $properties; public function getId(): ?int { return $this->id; } } namespace App\Validator\Constraints; use Symfony\Component\Validator\Constraint; #[\Attribute] class MinimalProperties extends Constraint { public $message = 'The product must have the minimal properties required ("description", "price")'; } namespace App\Validator\Constraints; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; final class MinimalPropertiesValidator extends ConstraintValidator { public function validate($value, Constraint $constraint): void { if (!\array_key_exists('description', $value) || !\array_key_exists('price', $value)) { $this->context->buildViolation($constraint->message)->addViolation(); } } } // curl -X POST -H "Content-Type: application/ld+json" \ // -d '{"name": "test", "properties": {"description": "foo"}}' \ // http://localhost/products // // Returns 422 Unprocessable Entity: // { // "@context": "/contexts/ConstraintViolationList", // "@type": "ConstraintViolationList", // "title": "An error occurred", // "description": "properties: The product must have the minimal properties required...", // "violations": [ // { // "propertyPath": "properties", // "message": "The product must have the minimal properties required..." // } // ] // } ``` ## Subresources ### Declaring Resource Relationships Define subresources to access related entities through parent resources. ```php <?php namespace App\Entity; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Post; use Doctrine\ORM\Mapping as ORM; #[ApiResource( operations: [new Post()] )] #[ApiResource( uriTemplate: '/companies/{companyId}/employees/{id}', uriVariables: [ 'companyId' => new Link(fromClass: Company::class, toProperty: 'company'), 'id' => new Link(fromClass: Employee::class), ], operations: [new Get()] )] #[ApiResource( uriTemplate: '/companies/{companyId}/employees', uriVariables: [ 'companyId' => new Link(fromClass: Company::class, toProperty: 'company'), ], operations: [new GetCollection()] )] #[ORM\Entity] class Employee { #[ORM\Id, ORM\Column, ORM\GeneratedValue] public ?int $id; #[ORM\Column] public string $name; #[ORM\ManyToOne(targetEntity: Company::class)] public ?Company $company; } #[ORM\Entity] #[ApiResource] class Company { #[ORM\Id, ORM\Column, ORM\GeneratedValue] public ?int $id; #[ORM\Column] public string $name; } // curl http://localhost/companies/1/employees // curl http://localhost/companies/1/employees/5 // curl -X POST -H "Content-Type: application/ld+json" \ // -d '{"name": "John Doe", "company": "/companies/1"}' \ // http://localhost/employees ``` ## Documentation ### Extending OpenAPI Documentation Customize OpenAPI specification for operations. ```php <?php namespace App\ApiResource; use ApiPlatform\Metadata\Post; use ApiPlatform\OpenApi\Model\Operation; use ApiPlatform\OpenApi\Model\RequestBody; use ApiPlatform\OpenApi\Model\Response; #[Post( openapi: new Operation( responses: [ '200' => new Response(description: 'Ok'), ], summary: 'Add a book to the library.', description: 'My awesome operation', requestBody: new RequestBody( content: new \ArrayObject([ 'application/ld+json' => [ 'schema' => [ 'properties' => [ 'id' => ['type' => 'integer', 'required' => true, 'description' => 'id'], ], ], 'example' => [ 'id' => 12345, ], ], ]) ) ) )] class Book { public int $id; public string $title; } // curl http://localhost/docs.json // OpenAPI spec includes custom documentation ``` ## HTTP Caching ### Custom Cache Tags Customize HTTP cache tags for cache invalidation strategies. ```php <?php namespace App\ApiResource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\Operation; #[ApiResource( operations: [ new Get(provider: Book::class.'::provide'), ], )] class Book { #[ApiProperty(identifier: true)] public string $id = "unique-id-1"; public string $title; public static function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null { $book = new self(); $book->title = 'Sample Book'; return $book; } } namespace App\Service; use ApiPlatform\Serializer\TagCollectorInterface; use App\ApiResource\Book; class TagCollector implements TagCollectorInterface { public function collect(array $context = []): void { if (isset($context['property_metadata'])) { return; } $iri = $context['iri'] ?? null; $object = $context['object'] ?? null; if ($object && $object instanceof Book) { $iri = $object->id; } if (!$iri) { return; } $context['resources'][$iri] = $iri; } } namespace App\DependencyInjection; use App\Service\TagCollector; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; function configure(ContainerConfigurator $configurator): void { $services = $configurator->services(); $services->set('api_platform.http_cache.tag_collector', TagCollector::class); } // curl -I http://localhost/books/unique-id-1.jsonld // Response includes: // Cache-Tags: unique-id-1 ``` ## Testing ### API Test Case Test API endpoints with built-in test utilities. ```php <?php namespace App\Tests; use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; use App\ApiResource\Book; final class BookTest extends ApiTestCase { public function testBookDoesNotExists(): void { $client = static::createClient(); $client->request(method: 'GET', url: '/books/1'); $this->assertResponseStatusCodeSame(404); $this->assertJsonContains([ 'detail' => 'Not Found', ]); } public function testGetCollection(): void { $response = static::createClient()->request(method: 'GET', url: '/books'); $this->assertMatchesResourceCollectionJsonSchema(Book::class); $this->assertCount(0, $response->toArray()['member']); } public function testCreateBook(): void { $response = static::createClient()->request(method: 'POST', url: '/books', options: [ 'json' => ['title' => 'New Book'], 'headers' => ['content-type' => 'application/ld+json'], ]); $this->assertResponseStatusCodeSame(201); $this->assertJsonContains([ '@type' => 'Book', 'title' => 'New Book', ]); } } // Run tests: // php bin/phpunit tests/BookTest.php ``` ## Summary API Platform Core streamlines API development by providing a declarative, attribute-based approach to resource definition. The primary use cases include building REST APIs for modern web and mobile applications, creating GraphQL endpoints, exposing database entities through standardized interfaces, and implementing microservices with rich hypermedia support. The framework excels at rapid prototyping while maintaining production-grade quality through built-in features like validation, filtering, pagination, and comprehensive OpenAPI documentation. Integration patterns typically involve declaring resources as PHP classes with attributes, leveraging Doctrine ORM for persistence, implementing custom Providers and Processors for specialized data handling, and extending functionality through Symfony's dependency injection container. The framework supports multiple serialization formats out of the box, enabling seamless content negotiation. Developers can start with zero configuration for simple CRUD operations and progressively customize behavior at any layer - from operation-level hooks to global event listeners - making it suitable for projects ranging from simple REST APIs to complex, standards-compliant hypermedia systems with advanced caching and real-time update capabilities.