axebear/php-magic

PHP 微型框架,用于通过 docblocks 和属性使用魔法方法。

1.0.0 2024-08-08 23:48 UTC

This package is auto-updated.

Last update: 2024-09-09 00:04:14 UTC


README

这个 PHP 包提供了一种使用自定义属性和 docblocks 添加魔法属性和方法的工具。亮点包括

  • 自动属性,使用 @property 标签创建。
  • 自动缓存的计算属性,使用 @property-read 标签和相同名称的方法。
  • 自动流畅方法,使用 @method 标签。
  • 更简单的重载方法。使用 @method#[Overloaded] 属性将重载方法的逻辑拆分到单独的函数中。此包将根据参数调用正确的方法。
  • 转换属性,在设置或获取时使用 #[MagicProperty] 属性。
  • 使用 Magic 特性完全访问以添加更多自定义处理程序。

试试吧。它很神奇!

use AxeBear\Magic\Traits\MagicProperties;
use AxeBear\Magic\Traits\OverloadedMethods;
use AxeBear\Magic\Attributes\Overloaded;

/**
 * This example class shows how class comments
 * can be used to define magic properties and methods.
 *
 * @property string $name
 * @method self name(string $name)
 *
 * @property int $count
 * @method self count(int $count)
 *
 * @property string $repeatedName
 *
 * @method void update(...$args)
 */
class Model {
  use MagicProperties;
  use OverloadedMethods;

  #[Overloaded('update')]
  public function updateFromArray(array $data): void {
    $this->name ??= $data['name'] ?? null;
    $this->count ??= $data['count'] ?? null;
  }

  #[Overloaded('update')]
  public function updateFromValues(string $name, int $count): void {
    $this->name = $name;
    $this->count = $count;
  }

  public function repeatedName(string $name, int $count): string {
    return str_repeat($name, $count);
  }
}

$model = new Model();
$model->name('Axe Bear')->count(1);
$model->update(['name' => 'Axe', 'count' => 2]);
$model->update('Bear', 2);
echo $model->name; // Bear
echo $model->count; // 2
echo $model->repeatedName; // BearBear

安装

composer require axebear/php-magic

脚本

  • composer test:使用 Pest 进行测试
  • composer cli:打开一个加载了包的 Psy shell

入门指南

魔法属性

MagicProperties 特性会检查你的类文档中的 @property@property-read@property-write 标签,并将相应的魔法方法添加到你的类中,以便这些属性可以工作。你可以使用 #[MagicProperty] 属性为任何属性添加配置。

计算属性 你可以为 @property-read 属性提供一个后端受保护的或私有方法。如果你这样做,那么这个方法将被用作属性的 getter。这允许你创建计算属性。此方法的命名参数映射到类的属性,并且输出被缓存。

类型强制转换 根据属性标签中的类型提示应用简单的类型强制转换。PHPStan 支持的大多数 PHPDoc 类型 会自动为你转换。如果类型不受支持,你可以在属性上添加 #[MagicProperty] 属性并定义 onSetonGet 方法来添加自定义类型强制转换。

流畅的获取器和设置器 除了映射属性外,你还可以使用类文档中的 @method 标签创建魔法获取器和设置器方法。这允许你为你的类提供一个流畅的接口,以便可以将多个设置器调用链接在一起。

AxeBear\Magic\Traits\OverloadedMethods

AxeBear\Magic\Traits\TrackChanges

AxeBear\Magic\Traits\Magic

此基础特性是所有处理程序的注册表,当需要调用魔法方法时将调用它们。此包中的其他特性使用此特性作为核心来提供魔法功能,但它也可以直接使用。

重要提示

使用 Magic 特性时,你使用的属性和方法的 可见性 很重要。正在被 重载 的类成员应该是不可访问的,即 protectedprivate,这样魔法方法才能被调用。

事件

当调用魔法方法时,Magic 特性将生成一个 MagicEvent 实例,并将其传递给任何注册的处理程序,这些处理程序与事件名称匹配(使用 fnmatch)。

基础 MagicEvent 实例包含以下属性

  • name:被调用的类成员的名称。
  • stopped:一个布尔值,可以设置为true来阻止事件被任何其他处理程序处理。

此类还提供了设置输出值的能力,该值将由魔法方法返回。当处理魔法方法时,Magic特性将返回此值。输出可以由为事件注册的任何处理程序依次操纵,这意味着您可以将输出值通过多个函数。

  • setOutput(mixed $value): static 设置事件的输出值。
  • hasOutput(): bool 检查事件是否设置了输出值。(这是重要的,因为null是一个有效的输出值。)
  • getOutput(?Closure $defaultValue = null): mixed 获取事件的输出值。

__get

  • 监听器:onMagicGet(string $name, Closure ...$handlers): static
  • 事件:MagicGetEvent
public __get(string $name): mixed

要连接到此事件,请使用$this->onMagicGet($pattern, Closure ...$handlers)方法注册一个或多个处理程序。闭包应期望一个MagicGetEvent实例作为其参数。

__set

  • 监听器:onMagicSet(string $name, Closure ...$handlers): static
  • 事件:MagicSetEvent
public __set(string $name, mixed $value): void

要连接到此事件,请使用$this->onMagicSet($pattern, Closure ...$handlers)方法注册一个或多个处理程序。闭包应期望一个MagicSetEvent实例作为其参数。此事件包括一个额外的value属性,其中包含正在设置的值。

__call

  • 监听器:onMagicCall(string $name, Closure ...$handlers): static
  • 事件:MagicCallEvent
public __call(string $name, array $arguments): mixed

