mrshu/php-generics

PHP 泛型库

安装次数: 927

依赖: 1

建议者: 0

安全: 0

星标: 213

关注者: 6

分支: 7

开放问题: 1

类型:composer-plugin

1.5.1 2024-08-02 14:38 UTC

README

目录

工作原理

简要说明

  • 解析泛型类;
  • 基于它们生成具体类(可以选择 monomorphizationtype-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个泛型类的具体类 BoxForIntBoxForString
  • 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