makinacorpus / generated-hydrator-bundle
Provide integration of ocramius/generated-hydrator for Symfony
Installs: 4 939
Dependents: 2
Suggesters: 0
Security: 0
Stars: 1
Watchers: 13
Forks: 0
Open Issues: 1
Requires
- php: >=8.0
- ocramius/generated-hydrator: ^4.3
Requires (Dev)
- phpdocumentor/reflection-docblock: ^5.3
- phpunit/phpunit: ^8.5
- ramsey/uuid: ^4.0
- symfony/config: ^5.0|^6.0
- symfony/dependency-injection: ^5.0|^6.0
- symfony/filesystem: ^5.0|^6.0
- symfony/http-kernel: ^5.0|^6.0
- symfony/property-info: ^5.0|^6.0
- symfony/yaml: ^5.0|^6.0
This package is not auto-updated.
Last update: 2024-10-08 16:00:25 UTC
README
Integrates ocramius/generated-hydrator library with Symfony.
It also brings some new features:
-
A nested hydrator, that from PHP properties type will cascade hydration into an object graph.
-
Value hydrators, for each PHP type you can plug your own global hydrator implementation for dealing with custom types.
-
Next planned feature will be object configuration using attributes.
Installation
First install the dependency:
composer require makinacorpus/generated-hydrator-bundle
Then add into your config/bundles.php
file:
<?php return [ // ... \GeneratedHydrator\Bridge\Symfony\GeneratedHydratorBundle::class => ['all' => true], ];
Configuration
Change generated PHP file location
Default configuration will attempt to write generated hydrator code into the
%kernel.project_dir%/hydrator
folder. For this to work, you probably want to
add in your composer.json
file:
{ "autoload": { "classmap": [ "hydrator" ] } }
You can change the target by adding the following
config/packages/generated-hydrator.yaml
file:
generated_hydrator: target_directory: "%kernel.project_dir%/hydrator"
Blacklist classes from hydration
You may experience bugs at some point when the hydrator attempts for example to hydrate PHP core classes. In order to avoid this from happening, you can completely disable hydration attempts on any PHP type, by using the following configuration:
generated_hydrator: class_blacklist: - App\SomeClass - DateTime - DateTimeImmutable - DateTimeInterface - Ramsey\Uuid\Uuid - Ramsey\Uuid\UuidInterface # ...
This will prevent the nested object hydrator from those classes hydration attempt.
Pre-generate class hydrators for production
You can setup a list of class for which you need to pre-generate hydrators:
generated_hydrator: class_list: - App\Entity\Foo - App\Entity\Bar # ...
This will allow the generated-hydrator:generate
command to pre-generate
all hydrators.
Autowiring
You can use the GeneratedHydrator\Bridge\Symfony\HydratorAware
interface
and set it on services, which will make this bundle autoconfigure the
service injection for you.
You can also use the GeneratedHydrator\Bridge\Symfony\HydratorAwareTrait
if you don't want to implement the setObjectHydrator()
method by yourself.
Usage
Basic usage
Inject the generated_hydrator
service, or type hint with
GeneratedHydrator\Bridge\Symfony\Hydrator
for auto-wiring.
In order to hydrate an object:
use App\Domain\Model\SomeEntity; use GeneratedHydrator\Bridge\Symfony\Hydrator; function some_function(Hydrator $hydrator) { $object = $hydrator->createAndHydrate( SomeEntity::class, [ // Scalar values 'foo' => 1, // ... // It also handles nested objects 'bar' => [ 'baz' => 2, // ... ], ] ); }
Or extract its values:
use App\Domain\Model\SomeEntity; use GeneratedHydrator\Bridge\Symfony\Hydrator; function some_function(Hydrator $hydrator) { $object = new SomeEntity(); $valueArray = $hydrator->extract($object); }
Value hydrator
Let's consider you have the following class:
namespace App\Entity; interface Identifier { public function __construct(mixed $value); public function __toString(): string; } class FooId { public function __construct( private mixed $value, ) {} public function __toString(): string { return (string) $this->id; } }
And use it as an identifier class for the following entity class:
namespace App\Entity; class Foo { public function __construct( private FooId $id, ) {} // ... }
If you need to hydrate some Foo
instance using the following data from database:
$foo = $hydrator->createAndHydrate( \App\Entity\Foo::class, [ 'id' => '12345', ], );
This will fail because the $id
parameter cannot accept a string
value.
If you use the same pattern all over your site, you might want to use a global value hydrator, as such:
namespace App\ValueHydrator; use App\Entity\Identifier; use GeneratedHydrator\Bridge\Symfony\Error\CannotHydrateValueError; use GeneratedHydrator\Bridge\Symfony\ValueHydrator\ValueHydrator; class IdentifierValueHydrator implements ValueHydrator { /** * {@inheritdoc} */ public function supports(string $phpType): bool { return \is_subclass_of($phpType, Identifier::class); } /** * {@inheritdoc} */ public function hydrate(string $phpType, mixed $value): mixed { if (\is_subclass_of($phpType, Identifier::class)) { return new $phpType($value); } throw new CannotHydrateValueError(); } }
And register it either by explicitely setting the generated_hydrator.value_hydrator
service tag, or by adding into the container using autowiring
and autoconfiguration
set to true
on this service:
services: App\ValueHydrator\IdentifierValueHydrator: autowire: true autoconfigure: true
Once this set, each property value that needs to be hydrated implementing
your App\Entity\Identifier
interface will have its value correctly
instanciated automatically.
Some notes
For the nested hydrator to work, it needs to be able to use introspection on you classes for finding the properties types.
If for some reason introspection fails, you can explicitely install
the symfony/property-access
component, which may find some types that this
API is unable to find using reflection.
Roadmap
Reach alpha release (mandatory):
- implement class blacklist, some classes such as
\DateTime
and\Ramsey\Uuid\
should be dealt as terminal types, and normalized in the business layer, - autoload classes when they are just generated,
- register automatically fallback autoloader for generated hydrator classes, without this, classes generated within a cache directory will no be autoloadable.
Industrialisation (1.0):
- allow usage of hydrator without the nested implementation explicitely by the API user, maybe using a specific interface and a specific service identifier,
- nested hydrator is a hack, it should not be the default,
- write advanced configuration for users,
- write more tests, lots of test.
Far far away:
- handle collections in nested extraction/hydration,
- add an option to disable property-info usage even when classes are loaded,
- remove PHP docblock parser in flavor of our custom one, for this, we need to be able to resolve relative class namespaces.