Цепочка вызовов в PHP


У PHP не самая лучшая репутация среди языков программирования. Язык развивался постепенно с течением времени, и это видно. Но команда разработчиков PHP последовательно улучшает язык с каждым выпуском. PHP 7, в частности, добился больших успехов как в функциях, так и в производительности.

Другие преимущества языка включают в себя хорошую документацию, отсутствие проблем с хостингом и значительное количество онлайн-ресурсов для обучения. И да, все крупнейшие CMS-платформы (Wordpress, Drupal, Joomla) основаны на PHP.

Итак, для тех из нас, кто использует PHP и хочет писать красивый код, как этого можно достичь?

Скептики могут утверждать, что это невозможно, но я не согласен. Фреймворки вроде Laravel (“фреймворк PHP для веб-мастеров”) уже доказали их ошибочность. Один из паттернов, который часто реализуют Laravel и другие уважаемые фреймворки PHP, - это цепочка методов.

Вот пример того, о чем я говорю, API конструктора запросов Laravel:

$users = DB::table('users')
    ->where('votes', '>', 100)
    ->orWhere('name', 'John')
    ->get();
Этот синтаксис “цепочки” хорош тем, что позволяет разработчикам писать код, который делает одну вещь за раз выразительным и читаемым способом. В приведенном выше примере легко увидеть, что происходит:

  • Выбираем таблицу users.
  • Где голосов больше 100.
  • Или где имя пользователя - Джон.
  • Получаем результаты.
Красиво, правда? Давайте погрузимся в то, как этот паттерн может быть воплощен в жизнь.

Конкретный пример

Чтобы проиллюстрировать, как работает цепочка методов, давайте рассмотрим в качестве примера операции с массивами. PHP имеет много полезных функций массива, таких как array_map, array_filter, usort и т. д. Предположим, что у нас есть массив имен героев мультфильма Симпсоны, и мы хотим применить следующие преобразования:
  • Отфильтруйте все имена, которые не заканчиваются на “Симпсон”.
  • Сопоставьте каждое полное имя только с именем. Например, мы заменили бы “Лиза Симпсон” просто “Лиза”.
  • Отсортируйте наши результаты по алфавиту.
Вот один из способов решения нашей задачи:

$characters = [
    'Maggie Simpson',
    'Edna Krabappel',
    'Marge Simpson',
    'Lisa Simpson',
    'Moe Szyslak',
    'Waylon Smithers',
    'Homer Simpson',
    'Bart Simpson'
];

// Ищем полные имена, которые заканчиваются на Simpson
// Отфильтруем элементы, которые не совпадают

$simpsons = array_filter($characters, function ($character) {
    return preg_match('/^.+\sSimpson$/', $character);
});

// Заменяем "Simpson" пустой строкой для каждого элемента
$simpsons = array_map(function ($character) {
    return str_replace(' Simpson', '', $character);
}, $simpsons);


// Сортировка элементов с помощью PHP "strcasecmp"
usort($simpsons, function ($a, $b) {
return strcasecmp($a, $b);
});
var_dump($simpsons); // ['Bart', 'Homer', 'Lisa', 'Maggie', 'Marge']
Это дает нам правильный результат, но те, у кого острый глаз, могут заметить несколько деталей: array_filter принимает массив и обратный вызов, но array_map принимает обратный вызов и массив. Почему эти функции принимают аргументы в другом порядке? Операции фильтрации и сопоставления сохраняются в переменной $simpsons, но usort обращается к нашему массиву по ссылке. Почему такая непоследовательность?

Это не очень хорошо выглядит, если честно. Для сравнения давайте посмотрим, как эта задача может быть решена в JavaScript:

const simpsons = characters
  .filter(character => character.match(/^.+\sSimpson$/))
  .map(character => character.replace(' Simpson', ''))
  .sort((a, b) => b < a)
console.log(simpsons)
Решение JavaScript гораздо более элегантно. Массивы - это тип объекта в JavaScript, и это позволяет связывать операции с массивами. Кроме того, gриведенный выше JavaScript использует функции стрелок для краткости. PHP еще не имеет функций стрелок, но они могут появиться у нас в ближайшее время!

