grzegorz-jamroz / sf-doctrine-api-bundle
Bundle provides basic features for Symfony Doctrine Api
Requires
- doctrine/doctrine-bundle: ^2.11
- doctrine/orm: ^2.17
- grzegorz-jamroz/foundations: ^0.0.3
- grzegorz-jamroz/sf-api-bundle: ^6.2.2
- grzegorz-jamroz/sf-api-foundation: ^6.2
- ramsey/uuid-doctrine: ^2.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.40
- grzegorz-jamroz/filesystem: ^0.1
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^9.5
- symfony/dotenv: ^6.0|^7.0
- symfony/yaml: ^6.0|^7.0
README
Bundle provides basic features for Symfony Doctrine Api
Installation
composer require grzegorz-jamroz/sf-doctrine-api-bundle
- Update routing configuration in your project:
# config/routes.yaml controllers: resource: ../src/Controller/ type: attribute # ... # add those lines: ifrost_doctrine_api_controllers: resource: ../src/Controller/ type: doctrine_api_attribute # ...
- Configure Doctrine to store UUIDs as binary strings
# config/packages/doctrine.yaml doctrine: dbal: types: uuid_binary: Ramsey\Uuid\Doctrine\UuidBinaryType # Uncomment if using doctrine/orm <2.8 # mapping_types: # uuid_binary: binary
Note: It is possible to configure Doctrine to store UUIDs in different way - you can read about it here. Please note that bundle will work only with UUIDs stored as binary types.
- Create Entity which implements EntityInterface and in this case annotate properties by setting the
@Column
type touuid_binary
, and define custom generator ofRamsey\Uuid\Doctrine\UuidV7Generator
.
Exmple:
<?php // src/Entity/Product.php namespace App\Entity; use App\Repository\ProductRepository; use Doctrine\ORM\Mapping as ORM; use Ifrost\DoctrineApiBundle\Entity\EntityInterface; use PlainDataTransformer\Transform; use Ramsey\Uuid\Doctrine\UuidV7Generator; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; #[ORM\Entity(repositoryClass: ProductRepository::class)] class Product implements EntityInterface { #[ORM\Id] #[ORM\Column(type: "uuid_binary", unique: true)] #[ORM\GeneratedValue(strategy: "CUSTOM")] #[ORM\CustomIdGenerator(class: UuidV7Generator::class)] private UuidInterface $uuid; #[ORM\Column(length: 255)] private string $name; public function __construct( UuidInterface $uuid, string $name, ) { $this->uuid = $uuid; $this->name = $name; } public function getUuid(): UuidInterface { return $this->uuid; } public function getName(): string { return $this->name; } public static function getTableName(): string { return 'product'; } /** * @return array<int, string> */ public static function getFields(): array { return array_keys(self::createFromArray([])->getWritableFormat()); } public function getWritableFormat(): array { return [ 'uuid' => $this->uuid->getBytes(), 'name' => $this->name, ]; } public function jsonSerialize(): array { return [ 'uuid' => (string) $this->uuid, 'name' => $this->name, ]; } public static function createFromArray(array $data): static|self { return new self( $data['uuid'] ?? Uuid::uuid7(), Transform::toString($data['name'] ?? ''), ); } public static function createFromRequest(array $data): static|self { return new self( $data['uuid'] === null ? Uuid::uuid7() : Uuid::fromString($data['uuid']), Transform::toString($data['name'] ?? ''), ); } }
- Create your controller:
<?php declare(strict_types=1); namespace App\Controller; use App\Entity\Product; use Ifrost\ApiFoundation\Attribute\Api; use Ifrost\DoctrineApiBundle\Controller\DoctrineApiController; #[Api(entity: Product::class, path: 'products')] class ProductController extends DoctrineApiController { }
- Now you can debug your routes. Run command:
php bin/console debug:router
you should get output:
------------------- -------- -------- ------ --------------------------
Name Method Scheme Host Path
------------------- -------- -------- ------ --------------------------
_preview_error ANY ANY ANY /_error/{code}.{_format}
products_find GET ANY ANY /products
products_find_one GET ANY ANY /products/{uuid}
products_create POST ANY ANY /products
products_update PUT ANY ANY /products/{uuid}
products_modify PATCH ANY ANY /products/{uuid}
products_delete DELETE ANY ANY /products/{uuid}
------------------- -------- -------- ------ --------------------------
More custom usage
If you decided that you want to change routing configuration for some specific route just add Route
attribute with new parameters. For example:
<?php declare(strict_types=1); namespace App\Controller; use App\Entity\Product; use Ifrost\ApiFoundation\Attribute\Api; use Ifrost\DoctrineApiBundle\Controller\DoctrineApiController; use Symfony\Component\Routing\Annotation\Route; #[Api( entity: Product::class, path: 'products' )] class ProductController extends DoctrineApiController { #[Route('/create_products', name: 'products_create', methods: ['POST'])] public function create(): Response { return $this->getApi()->create(); } }
now output from php bin/console debug:router
will be:
------------------- -------- -------- ------ --------------------------
Name Method Scheme Host Path
------------------- -------- -------- ------ --------------------------
_preview_error ANY ANY ANY /_error/{code}.{_format}
products_create POST ANY ANY /create_products
products_find GET ANY ANY /products
products_find_one GET ANY ANY /products/{uuid}
products_update PUT ANY ANY /products/{uuid}
products_modify PATCH ANY ANY /products/{uuid}
products_delete DELETE ANY ANY /products/{uuid}
------------------- -------- -------- ------ --------------------------
It is possible do disable some actions at all. In this case you can use excludedActions
metadata.
<?php declare(strict_types=1); namespace App\Controller; use App\Entity\Product; use Ifrost\ApiFoundation\Attribute\Api; use Ifrost\DoctrineApiBundle\Controller\DoctrineApiController; #[Api( entity: Product::class, path: 'products', excludedActions: [ Action::CREATE, Action::UPDATE, Action::MODIFY, 'delete', 'not_valid_actions_will_be_omitted' ])] )] class ProductController extends DoctrineApiController { }
now output from php bin/console debug:router
will be:
------------------- -------- -------- ------ --------------------------
Name Method Scheme Host Path
------------------- -------- -------- ------ --------------------------
_preview_error ANY ANY ANY /_error/{code}.{_format}
products_find GET ANY ANY /products
products_find_one GET ANY ANY /products/{uuid}
------------------- -------- -------- ------ --------------------------
Configuration
Default config
You can add config/packages/ifrost_doctrine_api.yaml
in your project to enable/disable some features if not necessary
# config/packages/ifrost_doctrine_api.yaml # default config ifrost_doctrine_api: doctrine_dbal_types_uuid: true dbal_cache_adapter: enabled: false db_client: enabled: true # ...
You can enable default cache adapter Symfony\Component\Cache\Adapter\FilesystemAdapter
(optional)
# config/packages/ifrost_doctrine_api.yaml ifrost_doctrine_api: doctrine_dbal_types_uuid: true dbal_cache_adapter: enabled: true db_client: enabled: true # ...