Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/mrsuh/php-generics

PHP generics written in PHP
https://github.com/mrsuh/php-generics

Last synced: 4 days ago
JSON representation

PHP generics written in PHP

Awesome Lists containing this project

README

        

# PHP generics written in PHP

![](https://github.com/mrsuh/php-generics/actions/workflows/tests.yml/badge.svg)
![](https://img.shields.io/github/license/mrsuh/php-generics.svg)
![](https://img.shields.io/github/v/release/mrsuh/php-generics)

## Table of contents

* [How it works](#how-it-works)
* [Installation](#installation)
* [Monomorphization](#monomorphization)
* [Type erasure](#type-erasure)
* [Features](#features)
* [Tests](#tests)

## How it works

In a nutshell:
+ parse generics classes;
+ generate concrete classes based on them (you can choose `monomorphization` or `type-erasure`);
+ autoload concrete classes instead of generics classes.

For example, you need to add several PHP files:
+ generic class `Box`;
+ class `Usage` for use generic class;
+ script with composer autoload and `Usage` class.

src/Box.php
```php
{

private ?T $data = null;

public function set(T $data): void {
$this->data = $data;
}

public function get(): ?T {
return $this->data;
}
}
```

src/Usage.php
```php
();
$stringBox->set('cat');
var_dump($stringBox->get()); // string "cat"

$intBox = new Box();
$intBox->set(1);
var_dump($intBox->get()); // integer 1
}
}
```

bin/test.php
```php
run();
```

Generate concrete classes from generic classes with `composer dump-generics` command
```bash
composer dump-generics -vv
```

What the `composer dump-generics` command does?
+ finds all generic uses in classes (`src/Usage.php` for example).
+ generates concrete classes from generic classes with unique names based on name and arguments of generic class.
+ replaces generic class names to concrete class names in places of use.

In this case should be generated:
+ 2 concrete classes of generics `BoxForInt` and `BoxForString`;
+ 1 concrete class `Usage` with replaced generics class names to concrete class names.

Generate vendor/autoload.php with `composer dump-autoload` command
```bash
composer dump-autoload
```

Run bin/test.php script
```bash
php bin/test.php
```

Composer autoload first checks the "cache" directory and then the "src" directory to load the classes.

:blue_book: You can find repository with this example [here](https://github.com/mrsuh/php-generics-example).

## Installation

#### Require
* PHP >= 7.4
* Composer (PSR-4 Autoload)

Install library
```bash
composer require mrsuh/php-generics
```

Add directory(`"cache/"`) to composer autoload PSR-4 for generated classes. It should be placed before the main directory.

composer.json
```json
{
"autoload": {
"psr-4": {
"App\\": ["cache/","src/"]
}
}
}
```

## Monomorphization

A new class is generated for each generic argument combination.

Before `monomorphization`:
```php
{

private ?T $data = null;

public function set(T $data): void
{
$this->data = $data;
}

public function get(): ?T
{
return $this->data;
}
}
```

After `monomorphization`:
```php
data = $data;
}

public function get() : ?int
{
return $this->data;
}
}
```
#### Command
```bash
composer dump-generics
```

#### Where in class can generics be used?
```php
implements GenericInterface { // <-- extends/implements

use GenericTrait; // <-- trait use

private GenericClass|GenericClass $var; // <-- property type

public function test(GenericInterface|GenericInterface $var): GenericClass|GenericClass { // <-- method argument/return type

var_dump($var instanceof GenericInterface); // <-- instanceof

var_dump(GenericClass::class); // <-- class constants
var_dump(GenericClass::CONSTANT); // <-- class constants

return new GenericClass(); // <-- new
}
}
```

#### Where in generic class can parameters be used?
```php
extends GenericClass implements GenericInterface { // <-- extends/implements

use GenericTrait; // <-- trait use
use T; // <-- trait use

private T|GenericClass $var; // <-- property type

public function test(T|GenericInterface $var): T|GenericClass { // <-- method argument/return type

var_dump($var instanceof GenericInterface); // <-- instanceof
var_dump($var instanceof T); // <-- instanceof

var_dump(GenericClass::class); // <-- class constants
var_dump(T::class); // <-- class constants
var_dump(GenericClass::CONSTANT); // <-- class constants
var_dump(T::CONSTANT); // <-- class constants

$obj1 = new T(); // <-- new
$obj2 = new GenericClass(); // <-- new

return $obj2;
}
}
```

:blue_book: You can read more about `monomorphization` [here](https://dev.to/mrsuh/generics-implementation-approaches-3bf0).

## Type erasure

A new class is generated without generics arguments.

Before `type erasure`:
```php
{

private ?T $data = null;

public function set(T $data): void
{
$this->data = $data;
}

public function get(): ?T
{
return $this->data;
}
}
```

After `type erasure`:
```php
data = $data;
}

public function get()
{
return $this->data;
}
}
```

#### Command
```bash
composer dump-generics --type=type-erasure
```

#### Where in class can generics be used?
```php
implements GenericInterface { // <-- extends/implements

use GenericTrait; // <-- trait use

private GenericClass|GenericClass $var; // <-- property type

public function test(GenericInterface|GenericInterface $var): GenericClass|GenericClass { // <-- method argument/return type

var_dump($var instanceof GenericInterface); // <-- instanceof

var_dump(GenericClass::class); // <-- class constants
var_dump(GenericClass::CONSTANT); // <-- class constants

return new GenericClass(); // <-- new
}
}
```

#### Where in generic class can parameters be used?
```php
extends GenericClass implements GenericInterface { // <-- extends/implements

use GenericTrait; // <-- trait use

private GenericClass $var; // <-- property type

public function test(T|GenericInterface $var): T|GenericClass { // <-- method argument/return type

var_dump($var instanceof GenericInterface); // <-- instanceof

var_dump(GenericClass::class); // <-- class constants
var_dump(GenericClass::CONSTANT); // <-- class constants

return new GenericClass(); // <-- new
}
}
```

:blue_book: You can read more about `type-erasure` [here](https://dev.to/mrsuh/generics-implementation-approaches-3bf0).

## Features

#### What syntax is used?

The [RFC](https://github.com/PHPGenerics/php-generics-rfc) does not define a specific syntax so i took [this one](https://github.com/PHPGenerics/php-generics-rfc/issues/45) implemented by Nikita Popov

Syntax example:
```php
{

public function test(T $var): V {

}
}
```

#### Syntax problems

I had to upgrade [nikic/php-parser](https://github.com/nikic/PHP-Parser) for parse code with new syntax.

You can see [here](https://github.com/mrsuh/PHP-Parser/pull/1/files#diff-14ec37995c001c0c9808ab73668d64db5d1acc1ab0f60a360dcb9c611ecd57ea) the grammar changes that had to be made for support generics.

Parser use [PHP implementation](https://github.com/ircmaxell/PHP-Yacc) of [YACC](https://wikipedia.org/wiki/Yacc).

The YACC([LALR](https://wikipedia.org/wiki/LALR(1))) algorithm and current PHP syntax make it impossible to describe the full syntax of generics due to collisions.

Collision example:
```php
('now')); // is it generic?
var_dump( (new \DateTime < FOO) , ( BAR > 'now') ); // no, it is not
```

[Solution options](https://github.com/PHPGenerics/php-generics-rfc/issues/35#issuecomment-571546650)

Therefore, nested generics are not currently supported.
```php
, Value>();//not supported
}
}
```

#### Parameter names have not special restrictions

```php
{
private T $var1;
private varType $var2;
private myCoolLongParaterName $var3;
}
```

#### Several generic parameters support

```php
{

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;
}
}
```

#### Default generic parameter support

```php
{

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
();//be sure to add "<>"
$map->set('key', 1);
var_dump($map->get('key'));
}
}
```

#### How fast is it?

All concrete classes are pre-generated and can be cached(should not affect performance).

Generating many concrete classes should negatively impact performance when:
+ resolves concrete classes;
+ storing concrete classes in memory;
+ type checking for each concrete class.

I think it's all individual for a specific case.

#### Doesn't work without composer autoload

Autoload magic of concrete classes works with composer autoload only.

Nothing will work because of syntax error if you include file by "require"

#### Reflection

PHP does type checks in [runtime](https://github.com/PHPGenerics/php-generics-rfc/issues/43).

Therefore, all generics arguments [must me available](https://github.com/PHPGenerics/php-generics-rfc/blob/cc7219792a5b35226129d09536789afe20eac029/generics.txt#L426-L430) through reflection in runtime.

It can't be, because information about generics arguments is erased after concrete classes are generated.

## Tests

### How to run tests?

```bash
composer test
```

### How to add test?

+ Add directory 00-your-dir-name to ./tests/{monomorphic/type-erased}
+ Generate output files and check it
```bash
php bin/generate.php monomorphic tests/monomorphic/000-your-dir-name
php bin/generate.php type-erased tests/type-erased/000-your-dir-name
```