使用自定义对象构造器¶
对象可能有自定义的创建方式,在这种情况下,需要将这些构造函数注册到映射器才能使用。构造函数可以是:
- 命名构造函数,也称为静态工厂方法
- 服务的方法——例如存储库
- “可调用对象”——声明
__invoke方法的类 - 任何其他可调用的——包括匿名函数
无论如何,可调用对象的返回类型将由映射器解析以了解何时使用它。可以提供任何参数,并将使用给定的源自动映射。然后可以使用这些参数以所需的方式实例化对象。
如果注册了多个构造函数,它们必须提供不同的签名以防止映射期间发生冲突 - 这意味着如果两个构造函数需要多个具有完全相同名称的参数,则映射将失败。
注意
为类注册构造函数将阻止映射器处理其原生构造函数( __construct方法)。如果需要再次启用它,则必须显式注册。
Constructor特性¶
注册构造函数最快、最简单的方法是使用Constructor特性,该特性会将方法标记为映射器可以使用的构造函数。
目标方法必须是公共的、静态的,并返回它所属类的实例。
final readonly class Email
{
// When another constructor is registered for the class, the native
// constructor is disabled. To enable it again, it is mandatory to
// explicitly register it again.
#[\CuyZ\Valinor\Mapper\Object\Constructor]
public function __construct(public string $value) {}
#[\CuyZ\Valinor\Mapper\Object\Constructor]
public static function createFrom(string $userName, string $domainName): self
{
return new self($userName . '@' . $domainName);
}
}
(new \CuyZ\Valinor\MapperBuilder())
->mapper()
->map(Email::class, [
'userName' => 'john.doe',
'domainName' => 'example.com',
]); // john.doe@example.com
手动注册构造函数¶
在某些情况下,无法使用Constructor特性,例如,当该类是无法修改的外部依赖项时。在这种情况下, registerConstructor方法可用于将任何可调用对象注册为构造函数。
(new \CuyZ\Valinor\MapperBuilder())
->registerConstructor(
// When another constructor is registered for the class, the native
// constructor is disabled. To enable it again, it is mandatory to
// explicitly register it again by giving the class name to this method.
Color::class,
// Register a named constructor
Color::fromHex(...),
/**
* An anonymous function can also be used, for instance when the desired
* object is an external dependency that cannot be modified.
*
* @param 'red'|'green'|'blue' $color
* @param 'dark'|'light' $darkness
*/
function (string $color, string $darkness): Color {
$main = $darkness === 'dark' ? 128 : 255;
$other = $darkness === 'dark' ? 0 : 128;
return new Color(
$color === 'red' ? $main : $other,
$color === 'green' ? $main : $other,
$color === 'blue' ? $main : $other,
);
}
)
->mapper()
->map(Color::class, [/* … */]);
final readonly class Color
{
/**
* @param int<0, 255> $red
* @param int<0, 255> $green
* @param int<0, 255> $blue
*/
public function __construct(
public int $red,
public int $green,
public int $blue
) {}
/**
* @param non-empty-string $hex
*/
public static function fromHex(string $hex): self
{
if (strlen($hex) !== 6) {
throw new DomainException('Must be 6 characters long');
}
/** @var int<0, 255> $red */
$red = hexdec(substr($hex, 0, 2));
/** @var int<0, 255> $green */
$green = hexdec(substr($hex, 2, 2));
/** @var int<0, 255> $blue */
$blue = hexdec(substr($hex, 4, 2));
return new self($red, $green, $blue);
}
}
接口实现构造函数¶
默认情况下,映射器无法实例化接口,因为它不知道要使用哪个实现。为此,可以以与类相同的方式为接口注册构造函数。
注意
由于映射器无法自动猜测接口可以使用哪个实现,因此无法使用Constructor特性,必须使用MapperBuilder::registerConstructor()方法。
在下面的示例中,映射器被教导如何从包ramsey/uuid实例化UuidInterface的实现:
(new \CuyZ\Valinor\MapperBuilder())
->registerConstructor(
// The static method below has return type `UuidInterface`; therefore,
// the mapper will build an instance of `Uuid` when it needs to
// instantiate an implementation of `UuidInterface`.
Ramsey\Uuid\Uuid::fromString(...)
)
->mapper()
->map(
Ramsey\Uuid\UuidInterface::class,
'663bafbf-c3b5-4336-b27f-1796be8554e0'
);
自定义枚举构造函数¶
如上所述,为枚举注册构造函数的方式与为类注册构造函数的方式相同。
enum Color: string
{
case LIGHT_RED = 'LIGHT_RED';
case LIGHT_GREEN = 'LIGHT_GREEN';
case LIGHT_BLUE = 'LIGHT_BLUE';
case DARK_RED = 'DARK_RED';
case DARK_GREEN = 'DARK_GREEN';
case DARK_BLUE = 'DARK_BLUE';
#[\CuyZ\Valinor\Mapper\Object\Constructor]
public static function fromMatrix(string $type, string $color): Color
{
return self::from($type . '_' . $color);
}
}
(new \CuyZ\Valinor\MapperBuilder())
->mapper()
->map(Color::class , [
'type' => 'DARK',
'color' => 'RED'
]); // Color::DARK_RED
注意
枚举构造函数可以针对特定模式:
enum Color: string
{
case LIGHT_RED = 'LIGHT_RED';
case LIGHT_GREEN = 'LIGHT_GREEN';
case LIGHT_BLUE = 'LIGHT_BLUE';
case DARK_RED = 'DARK_RED';
case DARK_GREEN = 'DARK_GREEN';
case DARK_BLUE = 'DARK_BLUE';
/**
* This constructor will be called only when pattern `SomeEnum::DARK_*`
* is requested during mapping.
*
* @return Color::DARK_*
*/
#[\CuyZ\Valinor\Mapper\Object\Constructor]
public static function darkFrom(string $value): Color
{
return self::from('DARK_' . $value);
}
}
(new \CuyZ\Valinor\MapperBuilder())
->mapper()
->map(Color::class . '::DARK_*', 'RED'); // Color::DARK_RED
动态构造函数¶
在某些情况下,构造函数处理的类型仅在运行时才知道,在这种情况下,构造函数需要知道必须使用哪个类来实例化对象。
例如,接口可以声明一个静态构造函数,然后由多个子类实现该构造函数。一种解决方案是为每个子类注册构造函数,这会导致大量样板代码,并且每次创建新子类时都需要重新注册。另一种方法是使用特性\CuyZ\Valinor\Mapper\Object\DynamicConstructor 。
当构造函数使用此特性时,其第一个参数必须是字符串,并且将填充调用构造函数时映射器需要构建的实际类的名称。可以添加其他参数并将其正常映射,具体取决于提供给映射器的源。
interface InterfaceWithStaticConstructor
{
public static function from(string $value): self;
}
final readonly class ClassWithInheritedStaticConstructor implements InterfaceWithStaticConstructor
{
private function __construct(private SomeValueObject $value) {}
public static function from(string $value): self
{
return new self(new SomeValueObject($value));
}
}
(new \CuyZ\Valinor\MapperBuilder())
->registerConstructor(
#[\CuyZ\Valinor\Mapper\Object\DynamicConstructor]
function (string $className, string $value): InterfaceWithStaticConstructor {
return $className::from($value);
}
)
->mapper()
->map(ClassWithInheritedStaticConstructor::class, 'foo');