PHP Generics
Here’s an example of how the PHP Generics library works:
<?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;
}
}
<?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
}
}
The library uses monomorphization
to implement generics in PHP.
You can read more about the different
approaches to implementing generics.
How It Works
The library generates concrete classes from generic classes with unique names based on the name and arguments of the generic class. These classes are stored in the cache
folder:
Box<string> -> BoxForString
Box<int> -> BoxForInt
Composer then autoloads the concrete classes instead of the original generic classes.
{
"autoload": {
"psr-4": {
"App\\": ["cache/","src/"]
}
}
}
Learn more about how the library works in this article or check out these slides.
Memory Usage
Items in collection: 10000
type | var_class_sizeof(bytes) | var_sizeof(bytes) | memory_get_usage(bytes) |
---|---|---|---|
array(count: 10000) | 0 | 822,224 | 1,051,320 |
psalm(count: 10000) | 1,510 | 822,432 | 1,051,560 |
monomorphic(count: 10000) | 1,528 | 822,432 | 1,051,560 |
type-erased(count: 10000) | 1,512 | 822,432 | 1,051,560 |
Performance
PHPBench (1.2.3) running benchmarks...
with configuration file: /app/phpbench.json
with PHP version 8.1.3, xdebug ❌, opcache ❌
\App\Tests\TypHintBench
benchWithoutType........................R1 I6 - Mo240.713μs (±0.47%)
benchWithArrayType......................R1 I70 - Mo247.663μs (±0.45%)
benchWithMixedType......................R2 I59 - Mo249.293μs (±0.54%)
benchWithClassType......................R1 I26 - Mo306.533μs (±0.48%)
Subjects: 4, Assertions: 0, Failures: 0, Errors: 0
+--------------+--------------------+-----+------+-----+-----------+-----------+--------+
| benchmark | subject | set | revs | its | mem_peak | mode | rstdev |
+--------------+--------------------+-----+------+-----+-----------+-----------+--------+
| TypHintBench | benchWithoutType | | 1000 | 100 | 674.272kb | 240.713μs | ±0.47% |
| TypHintBench | benchWithArrayType | | 1000 | 100 | 674.272kb | 247.663μs | ±0.45% |
| TypHintBench | benchWithMixedType | | 1000 | 100 | 674.272kb | 249.293μs | ±0.54% |
| TypHintBench | benchWithClassType | | 1000 | 100 | 674.272kb | 306.533μs | ±0.48% |
+--------------+--------------------+-----+------+-----+-----------+-----------+--------+
PHP uses dynamic typing, so it’s no surprise that adding types slightly reduces performance. Additionally, arrays consume less memory compared to class wrappers.
You can read more about performance and memory
comparisons here.