Цепочка вызовов в 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 - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.
Отправляя сообщение я подтверждаю, что ознакомлен и согласен с политикой конфиденциальности данного сайта.