mrshu / php-generics
PHP 泛型库
Requires
- php: >=7.4
- composer-plugin-api: ^1.0|^2.0
- mrsuh/php-parser: 95.1.0
- symfony/console: ^4.0|^5.0|^6.0
- symfony/filesystem: ^4.0|^5.0|^6.0
- symfony/finder: ^4.0|^5.0|^6.0
Requires (Dev)
- composer/composer: ^1.0.2|^2.0
README
目录
工作原理
简要说明
- 解析泛型类;
- 基于它们生成具体类(可以选择
monomorphization
或type-erasure
); - 自动加载具体类而不是泛型类。
例如,您需要添加几个PHP文件
- 泛型类
Box
; - 用于使用泛型类的
Usage
类; - 包含composer自动加载和
Usage
类的脚本。
src/Box.php
<?php namespace App; class Box<T> { private ?T $data = null; public function set(T $data): void { $this->data = $data; } public function get(): ?T { return $this->data; } }
src/Usage.php
<?php namespace App; class Usage { public function run(): void { $stringBox = new Box<string>(); $stringBox->set('cat'); var_dump($stringBox->get()); // string "cat" $intBox = new Box<int>(); $intBox->set(1); var_dump($intBox->get()); // integer 1 } }
bin/test.php
<?php require_once __DIR__ . '/../vendor/autoload.php'; use App\Usage; $usage = new Usage(); $usage->run();
使用 composer dump-generics
命令从泛型类生成具体类
composer dump-generics -vv
composer dump-generics
命令做了什么?
- 查找类中的所有泛型使用(例如
src/Usage.php
)。 - 根据泛型类的名称和参数生成具有唯一名称的具体类。
- 将泛型类名替换为具体类名。
在这种情况下,应生成以下内容
- 2个泛型类的具体类
BoxForInt
和BoxForString
; - 1个具体类
Usage
,泛型类名已替换为具体类名。
使用 composer dump-autoload
命令生成 vendor/autoload.php
composer dump-autoload
运行 bin/test.php 脚本
php bin/test.php
Composer 自动加载首先检查 "cache" 目录,然后是 "src" 目录来加载类。
📘 您可以在以下位置找到包含此示例的存储库 这里。
安装
需求
- PHP >= 7.4
- Composer (PSR-4 自动加载)
安装库
composer require mrsuh/php-generics
将目录("cache/"
)添加到 composer 自动加载 PSR-4,它应放在主目录之前。
composer.json
{ "autoload": { "psr-4": { "App\\": ["cache/","src/"] } } }
单态化
为每个泛型参数组合生成一个新类。
在 monomorphization
之前
<?php namespace App; class Box<T> { private ?T $data = null; public function set(T $data): void { $this->data = $data; } public function get(): ?T { return $this->data; } }
在 monomorphization
之后
<?php namespace App; class BoxForInt { private ?int $data = null; public function set(int $data) : void { $this->data = $data; } public function get() : ?int { return $this->data; } }
命令
composer dump-generics
在类中哪里可以使用泛型?
<?php namespace App; use App\Entity\Cat; use App\Entity\Bird; use App\Entity\Dog; class Test extends GenericClass<Cat> implements GenericInterface<Bird> { // <-- extends/implements use GenericTrait<Dog>; // <-- trait use private GenericClass<int>|GenericClass<Dog> $var; // <-- property type public function test(GenericInterface<int>|GenericInterface<Dog> $var): GenericClass<string>|GenericClass<Bird> { // <-- method argument/return type var_dump($var instanceof GenericInterface<int>); // <-- instanceof var_dump(GenericClass<int>::class); // <-- class constants var_dump(GenericClass<array>::CONSTANT); // <-- class constants return new GenericClass<float>(); // <-- new } }
在泛型类中哪里可以使用参数?
<?php namespace App; class Test<T,V> extends GenericClass<T> implements GenericInterface<V> { // <-- extends/implements use GenericTrait<T>; // <-- trait use use T; // <-- trait use private T|GenericClass<V> $var; // <-- property type public function test(T|GenericInterface<V> $var): T|GenericClass<V> { // <-- method argument/return type var_dump($var instanceof GenericInterface<V>); // <-- instanceof var_dump($var instanceof T); // <-- instanceof var_dump(GenericClass<T>::class); // <-- class constants var_dump(T::class); // <-- class constants var_dump(GenericClass<T>::CONSTANT); // <-- class constants var_dump(T::CONSTANT); // <-- class constants $obj1 = new T(); // <-- new $obj2 = new GenericClass<V>(); // <-- new return $obj2; } }
📘 您可以在以下位置了解更多关于 monomorphization
的信息 这里。
类型擦除
不使用泛型参数生成新类。
在 type erasure
之前
<?php namespace App; class Box<T> { private ?T $data = null; public function set(T $data): void { $this->data = $data; } public function get(): ?T { return $this->data; } }
在 type erasure
之后
<?php namespace App; class Box { private $data = null; public function set($data) : void { $this->data = $data; } public function get() { return $this->data; } }
命令
composer dump-generics --type=type-erasure
在类中哪里可以使用泛型?
<?php namespace App; use App\Entity\Cat; use App\Entity\Bird; use App\Entity\Dog; class Test extends GenericClass<Cat> implements GenericInterface<Bird> { // <-- extends/implements use GenericTrait<Dog>; // <-- trait use private GenericClass<int>|GenericClass<Dog> $var; // <-- property type public function test(GenericInterface<int>|GenericInterface<Dog> $var): GenericClass<string>|GenericClass<Bird> { // <-- method argument/return type var_dump($var instanceof GenericInterface<int>); // <-- instanceof var_dump(GenericClass<int>::class); // <-- class constants var_dump(GenericClass<array>::CONSTANT); // <-- class constants return new GenericClass<float>(); // <-- new } }
在泛型类中哪里可以使用参数?
<?php namespace App; class Test<T,V> extends GenericClass<T> implements GenericInterface<V> { // <-- extends/implements use GenericTrait<T>; // <-- trait use private GenericClass<V> $var; // <-- property type public function test(T|GenericInterface<V> $var): T|GenericClass<V> { // <-- method argument/return type var_dump($var instanceof GenericInterface<V>); // <-- instanceof var_dump(GenericClass<T>::class); // <-- class constants var_dump(GenericClass<T>::CONSTANT); // <-- class constants return new GenericClass<V>(); // <-- new } }
📘 您可以在以下位置了解更多关于 type-erasure
的信息 这里。
功能
使用了什么语法?
《RFC》没有定义特定的语法,所以我使用了Nikita Popov实现的这个语法 这里。
语法示例
<?php namespace App; class Generic<in T: Iface = int, out V: Iface = string> { public function test(T $var): V { } }
语法问题
我必须升级 nikic/php-parser 以使用新语法解析代码。
您可以在以下位置查看为了支持泛型而必须进行的语法更改 这里。
解析器使用 PHP 实现 的 YACC。
由于冲突,YACC(LALR(1))算法和当前的PHP语法使得无法描述泛型的完整语法。
碰撞示例
<?php const FOO = 'FOO'; const BAR = 'BAR'; var_dump(new \DateTime<FOO,BAR>('now')); // is it generic? var_dump( (new \DateTime < FOO) , ( BAR > 'now') ); // no, it is not
因此,嵌套泛型目前不支持。
<?php namespace App; class Usage { public function run() { $map = new Map<Key<int>, Value<string>>();//not supported } }
参数名没有特殊限制
<?php namespace App; class GenericClass<T, varType, myCoolLongParaterName> { private T $var1; private varType $var2; private myCoolLongParaterName $var3; }
支持多个泛型参数
<?php namespace App; class Map<keyType, valueType> { private array $map; public function set(keyType $key, valueType $value): void { $this->map[$key] = $value; } public function get(keyType $key): ?valueType { return $this->map[$key] ?? null; } }
支持默认泛型参数
<?php namespace App; class Map<keyType = string, valueType = int> { private array $map = []; public function set(keyType $key, valueType $value): void { $this->map[$key] = $value; } public function get(keyType $key): ?valueType { return $this->map[$key] ?? null; } }
<?php namespace App; class Usage { public function run() { $map = new Map<>();//be sure to add "<>" $map->set('key', 1); var_dump($map->get('key')); } }
速度有多快?
所有具体类都是预先生成的,并且可以被缓存(不应影响性能)。
当生成许多具体类时,这会负面影响性能:
- 解析具体类;
- 在内存中存储具体类;
- 对每个具体类进行类型检查。
我认为这完全取决于具体案例。
没有composer自动加载则无法工作
只有与composer自动加载配合,具体类的自动加载魔法才能工作。
如果你通过"require"包含文件,因为语法错误,什么都不会工作。
反射
PHP在运行时进行类型检查。
因此,所有泛型参数必须在运行时通过反射来提供。
这是不可能的,因为泛型参数的信息在生成具体类后会丢失。
测试
如何运行测试?
composer test
如何添加测试?
- 将目录00-your-dir-name添加到./tests/{monomorphic/type-erased}
- 生成输出文件并检查它
php bin/generate.php monomorphic tests/monomorphic/000-your-dir-name php bin/generate.php type-erased tests/type-erased/000-your-dir-name