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