pixelfederation / doctrine-generic-types-bundle
Symfony bundle for Doctrine Generic Types.
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
Type:symfony-bundle
pkg:composer/pixelfederation/doctrine-generic-types-bundle
Requires
- php: ^8.3
- composer/class-map-generator: ^1.6
- doctrine/dbal: ^3
- doctrine/doctrine-bundle: ^2.16
- doctrine/orm: ^3.5
- symfony/dotenv: ^7.3
- symfony/expression-language: ^7.3
- symfony/http-kernel: ^7.3
- symfony/yaml: ^7.3
Requires (Dev)
- dama/doctrine-test-bundle: ^8.4
- ergebnis/composer-normalize: ^2.48
- friendsofphp/php-cs-fixer: ^3.89
- jms/serializer: ^3.32
- matthiasnoback/symfony-dependency-injection-test: ^6.1
- php-parallel-lint/php-parallel-lint: ^1.4
- phpmd/phpmd: ^2.15
- phpro/grumphp: ^2.17
- phpstan/phpstan: ^2.1
- pixelfederation/coding-standards: ^5.0
- psalm/phar: ^6.13
- ramsey/uuid: ^4.9
- roave/security-advisories: dev-latest
- symfony/phpunit-bridge: ^7.3
- symfony/test-pack: ^1.2
README
PixelFederation DoctrineGenericTypesBundle
Installation
install via Composer:
composer require pixelfederation/doctrine-generic-types-bundle
register bundle in config/bundles.php (if you don't use Symfony Flex)
return [ // ... PixelFederation\DoctrineGenericTypesBundle\PixelFederationDoctrineGenericTypesBundle::class => ['all' => true], ];
bundle configuration:
# config/packages/pixel_federation_doctrine_generic_types.yaml pixel_federation_doctrine_generic_types: generic_types: PixelFederation\DoctrineGenericTypesBundle\Value\BooleanValue: PixelFederation\DoctrineGenericTypesBundle\Doctrine\Type\BooleanValueType PixelFederation\DoctrineGenericTypesBundle\Value\FloatValue: PixelFederation\DoctrineGenericTypesBundle\Doctrine\Type\FloatValueType PixelFederation\DoctrineGenericTypesBundle\Value\IntegerValue: PixelFederation\DoctrineGenericTypesBundle\Doctrine\Type\IntegerValueType PixelFederation\DoctrineGenericTypesBundle\Value\StringValue: PixelFederation\DoctrineGenericTypesBundle\Doctrine\Type\StringValueType # https://github.com/ramsey/uuid integration PixelFederation\DoctrineGenericTypesBundle\Bridge\RamseyUuid\Value\UuidValue: PixelFederation\DoctrineGenericTypesBundle\Bridge\RamseyUuid\Doctrine\Type\UuidValueType # Directories where to find your Value Objects directories: - ./src/App/Value - ./src/App/OtherValue
Usage
Crete your Value Object:
<?php declare(strict_types=1); namespace App\Value; use PixelFederation\DoctrineGenericTypesBundle\Value\StringValue; final class FirstName extends StringValue { }
Use it in your Doctrine entity:
<?php declare(strict_types=1); namespace App\Entity; use Doctrine\ORM\Mapping as ORM; use App\Value\FirstName; #[ORM\Entity] #[ORM\Table(name: 'person')] class Person { public function __construct( #[ORM\Column(type: FirstName::class)] public FirstName $firstName, ) { } }
doctrine will handle persisting and retrieving your Value Object automatically.
How to create custom Generic Types
Create abstract Value class extending PixelFederation\DoctrineGenericTypesBundle\Value\Value or PixelFederation\DoctrineGenericTypesBundle\Value\BaseValue:
<?php declare(strict_types=1); namespace App\CustomValue; use PixelFederation\DoctrineGenericTypesBundle\Value\Value; abstract class MoneyValue implements Value { public function __construct( public readonly float $value, public readonly string $currency, ) { // value object validation logic } }
Create Doctrine Type class extending PixelFederation\DoctrineGenericTypesBundle\Doctrine\Type\GenericType:
<?php declare(strict_types=1); namespace App\Doctrine\Type; use App\CustomValue\MoneyValue; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Types\JsonType; use Doctrine\DBAL\Types\Type; use InvalidArgumentException; use JsonException; use Override; use PixelFederation\DoctrineGenericTypesBundle\Doctrine\Type\GenericType; final class MoneyValueType extends JsonType implements GenericType { /** * @var class-string<MoneyValue> */ protected string $class; public static function createForValue(string $class): Type { if (!is_a($class, MoneyValue::class, true)) { throw new InvalidArgumentException(sprintf( 'Doctrine Type %s must handle class %s. Got %s', self::class, MoneyValue::class, $class, )); } $self = new self(); $self->class = $class; return $self; } #[Override] public function getName(): string { return $this->class; } #[Override] public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string { if ($value === null) { return null; } $class = $this->class; if (!$value instanceof $class) { throw ConversionException::conversionFailedInvalidType( $value, $this->getName(), ['null', $class], ); } try { return json_encode(['value' => $value->value, 'currency' => $value->currency], JSON_THROW_ON_ERROR); } catch (JsonException $e) { throw ConversionException::conversionFailedSerialization($value, 'json', $e->getMessage()); } } /** * @return object<MoneyValue>|null */ #[Override] public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed { if ($value === null) { return null; } if (!is_string($value)) { throw ConversionException::conversionFailedFormat($value, $this->getName(), 'json'); } try { $data = json_decode($value, true, 512, JSON_THROW_ON_ERROR); assert(is_array($data)); } catch (JsonException $e) { throw ConversionException::conversionFailedUnserialization($value, $e->getMessage()); } $dataValue = $data['value'] ?? null; $dataCurrency = $data['currency'] ?? null; if (!is_float($dataValue) || !is_string($dataCurrency)) { throw ConversionException::conversionFailedFormat( $value, $this->getName(), '{"value": float, "currency": string}', ); } return new ($this->class)($dataValue, $dataCurrency); } #[Override] public function requiresSQLCommentHint(AbstractPlatform $platform): bool { return false; } }
Register your custom Generic Type in the bundle configuration:
# config/packages/pixel_federation_doctrine_generic_types.yaml pixel_federation_doctrine_generic_types: generic_types: # ... App\CustomValue\MoneyValue: App\Doctrine\Type\MoneyValueType directories: # ... - ./src/App/CustomValue