Зарегистрируйтесь, чтобы продолжить обучение

Fluent Interface PHP: Объектно-ориентированный дизайн

Текучий интерфейс (Fluent Interface) удобен для создания DSL. Domain Specific Language (Предметно-ориентированный язык) — язык, специализированный под конкретную область применения. Структура такого языка отражает специфику решаемых с его помощью задач. Яркий пример подобного языка — библиотека jQuery, с которой знакомо большинство программистов (или хотя бы слышали о ней).

// Вызов методов через точку в одной строчке
$('#test').css('color', '#333').height(200);

На техническом уровне есть ровно два способа создать такой интерфейс.

This

Первый способ основан на возврате $this из методов, которые участвуют в построении цепочек. $this — ссылка на тот объект, в контексте которого вызывается метод, а, следовательно, его можно возвращать как обычное значение.

<?php

class Collection
{
    private $coll;

    public function __construct(array $coll)
    {
        $this->coll = $coll;
    }

    public function map(callable $fn)
    {
        $this->coll = array_map($fn, $this->coll);

        return $this;
    }

    public function filter(callable $fn)
    {
        $this->coll = array_filter($this->coll, $fn);

        return $this;
    }

    // Возвращает саму коллекцию, а не this.
    // Этот метод всегда последний в цепочке вызовов Collection.
    public function all()
    {
        return $this->coll;
    }
}

$cars = new Collection([
    ['model' => 'rapid', 'year' => 2016],
    ['model' => 'rio', 'year' => 2013],
    ['model' => 'mondeo', 'year' => 2011],
    ['model' => 'octavia', 'year' => 2014]
]);

$cars->filter(fn($car) => $car['year'] > 2013)
     ->map(fn($car) => $car['model']);
$cars->all(); // [rapid, octavia]

У этого способа есть один серьёзный недостаток — объект изменяется. Это значит, что нельзя взять и просто так переиспользовать объект-коллекцию для разных выборок, потому что они начнут накладываться друг на друга.

На практике часто используется другой подход, с которым мы уже познакомились в прошлом курсе. Все, что нужно сделать — добавить немного функциональности в ооп, то есть возвращать не $this, а создавать новый объект того же типа с обновлённой коллекцией.

<?php

class Collection
{
    private $coll;

    public function __construct(array $coll)
    {
        $this->coll = $coll;
    }

    public function map(callable $fn)
    {
        $coll = array_map($fn, $this->coll);

        return new Collection($coll);
    }

    public function filter(callable $fn)
    {
        $coll = array_filter($this->coll, $fn);

        return new Collection($coll);
    }

    // Возвращает саму коллекцию, а не this. Этот метод всегда последний в цепочке вызовов Collection.
    public function all()
    {
        return $this->coll;
    }
}

$cars = new Collection([
    ['model' => 'rapid', 'year' => 2016],
    ['model' => 'rio', 'year' => 2013],
    ['model' => 'mondeo', 'year' => 2011],
    ['model' => 'octavia', 'year' => 2014]
]);

$filteredCars = $cars->filter(fn($car) => $car['year'] > 2013);
$mappedCars = $filteredCars->map(fn($car) => $car['model']);
$mappedCars->all(); // [rapid, octavia]
$cars->all();
// [
//   ['model' => 'rapid', 'year' => 2016],
//   ['model' => 'rio', 'year' => 2013],
//   ['model' => 'mondeo', 'year' => 2011],
//   ['model' => 'octavia', 'year' => 2014]
// ]

Теперь каждый вызов возвращает новый объект. Такой код значительно безопаснее в использовании и позволяет без проблем переиспользовать новые коллекции. Изменение одной не приведёт к автоматическому изменению всех остальных.

self

В каждом методе, который участвует в создании текучего интерфейса, последняя строчка всегда содержит один и тот же вызов: new Collection($coll). Её можно записать проще, не дублируя названия класса. Помните, как в предыдущем курсе использовался self для работы со статическими членами класса? Так вот self работает и с обычными методами, вызов new self($coll) идентичен вызову new Collection($coll), другими словами вместо self подставляется текущий класс. У такого вызова есть ещё одно преимущество, о котором мы поговорим в следующем ООП курсе, в теме наследования. В двух словах self реализуется посредством позднего связывания и при наследовании раскрывается в тот класс, с которым прямо сейчас идёт работа.

<?php

class Collection
{
    // ...

    public function map(callable $fn)
    {
        $coll = array_map($fn, $this->coll);

        return new self($coll);
    }

    // ...
}

Дополнительные материалы

  1. Текучий интерфейс

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 25 000 ₸ в месяц
Разработка веб-приложений на Laravel
10 месяцев
с нуля
Старт 26 декабря

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»