PHP still missing bits: generics.

This article is the first of a series about PHP missing bits.
Later articles will talk about other typing concepts, like accessors and data types.

PHP 7.2 is around the corner, bringing type-related changes like an object type in signature, or parameter type widening.
These confirm the will of the PHP community to harden the PHP type system, and improve type safety.

At Libcast, we appreciate these changes which allow to rely on IDE to show type bugs as you type, and verifying types at compile time, hence reducing the time required to find and fix a bug.
Generics is a feature we hope will hit PHP soon, and allow generic containers of a given type.

Generics

Generic classes allow to declare a generic container which has to be specialized at use (you cannot use a generic class directly).
Any type can be used to specialize a generic class as long as they respect signatures used in the generic class.

The generic class can use several generic types, and then be partially specialized by fixing only a subset of the generic types.

Generics in PHP

A RFC is in draft but has not been accepted yet.

Hack/HHVM already adopted generics.

ircmaxell did an experiment in PHP userland for fun(do not use this in prod of course).

Generics would be useful in PHP for containers : abstract data type (stack, queue, map, …), and domain containers (set of days, group of people, …).
They would also allow more precise signatures : at this time, PHP has iterable to declare a collection in a signature, but generic declaration iterable<Book> would allow to say we are asking for a book collection only.

A case for generics at Libcast

We created a mock of Libcast new secure video platform API in which we need to generate data randomly, but sometimes following a given distribution.

For example, we want to generate domain events each with a given chance to be emitted, this to generate realistic data in the mock.

We would like also to mock fake visitors according to the real geographical distribution of our current visitor base.

We see an opportunity to extract the part where the choice is made according to the distribution in a common class.

Here is a simplified first shot to handle cited examples:

// Generates IP addresses
final class IpGenerator
{
public function generate(string $countryCode = 'FR'): Ip
{
// ...
}
}
final class DistributedIpGenerator
{
/** @var IpGenerator */
private $generator;
    /** @var array */
private $distribution;
    public function __construct(IpGenerator $generator, array $distribution)
{
$this->generator = $generator;
$this->distribution = $distribution;
}
    public function generate(): Ip
{
// Pick a country code wisely
// $countryCode = ...
        return $this->generator->generate($countryCode);
}
}
// Generate domain events
final class EventGenerator
{
public function generate(string $eventType): Event
{
}
}
final class DistributedEventGenerator
{
/** @var EventGenerator */
private $generator;
    /** @var array */
private $distribution;
    public function __construct(EventGenerator $generator, array $distribution)
{
$this->generator = $generator;
$this->distribution = $distribution;
}
    public function generate(): Event
{
// Pick an event type wisely
// $eventType = ...
        return $this->generator->generate($eventType);
}
}

DistributedIpGenerator and DistributedEventGenerator classes are almost identical, except for signatures and return types.
If we had generics, we could have used a single class:

class DistributedGenerator<DataType>
{
/** @var callable */
private $generator;
    /** @var array */
private $distribution;
    public function __construct(callable $generator, array $distribution)
{
$this->generator = $generator;
$this->distribution = $distribution;
}
    public function generate(): DataType
{
// Pick a value wisely
// $value = ...
        return $this->generator($value);
}
}

This generic class could be even more specialised if we want to generate event of a given type: UserEvent, VideoEvent, ….
Note that you can also include the generator type in the list of generic types, but we could have to handle several implementations of the distributed generator:

class DistributedGenerator<GeneratorType, DataType>
{
/** @var GeneratorType */
private $generator;
    /** @var array */
private $distribution;
    public function __construct(GeneratorType $generator, array $distribution)
{
$this->generator = $generator;
$this->distribution = $distribution;
}
    public function generate(): DataType
{
// Pick a value wisely
// $value = ...
        // This part would change for each GeneratorType:
return $this->generator->whatever($value);
}
}

Generics are linked to all sort of typing concepts, and we may talk about others in further articles.

References: