Installation
composer require condenast-ru/basic-api-bundle
Enable the bundle in config/bundles.php:
Condenast\BasicApiBundle\CondenastBasicApiBundle::class => ['all' => true],
Define a DTO
Create a class annotated with #[ApiResource] and #[ApiProperty] for fields:
use Condenast\BasicApiBundle\Annotation\ApiProperty;
use Condenast\BasicApiBundle\Annotation\ApiResource;
#[ApiResource]
class CreateUserDto
{
#[ApiProperty]
public string $name;
#[ApiProperty]
public string $email;
}
Annotate a Controller
Use #[ApiAction] to define request/response DTOs:
use Condenast\BasicApiBundle\Annotation\ApiAction;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class UserController
{
#[ApiAction(
input: CreateUserDto::class,
output: UserDto::class
)]
public function create(Request $request): Response
{
$dto = $request->attributes->get('input');
// Business logic here
return new Response(json_encode($dto));
}
}
Test the Endpoint Send a POST request with JSON body:
{ "name": "John Doe", "email": "[email protected]" }
The bundle auto-deserializes, validates, and serializes responses.
#[ApiAction(input: YourDto::class)] to auto-bind request payloads.@Assert\NotBlank) alongside #[ApiProperty] for field-level validation.
#[ApiProperty]
#[Assert\Email]
public string $email;
#[ApiAction(query: YourQueryDto::class)].JsonSerializable in DTOs for complex logic:
class UserDto implements JsonSerializable
{
public function jsonSerialize(): array
{
return ['id' => $this->id, 'name' => $this->name];
}
}
#[ApiError] annotations:
#[ApiError(status: 400, message: "Invalid input")]
class ValidationException extends \RuntimeException {}
#[ApiAction(error: YourErrorDto::class)] to customize error responses.#[ApiResource], #[ApiProperty]) auto-generate OpenAPI docs.NelmioApiDocBundle configuration in config/packages/nelmio_api_doc.yaml:
nelmio_api_doc:
documentation:
info:
title: My API
description: Powered by BasicApiBundle
kernel.request or kernel.exception to modify DTOs or responses:
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
$eventDispatcher->addListener(KernelEvents::EXCEPTION, function (ExceptionEvent $event) {
$exception = $event->getThrowable();
if ($exception instanceof ValidationException) {
$event->setResponse(new Response(json_encode(['error' => $exception->getMessage()])));
}
});
#[ApiProperty]:
#[Assert\CustomConstraint(MyConstraint::class)]
#[ApiProperty]
public string $customField;
BasicApiBundle's test utilities:
use Condenast\BasicApiBundle\Test\ApiTestTrait;
class UserControllerTest extends WebTestCase
{
use ApiTestTrait;
public function testCreateUser()
{
$client = static::createClient();
$client->request('POST', '/users', [
'json' => ['name' => 'Test', 'email' => '[email protected]']
]);
$this->assertResponseIsSuccessful();
}
}
Annotation Caching
php bin/console cache:clear
#[ApiResource(cache: false)] to disable caching for specific resources.Circular References
User ↔ Profile) may cause serialization errors.JsonSerializable or use #[ApiProperty(ignore: true)] for problematic fields.Query Parameter Conflicts
#[ApiAction(query: YourDto::class, queryParamPrefix: "filter_")] to namespace params.Nelmio Doc Mismatches
#[ApiResource]:
#[ApiResource(
description: "Creates a user with validation",
examples: {"name": "John", "email": "[email protected]"}
)]
APP_DEBUG=true in .env to see detailed validation errors.dump($request->attributes->get('input')); // Debug deserialized DTO
#[ApiAction(validationGroups: {"create", "update"})] to apply group-specific validation.Custom Serializers
Condenast\BasicApiBundle\Serializer\Serializer to add custom normalization:class CustomSerializer extends Serializer
{
public function normalize($object, $format, array $context = [])
{
if ($object instanceof YourDto) {
return ['custom' => 'value'];
}
return parent::normalize($object, $format, $context);
}
}
Register as a service with tags: ["basic_api.serializer"].
Dynamic DTOs
#[ApiProperty(type: "array")] for dynamic arrays or #[ApiProperty(type: "object")] for JSON blobs.Middleware Integration
class ApiMiddleware implements MiddlewareInterface
{
public function handle(Request $request, callable $next): Response
{
$request->attributes->set('custom_header', $request->headers->get('X-Custom'));
return $next($request);
}
}
config/packages/validator.yaml:
validator:
default_groups: [default, "basic_api"]
#[ApiAction(serializerContext: {"groups": "api"})].#[ApiProperty(type: "array", itemType: YourDto::class)] with validation_groups to optimize validation.#[ApiAction(validation: false)]
How can I help you explore Laravel packages today?