immutablephp / immutable
不可变基础对象、值对象和值包。
Requires
- php: >=7.2.0
Requires (Dev)
- pds/skeleton: ~1.0
- phpunit/phpunit: ~7.0
README
提供真正不可变的值对象和不可变的值包,以及为您的对象提供的基类 Immutable 和 ValueObject。它有助于防止文章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()
以重新初始化对象属性。
此外,基类 ValueObject 的 with()
方法检查所有传入值的类型,以确保它们自身是不可变的。它通过 Type 类的静态方法来完成。
Type 类将标量和null视为不可变。所有其他非对象值(如资源数组和数组)都被拒绝为可变的。
对于对象,Type 类将来自 Immutable 的所有派生类以及 DateTimeImmutable 视为不可变。为了使 Type 能够识别其他不可变类,请使用完全限定类名调用 Type::register()
,传入您想要视为不可变的类名列表。
创建自己的不可变值对象
注意
此包只能做这么多以防止您意外忽略可变性。例如,Immutable 和 ValueObject 类无法阻止您故意添加自己的可变行为。同样,也不可能阻止使用反射从外部修改对象。
要创建自己的不可变值对象,请使用自己的属性扩展 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, }