icanboogie / accessor
实现获取器/设置器、只读/只写属性、易变默认值、类型控制……
Requires
- php: >=7.2.5
- icanboogie/common: ^1.3.2|^2.0|^6.0
Requires (Dev)
- phpstan/phpstan: ^1.9.17
- phpunit/phpunit: ^8.5.32
README
icanboogie/accessor 包允许类实现 ICanBoogie 的访问器设计模式。通过组合获取器、设置器、属性和属性可见性,您可以创建只读属性、只写属性、虚拟属性;并实现默认值、类型控制、保护机制和延迟加载。
安装
composer require icanboogie/inflector
前言
由于该包是 ICanBoogie 领域的公民,它早在很久以前就选择了蛇形命名法以提高可读性,因此以下示例使用相同的命名方式,但到文档末尾我们将了解到 CamelCase 同样被支持。实际上,由于所有获取器和设置器都使用 accessor_format 特性方法格式化,因此只需通过重写该方法,就可以轻松地将格式绑定到自己的需求。
获取器和设置器
获取器是一个获取特定属性值的方法。设置器是一个设置特定属性值的方法。您可以使用 AccessorTrait 特性在类上定义获取器和设置器,并且可以选择通过实现 HasAccessor 接口来通知其功能。
需要注意的是:只有在相应的属性不可访问时才会调用获取器和设置器。这在使用延迟加载时特别重要,延迟加载在调用时创建相关属性。
另外需要注意的是:您不需要为所有内容及其子项使用获取器/设置器,PHP 不是 Java,拥有公共属性是可以的。
只读属性
只读属性通过仅定义获取器来创建。尝试设置只读属性将抛出 PropertyNotWritable 异常。
以下示例演示了如何实现 property
只读属性
<?php use ICanBoogie\Accessor\AccessorTrait; /** * @property-read mixed $property */ class ReadOnlyProperty { use AccessorTrait; protected function get_property() { return 'value'; } } $a = new ReadOnlyProperty; echo $a->property; // value $a->property = null; // throws ICanBoogie\PropertyNotWritable
可以通过将现有属性的可见性设置为 protected
或 private
来使其成为只读属性
<?php use ICanBoogie\Accessor\AccessorTrait; /** * @property-read mixed $property */ class ReadOnlyProperty { use AccessorTrait; private $property = "value"; protected function get_property() { return $this->property; } } $a = new ReadOnlyProperty; echo $a->property; // value $a->property = null; // throws ICanBoogie\PropertyNotWritable
保护 构造函数 属性
只读属性通常用于提供对在 构造函数 中提供的属性的读取访问,该属性在实例的生命周期中应保持不变。
以下示例演示了如何在 构造函数 中传递的 connection
属性之后只能读取。属性的可见性被设置为 private,这样甚至扩展类也无法修改该属性。
<?php use ICanBoogie\Accessor\AccessorTrait; class Connection { // … } /** * @property-read Connection $connection */ class Model { use AccessorTrait; /** * @var Connection */ private $connection; protected function get_connection(): Connection { return $this->connection; } protected $options; public function __construct(Connection $connection, array $options) { $this->connection = $connection; $this->options = $options; } } $connection = new Connection(…); $model = new Model($connection, …); $connection === $model->connection; // true $model->connection = null; // throws ICanBoogie\PropertyNotWritable
只写属性
只写属性通过仅定义设置器来创建。尝试获取只写属性将抛出 PropertyNotReadable 异常。
以下示例演示了如何实现 property
只写属性
<?php use ICanBoogie\Accessor\AccessorTrait; /** * @property-write mixed $property */ class WriteOnlyProperty { use AccessorTrait; protected function set_property($value) { // … } } $a = new WriteOnlyProperty; $a->property = 'value'; echo $a->property; // throws ICanBoogie\PropertyNotReadable
可以通过将现有属性的可见性设置为 protected
或 private
来使其成为只写属性
<?php use ICanBoogie\Accessor\AccessorTrait; /** * @property-write mixed $property */ class WriteOnlyProperty { use AccessorTrait; private $property = 'value'; protected function set_property($value) { $this->property = $value; } } $a = new WriteOnlyProperty; $a->property = 'value'; echo $a->property; // throws ICanBoogie\PropertyNotReadable
虚拟属性
虚拟属性通过定义一个getter和setter但没有任何对应的属性来创建。虚拟属性通常提供对另一个属性或数据结构的接口。
以下示例演示了如何将一个minutes
虚拟属性实现为一个对seconds
属性的接口。
<?php use ICanBoogie\Accessor\AccessorTrait; /** * @property int $minutes */ class Time { use AccessorTrait; public $seconds; protected function set_minutes(int $minutes) { $this->seconds = $minutes * 60; } protected function get_minutes(): int { return $this->seconds / 60; } } $time = new Time; $time->seconds = 120; echo $time->minutes; // 2 $time->minutes = 4; echo $time->seconds; // 240
在设置属性之前提供默认值
因为当对应的属性不可访问时,会调用getter,而未设置的属性当然是不可访问的,所以可以定义提供默认值的getter,直到实际设置值。
以下示例演示了在属性不可访问(在这种情况下为未设置)时如何提供默认值。在构造期间,如果slug
属性为空,则未设置,使其不可访问。因此,直到实际设置属性,当读取slug
属性时,其getter会被调用并返回由title
属性创建的默认值。
<?php use ICanBoogie\Accessor\AccessorTrait; class Article { use AccessorTrait; public $title; public $slug; public function __construct(string $title, string $slug = null) { $this->title = $title; if ($slug) { $this->slug = $slug; } else { unset($this->slug); } } protected function get_slug(): string { return \ICanBoogie\normalize($this->title); } } $article = new Article("This is my article"); echo $article->slug; // this-is-my-article $article->slug = "my-article"; echo $article->slug; // my-article unset($article->slug); echo $article->slug; // this-is-my-article
外观属性(和类型控制)
有时您可能希望能够管理属性的类型,可以存储什么,可以检索什么,尽可能透明。这可以通过外观属性实现。
外观属性通过定义一个私有属性及其getter和setter来实现。
以下示例演示了如何实现一个created_at
属性。它可以设置为混合值,但总是以DateTime
实例的形式读取。
<?php use ICanBoogie\Accessor\AccessorTrait; use ICanBoogie\DateTime; /** * @property DateTime $created_at */ class Article { use AccessorTrait; private $created_at; protected function set_created_at($datetime) { $this->created_at = $datetime; } protected function get_created_at(): DateTime { $datetime = $this->created_at; if ($datetime instanceof DateTime) { return $datetime; } return $this->created_at = ($datetime === null) ? DateTime::none() : new DateTime($datetime, 'utc'); } }
外观属性在序列化时导出
尽管外观属性使用私有属性定义,但在实例序列化时它们会被导出,就像它们是公共或受保护的属性一样。
<?php $article = new Article; $article->created_at = 'now'; $test = unserialize(serialize($article)); echo get_class($test->created_at); // ICanBoogie/DateTime $article->created_at == $test->created_at; // true
延迟加载
延迟加载在调用时创建关联属性,使后续访问使用属性而不是getter。
在以下示例中,lazy_get_pseudo_uniqid
getter返回一个唯一值,但由于在getter被调用后以public
可见性创建了pseudo_uniqid
属性,任何后续对属性的访问都返回相同的值。
<?php use ICanBoogie\Accessor\AccessorTrait; /** * @property string $pseudo_uniqid */ class PseudoUniqID { use AccessorTrait; protected function lazy_get_pseudo_uniqid(): string { return uniqid(); } } $a = new PseudoUniqID; echo $a->pseudo_uniqid; // 5089497a540f8 echo $a->pseudo_uniqid; // 5089497a540f8
当然,取消设置的属性会重置此过程。
<?php unset($a->pseudo_uniqid); echo $a->pseudo_uniqid; // 508949b5aaa00 echo $a->pseudo_uniqid; // 508949b5aaa00
设置延迟属性
延迟属性类似于只读属性实现,通过定义一个获取值的方法,但与只读属性不同,延迟属性也可以写入。
<?php $a = new PseudoUniqID; echo $a->pseudo_uniqid; // a009b3a984a50 $a->pseudo_uniqid = 123456; echo $a->pseudo_uniqid; // 123456 unset($a->pseudo_uniqid); echo $a->pseudo_uniqid; // 57e5ada092180
需要记住的是,延迟属性实际上会创建一个属性,因此如果属性已经可访问,则不会调用getter。
重载getter和setter
因为getter和setter是经典方法,所以可以重载。也就是说,父类的setter或getter可以被扩展类重载。
以下示例演示了如何通过扩展Plain
类的Awesome
类将一个plain getter转换为awesome getter。
<?php use ICanBoogie\Accessor\AccessorTrait; /** * @property-read string $property */ class Plain { use AccessorTrait; protected function get_property() { return "value"; } } class Awesome extends Plain { protected function get_property() { return "awesome " . parent::get_property(); } } $plain = new Plain; echo $plain->property; // value $awesome = new Awesome; echo $awesome->property; // awesome value
支持驼峰式命名法
驼峰式命名法的getter和setter同样受支持。而不是使用AccessorTrait,请使用AccessorCamelTrait
<?php use ICanBoogie\Accessor\AccessorCamelTrait; /** * @property-read $camelProperty */ class CamelExample { use AccessorCamelTrait; private $camelProperty; protected function getCamelProperty() { return $this->camelProperty; } public function __construct($value) { $this->camelProperty = $value; } } $a = new CamelExample("value"); echo $a->camelProperty; // value
持续集成
项目由GitHub actions持续测试。
行为准则
本项目遵守贡献者行为准则。通过参与本项目及其社区,您应遵守此准则。
贡献
请参阅CONTRIBUTING以获取详细信息。
许可证
icanboogie/accessor 采用BSD-3-Clause许可证发布。