alexandrebulete/ddd-apiplatform-bridge
Installation
composer require alexandrebulete/ddd-apiplatform-bridge
Ensure api is in your providers array in config/app.php and ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle is registered.
Basic Configuration
Add the bridge service to your DDD domain layer by extending Domain\DomainServiceProvider (or equivalent):
use Alexandrebulete\DddApiPlatformBridge\Domain\ApiPlatformDomainService;
public function register()
{
$this->app->singleton(ApiPlatformDomainService::class, function ($app) {
return new ApiPlatformDomainService(
$app->make(\ApiPlatform\Core\Api\UrlGeneratorInterface::class),
$app->make(\ApiPlatform\Core\Api\IriConverterInterface::class)
);
});
}
First Use Case: Domain-to-API Mapping
Inject ApiPlatformDomainService into your domain service and use it to generate IRIs or URLs:
use Alexandrebulete\DddApiPlatformBridge\Domain\ApiPlatformDomainService;
class OrderService
{
public function __construct(
private ApiPlatformDomainService $apiPlatformDomainService
) {}
public function getOrderUrl(Order $order): string
{
return $this->apiPlatformDomainService->generateUrl(
'api_order_item_collection',
['order_id' => $order->getId()]
);
}
}
Key Files to Review
src/Domain/ApiPlatformDomainService.php (core logic)src/Infrastructure/ApiPlatform/ApiPlatformResourceFactory.php (resource creation)tests/ (for edge cases and expected behavior)Domain Layer Abstraction
Use ApiPlatformDomainService to decouple domain logic from API concerns:
// Domain Service (no API dependency)
class OrderDomainService {
public function createOrder(OrderData $data): Order {
// Business logic
}
}
// Application Layer (API-aware)
class OrderApplicationService {
public function __construct(
private OrderDomainService $domainService,
private ApiPlatformDomainService $apiPlatformDomainService
) {}
public function handle(OrderRequest $request): OrderDto {
$order = $this->domainService->createOrder($request->toDomainData());
return new OrderDto(
$order,
$this->apiPlatformDomainService->generateIri($order)
);
}
}
Resource Creation Pattern Dynamically create API resources from domain entities:
use Alexandrebulete\DddApiPlatformBridge\Infrastructure\ApiPlatform\ApiPlatformResourceFactory;
$resourceFactory = new ApiPlatformResourceFactory();
$resource = $resourceFactory->createFromDomain($order, OrderResource::class);
Event-Driven Workflows Trigger API operations (e.g., notifications) from domain events:
class OrderCreatedEventHandler {
public function __construct(
private ApiPlatformDomainService $apiPlatformDomainService
) {}
public function __invoke(OrderCreatedEvent $event): void {
$url = $this->apiPlatformDomainService->generateUrl(
'api_order_show',
['id' => $event->getOrderId()]
);
// Send email with $url
}
}
Validation Bridge Reuse domain validation rules in API Platform:
# config/packages/api_platform.yaml
api_platform:
formats:
jsonld:
validation_context:
groups: ['domain', 'api']
State Preservation Use the bridge to preserve domain state in API responses:
class OrderResource extends JsonLDResource {
public function getAdditionalContext(): array
{
return [
'@id' => $this->getContextBuilder()->getIriConverter()->getIriFromResource($this->data),
'@type' => 'Order',
'status' => $this->data->getStatus()->value,
];
}
}
Custom Operations Define domain-aware API operations:
# config/api_platform/resources.yaml
App\Entity\Order:
collectionOperations:
create_order:
method: 'POST'
path: '/orders'
controller: App\Controller\OrderDomainController::createAction
deserializationContext:
groups: ['order:create']
Serialization Groups Align API serialization with domain layers:
// Domain Entity
#[Groups(['order:read'])]
public function getCustomerName(): string { ... }
// API Resource
#[Groups(['order:api'])]
public function getCustomer(): CustomerDto { ... }
Pagination Control Delegate pagination to domain services:
class OrderCollection {
public function __construct(
private ApiPlatformDomainService $apiPlatformDomainService,
private OrderRepository $repository
) {}
public function getItems(int $page = 1): array
{
$offset = ($page - 1) * 20;
return $this->repository->findBy([], null, 20, $offset);
}
}
Circular Dependencies
ApiPlatformDomainService into domain entities can violate DDD purity.IRI Generation Edge Cases
generateIri() may fail if the resource isn’t registered with API Platform.if (!$this->apiPlatformDomainService->isResourceRegistered(Order::class)) {
throw new \RuntimeException('Resource not configured in API Platform.');
}
State Inconsistency
$order->addEvent(new OrderStatusChangedEvent($order->getId(), $newStatus));
Performance Overhead
ApiPlatformResourceFactory) can be slow for bulk operations.Resource Not Found
@ApiResource.generateUrl().Serialization Errors
APP_DEBUG=1 APP_ENV=dev php bin/console debug:api
Event Dispatching
php bin/console debug:event-dispatcher
Custom URL Generators
$this->app->bind(\ApiPlatform\Core\Api\UrlGeneratorInterface::class, function ($app) {
return new CustomUrlGenerator($app->make('router'));
});
Dynamic Route Names
$url = $this->apiPlatformDomainService->generateUrl(
'api_{resource}_collection',
['resource' => 'order']
);
Context-Specific IRIs
generateIri():
$iri = $this->apiPlatformDomainService->generateIri(
$order,
['groups' => ['order:api']]
);
Custom Domain Services
Extend ApiPlatformDomainService to add domain-specific logic:
class CustomApiPlatformDomainService extends ApiPlatformDomainService {
public function generateOrderUrl(Order $order): string {
return $this->generateUrl(
'api_order_show',
['id' => $order->getId(), 'version' => $order->getVersion()]
);
}
}
Resource Factories Create domain-specific factories:
class OrderResourceFactory extends ApiPlatformResourceFactory {
public function createFromDomain(Order $order): OrderResource {
$resource = parent::createFromDomain($order, OrderResource::class);
$resource->setStatus($order->getStatus()->value);
return $resource;
}
}
Event Listeners Add domain-aware API Platform listeners:
class OrderApiEventSubscriber implements EventSubscriberInterface {
public static function getSubscribedEvents(): array {
return [
KernelEvents::VIEW => ['onKernelView', 10],
];
How can I help you explore Laravel packages today?