immutablephp/immutable

不可变基础对象、值对象和值包。

1.0.0 2019-02-04 20:12 UTC

This package is auto-updated.

Last update: 2024-09-11 04:19:35 UTC


README

提供真正不可变的值对象和不可变的值包,以及为您的对象提供的基类 ImmutableValueObject。它有助于防止文章Avoiding Quasi-Immutable Objects in PHP中描述的疏忽。

概览

基类 Immutable 防止了PHP中不可变的常见疏忽

  • 它定义了 final public function __set()final public function __unset() 以防止添加和修改未定义的属性。

  • 它定义了 final public function offsetSet()final public function offsetUnset() 以防止通过 ArrayAccess 添加和修改值。

  • 它阻止多次调用 __construct() 以重新初始化对象属性。

此外,基类 ValueObjectwith() 方法检查所有传入值的类型,以确保它们自身是不可变的。它通过 Type 类的静态方法来完成。

Type 类将标量和null视为不可变。所有其他非对象值(如资源数组和数组)都被拒绝为可变的。

对于对象,Type 类将来自 Immutable 的所有派生类以及 DateTimeImmutable 视为不可变。为了使 Type 能够识别其他不可变类,请使用完全限定类名调用 Type::register(),传入您想要视为不可变的类名列表。

创建自己的不可变值对象

注意

此包只能做这么多以防止您意外忽略可变性。例如,ImmutableValueObject 类无法阻止您故意添加自己的可变行为。同样,也不可能阻止使用反射从外部修改对象。

要创建自己的不可变值对象,请使用自己的属性扩展 ValueObject

use Immutable\ValueObject\ValueObject;

class Address extends ValueObject
{
    protected $street;
    protected $city;
    protected $region;
    protected $postcode;
}

然后添加一个 with*() 方法,允许在对象的副本上更改这些值,使用基 ValueObject 上的受保护的 with() 方法。

class Address extends ValueObject
{
    protected $street;
    protected $city;
    protected $region;
    protected $postcode;

    public function withChanged(
        string $street,
        string $city,
        string $region,
        string $postcode
    ) {
        return $this->with([
            'street' => $street,
            'city' => $city,
            'region' => $region,
            'postcode' => $postcode
        ]);
    }
}

最后,在构造函数中使用该方法初始化属性,并调用 parent::__construct() 以完成初始化。

class Address extends ValueObject
{
    protected $street;
    protected $city;
    protected $region;
    protected $postcode;

    public function __construct(
        string $street,
        string $city,
        string $region,
        string $postcode
    ) {
        $this->withChanged($street, $city, $region, $postcode);
        parent::__construct();
    }

    public function withChanged(
        string $street,
        string $city,
        string $region,
        string $postcode
    ) : self
    {
        return $this->with([
            'street' => $street,
            'city' => $city,
            'region' => $region,
            'postcode' => $postcode
        ]);
    }
}

警告

如果您没有调用 parent::__construct(),则值对象将不知道它已被初始化,并且可以多次调用构造函数以重新初始化对象。

现在您有一个不可变的值对象。

您可能会发现添加验证很有用;在您的 with*() 方法中执行此操作,无论是直接执行还是调用验证机制。

    public function withChanged(
        string $street,
        string $city,
        string $region,
        string $postcode
    ) {

        $valid = AddressValidator::validate($street, $city, $region, $postcode);
        if (! $valid) {
            throw new \RuntimeException('address is not valid');
        }

        return $this->with([
            'street' => $street,
            'city' => $city,
            'region' => $region,
            'postcode' => $postcode
        ]);
    }

提供的不可变值对象

此包提供了一些值对象,包括示例和常用。

信用卡

use Immutable\ValueObject\CreditCard;

$creditCard = new CreditCard('5555-5555-5555-4444');

// reading
$creditCard->getNumber(); // '5555555555554444'
$creditCard->getBrand(); // 'VISA'

// changing
$newCreditCard = $creditCard->withNumber('4111-1111-1111-1111');
$newCreditCard->getNumber(); // '4111111111111111'
$newCreditCard->getBrand(); // 'MASTERCARD'

电子邮件

use Immutable\ValueObject\Email;

$email = new Email('bolivar@example.com');

// reading
$email->get(); // 'bolivar@example.com'

// changing
$newEmail = $email->withAddress('boshag@example.net');
$newEmail->get(); // 'boshag@example.net'

IP地址

use Immutable\ValueObject\Ip;

$ip = new Ip('127.0.0.1');

// reading
$ip->get(); // '127.0.0.1'

// changing
$newIp = $ip->withAddress('192.168.0.1');
$newIp->get(); // '192.168.0.1'

ISBN

use Immutable\ValueObject\Isbn;

$isbn = new Isbn('960-425-059-0');

// reading
$isbn->get(); // '960-425-059-0'

// changing
$newIsbn = $ip->withAddress('0-8044-2957-X');
$newIsbn->get(); // '0-8044-2957-X'

URI\HttpUri

use Immutable\ValueObject\Uri\HttpUri;

$httpUri = new HttpUri(
    'http://boshag:bopass@example.com:8080/foo?bar=baz#dib'
);

// reading
$httpUri->getScheme(); // 'http'
$httpUri->getHost(); // 'example.com'
$httpUri->getPort(); // 8080
$httpUri->getUser(); // 'boshag'
$httpUri->getPass(); // 'bopass'
$httpUri->getPath(); // /'foo'
$httpUri->getQuery(); // 'bar=baz'
$httpUri->getFragment(); // 'dib'

// changing
$newHttpUri = $httpUri
    ->withScheme('https')
    ->withHost('example.net')
    ->withPort('8888')
    ->withUser('newuser')
    ->withPass('newpass')
    ->withPath('/foo2')
    ->withQuery('zim=gir')
    ->withFragment('irk');

$newHttpUri->get(); // 'https://newuser:newpass@example.net:8888/foo2/?zim=gir#irk'

UUID

use Immutable\ValueObject\Uuid;

$uuid = new Uuid('12345678-90ab-cdef-1234-567890123456');

// reading
$uuid->get(); // '12345678-90ab-cdef-1234-567890123456'

// changing
$newUuid = $uuid->withIdentifier('11111111-1111-1111-1111-111111111111');
$newUuid->get(); // '11111111-1111-1111-1111-111111111111'

// create a new random UUIDv4 identifier
$uuidv4 = Uuid::newVersion4();

不可变包

Bag 用于任意不可变值的集合,可以用于不可变地表示JSON数据。

use Immutable\Bag;

$bag = new Bag(['foo' => 'bar']);
echo $bag->foo; // bar
echo $bag['foo']; // bar

$bag->foo = 'baz'; // ImmutableObjectException
$bag = $bag->with('foo', 'baz');
echo $bag->foo; // baz
echo $bag['foo']; // baz

unset($bag->foo); // ImmutableObjectException
$bag = $bag->without('foo');

$bag->dib; // Notice: $dib not defined

$bag = $bag->with('dib', ['zim', 'gir']);
foreach ($bag->dib as $key => $value) {
    echo "$key:$value,"; // 0:zim,1:gir,
}