funeralzone / valueobjects

PHP 7.1 值对象辅助库。

0.5 2020-12-18 17:34 UTC

README

Build Status Version

要求

需要 PHP >= 7.1

安装

显然是通过 Composer 来安装

composer require funeralzone/valueobjects

扩展

此库仅处理基本值(标量)。我们还发布了一个扩展库,它为更复杂值的实现提供了一个起点。

我们的方法

我们在PHP 中编写更好的值对象方法一文中记录了我们关于 PHP 值对象的哲学。

单个值对象

如果你的 VO 封装了一个值,那么它很可能是标量。我们在 src/Scalars 下提供了一些特性来处理标量。

假设你有一个名为 'User Email' 的领域值。你需要创建一个实现了 ValueObject 接口类的类

final class UserEmail implements ValueObject {
...

你现在需要实现该接口。但是,因为电子邮件本质上可以被认为是字符串的一种特殊类型(在这种情况下是简单的),所以 StringTrait 辅助特性可以为你实现接口的大部分内容

final class UserEmail implements ValueObject {

    use StringTrait;
...

在我们的例子中,用户的电子邮件有其他领域逻辑,我们可以将其封装在我们的 VO 中。用户电子邮件必须是有效的电子邮件

...
    public function __construct(string $string)
    {
        Assert::that($string)->email();
        $this->string = $string;
    }
...

你可以在示例目录中看到如何实现单个值对象的示例。

枚举

可以通过使用 EnumTrait 轻松地定义枚举。然后,枚举值简单地作为类上的常量列出。

final class Fastening implements ValueObject
{
    use EnumTrait;
    
    public const BUTTON = 0;
    public const CLIP = 1;
    public const PIN = 2;
    public const ZIP = 3;
}

在处理值对象序列化时,使用常量名称。它们是区分大小写的。因此

$fastening = Fastening::fromNative('BUTTON');
$fastening->toNative(); // Equals to string: 'BUTTON'

在代码中,特性利用魔术方法根据常量名称创建对象,如下所示

$fastening = Fastening::ZIP();
$fastening->toNative(); // Equals 'ZIP'

如果你的 IDE 支持代码补全,并且你想使用命名方法来创建枚举,则可以在枚举类中添加以下 PHPDoc 块

/**
 * @method static Fastening BUTTON()
 * @method static Fastening CLIP()
 * @method static Fastening PIN()
 * @method static Fastening ZIP()
 */
final class Fastening implements ValueObject

复合值对象

复合值对象是由其他值组成的更复杂值。

final class Location implements ValueObject
{
    use CompositeTrait;
    
    private $latitude;
    private $longitude;
    
    public function __construct(Latitude $latitude, Longitude $longitude)
    {
        $this->latitude = $latitude;
        $this->longitude = $longitude;
    }
    
    public function getLatitude(): Latitude
    {
        return $this->latitude;
    }
    
    public function getLongitude(): Longitude
    {
        return $this->longitude;
    }
...

一个 Location 由两个 VO(纬度,经度)组成。我们提供了一个 CompositeTrait 来轻松实现 ValueObject 接口的大部分内容。它通过反射返回一个包含所有类属性的数组来处理 toNative 序列化。

CompositeTrait 不实现 fromNative。我们留给您来构建您的对象。

...
    public static function fromNative($native)
    {
        return new static(
            Latitude::fromNative($native['latitude']),
            Longitude::fromNative($native['longitude'])
        );
    }
...

你可以在示例目录中看到如何实现复合对象的示例。

Nulls,NonNulls 和 Nullables

此包允许您处理可空值对象。

首先创建一个值对象类型。

interface PhoneNumber extends ValueObject
{
}

实现一个非空版本的价值对象。

final class NonNullPhoneNumber implements PhoneNumber
{
    use StringTrait;
}

实现一个空版本的价值对象。

final class NullPhoneNumber implements PhoneNumber
{
    use NullTrait;
}

实现一个可空版本的价值对象。

final class NullablePhoneNumber extends Nullable implements PhoneNumber
{
    protected static function nonNullImplementation(): string
    {
        return NonNullPhoneNumber::class;
    }
    
    protected static function nullImplementation(): string
    {
        return NullPhoneNumber::class;
    }
}

此 '可空' 处理根据原生输入自动创建接口的空或非空版本。例如

$phoneNumber = NullablePhoneNumber::fromNative(null);

上面的 $phoneNumber 将自动使用上面指定的 NullPhoneNumber 实现。

或者

$phoneNumber = NullablePhoneNumber::fromNative('+44 73715525763');

上面的 $phoneNumber 将自动使用上面指定的 NonNullPhoneNumber 实现。

值对象集合

一套值对象应该实现Set接口。它只是对ValueObject接口的简单扩展,增加了几个功能。

interface Set extends ValueObject, \IteratorAggregate, \ArrayAccess, \Countable
{
    public function add($set);
    public function remove($set);
    public function contains(ValueObject $value): bool;
    public function toArray(): array;
}
  • add 将另一个集合中的值添加到当前集合中。
  • remove 从当前集合中移除另一个集合中包含的所有值。
  • contains 如果当前集合中存在该值,则返回true
  • toArray 返回一个包含所有值对象的简单PHP数组。

Set接口扩展的其他接口(\IteratorAggregate\ArrayAccess\Countable)用于将集合对象作为数组访问。

非空集合

库提供了一个接口的默认实现。

final class SetOfLocations extends NonNullSet implements Set
{
    protected function typeToEnforce(): string
    {
        return Location::class;
    }

    public static function valuesShouldBeUnique(): bool
    {
        return true;
    }
}

需要实现两个抽象方法。

  • typeToEnforce应返回您想要创建集合的值对象的类名字符串。
  • valuesShouldBeUnique应返回一个布尔值,表示您是否想要强制集合唯一。

如果集合设置为唯一,在实例化或通过add方法添加重复值时,将过滤掉重复项。

空集合和可空集合

与标准值对象类似,有一些结构可以帮助创建可空集合和空集合。有关更多信息,请参阅“空值、非空值和可空值”部分。

  • NullableSetNullable的集合等价物。
  • NullSetTraitNullTrait的集合等价物。

集合的用法

迭代、访问和计数

// Iteration
$set = new SetOfLocations([$one, $two]);
foreach($set as $value) {
    // TODO: Do something with each value object
}

// Access
$one = $set[0];
$two = $set[1];

//Counting
$count = count($set); // Returns 2

add

合并另一个集合。

$set = new SetOfLocations([$one, $two]);
$anotherSet = new SetOfLocations([$three]);
$mergedSet = $set->add($anotherSet);
count($mergedSet) // Equals: 3

remove

通过使用另一个集合作为参考值来从集合中移除值。

$set = new SetOfLocations([$one, $two, $three]);
$anotherSet = new SetOfLocations([$one]);
$remove = $set->remove($anotherSet);
count($remove) // Equals: 2

contains

检查集合是否包含特定的值对象。

$set = new SetOfLocations([$one, $two, $three]);
$one = new Location(0);
$check = $set->contains($one);