要连接到此事件,请使用$this->onMagicSet($pattern, Closure ...$handlers)方法注册一个或多个处理程序。闭包应期望一个MagicCallEvent实例作为其参数。此事件包括一个额外的arguments属性,其中包含传递给方法的自变量。

魔法属性

此特性会检查您的类文档中的@property@property-read@property-write标记,并将相应的魔法方法添加到您的类中,以便这些属性可以工作。您可以使用#[MagicProperty]属性为任何属性添加配置。

基本用法

在最简单的情况下,当您在类文档中包含@property标记时,MagicProperties特性将为该属性添加getter和setter。

如果类包含同名受保护的或私有属性,则它将用作属性的底层存储。如果没有具有该名称的属性,则值将存储在特性中定义的unboundProperties数组中。

在两种情况下,您都可以使用getRawValue方法获取属性的原始值,绕过可能应用的任何转换。(有关更多信息,请参阅转换值的部分。)

/**
 * @property string $name
 * @property int $count
 */
class Model {
  use MagicProperties;
}

$model = new Model();

$model->name = 'ernst';
echo $model->name; // ernst

$model->count = 5;
echo $model->count; // 5

// Simple type coercion is applied based on the type hint in the property tag.
$model->count = '6';
echo $model->count; // 6

只读和只写属性

您还可以使用@property-read@property-write标记定义只读和只写属性。它们不能解绑。它们需要在您的类中有一个底层属性。否则,只读属性将没有初始值,只写属性将没有存储值的地方。

/**
 * @property-read string $defaultName
 * @property-write string $newName
 */
class Model {
  use MagicProperties;

  protected string $defaultName = 'leonora';

  protected string $newName;
}

$model = new Model();
echo $model->defaultName; // leonora
$model->newName = 'ernst';

计算属性

您还可以通过在类文档中添加一个@property-read标记并定义一个与属性同名受保护的或私有方法来定义计算属性。

如果计算依赖于其他类值,请将这些值作为参数添加到方法中。使用与类成员相同的名称。计算属性的输出被缓存,并且任何包括在方法签名中的参数将用于计算缓存。

/**
 * @property string $name
 * @property int $count
 * @property-read string $repeatedName
 */
class Model {
  use MagicProperties;

  protected string $name;

  protected int $count;

  protected function repeatedName(string $name, int $count): string
  {
    return str_repeat($name, $count);
  }
}

$model = new Model();
$model->name = 'ernst';
$model->count = 3;
echo $model->repeatedName; // ernsternsternst

转换值

您还可以通过将#[MagicProperty]属性添加到属性中来自定义属性设置或检索的方式。#[MagicProperty]属性接受onGetonSet参数,允许在设置值之前修改它。

Both onSet and onGet 接受一个可调用对象数组,这些对象将按照定义的顺序被调用。可调用对象应该接受值作为第一个参数并返回修改后的值。您可以使用内置的PHP函数或定义在类上的自定义类方法。

/**
 * @property string $message
 */
class Model {
  use MagicProperties;

  #[MagicProperty(onSet: ['encode'], onGet: ['decode'])]
  protected string $message;

  protected function encode(string $value): string
  {
      return base64_encode($value);
  }

  protected function decode(string $value): string
  {
      return base64_decode($value);
  }
}
$model = new Model();
$model->message = 'ernst';
echo $model->message; // ernst
echo $model->getRawValue('message'); // ZXJuc3Q=

流畅的获取器和设置器

除了映射属性外,您还可以使用类文档中的 @method 标签创建魔法获取器和设置器方法。当您希望为类提供流畅的接口时,这非常有用。当 MagicProperties 特性检测到带有零个或一个参数的 @method 标签时,它会自动将魔法方法添加到类中。

如果 @method 标签包含一个参数,则 MagicProperties 特性将添加一个设置器方法。如果 @method 标签包含零个参数,则 MagicProperties 特性将添加一个获取器方法。

/**
 * @method string name()
 * @method self name(string $name)
 */
class Model {
  use MagicProperties;
}

$model = new Model();
$model->name('ernst');
echo $model->name(); // ernst

方法重载

PHP 还没有提供用于方法重载的干净语法。通过使用 #[Overloaded] 属性和 OverloadedMethods 特性,您可以将重载方法的逻辑拆分到单独的方法中,这些方法根据传递给方法的参数类型进行调用。

而不是

class Model {
  public function find(...$args) {
    if (count($args) === 1 && is_int($args[0])) {
      return $this->findById($args[0]);
    }
    if (count($args) === 1 && is_string($args[0])) {
      return $this->findBySlug($args[0]);
    }
    if (count($args) === 2 && is_string($args[0]) && is_int($args[1])) {
      return $this->findBySlugAndId($args[0], $args[1]);
    }

    throw new InvalidArgumentException('Invalid arguments');
  }

  protected function findById(int $id) {
    return "id: $id";
  }

  protected function findBySlug(string $slug) {
    return "slug: $slug";
  }

  protected function findBySlugAndId(string $slug, int $id) {
    return "slug: $slug, id: $id";
  }
}

您可以这样做

use AxeBear\Magic\Attributes\Overloaded;
use AxeBear\Magic\Traits\OverloadedMethods;

/**
 * @method string find(...$args)
 */
class Model {
  use OverloadedMethods;

  #[Overloaded('find')]
  protected function findById(int $id) {
    return "id: $id";
  }

  #[Overloaded('find')]
  protected function findBySlug(string $slug) {
    return "slug: $slug";
  }

  #[Overloaded]('find')
  protected function findBySlugAndId(string $slug, int $id) {
    return "slug: $slug, id: $id";
  }
}