Apie is a suite of composer packages to work with domain objects. It tries to aim to follow a Domain-objects-first approach and not a database first approach that you find in many PHP frameworks nowadays.
This type converter package is written outside the monorepo and provides a simple tooling to convert objects into other objects.
Easiest usage:
<?php
use Apie\TypeConverter\DefaultConvertersFactory;
$converter = DefaultConvertersFactory::create();
var_dump($converter->convertTo(12, 'string')); // '12'
This is not a very useful example. Normally you try to convert a DTO to a domain object or the other way around.
More serious example:
<?php
use Apie\TypeConverter\DefaultConvertersFactory;
class Dto {
public string $description;
public string $name;
}
class DomainObject {
public function __construct(
private string $description,
private string $name
) {
}
public function getName(): string
{
return $this->name;
}
public function getDescription(): string
{
return $this->description;
}
}
use Apie\TypeConverter\DefaultConvertersFactory;
$converter = DefaultConvertersFactory::create();
$dto = ($converter->convertTo(new DomainObject('description', 'name'), Dto::class));
var_dump($dto->name); // 'name'
$converter->convertTo($dto, DomainObject::class);
It's very easy to create your own converters. All you need to do is make a class that implements ConverterInterface. In case you use phpstan for static code analysis a small phpdoc can be added to make phpstan understand the type conversion.
<?php
use Apie\Core\ValueObjects\Interfaces\StringValueObjectInterface;
use Apie\TypeConverter\ConverterInterface;
/**
* @implements ConverterInterface<StringValueObjectInterface, string>
*/
class StringValueObjectToStringConverter implements ConverterInterface
{
public function convert(StringValueObjectInterface $valueObject): string
{
return $valueObject->toNative();
}
}
use Apie\TypeConverter\DefaultConvertersFactory;
$converter = DefaultConvertersFactory::create(new StringValueObjectToStringConverter());
$converter->convertTo(new Email('[email protected]'), 'string');
DefaultConvertsFactory creates an instance of TypeConverter with sensible defaults. You can configure everything by just calling the constructor. Remember that all default converters require to be added manually too.
<?php
use Apie\TypeConverter\Converters\FloatToStringConverter;
use Apie\TypeConverter\Converters\IntToStringConverter;
use Apie\TypeConverter\Converters\ObjectToObjectConverter;
use Apie\TypeConverter\TypeConverter;
$converter = new TypeConverter(
new ObjectToObjectConverter(),
new IntToStringConverter(),
new FloatToStringConverter()
);
In the example above we can convert a string value object to string, but how do we make it the other way around?
We could make a converter for every value object, but that would mean many similar classes.
<?php
use Apie\Core\ValueObjects\Interfaces\StringValueObjectInterface;
use Apie\TypeConverter\ConverterInterface;
/**
* @implements ConverterInterface<string, Email>
*/
class StringToEmailConverter implements ConverterInterface
{
public function convert(string $value): Email
{
return Email::fromNative($value);
}
}
Luckily we have a better solution by providing the wanted type as second argument.
<?php
use Apie\Core\ValueObjects\Interfaces\StringValueObjectInterface;
use Apie\TypeConverter\ConverterInterface;
/**
* @implements ConverterInterface<string, StringValueObjectInterface>
*/
class StringToStringValueObjectConverter implements ConverterInterface
{
public function convert(string $value, \ReflectionNamedType $wantedType): StringValueObjectInterface
{
$className = $wantedType->getName();
return $className::fromNative($value);
}
}
It is also possible to get the TypeConverter instance to make recursive conversions.
<?php
use Apie\Core\ValueObjects\Interfaces\StringValueObjectInterface;
use Apie\TypeConverter\ConverterInterface;
use Apie\TypeConverter\TypeConverter;
/**
* @implements ConverterInterface<int, StringValueObjectInterface>
*/
class IntToStringValueObjectConverter implements ConverterInterface
{
public function convert(int $value, \ReflectionNamedType $wantedType, TypeConverter $typeConverter): StringValueObjectInterface
{
$value = $typeConverter->convertTo($value, 'string');
$className = $wantedType->getName();
return $className::fromNative($value);
}
}
If multiple converters could perform the conversion, then the most accurate one is the one that gets the priority.
<?php
use Apie\TypeConverter\Converters\FloatToStringConverter;
use Apie\TypeConverter\Converters\IntToStringConverter;
use Apie\TypeConverter\Converters\ObjectToObjectConverter;
use Apie\TypeConverter\TypeConverter;
$converter = new TypeConverter(
new ObjectToObjectConverter(),
new IntToStringConverter(),
new FloatToStringConverter()
);
If I would try to convert '1' IntToStringConverter and FloatToStringConverter would apply, but since int is more accurate it uses IntToStringConverter.