技术/可调用反射

一个简化任何可调用或构造函数反射的便捷库。PHP8就绪!

0.4.1 2023-03-14 16:42 UTC

This package is auto-updated.

Last update: 2024-09-04 11:17:20 UTC


README

Status

技术可调用反射

Technically\CallableReflection 是一个简化任何 callable 反射的便捷库。它提供了一个统一的接口,无缝支持 PHP8 联合类型,用于读取可调用参数。它还允许您通过 call()apply() 简单地调用可调用,支持命名参数。

特性

  • 统一且简化的反射可调用参数接口
  • 无依赖项
  • PHP 8.0 就绪(支持联合类型提示;请参阅以下示例)
  • PHP 7.1+ 兼容
  • 语义化版本控制
  • 测试

安装

使用 Composer 包管理器将 Technically\CallableReflection 添加到您的项目中

composer require technically/callable-reflection

示例

反射可调用属性

<?php
$function = function (string $abstract, Closure|string|null $concrete): mixed {
    // function body
};

$reflection = CallableReflection::fromCallable($function);

var_dump($reflection->isFunction()); // false
var_dump($reflection->isMethod()); // false
var_dump($reflection->isClosure()); // true

[$p1, $p2] = $reflection->getParameters();

var_dump($p2->getName()); // "concrete"
var_dump($p2->isNullable()); // true
var_dump($p2->isOptional()); // false
var_dump($p2->hasTypes()); // true

[$t1, $t2, $t3] = $p2->getTypes();

var_dump($t1->isScalar()); // false 
var_dump($t1->isClassName()); // true 
var_dump($t1->getType()); // "Closure" 

var_dump($t2->isScalar()); // true 
var_dump($t2->isClassName()); // false 
var_dump($t2->getType()); // "string"

var_dump($t3->isNull()); // true
var_dump($t3->isScalar()); // false 
var_dump($t3->isClassName()); // false 
var_dump($t3->getType()); // "null" 

反射任意类构造函数

<?php
final class MyService {
    public function __construct(LoggerInterface $logger, bool $debug = false)
    {
        // ...
    }
}

$reflection = CallableReflection::fromConstructor(MyService::class);

var_dump($reflection->isConstructor()); // true
var_dump($reflection->isFunction()); // false
var_dump($reflection->isMethod()); // false
var_dump($reflection->isClosure()); // false

[$p1, $p2] = $reflection->getParameters();

var_dump($p1->getName()); // "logger"
var_dump($p1->isNullable()); // false
var_dump($p1->isOptional()); // false
var_dump($p1->hasTypes()); // true

var_dump($p2->getName()); // "debug"
var_dump($p2->isNullable()); // false
var_dump($p2->isOptional()); // true
var_dump($p2->hasTypes()); // true

检查值是否满足参数类型声明

$function = function (int|string $value = null): mixed {
    // function body
};

$reflection = CallableReflection::fromCallable($function);

[$param] = $reflection->getParameters();

var_dump($param->satisfies(null)); // true
var_dump($param->satisfies(1)); // true
var_dump($param->satisfies('Hello')); // true
var_dump($param->satisfies(2.5)); // false
var_dump($param->satisfies([])); // false
var_dump($param->satisfies(true)); // false

通过反射调用可调用

$function = function (string $abstract, Closure|string|null $concrete): mixed {
// function body
};

$reflection = CallableReflection::fromCallable($function);

// 1) call with positional parameters
$result = $reflection->call(LoggerInterface::class, MyLogger::class);

// 1) call with named parameters
$result = $reflection->call(concrete: MyLogger::class, abstract: LoggerInterface::class);

// 2) call with positional parameters array 
$result = $reflection->apply([LoggerInterface::class, MyLogger::class]);

// 3) call with named parameters array 
$result = $reflection->apply(['concrete' => MyLogger::class, 'abstract' => LoggerInterface::class]);

// 4) call with mixed named and positional parameters array 
$result = $reflection->apply([LoggerInterface::class, 'concrete' => MyLogger::class]);

// 5) CallableReflection is a callable by itself
$result = $reflection(LoggerInterface::class, MyLogger::class);

通过反射调用构造函数

final class MyService {
    public function __construct(LoggerInterface $logger, bool $debug = false)
    {
        // ...
    }
}

$reflection = CallableReflection::fromConstructor(MyService::class);

$service = $reflection->call(new NullLogger());
// or alternatively:
// $service = $reflection->apply(['logger' => new NullLogger()]);
assert($service instanceof MyService);

这比反射 Closure::fromCallable() 好在哪里

这个库的功能与标准 Closure::fromCallable() 的反射功能有些相似。如果这对您的用例足够,我建议使用标准代码。

然而,这个库提供了一些额外的价值

  • 它统一了与所有类型的可调用的交互,包括类构造函数。这实际上是我考虑的主要用例——构建一个依赖注入服务容器。

    例如,您不能使用 Closure::fromCallable() 创建一个新实例

    $reflection = new ReflectionFunction(Closure::fromCallable([MyRemoteService::class, '__construct']));
    $service = $reflection->call($token); // (!) doesn't work

    但您可以使用这个库调用构造函数

    $reflection = CallableReflection::fromConstructor(MyRemoteService::class);
    $service = $reflection->call($token);
  • 它可以知道并告知反射了哪种类型可调用,而 Closure::fromCallable() 则丢失了这些信息。这可能对某些用例很重要,或者不一定。取决于场景。

  • 它还有一些方便的额外获取器和检查器。例如 satisfies()isOptional()isNull()isScalar() 等。

  • 我还发现这个 API 比较直观和方便(当然,这是主观的,可以讨论),因为原生反射 API 由于向后兼容性添加而略受污染。

  • 在性能方面,我相信这个实现肯定比使用原生代码要慢。尽管我没有测试以提供确切的数字。它基本上在做几乎相同的事情,但额外添加了一些代码——只是在反射 API 之上操作。

变更日志

此项目的所有重要更改都将记录在 CHANGELOG 文件中。

鸣谢

由 👾 Ivan Voskoboinyk 实现。