Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Add Docs
API Platform for Laravel
https://github.com/api-platform/laravel
Admin
API Platform for Laravel integrates the Laravel framework and Illuminate components with the API
...
Tokens:
15,700
Snippets:
92
Trust Score:
8.3
Update:
2 weeks ago
Context
Skills
Chat
Benchmark
86
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# API Platform for Laravel API Platform for Laravel (`api-platform/laravel`) is a full-featured REST and GraphQL framework that integrates the [API Platform](https://api-platform.com) ecosystem directly into Laravel applications. It auto-generates CRUD endpoints, OpenAPI/Swagger documentation, JSON-LD/Hydra hypermedia, JSON:API, and HAL responses from plain Eloquent models or plain PHP classes decorated with PHP attributes. The package wires itself into Laravel's service container via three service providers (`ApiPlatformProvider`, `ApiPlatformDeferredProvider`, `ApiPlatformEventProvider`) and requires PHP ≥ 8.2 with Laravel 11, 12, or 13. The core design centers on two extension points: **State Providers** (read data) and **State Processors** (write/delete data). Eloquent-aware default implementations ship out of the box (`CollectionProvider`, `ItemProvider`, `PersistProcessor`, `RemoveProcessor`), so annotating a model with `#[ApiResource]` is sufficient to get a fully-functional REST API with pagination, filtering, validation, content-negotiation, and OpenAPI docs. Optional packages enable GraphQL (via `api-platform/graphql`), HTTP cache invalidation (`api-platform/http-cache`), and Model Context Protocol support for AI agents (`api-platform/mcp`). --- ## Installation Install the package and publish assets/config. ```bash composer require api-platform/laravel # Publish configuration to config/api-platform.php and public assets php artisan api-platform:install ``` --- ## Configuration — `config/api-platform.php` Central configuration file controlling formats, pagination, GraphQL, Swagger UI, caching, and more. ```php // config/api-platform.php (published after install) return [ 'title' => 'My API', 'description' => 'The best API ever', 'version' => '1.0.0', // Directories scanned for #[ApiResource] classes 'resources' => [ app_path('Models'), app_path('ApiResource'), ], // Enabled response formats 'formats' => [ 'jsonld' => ['application/ld+json'], 'json' => ['application/json'], 'jsonapi' => ['application/vnd.api+json'], // opt-in 'csv' => ['text/csv'], // opt-in ], 'patch_formats' => [ 'json' => ['application/merge-patch+json'], ], // When true, PATCH replaces 'required' rules with 'sometimes' 'partial_patch_validation' => false, 'defaults' => [ 'pagination_enabled' => true, 'pagination_items_per_page' => 30, 'pagination_maximum_items_per_page' => 30, 'pagination_client_items_per_page' => false, 'pagination_client_enabled' => false, 'route_prefix' => '/api', 'middleware' => [], ], 'pagination' => [ 'page_parameter_name' => 'page', 'items_per_page_parameter_name' => 'itemsPerPage', 'enabled_parameter_name' => 'pagination', 'partial_parameter_name' => 'partial', ], 'graphql' => [ 'enabled' => false, // set true + require api-platform/graphql 'nesting_separator' => '__', 'introspection' => ['enabled' => true], 'max_query_complexity' => 500, 'max_query_depth' => 200, ], 'swagger_ui' => ['enabled' => true], 'scalar' => ['enabled' => true], 'redoc' => ['enabled' => true], // 'file' or 'apcu' recommended for production 'cache' => 'file', 'name_converter' => \Symfony\Component\Serializer\NameConverter\SnakeCaseToCamelCaseNameConverter::class, 'exception_to_status' => [ \Illuminate\Auth\AuthenticationException::class => 401, \Illuminate\Auth\Access\AuthorizationException::class => 403, ], 'error_handler' => [ 'extend_laravel_handler' => true, // integrates with Laravel's exception handler ], // HTTP Cache (requires api-platform/http-cache) // 'http_cache' => [ // 'etag' => true, // 'max_age' => 60, // 'shared_max_age' => 3600, // ], ]; ``` --- ## `#[ApiResource]` — Exposing an Eloquent Model Attach `#[ApiResource]` to any Eloquent model (or plain PHP class) to generate full CRUD REST operations automatically. ```php <?php // app/Models/Book.php namespace App\Models; use ApiPlatform\Laravel\Eloquent\Filter\BooleanFilter; use ApiPlatform\Laravel\Eloquent\Filter\DateFilter; use ApiPlatform\Laravel\Eloquent\Filter\EqualsFilter; use ApiPlatform\Laravel\Eloquent\Filter\OrderFilter; use ApiPlatform\Laravel\Eloquent\Filter\PartialSearchFilter; use ApiPlatform\Laravel\Eloquent\Filter\RangeFilter; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Put; use ApiPlatform\Metadata\QueryParameter; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; #[ApiResource( paginationEnabled: true, paginationItemsPerPage: 5, paginationClientItemsPerPage: true, operations: [ new Get(), new GetCollection(), new Post(), new Put(), new Patch(), new Delete(), ] )] // Query parameters bound to Eloquent filter classes: #[QueryParameter(key: 'title', filter: PartialSearchFilter::class)] #[QueryParameter(key: 'author', filter: EqualsFilter::class)] #[QueryParameter(key: 'publicationDate', filter: DateFilter::class, property: 'publication_date')] #[QueryParameter(key: 'price', filter: RangeFilter::class)] #[QueryParameter(key: 'available', filter: BooleanFilter::class)] #[QueryParameter(key: 'order[:property]', filter: OrderFilter::class)] class Book extends Model { protected $fillable = ['title', 'author', 'isbn', 'publication_date', 'price', 'available']; protected $casts = ['available' => 'boolean']; public function author(): BelongsTo { return $this->belongsTo(Author::class); } } // Generated REST routes (prefix /api by default): // GET /api/books → collection with pagination // POST /api/books → create // GET /api/books/{id} → single item // PUT /api/books/{id} → full replace // PATCH /api/books/{id} → partial update // DELETE /api/books/{id} → delete // Example requests: // curl https://example.com/api/books // curl "https://example.com/api/books?title=laravel&order[title]=asc&price[gte]=10&price[lte]=50" // curl -X POST https://example.com/api/books \ // -H 'Content-Type: application/ld+json' \ // -d '{"title":"Clean Code","author":"Robert Martin","isbn":"9780132350884","price":39.99}' ``` --- ## `#[ApiResource]` with a Separate DTO (ObjectMapper) Use a plain PHP DTO class as the API surface while keeping the Eloquent model as the persistence layer, via `stateOptions` and `#[Map]`. ```php <?php // app/ApiResource/Product.php namespace App\ApiResource; use ApiPlatform\Laravel\Eloquent\State\Options; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; use App\Models\Product as ProductModel; use Symfony\Component\ObjectMapper\Attribute\Map; #[ApiResource( shortName: 'Product', stateOptions: new Options(modelClass: ProductModel::class), )] #[Map(source: ProductModel::class)] // auto-maps ProductModel → Product DTO class Product { #[ApiProperty(identifier: true)] public ?int $id = null; public ?string $name = null; public ?float $price = null; } // app/Models/Product.php — the actual Eloquent model (not exposed directly) namespace App\Models; use Illuminate\Database\Eloquent\Model; class Product extends Model { protected $fillable = ['name', 'price']; } // curl https://example.com/api/products // Response (JSON-LD): // { // "@context": "/api/contexts/Product", // "@id": "/api/products", // "@type": "hydra:Collection", // "hydra:member": [ // {"@id": "/api/products/1", "@type": "Product", "id": 1, "name": "Widget", "price": 9.99} // ], // "hydra:totalItems": 1 // } ``` --- ## `#[ApiResource]` with a Custom State Provider Provide data from any source (external API, cache, etc.) by implementing `ProviderInterface` and setting `provider`. ```php <?php // app/State/BookProvider.php namespace App\State; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; class BookProvider implements ProviderInterface { public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null { // Fetch from external source, cache, etc. if ($uriVariables) { return Book::findOrFail($uriVariables['id']); } return Book::paginate(10); } } // app/Models/Book.php — wire the provider: use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GetCollection; use App\State\BookProvider; #[ApiResource( operations: [new GetCollection(provider: BookProvider::class)] )] class Book extends Model {} // Or use a static method directly on the resource class (no dedicated provider class needed): #[ApiResource(provider: [self::class, 'provide'])] class Staff { public static function provide(): array { return []; // return items from anywhere } } ``` --- ## `#[ApiResource]` with a Custom State Processor Intercept write operations (create/update/delete) to run custom logic such as sending emails, dispatching jobs, or calling external services. ```php <?php // app/State/BookPersistProcessor.php namespace App\State; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProcessorInterface; use Illuminate\Support\Facades\Mail; class BookPersistProcessor implements ProcessorInterface { public function __construct( private readonly ProcessorInterface $inner // decorate the default PersistProcessor ) {} public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed { $result = $this->inner->process($data, $operation, $uriVariables, $context); // Custom side-effect after persistence Mail::to('admin@example.com')->send(new BookCreatedMail($result)); return $result; } } // app/Models/Book.php — wire the processor: use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Post; use App\State\BookPersistProcessor; #[ApiResource( operations: [new Post(processor: BookPersistProcessor::class)] )] class Book extends Model {} ``` --- ## Artisan Generator Commands Scaffold boilerplate for custom state providers, processors, and filters using Artisan. ```bash # Generate a state provider skeleton in app/State/ php artisan make:state-provider # Generates: app/State/YourNameProvider.php # implementing ApiPlatform\State\ProviderInterface # Generate a state processor skeleton in app/State/ php artisan make:state-processor # Generates: app/State/YourNameProcessor.php # implementing ApiPlatform\State\ProcessorInterface # Generate a custom Eloquent filter skeleton in app/Filter/ php artisan make:filter # Generates: app/Filter/YourNameFilter.php # implementing ApiPlatform\Laravel\Eloquent\Filter\FilterInterface # AND automatically tags it in AppServiceProvider for DI # Export the full OpenAPI specification as JSON php artisan api:openapi:export php artisan api:openapi:export --output=openapi.json ``` --- ## `ApiPlatformMiddleware` — Operation Resolution The middleware resolves the current API Platform operation from the route and sets the response format from the URL extension (e.g., `.json`, `.jsonld`). ```php <?php // The middleware is applied automatically to all API Platform routes via routes/api.php. // To apply it manually to custom routes: use ApiPlatform\Laravel\ApiPlatformMiddleware; use Illuminate\Support\Facades\Route; // In routes/api.php or a RouteServiceProvider: Route::get('/custom-endpoint', MyController::class) ->middleware(ApiPlatformMiddleware::class . ':_api_my_operation_name'); // The middleware: // 1. Reads the operation name from the route parameter // 2. Fetches the OperationMetadata and stores it in $request->attributes->get('_api_operation') // 3. Parses the format suffix (e.g., /books.json → format = 'json') and sets '_format' attribute // 4. Calls $next($request) — fully transparent to the controller // Accessing the resolved operation inside a controller or provider: public function provide(Operation $operation, array $uriVariables = [], array $context = []): mixed { $request = $context['request']; // Illuminate\Http\Request $resolvedOp = $request->attributes->get('_api_operation'); // HttpOperation instance $format = $request->attributes->get('_format'); // e.g. 'json', 'jsonld', '' } ``` --- ## Eloquent Filters ### `EqualsFilter` — Exact match filter Filters a collection by an exact value on a property, supports nested relation joins. ```php use ApiPlatform\Laravel\Eloquent\Filter\EqualsFilter; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\QueryParameter; use Illuminate\Database\Eloquent\Model; #[ApiResource] #[QueryParameter(key: 'status', filter: EqualsFilter::class)] #[QueryParameter(key: 'author', filter: EqualsFilter::class, property: 'author_id')] class Book extends Model {} // GET /api/books?status=published // GET /api/books?author=42 ``` --- ### `PartialSearchFilter` — LIKE %value% search Performs a case-insensitive partial string match (`LIKE '%value%'`) on the target column, including across relations. ```php use ApiPlatform\Laravel\Eloquent\Filter\PartialSearchFilter; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\QueryParameter; use Illuminate\Database\Eloquent\Model; #[ApiResource] #[QueryParameter(key: 'title', filter: PartialSearchFilter::class)] #[QueryParameter(key: 'isbn', filter: PartialSearchFilter::class, constraints: 'min:2')] class Book extends Model {} // GET /api/books?title=laravel // → WHERE title LIKE '%laravel%' // GET /api/books?isbn=978 // → WHERE isbn LIKE '%978%' ``` --- ### `StartSearchFilter` / `EndSearchFilter` — Prefix and suffix search `StartSearchFilter` matches `LIKE 'value%'`; `EndSearchFilter` matches `LIKE '%value'`. ```php use ApiPlatform\Laravel\Eloquent\Filter\EndSearchFilter; use ApiPlatform\Laravel\Eloquent\Filter\StartSearchFilter; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\QueryParameter; use Illuminate\Database\Eloquent\Model; #[ApiResource] #[QueryParameter(key: 'nameStart', filter: StartSearchFilter::class, property: 'name')] #[QueryParameter(key: 'nameEnd', filter: EndSearchFilter::class, property: 'name')] class Author extends Model {} // GET /api/authors?nameStart=Jo → WHERE name LIKE 'Jo%' // GET /api/authors?nameEnd=son → WHERE name LIKE '%son' ``` --- ### `BooleanFilter` — Boolean column filter Accepts `true`, `false`, `1`, or `0` as the query value and applies a `WHERE` on the target boolean column. ```php use ApiPlatform\Laravel\Eloquent\Filter\BooleanFilter; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\QueryParameter; use Symfony\Component\TypeInfo\Type\BuiltinType; use Symfony\Component\TypeInfo\TypeIdentifier; use Illuminate\Database\Eloquent\Model; #[ApiResource] #[QueryParameter( key: 'published', filter: BooleanFilter::class, nativeType: new BuiltinType(TypeIdentifier::BOOL) // advertised in OpenAPI schema )] class Book extends Model {} // GET /api/books?published=true → WHERE published = 'true' // GET /api/books?published=0 → WHERE published = '0' ``` --- ### `RangeFilter` — Numeric range filter Filters by `gt`, `lt`, `gte`, `lte` operators on a numeric column. Exposes four OpenAPI parameters automatically. ```php use ApiPlatform\Laravel\Eloquent\Filter\RangeFilter; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\QueryParameter; use Illuminate\Database\Eloquent\Model; #[ApiResource] #[QueryParameter(key: 'price', filter: RangeFilter::class)] #[QueryParameter(key: 'isbn', filter: RangeFilter::class, property: 'isbn')] class Book extends Model {} // GET /api/books?price[gte]=10&price[lte]=50 // → WHERE price >= 10 AND price <= 50 // GET /api/books?price[gt]=0 // → WHERE price > 0 ``` --- ### `DateFilter` — Date/datetime range filter Filters on date columns using `eq`, `gt`, `lt`, `gte`, `lte` operators. Supports an `include_nulls` context option to also return rows where the column is NULL. ```php use ApiPlatform\Laravel\Eloquent\Filter\DateFilter; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\QueryParameter; use Illuminate\Database\Eloquent\Model; #[ApiResource] #[QueryParameter( key: 'publishedAfter', filter: DateFilter::class, property: 'publication_date' )] #[QueryParameter( key: 'publishedAfterOrUnknown', filter: DateFilter::class, property: 'publication_date', filterContext: ['include_nulls' => true] )] class Book extends Model {} // GET /api/books?publishedAfter[gte]=2020-01-01&publishedAfter[lte]=2023-12-31 // → WHERE DATE(publication_date) >= '2020-01-01' AND DATE(publication_date) <= '2023-12-31' // GET /api/books?publishedAfterOrUnknown[gte]=2020-01-01 // → WHERE (DATE(publication_date) >= '2020-01-01' OR publication_date IS NULL) ``` --- ### `OrderFilter` — Sort filter Sorts the collection by a column ASC or DESC. Supports nested relation sorting via LEFT JOINs. ```php use ApiPlatform\Laravel\Eloquent\Filter\OrderFilter; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\QueryParameter; use Illuminate\Database\Eloquent\Model; #[ApiResource] #[QueryParameter(key: 'orderByTitle', filter: OrderFilter::class, property: 'title')] #[QueryParameter( key: 'order[:property]', // wildcard for multi-column ordering filter: OrderFilter::class, properties: ['title' => 'title', 'price' => 'price'] )] class Book extends Model {} // GET /api/books?orderByTitle=asc // → ORDER BY title ASC // GET /api/books?order[price]=desc&order[title]=asc // → ORDER BY price DESC, title ASC ``` --- ### `OrFilter` — OR-condition wrapper Wraps any other filter so that multiple values are combined with `OR` instead of `AND`. ```php use ApiPlatform\Laravel\Eloquent\Filter\EqualsFilter; use ApiPlatform\Laravel\Eloquent\Filter\OrFilter; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\QueryParameter; use Illuminate\Database\Eloquent\Model; #[ApiResource] #[QueryParameter( key: 'name[]', filter: new OrFilter(new EqualsFilter()), property: 'name' )] class Book extends Model {} // GET /api/books?name[]=Clean+Code&name[]=Refactoring // → WHERE (name = 'Clean Code' OR name = 'Refactoring') ``` --- ### JSON:API `SortFilter` — JSON:API compliant sorting Implements the [JSON:API sort parameter](https://jsonapi.org/format/#fetching-sorting) (`sort=field,-otherField`). Requires `SortFilterParameterProvider` to parse the sort string into an ordered map. ```php use ApiPlatform\Laravel\Eloquent\Filter\JsonApi\SortFilter; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\QueryParameter; use Illuminate\Database\Eloquent\Model; #[ApiResource] #[QueryParameter(key: 'sort', filter: SortFilter::class)] class Book extends Model {} // GET /api/books?sort=title,-price // → ORDER BY title ASC, price DESC ``` --- ## `CollectionProvider` — Eloquent Collection State Provider Reads a paginated or full list of Eloquent models, applies query extensions (filters), handles relation links, and returns an API Platform `Paginator` or raw `Collection`. ```php <?php // CollectionProvider is the default provider for GetCollection operations on Eloquent models. // To customise behaviour, implement QueryExtensionInterface: namespace App\Eloquent\Extension; use ApiPlatform\Laravel\Eloquent\Extension\QueryExtensionInterface; use ApiPlatform\Metadata\Operation; use Illuminate\Database\Eloquent\Builder; class ActiveOnlyExtension implements QueryExtensionInterface { public function apply(Builder $builder, array $uriVariables, Operation $operation, array $context): Builder { // Automatically applied to EVERY query through CollectionProvider / ItemProvider return $builder->where('active', true); } } // Register in AppServiceProvider: // $this->app->tag(ActiveOnlyExtension::class, QueryExtensionInterface::class); // CollectionProvider respects pagination config: // GET /api/books → paginate(30) by default // GET /api/books?page=2 → paginate page 2 // GET /api/books?itemsPerPage=5 → paginate(5) if clientItemsPerPage enabled // GET /api/books?pagination=false → get() — no pagination (if client-enabled) ``` --- ## `ItemProvider` — Eloquent Single-Item State Provider Resolves one Eloquent model by its URI variables (primary key or custom identifier), supporting relation constraints and query extensions. ```php <?php // ItemProvider is the default provider for Get (single-item) operations. // Custom links handler example: use ApiPlatform\Laravel\Eloquent\State\LinksHandlerInterface; use ApiPlatform\Metadata\Operation; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; class CustomLinksHandler implements LinksHandlerInterface { public function handleLinks(Builder $builder, array $uriVariables, array $context): Builder { // e.g., scope by tenant return $builder->where('tenant_id', auth()->user()->tenant_id) ->where('id', $uriVariables['id'] ?? null); } } // GET /api/books/01J3K9QW00A1B2C3D4E5F6G7H8 // → SELECT * FROM books WHERE tenant_id = ? AND id = ? LIMIT 1 // Returns 404 automatically if null (no result) ``` --- ## `PersistProcessor` — Eloquent Save Processor Handles `POST`, `PUT`, and `PATCH` write operations. Automatically manages `BelongsTo`, `HasMany`, and `MorphMany` relations by saving related models and associating/dissociating as needed. ```php <?php // PersistProcessor is the default for Post/Put/Patch on Eloquent models. // It handles relation saving automatically: // Example: POST /api/books with a nested author relation // Request body (application/ld+json): // { // "title": "Laravel Up & Running", // "author": {"name": "Matt Stauffer"} // } // PersistProcessor will: // 1. Detect the BelongsTo 'author' relation // 2. Save the new Author model if it doesn't exist // 3. Associate it with the Book via author_id // 4. Call $book->saveOrFail() then $book->refresh() // For PUT (full replacement), PersistProcessor preserves the primary key from the // existing record ($previousData) to prevent key conflicts: // PUT /api/books/42 → $data->id is forced to 42 even if omitted in body // RemoveProcessor (for DELETE) is equally simple: // DELETE /api/books/42 → $book->delete() → returns null (204 No Content) ``` --- ## `ValidateProvider` — Laravel Validation Integration Wraps the deserialization pipeline to run Laravel validation rules (array rules or a `FormRequest` class) before the data reaches the processor. ```php <?php // Validation via inline rules array: use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Post; use Illuminate\Database\Eloquent\Model; #[ApiResource( operations: [ new Post(rules: [ 'title' => 'required|string|max:255', 'isbn' => 'required|string|unique:books,isbn', 'price' => 'required|numeric|min:0', ]) ] )] class Book extends Model {} // Validation via FormRequest class (preferred — supports authorization too): // app/Http/Requests/BookFormRequest.php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class BookFormRequest extends FormRequest { public function authorize(): bool { return true; } public function rules(): array { return [ 'title' => 'required|string|max:255', 'isbn' => 'required|string', 'price' => 'numeric|min:0', ]; } } // Wire to the resource: #[ApiResource(rules: BookFormRequest::class)] class Book extends Model {} // On validation failure the API returns HTTP 422 with application/problem+json: // { // "type": "https://tools.ietf.org/html/rfc2616#section-10", // "title": "An error occurred", // "detail": "title: This value should not be blank.", // "violations": [{"propertyPath": "title", "message": "This value should not be blank."}] // } ``` --- ## `ResourceAccessChecker` — Laravel Gate / Policy Integration Bridges API Platform's security expressions (`security`, `securityPostDenormalize`) with Laravel's `Gate::allows()` / policies. ```php <?php // Define a Laravel policy (php artisan make:policy BookPolicy --model=Book): namespace App\Policies; use App\Models\Book; use App\Models\User; class BookPolicy { public function update(User $user, Book $book): bool { return $user->id === $book->user_id; } public function delete(User $user, Book $book): bool { return $user->is_admin; } } // Register in AuthServiceProvider: // protected $policies = [Book::class => BookPolicy::class]; // Use security expressions on operations: use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Patch; use Illuminate\Database\Eloquent\Model; #[ApiResource( operations: [ new Patch(security: 'is_granted("update", object)'), new Delete(security: 'is_granted("delete", object)'), ] )] class Book extends Model {} // is_granted("update", object) calls Gate::allows('update', $book) // → resolves to BookPolicy::update($currentUser, $book) // Returns HTTP 403 if denied ``` --- ## HTTP Routes — Auto-Generated Endpoints All API Platform routes are registered automatically via `routes/api.php` during the `boot()` phase. The route prefix, domain, and middleware are all configurable. ```php // All routes are generated at boot time; these are the built-in special routes: // (assuming route_prefix = '/api') // GET /api/ → Hydra API entrypoint (list of all resource IRIs) // GET /api/docs{.format?} → OpenAPI documentation (JSON / HTML / Swagger UI) // GET /api/contexts/{shortName} → JSON-LD context document // GET /api/.well-known/genid/{id} → Skolem IRI (blank node support) // When GraphQL is enabled: // GET|POST /api/graphql → GraphQL endpoint // GET /api/graphiql → GraphiQL browser IDE // MCP endpoint (when api-platform/mcp is installed): // GET|POST|DELETE|OPTIONS /mcp → Model Context Protocol for AI agents // Customise the prefix and add global middleware in config: // 'defaults' => ['route_prefix' => '/v1'], // 'routes' => ['middleware' => ['auth:sanctum']], // curl examples: // curl https://example.com/api/ // curl https://example.com/api/docs.json // curl -H 'Accept: application/ld+json' https://example.com/api/books // curl https://example.com/api/books.jsonld // curl https://example.com/api/books.json?page=2&itemsPerPage=10 ``` --- ## GraphQL Support Enable GraphQL by installing `api-platform/graphql` and setting `graphql.enabled = true`. Operations are declared with GraphQL-specific attributes. ```php <?php // composer require api-platform/graphql // config/api-platform.php: // 'graphql' => ['enabled' => true] use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GraphQl\Mutation; use ApiPlatform\Metadata\GraphQl\Query; use ApiPlatform\Metadata\GraphQl\QueryCollection; use ApiPlatform\Metadata\QueryParameter; use ApiPlatform\Laravel\Eloquent\Filter\OrderFilter; use Illuminate\Database\Eloquent\Model; #[ApiResource( graphQlOperations: [ new Query(), // book(id: ID!): Book new QueryCollection( // books(...): BookConnection paginationItemsPerPage: 5, parameters: [ new QueryParameter(key: 'order[:property]', filter: OrderFilter::class), ] ), new QueryCollection( paginationItemsPerPage: 3, name: 'simplePagination', paginationType: 'page', // cursor-free page-based pagination ), new Mutation(name: 'create'), // createBook(input: CreateBookInput!): CreateBookPayload ] )] class Book extends Model {} // GraphQL query example: // POST /api/graphql // Content-Type: application/json // { // "query": "{ books { edges { node { id title price } } totalCount } }" // } // Mutation example: // { // "query": "mutation { createBook(input: {title: \"DDD\", price: 49.99}) { book { id title } } }" // } ``` --- ## MCP (Model Context Protocol) Support Expose API resources as AI-agent tools via the Model Context Protocol by installing `api-platform/mcp`. ```php <?php // composer require api-platform/mcp symfony/mcp-bundle // config/api-platform.php: // 'mcp' => ['enabled' => true] // All #[ApiResource] classes are automatically registered as MCP tools. // The /mcp endpoint handles GET/POST/DELETE/OPTIONS using the Symfony MCP bundle controller. // To mark a specific resource as an MCP tool with custom metadata: use ApiPlatform\Mcp\Attribute\McpTool; use ApiPlatform\Metadata\ApiResource; use Illuminate\Database\Eloquent\Model; #[ApiResource] #[McpTool(description: 'Manages the book catalog for the library system')] class Book extends Model {} // AI agent interaction (MCP JSON-RPC 2.0): // POST /mcp // { // "jsonrpc": "2.0", // "method": "tools/call", // "params": { // "name": "listBooks", // "arguments": {"page": 1, "itemsPerPage": 5} // }, // "id": 1 // } ``` --- ## Custom Eloquent Filter via `make:filter` Implement `FilterInterface` to create reusable, tagged, auto-wired Eloquent query modifiers. ```php <?php // Generated skeleton after: php artisan make:filter // app/Filter/ActiveFilter.php namespace App\Filter; use ApiPlatform\Laravel\Eloquent\Filter\FilterInterface; use ApiPlatform\Metadata\JsonSchemaFilterInterface; use ApiPlatform\Metadata\Parameter; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; class ActiveFilter implements FilterInterface, JsonSchemaFilterInterface { /** * @param Builder<Model> $builder */ public function apply(Builder $builder, mixed $values, Parameter $parameter, array $context = []): Builder { if ('true' === $values || '1' === $values) { return $builder->where('deleted_at', null)->where('status', 'active'); } return $builder; } public function getSchema(Parameter $parameter): array { return ['type' => 'boolean']; } } // The make:filter command also auto-tags the filter in AppServiceProvider. // Use it in a resource: use ApiPlatform\Laravel\Eloquent\Filter\OrderFilter; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\QueryParameter; use App\Filter\ActiveFilter; use Illuminate\Database\Eloquent\Model; #[ApiResource] #[QueryParameter(key: 'active', filter: ActiveFilter::class)] class Book extends Model {} // GET /api/books?active=true → WHERE deleted_at IS NULL AND status = 'active' ``` --- ## Summary API Platform for Laravel provides a convention-over-configuration approach to building hypermedia-driven REST and GraphQL APIs on top of Laravel and Eloquent. The primary workflow is to annotate Eloquent models (or plain PHP classes) with `#[ApiResource]` and optional `#[QueryParameter]` filter attributes; the framework then auto-generates routes, OpenAPI/Swagger documentation, JSON-LD/Hydra/HAL/JSON:API serialization, pagination, and input validation with zero boilerplate. Developers can customise every layer through custom State Providers, Processors, and Eloquent Query Extensions, all of which are auto-discovered and tagged in Laravel's service container via the deferred `ApiPlatformDeferredProvider`. Integration patterns typically follow one of three paths: (1) **direct Eloquent exposure** — attach `#[ApiResource]` directly to a Model for the fastest path to a working CRUD API; (2) **DTO pattern** — separate the API surface from persistence using a plain PHP DTO with `#[Map]` and `stateOptions: new Options(modelClass: ...)`, ideal for data transformation or security boundaries; (3) **fully custom providers/processors** — implement `ProviderInterface` / `ProcessorInterface` for non-Eloquent data sources, microservice aggregation, or complex write workflows. All three patterns support the same filter, validation, security, pagination, serialization, and documentation features, and can be incrementally composed using Laravel's service container decoration capabilities.