Skip to content

Repository files navigation

AutoType

Library for auto-registering value objects (crates, DTOs) as Doctrine types.

Wouldn't it be nice if you could use your Value Objects as entity type directly? With AutoType you do not have to create doctrine type for every single value object type.

Usage

composer require martingold/autotype

You have few options:

  1. Add #[ValueGetter] and #[Constructor] (not required) attributes to your ValueObject, DTO, Crate, ...
  2. Make your objects implement ValueObject interface
  3. Create your own driver (implement your own TypeDefinitionDriver). See below 👇

Example using attributes:

finalreadonlyclass Url
{
privatefunction__construct(
privatestring$value,
) {
}
// Regular constructor is used when attribute not found
#[Constructor]
publicstaticfunctioncreate(string$url): self
{
if (filter_var($value, FILTER_VALIDATE_URL) === false) {
throw MalformedUrl::fromString($value);
}
returnnew self($url)
}
#[ValueGetter]
publicfunctiongetValue(): string
{
return$this->value;
}
publicfunctionisSecure(): bool
{
returnstr_starts_with('https://', $this->value);
}
}

Register types at the entry point of your application (kernel boot when using symfony):

// Get a PSR-6 cache instance$cache = $this->container->get(CacheItemPoolInterface::class);
// Alternatively, use Doctrine's PSR-6 metadata cache$entityManager = $this->container->get(EntityManagerInterface::class);
$cache = $entityManager->getConfiguration()->getMetadataCache();
// Create a type provider$cachedTypeFinder = newCachedTypeDefinitionProvider(
newDefaultTypeDefinitionProvider(__DIR__ . '/../ValueObject'),
$cache
);
// Register dynamic types
(newDynamicTypeRegistry($cachedTypeFinder))->register();

Use the value object directly in your entities.

#[Entity]
class Company
{
#[Column]
privatestring$name;
#[Column(type: Url::class)]
privateUrl$url;
}

Drivers

See tests/Entity for the example usage of the drivers. The library comes with two default drivers:

AttributeTypeDefinitionDriver

This driver registers all classes with a #[ValueGetter] method as Doctrine types. If a static factory method is needed, add the #[Constructor] to the method which should be used for constructing the object back from database value.

InterfaceTypeDefinitionDriver

This driver registers all classes implementing ValueObject interface.

Custom driver

If you have existing value objects based on your project's conventions and do not want to add additional interfaces or custom attributes, you can implement your own driver and use it during type registration:

$typeDefinitionProvider = newCachedTypeDefinitionProvider(
newScanTypeDefinitionProvider($sourceFolder, [
newCustomTypeDefinitionDriver(),
]),
$cache,
);
(newDynamicTypeRegistry($typeDefinitionProvider))->register();

The possibilities are endless. You can even specify your own custom dynamic types in case you have special requirements like column length or database-specific optimizations. See AttributeTypeDefinitionDriver and InterfaceTypeDefinitionDriver for more examples.

class CustomTypeDefinitionDriver implements TypeDefinitionDriver
{
/** * Should be the class treated as doctrine type? * @param ReflectionClass<object> $class */publicfunctionsupports(ReflectionClass$class): bool
{
returnstr_ends_with($class->getShortName(), 'Crate');
}
/** * Get dynamic type class. Whether it is value a string or int. * @param ReflectionClass<object> $class * * @return class-string<DynamicType&Type> */publicfunctiongetDynamicTypeClass(ReflectionClass$class): string
{
returnmatch ($this->getValueMethodReturnType($class)) {
'string' => StringDynamicType::class,
'int' => IntegerDynamicType::class,
default => thrownewUnsupportedType('Only string|int type is supported.'),
};
}
/** * Name of the method which should be used when persisting object to database.  * @param ReflectionClass<object> $class */publicfunctiongetValueMethodName(ReflectionClass$class): string
{
return'getValue';
}
/** * Method to use when creating the object from database value. Must have single argument.  * When null returned, the regular constructor is used.  */publicfunctiongetConstructorMethodName(ReflectionClass$class): string|null
{
return$class->hasMethod('of') ? 'of' : null;
}
/** * Get value getter method return type to determine if database value should be string or int * @param ReflectionClass<object> $class * * @throws UnsupportedType */privatefunctiongetValueMethodReturnType(ReflectionClass$class): string
{
$returnType = $class->getMethod('getValue')->getReturnType();
if (!$returnTypeinstanceof ReflectionNamedType) {
thrownewUnsupportedType("Intersection or union return type not supported in method {$class->getName()}::getValue()");
}
if (!$returnType->isBuiltin()) {
thrownewUnsupportedType("Only scalar return types are supported in {$class->getName()}::getValue()");
}
return$returnType->getName();
}
}

About

Library to automatically register doctrine types

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages