icanboogie/accessor

实现获取器/设置器、只读/只写属性、易变默认值、类型控制……

v4.2.0 2023-02-15 09:54 UTC

This package is auto-updated.

Last update: 2024-09-03 22:50:51 UTC


README

Release Code Quality Code Coverage Downloads

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

可以通过将现有属性的可见性设置为 protectedprivate 来使其成为只读属性

<?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

可以通过将现有属性的可见性设置为 protectedprivate 来使其成为只写属性

<?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持续测试。

Tests Static Analysis Code Style

行为准则

本项目遵守贡献者行为准则。通过参与本项目及其社区,您应遵守此准则。

贡献

请参阅CONTRIBUTING以获取详细信息。

许可证

icanboogie/accessor 采用BSD-3-Clause许可证发布。