Цепочка методов на массивах встроена в JavaScript, но мы можем эмулировать это в PHP, написав свой собственный пользовательский класс.

Создание класса с цепочкой вызовов

Назовем это Коллекцией. Экземпляр этого класса может быть создан путем передачи массива в конструктор.

class Collection
{
    private $array;
    public function __construct($array)
    {
        $this->array = $array;
    }
}
$characters = new Collection([
    'Maggie Simpson',
    'Edna Krabappel',
    'Marge Simpson',
    'Lisa Simpson',
    'Moe Szyslak',
    'Waylon Smithers',
    'Homer Simpson',
    'Bart Simpson'
]);
Пока что класс делает не так уж много — он просто сохраняет массив, переданный как частное свойство. Давайте сосредоточимся на добавлении метода публичного фильтра.

public function filter($callback)
{
    $this->array = array_filter($this->array, $callback);
    return $this;
}
Вместо того чтобы передавать как массив, так и функцию обратного вызова в качестве аргументов, как раньше, теперь требуется только функция обратного вызова. После преобразования свойства массива экземпляра возвращается сам экземпляр. Это делает возможным цепочку методов.

Далее давайте добавим методы map и sort:

public function map($callback)
{
    $this->array = array_map($callback, $this->array);
    return $this;
}
public function sort($callback)
{
    usort($this->array, $callback);
    return $this;
}
Теперь наши методы готовы к цепочке, но нам нужен способ вернуть конечный результат в виде массива. Это похоже на то, как конструктор запросов Laravel использует get() для выполнения запроса после того, как он был создан с помощью условных выражений.

public function execute()
{
    return $this->array;
}
И мы можем, наконец, связать методы массива следующим образом!

$characters = new Collection([
    'Maggie Simpson',
    'Edna Krabappel',
    'Marge Simpson',
    'Lisa Simpson',
    'Moe Szyslak',
    'Waylon Smithers',
    'Homer Simpson',
    'Bart Simpson'
]);
$simpsons = $characters
    ->filter(function ($character) {
        return preg_match('/^.+\sSimpson$/', $character);
    })
    ->map(function ($character) {
        return str_replace(' Simpson', '', $character);
    })
    ->sort(function ($a, $b) {
        return strcasecmp($a, $b);
    })
    ->execute();
var_dump($simpsons); // ['Bart', 'Homer', 'Lisa', 'Maggie', 'Marge']
Очень похоже на JavaScript! Проблемы с порядком аргументов и доступом к ссылкам все еще существуют под капотом, но наш класс коллекции обрабатывает это за кулисами. В результате получается код, который намного чище и читабельнее!

Резюме

PHP печально известен своей неряшливостью, но Laravel и другие фреймворки используют цепочки вызовов (методы (методов). Операции с массивами в PHP не цепочечны, как в JavaScript, но базовый класс утилит (например, наш класс коллекций!) может быть использован для эмуляции этого процесса и сохранения некоторых маленьких причуд PHP за занавесом.

Примечание. Использование цепочки без различения только для некоторой субъективной выразительности следует рассматривать как явный антипаттерн. В бизнес-доменах такие шаблоны, как DDD, защищают неизменяемость, которая нарушается цепочкой, поскольку вы возвращаете измененный $this.
Автор этого материала - я - Пахолков Юрий. Я оказываю услуги по написанию программ на языках Java, C++, C# (а также консультирую по ним) и созданию сайтов. Работаю с сайтами на CMS OpenCart, WordPress, ModX и самописными. Кроме этого, работаю напрямую с JavaScript, PHP, CSS, HTML - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.

тегизаметки, php




Отправляя сообщение я подтверждаю, что ознакомлен и согласен с политикой конфиденциальности данного сайта.




Урок 33. Вспомогательная функция JavaScript
Урок 9. Простое ограничение доступа в Laravel
Проверка параметров SSL вашего сервера