Как на PHP перебрать все комбинации значений нескольких массивов

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

foreach($city as $_city) {
    foreach($age as $_age) {
        foreach($gender as $_gender) {
            $response = request($_city, $_age, $_gender);
        }
    }
}

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

$city = [1, 2, 10, 37, 42, 49, 60, 61, 72, 73, 95, 99, 104, 110, 119, 123, 143, 151, 158];
$age = range(14, 60);
$gender = [1, 2];

Для начала объявляем исходные значения. Ну чтобы было.

do {
  $_city = current($city);
  $_age = current($age);
  $_gender = current($gender);
  $response = request($_city, $_age, $_gender);
  step($city, $age, $gender);
while(true);

Таким образом берём текущие значения массива и передаём их как аргументы в функции. Затем вызываем функцию step, которая хитрым образом передвигает внутренние указатели массивов.

function step(array &$city, array &$age, array &$gender) {
    if(cycle($age) ) {
        return;
    }
    if(cycle($gender) ) {
        return;
    }
    cycle($city, false);
}

В моём случае функция step является лишь фасадом функции cycle, которой передаются массивы с данными.

function cycle(array &$arr, $is_cycle = true) {
    if(empty($arr) || false === key($arr)) {
        return;
    }
    next($arr);
    if(key($arr) ) {
        return true;
    }
    if($is_cycle) {
        reset($arr);
    } else {
        throw new Exception('Finish!');
    }
}

Пожалуй, самая интересная часть скрыта в функции cycle. Функция next перемещает внутренний указатель массива на следующий элемент, затем функция key возвращает текущий индекс элемента, либо false в случае, если внутренний указатель вышел за границы массива. Если выход за границы массива произошёл, то указатель перемещается на первый элемент с помощью функции reset.

Если функция cycle возвращает true, то указатели других массивов трогать не нужно, в противном случае нужно так же переместить указатель следующего массива. Аргумент $is_cycle нужен для обозначения последнего массива, после полной итерации которого работу нужно завершить. В данном случае с помощью выброса исключения.

Можно инкапсулировать это решение и оформить его в качестве класса-итератора. Но не в этот раз =)