Installation
composer require bpolaszek/apifirst-bundle
Add to config/bundles.php:
return [
// ...
Bentools\ApiFirstBundle\BentoolsApiFirstBundle::class => ['all' => true],
];
Define a Resource Entity
Create an entity implementing Bentools\ApiFirstBundle\Model\ResourceInterface:
use Bentools\ApiFirstBundle\Model\ResourceInterface;
class User implements ResourceInterface
{
private $id;
private $name;
public function getId(): ?int
{
return $this->id;
}
}
Generate Handler & Actions Use the bundle’s CLI or manually create:
UserHandler extending AbstractResourceHandlerUserAction (e.g., UserCreateAction, UserUpdateAction) extending AbstractActionRoute & Use
# config/routes.yaml
user_create:
path: /users
controller: Bentools\ApiFirstBundle\Controller\ActionController::handle
defaults:
action: App\Action\UserCreateAction
Create a RESTful API endpoint for a User resource:
AbstractResourceHandler for UserHandler:
class UserHandler extends AbstractResourceHandler
{
protected function getFormType(): string
{
return UserType::class; // Custom form type
}
}
UserCreateAction:
class UserCreateAction extends AbstractAction
{
public function __invoke(Request $request, UserHandler $handler)
{
return $this ->handle($request, $handler, new User());
}
}
curl -X POST http://your-app/users -d '{"name":"John"}'
Form Handling (HTTP-Agnostic)
AbstractResourceHandler for validation/logic, decoupled from HTTP layer.$handler = $container->get(UserHandler::class);
$users = $handler->processBulk($rawDataArray); // No Request needed
Action Composition
create → sendWelcomeEmail):
class UserCreateAction extends AbstractAction
{
public function __invoke(Request $request, UserHandler $handler, EmailService $email)
{
$user = $this->handle($request, $handler, new User());
$email->sendWelcome($user);
return $this->json($user);
}
}
Resource Relationships
AbstractResourceHandler:
$handler = $container->get(PostHandler::class);
$post = $handler->process($request, $postEntity);
$handler->handleRelated('author', $post, $authorData); // Attach author
Symfony Forms Integration
AbstractType for forms:
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', TextType::class);
}
}
getFormType() in UserHandler.API Responses
AbstractAction’s handle() to auto-serialize resources (via Serializer):
return $this->json($this->handle($request, $handler, $user));
Testing
AbstractResourceHandler for unit tests:
$handler = $this->createMock(UserHandler::class);
$handler->method('process')->willReturn($user);
Event Listeners
ResourceEvents (e.g., PRE_VALIDATE, POST_CREATE):
$dispatcher->addListener(
ResourceEvents::POST_CREATE,
[$this, 'onUserCreated']
);
Deprecation Risk
ORM Assumptions
class CustomHandler extends AbstractResourceHandler
{
protected function getEntityManager(): EntityManagerInterface
{
return $this->container->get('custom.manager');
}
}
Validation Overrides
$builder->get('name')->setConstraint(null); // Disable Symfony Validator
Circular Dependencies
AbstractAction/AbstractResourceHandler:
// Bad: Handler injects Action which injects Handler
// Good: Use DTOs or services for decoupling.
Validation Errors
ValidationFormException for form errors:
try {
$user = $handler->process($request, $user);
} catch (ValidationFormException $e) {
return $this->json($e->getErrors(), 400);
}
Handler Not Found
# config/services.yaml
App\Handler\UserHandler: ~
Serialization Issues
serialize() in ResourceInterface:
public function serialize(): array
{
return [
'id' => $this->id,
'name' => $this->name,
];
}
Custom Actions
AbstractAction for non-CRUD logic:
class UserExportAction extends AbstractAction
{
public function __invoke(UserHandler $handler)
{
$users = $handler->findAll();
return $this->file($users->toCsv());
}
}
Dynamic Handlers
AbstractResourceHandler as a base for dynamic routes:
$handler = $container->get(sprintf('%sHandler', $resourceClass));
Event Customization
$dispatcher->dispatch(new ResourceEvent($resource, 'CUSTOM_EVENT'));
Testing Utilities
ResourceTestCase base class:
abstract class ResourceTestCase extends WebTestCase
{
protected function createHandler(): AbstractResourceHandler
{
return $this->getContainer()->get(UserHandler::class);
}
}
How can I help you explore Laravel packages today?