axebear / php-magic
PHP 微型框架,用于通过 docblocks 和属性使用魔法方法。
Requires
- php: >=8.2
- phpstan/phpdoc-parser: ^1.28
Requires (Dev)
- pestphp/pest: ^2.34
- psy/psysh: @stable
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] 属性并定义 onSet 和 onGet 方法来添加自定义类型强制转换。
流畅的获取器和设置器 除了映射属性外,你还可以使用类文档中的 @method 标签创建魔法获取器和设置器方法。这允许你为你的类提供一个流畅的接口,以便可以将多个设置器调用链接在一起。
AxeBear\Magic\Traits\OverloadedMethods
AxeBear\Magic\Traits\TrackChanges
AxeBear\Magic\Traits\Magic
此基础特性是所有处理程序的注册表,当需要调用魔法方法时将调用它们。此包中的其他特性使用此特性作为核心来提供魔法功能,但它也可以直接使用。
重要提示
使用 Magic 特性时,你使用的属性和方法的 可见性 很重要。正在被 重载 的类成员应该是不可访问的,即 protected 或 private,这样魔法方法才能被调用。
事件
当调用魔法方法时,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]属性接受onGet和onSet参数,允许在设置值之前修改它。
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"; } }