PHP Profi

Чего всё ещё не хватает в PHP: generics Перевод

РНР 7.2 не за горами, и эта версия принесёт нам такие изменения в типах, как возможность указать тип object в сигнатуре методов, или как расширение типа параметра.
Они подтверждают желание сообщества PHP укрепить систему типов в PHP и улучшить безопасность типов.

В Libcast (видеохостинг), мы ценим эти изменения, которые позволяют полагаться на IDE, которая отображает ошибки типов при вводе кода и проверяет типы во время компиляции, тем самым уменьшая время, необходимое для поиска и исправления ошибки.
Дженерики (Generics) - это фича, которая, как мы надеемся, скоро появятся в PHP и позволит создавать универсальные контейнеры указанного типа.

Дженерики (Generics)

Generic-классы позволяют объявить универсальный контейнер, тип которого должен быть задан во время использования (нельзя использовать общий класс - сам generic).
Для специализации generic-класса, может быть использован любой тип, если он соответствует сигнатуре в универсальном (generic) классе.

Generic-класс может использовать несколько generic-типов, или быть частично специализирован, задав только часть generic-типов.

Дженерики(Generics) в PHP

Generics-RFC всё ещё в черновике и не был принят.

Hack/HHVM уже реализовал дженерики.

ircmaxell провели эксперимент среди пользователей РНР, как говорится "just for fun" (не используйте это в продакшен, конечно).

Дженерики будут полезны в PHP для таких контейнеров: абстрактный тип данных (стек, очередь, карта ...), и контейнеров вашей предметной области (список дней, группа людей ...).
Также они позволяют иметь более точные сигнатуры: на данный момент, PHP имеет iterable для возможности объявить коллекцию в сигнатуре, но объявление generic-iterable позволит сказать, что мы просим коллекцию именно книг.

Дженерики у нас

Мы создали заглушку для нового API видеоплатформы Libcast, в котором мы должны генерировать случайные данные, но иногда следовать некоторым правилам (указанному распределению).

Например, мы хотим генерировать события нашей предметной области, каждое из которых с указанным шансом возникновения — для того, чтобы моделировать более реалистичные данные в заглушке.

Мы хотели бы также создавать фиктивных посетителей в соответствии с реальным географическим распределением в нашей нынешней базе посетителей.

Наиболее напрашивающейся для нас является возможность выделить в общий класс ту часть, где выбор делается по распределению.

Вот упрощенный первый вариант классов для обработки приведенных примеров:

// 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 и DistributedEventGenerator классы практически идентичны, за исключением сигнатур и возвращаемых типов.
С дженириками мы могли бы использовать всего один класс:

class DistributedGenerator
{
    /** @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);
    }
}

Этот класс может стать даже более специализированным, если мы хотим генерировать события таких типов: UserEvent, VideoEvent....
Обратите внимание, что вы также можете включить тип генератора в список типов дженериков. Но попробуем справиться с помощью нескольких имплементаций для генераторов:

class DistributedGenerator
{
    /** @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);
    }
}

Дженерики связаны со всеми концепциями типизации, о которых мы, возможно, поговорим в следующих статьях.

Ссылки:

2017-11-28 оригинал

Последние посты

Комментарии

авторизуйтесь или зарегистрируйтесь, чтобы оставить комментарий
evgwed (6 лет назад)

Идея клевая, но придется тогда пожертвовать скоростью работы или переходить на использование компиляции.

alek13 (6 лет назад)

Ну не совсем.
Пре-компиляция в байт-код и сейчас есть. Если при пре-компиляции будут генерироваться готовые конечные классы, которые используются в данном приложении (а скорее всего так и будут реализовывать), то скорость исполнения не увеличится. Немного возрастёт время пре-компиляции в байт-код, но это будет на столько не существенно, что не отразится на скорости в общем. А с учётом того, что пре-компилированный код кешируется, то разницы не будет вообще.