apie/composite-value-objects

apie库的Composer包:复合值对象

dev-main 2022-09-07 14:01 UTC

This package is auto-updated.

Last update: 2024-09-17 17:11:42 UTC


README

composite-value-objects

Latest Stable Version Total Downloads Latest Unstable Version License PHP Version Require Code coverage

PHP Composer

此包是Apie库的一部分。代码维护在monorepo中,因此PR需要发送到monorepo

文档

复合值对象主要是一个可以在值对象内部使用的特性,用于包含其他值对象或原始值的复合值对象。例如,范围值对象通常最好用作值对象本身,因为范围的开端和末端应该在限制范围内。

用法

您需要做的只是创建一个具有ValueObjectInterface和CompositeValueObject特性的对象。然后所有您需要的只是属性,fromNative和toNative将相应地更改。

例如

<?php
use Apie\CommonValueObjects\Texts\DatabaseText;
use Apie\CompositeValueObjects\CompositeValueObject;
use Stringable;

final class StreetAddress implements ValueObjectInterface, Stringable
{
    use CompositeValueObject;

    public function __construct(private DatabaseText $street, private DatabaseText $streetNumber)
    {}

    public function __toString(): string
    {
        return $this->street . ' ' . $this->streetNumber;
    }
}

// creates a StreetAddress value object from an array.
$address = StreetAddress::fromNative([
    'street' => 'Example Street',
    'streetNumber' => 42
]);
// $addressDisplay = 'Example Street 42';
$addressDisplay = (string) $address;

// return array again
$address->toNative();

// throws error for missing street number
$address = StreetAddress::fromNative([
    'street' => 'Example Street'
]);

请记住,示例中有一个构造函数,但这不是必需的,但如果您忘记添加一个,其他人可能会通过仅调用new ValueObject()而不传递构造函数参数来错误地使用您的值对象。您还可以创建一个私有构造函数,以强制人们使用fromNative()来创建您的对象。

可选字段

默认情况下,所有非静态字段都是必需的,如果缺失将抛出错误。要使字段可选,您有两种选择。您可以将Optional属性添加到属性中,或者为属性提供一个默认值。

以下两个示例具有相同的结果

<?php
use Apie\CommonValueObjects\Texts\DatabaseText;
use Apie\CompositeValueObjects\CompositeValueObject;

final class StreetAddress implements ValueObjectInterface
{
    use CompositeValueObject;

    private function __construct()
    {
        // this enforces other programmers to use fromNative
    }

    private DatabaseText $street;
    private DatabaseText $streetNumber;
    private ?DatabaseText $streetNumberSuffix = null;
}
<?php
use Apie\CommonValueObjects\Texts\DatabaseText;
use Apie\CompositeValueObjects\CompositeValueObject;
use Apie\Core\Attributes\Optional;

final class StreetAddress implements ValueObjectInterface
{
    use CompositeValueObject;

    private function __construct()
    {
        // this enforces other programmers to use fromNative
    }

    private DatabaseText $street;
    private DatabaseText $streetNumber;

    #[Optional]
    private DatabaseText $streetNumberSuffix;
}

请记住,在PHP中,如果您尝试读取未设置的类型提示属性,将会得到错误。如果未设置,toNative()将不会返回值。

验证

要添加验证,您可以添加一个validateState()方法。如果当前状态无效,此方法应抛出错误。如果该方法存在,它将由fromNative()调用,并且也应与任何自定义构造函数一起调用。

一个很好的例子是时间范围,其中开始时间需要在结束时间之前创建。

在这里,我们给出一个组合姓名和姓氏的示例,并且两个字段的总长度不应超过255个字符。

<?php
use Apie\CompositeValueObjects\CompositeValueObject;

final class FirstNameAndLastName implements ValueObjectInterface, Stringable
{
    use CompositeValueObject;

    private string $firstName;

    private string $lastName;

    public function __construct(private string $firstName, private string $lastName)
    {
        $this->validateState();
    }

    public function __toString(): string
    {
        return $this->firstName . ' ' . $this->lastName;
    }

    private function validateState(): void
    {
        if (strlen((string) $this) > 255) {
            throw new RuntimeException('Length of first name and last name should not exceed 255 characters');
        }
    }
}

### Union typehints
The composite value object trait supports union typehints. To avoid accidental casting and that the reflection API of PHP
will always return typehints in the same order(you don't have control over this) we check specific types first.

If the input is a string and string is a typehint it will pick string.

Otherwise the order of doing typecast is:
- objects+other types
- float
- int
- string

```php
<?php
use Apie\CommonValueObjects\Texts\DatabaseText;
use Apie\CompositeValueObjects\CompositeValueObject;
use Apie\Core\Attributes\Optional;

final class Example implements ValueObjectInterface
{
    use CompositeValueObject;

    private function __construct()
    {
        // this enforces other programmers to use fromNative
    }

    private string|int $value;

    public function getValue(): string|int
    {
        return $this->value;
    }
}
// getValue() returns '12' and is not casting to integer.
Example::fromNative(['value' => '12'])->getValue();
// getValue() returns 12 and is not casting to string.
Example::fromNative(['value' => 12])->getValue();