Installation:
composer require api-platform/api-pack
Note: Only use v1.3.0 or earlier for Laravel compatibility. v1.4.0+ requires Symfony and is not recommended for vanilla Laravel projects.
Publish Configuration:
php artisan vendor:publish --provider="ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle"
Warning: This may fail in Laravel due to Symfony-specific config structure. Use config/merge.php as a workaround.
First Use Case: Create a resource class with API Platform annotations:
// app/Entity/Book.php
use ApiPlatform\Core\Annotation\ApiResource;
#[ApiResource]
class Book
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
public string $title;
}
Laravel Note: Ensure Doctrine ORM is installed (composer require doctrine/orm) and configured.
Route Integration:
Add to routes/api.php:
Route::prefix('api')->group(function () {
// Delegate to API Platform's router (requires middleware)
Route::get('/books', [\App\Http\Controllers\ApiPlatformController::class, 'index']);
});
Workaround: Create a custom controller to bridge Laravel/Symfony routing.
Resource-Oriented Development:
#[ApiResource] on Eloquent models to auto-generate REST/GraphQL endpoints.#[ApiResource(
operations: ['get', 'post'],
normalizationContext: ['groups' => ['book:read']],
denormalizationContext: ['groups' => ['book:write']]
)]
class Book {}
#[ApiResource(security: "is_granted('view', object)")]
State Providers for Business Logic:
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
use ApiPlatform\Core\DataTransformer\ItemDataTransformerInterface;
class BookStateProvider implements ItemDataTransformerInterface
{
public function transform($object, string $to, array $context = [])
{
if ($object->title === 'Banned Book') {
throw new \RuntimeException('Title not allowed');
}
return $object;
}
}
config/api_platform.yaml (Symfony) or via Laravel service provider.Hybrid API Design:
Route::apiResource() for simple CRUD.#[ApiResource(type: 'graphql')] for complex queries.Route::prefix('graphql')->group(function () {
Route::post('', [\App\Http\Controllers\GraphQLController::class, 'handle']);
});
Mercure for Real-Time Updates:
use ApiPlatform\Core\Annotation\MercureUBO;
#[ApiResource]
#[MercureUBO]
class Book {}
composer require dunglas/mercure-bundle
Configure .env:
MERCURE_URL=http://mercure/.well-known/mercure
MERCURE_PUBLIC_URL=https://yourdomain.com/.well-known/mercure
MERCURE_JWT_SECRET=your-secret
Validation Groups:
use Symfony\Component\Validator\Constraints as Assert;
class Book
{
#[Assert\NotBlank(groups: ['create'])]
public string $title;
#[Assert\NotBlank(groups: ['update'])]
public string $author;
}
# config/api_platform.yaml
validation_context:
groups: ['create']
Development Workflow:
php artisan make:entity Book --api
php artisan api:docs:open
php artisan route:list to verify routes.Testing:
$this->partialMockBuilder(ApiPlatform\Core\Bridge\Symfony\Serializer\SerializerContextBuilder::class)
->disableOriginalConstructor()
->getMock();
Http::fake() to test API responses:
$response = Http::get('/api/books');
$response->assertJson([...]);
Deployment:
bootstrap/cache is cleared:
php artisan cache:clear
php artisan config:clear
# docker-compose.yml
mercure:
image: dunglas/mercure
ports:
- "3000:3000"
Laravel-Symfony Bridge:
// app/Facades/ApiPlatform.php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class ApiPlatform extends Facade
{
protected static function getFacadeAccessor() { return 'api_platform'; }
}
AppServiceProvider:
$this->app->singleton('api_platform', function () {
return new \ApiPlatform\Core\Bridge\Symfony\ApiPlatform(new \Symfony\Component\HttpKernel\HttpKernel());
});
Doctrine ORM:
config/database.php includes Doctrine:
'connections' => [
'default' => [
'driver' => 'pdo_mysql',
// ...
],
'doctrine' => [
'driver' => 'pdo_mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
// ...
],
],
config/api_platform.yaml:
doctrine: ~
Authentication:
#[ApiResource(security: "is_granted('ROLE_USER')")]
class Book {}
use ApiPlatform\Core\Bridge\Symfony\Security\UserCheckerInterface;
class LaravelUserChecker implements UserCheckerInterface
{
public function checkPostLoad(UserInterface $user): void
{
if (!$user instanceof \App\Models\User) {
throw new \RuntimeException('Invalid user class');
}
}
}
Custom Serialization:
use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface;
class CustomSerializerContextBuilder implements SerializerContextBuilderInterface
{
public function createFromRequest(Request $request): array
{
$context = parent::createFromRequest($request);
$context['groups'][] = 'custom_group';
return $context;
}
}
AppServiceProvider:
$this->app->bind(SerializerContextBuilderInterface::class, CustomSerializerContextBuilder::class);
Symfony Dependency Conflicts:
v1.4.0+ requires Symfony’s HttpKernel, which clashes with Laravel’s Illuminate\Foundation\HttpKernel.v1.3.0 or fork the package to remove Symfony dependencies.Routing Conflicts:
Route::prefix('api')->group(function () {
Route::get('/books', [ApiPlatformController::class, 'index'])->name('api.books');
});
Service Container Issues:
How can I help you explore Laravel packages today?