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

За примерами далеко ходить не придется. Вспомним сортировку. В PHP есть функция sort, которая принимает на вход массив и сортирует его. Казалось бы, все отлично, и не о чем больше мечтать, но давайте вообразим себе ситуацию: на вход в программу приходит список пользователей, который нужно отсортировать по возрасту и вывести на экран.

<?php

$users = [
    ['name' => 'Igor', 'age' => 19],
    ['name' => 'Danil', 'age' => 1],
    ['name' => 'Vovan', 'age' => 4],
    ['name' => 'Matvey', 'age' => 16],
];

При таких условиях функция sort становится абсолютно бесполезной, потому что она может сортировать только списки примитивных типов данных. Но выше я описал только лишь одну из тысяч возможных ситуаций. Мы можем захотеть сортировать по любому параметру (или даже по набору параметров) и в любом порядке. Сортировки нужны часто, и многие из них довольно сложны. Худшее, что можно начать делать — реализовывать функцию sort под каждую ситуацию. Так что же делать? Если покопаться в документации PHP, то можно обнаружить функцию usort. Ее определение звучит так:

Сортирует массив по значениям, используя пользовательскую функцию для сравнения элементов

bool usort ( array &$array , callable $value_compare_func )

Эта функция сортирует элементы массива, используя для сравнения значений callback-функцию, предоставленную пользователем. Используйте эту функцию, если вам нужно отсортировать массив по какому-нибудь необычному признаку. Слово callback означает то, что наша задача — передать функцию (но не вызывать!), а вызывать ее будет функция usort.

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

usort выполняет всю работу по непосредственному перемещению элементов в массиве, но то, какой элемент больше или меньше, — зависит от вас. Достигается подобная схема за счет той самой анонимной функции, передающейся вторым параметром. Эта функция принимает на вход два параметра — usort отдает в нее два элемента, которые она сравнивает в данный момент. В нашем случае элементы — пользователи. Ваша задача — внутри этой функции посчитать, что больше или меньше, и сделать следующее. Если элементы равны, то вы должны вернуть 0, если первый элемент больше второго, то считается, что они отсортированы правильно, и вы должны вернуть 1, иначе возвращается -1, а usort производит их сортировку.

Из кода выше видно, что внутри функции сравнение идет по свойству age переданных пользователей. Нетрудно догадаться, что эта функция вызывается внутри usort множество раз (а именно на каждое сравнение). Как только она начнет возвращать 1 для каждой пары элементов — сортировка завершена.

Код выше можно упростить воспользовавшись специальным оператором <=> (spaceship). Он возвращает 1, -1 или 0 в зависимости от того, как соотносятся его операнды, и работает так, как того ожидает сортировка:

<?php

3 <=> 5; //  -1
5 <=> 5; //  0
5 <=> 2; //  1

Функция usort относится к так называемым функциям высшего порядка (high order functions). Функции высшего порядка — это функции, которые либо принимают, либо возвращают другие функции, либо делают все сразу. Такие функции, как правило, реализуют некий обобщенный алгоритм (например, сортировку), а ключевую часть логики делегируют вам через анонимную функцию. Главный плюс от применения таких функций — серьезное повышение коэффициента повторного использования кода.

В примере выше не обязательно создавать переменную для функции. Говоря откровенно, их вообще редко записывают в переменные. Типичное использование выглядит как прямая передача функции в функцию:

<?php

usort($users, function ($a, $b) {
    if ($a['age'] == $b['age']) {
        return 0;
    }
    return $a['age'] > $b['age'] ? 1 : -1;
});

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

Осталось рассмотреть то, как происходит вызов внутри. С точки зрения синтаксиса ничего нового не будет.

<?php

function say(callable $fn) {
    echo $fn();
}

say(function () {
    return 'hi!';
}); // => hi!

Функция say делает вызов функции, находящейся внутри переменной $fn. В нашем примере функция возвращает строку, которая тут же выводится на экран.

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

const getJsFiles = dir => fs.readdirSync(dir)
  .filter(file => file.endsWith('js'))
  .map(file => path.resolve(dir, file));

В этом коде присутствует 2 функции высшего порядка (filter и map), 3 анонимные функции и два прохода (это делают функции высшего порядка) по содержимому директории dir. Подобный код на PHP, с циклами и без, займет примерно в 4 раза больше строк даже при использовании специальных библиотек, упрощающих использование функций высшего порядка (это те же библиотеки, которые мы рассматривали в курсе). Связано это с многословностью синтаксиса PHP.

В следующих уроках мы рассмотрим три самые главные функции высшего порядка, которыми можно решать практически любые задачи. Две из них используются в примере выше, это map и filter, а третья — reduce (ее еще называют fold). Они все доступны в стандартной библиотеке PHP.


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

  1. PHP The Right Way