Рекурсия в PHP. Функции php Рекурсивная функция php

Сегодня я расскажу, как на и MySQL создавать иерархическое дерево.

Такие деревья используются при построении категорий динамического сайта, например в интернет-магазине или при выводе комментариев к посту.

Вообще они строятся где только возможно. Главное правильно его построить и применить.

Самое главное, когда строишь иерархическое дерево — это правильная структура базы данных! Для примера рассмотрим структуру базы данных, где хранятся категории сайта. Для простого примера, таблица будет иметь 3 поля:

  1. id — ключ категории
  2. parent_id — id родительской категории
  3. name – название раздела

Создадим таблицу, выполнив SQL-запрос в PHPMyAdmin:

CREATE TABLE `categories` (`id` INT NOT NULL AUTO_INCREMENT , `parent_id` INT NOT NULL , `name` VARCHAR(50) NOT NULL , PRIMARY KEY (`id`));

Теперь нужно заполнить нашу таблицу записями. В результате, должна получится примерно такая таблица:

Можно заполнить тестовую таблицу запросом:

INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (1, 0, "Раздел 1"), (2, 0, "Раздел 2"), (3, 0, "Раздел 3"), (4, 1, "Раздел 1.1"), (5, 1, "Раздел 1.2"), (6, 4, "Раздел 1.1.1"), (7, 2, "Раздел 2.1"), (8, 2, "Раздел 2.2"), (9, 3, "Раздел 3.1");

И сейчас внимание! Дальше по логике нужно делать выборки из БД в цикле для выбора каждой категории и её подкатегории. НО! Ладно, если в БД несколько категорий, что тоже в принципе не правильно. А если сайт — интернет-магазин и у него сотня категорий и столько же подкатегорий? Тогда беда! Неведомое количество запросов к базе данных приведет к замедлению работы сайта или же к полному краху mysql-сервера.

Можно используя только один запрос к БД выбрать все категории и ихние подкатегории.

Сделаем запрос и сформируем удобный массив для дальнейшей работы.

//Выбираем данные из БД $result=mysql_query("SELECT * FROM categories"); //Если в базе данных есть записи, формируем массив if (mysql_num_rows($result) > 0){ $cats = array(); //В цикле формируем массив разделов, ключом будет id родительской категории, а также массив разделов, ключом будет id категории while($cat = mysql_fetch_assoc($result)){ $cats_ID[$cat["id"]] = $cat; $cats[$cat["parent_id"]][$cat["id"]] = $cat; } }

Выбираем все данные из таблицы categories и формируем ассоциативный массив $cats , ключем будет id родительской категорий.

Сейчас будем строить дерево. Для построения будем использовать рекурсивную функцию .

Иерархическое дерево будет иметь такую структуру:

  • Раздел 1
    • Раздел 1.1
      • Раздел 1.1.1
    • Раздел 1.2
  • Раздел 2
    • Раздел 1.1
    • Раздел 1.2
  • Раздел 3
    • Раздел 3.1

Создадим рекурсивную функцию build_tree() . Она будет строить наше иерархическое дерево абсолютно любой вложенности.

Function build_tree($cats,$parent_id,$only_parent = false){ if(is_array($cats) and isset($cats[$parent_id])){ $tree = "

    "; if($only_parent==false){ foreach($cats[$parent_id] as $cat){ $tree .= ""; } }elseif(is_numeric($only_parent)){ $cat = $cats[$parent_id][$only_parent]; $tree .= "
  • ".$cat["name"]." #".$cat["id"]; $tree .= build_tree($cats,$cat["id"]); $tree .= "
  • "; } $tree .= "
"; } else return null; return $tree; }

Функция принимает массив разделов и id раздела. В цикле перебираем подкатегории и если в них есть еще разделы, тогда функция запускается еще раз с новыми параметрами (новый массив разделов и id раздела, который нужно построить). Так формируется дерево любой вложенности!

Для построения дерева, в коде прописываем:

Echo build_tree($cats,0);

Так вот в два шага мы создали иерархическое дерево разделов сайта и не важно сколько там разделов!

UPD Если нужно дерево категорий в обратном порядке зная id категории, тогда нужно воспользоваться функцией:

Function find_parent ($tmp, $cur_id){ if($tmp[$cur_id]["parent_id"]!=0){ return find_parent($tmp,$tmp[$cur_id]["parent_id"]); } return (int)$tmp[$cur_id]["id"]; }

Данная функция принимает массив категорий, ключом которой есть id рубрики, и id категории от которой нужно идти вверх.

Для построения такого дерева запускаем функцию build_tree c такими параметрами:

Echo build_tree($cats,0,find_parent($cats_ID,ВАШ_ID_КАТЕГОРИИ));

Есть вопросы? Задавайте в комментариях

  • Алгоритмы
  • К написанию статьи сподвигли часы раздумий и экспериментов в области построения иерархических списков. Изначально логика обкатывалась на SQL запросах, но в последствии решил реализовать на PHP, дабы снять зависимость от СУБД. На простом примере я покажу как можно пройти от корня иерархии до каждого конечного элемента и обратно, информация скорее для новичков.

    Итак, тестовая иерархия, с которой нам предстоит работать:

    В базе данных имеется самая простая таблица на самом простом MSSQL сервере, тонкости подключения опустим, наша цель - разобраться с иерархией и рекурсией.

    Создадим таблицу:

    CREATE TABLE .( IDENTITY(1,1) NOT NULL, -- уникальное поле, автоинкрементное NULL, -- это поле указывает на элемент уровнем выше, содержит uid родителя (255) NULL, (50) NULL, -- права доступа) ON
    Наполним сведениями:

    Описание полей есть в комментариях, чуть подробнее о поле access :

    По умолчанию в моей системе для каждого нового документа проставляется inherit , то есть наследование от родителя. Для нашего эксперимента для некоторых эелементов пропишем доменные группы. В группе Domain Users моя учётная запись имеется, а вот в AD Group Secret меня нет.

    Что ещё мы имеем. Массив, содержащий список моих доменных групп. Достаётся он достаточно просто, на IIS включена аутентификация Windows, всё работает прозрачно, в PHP логин зашедшего находится в переменной $_SERVER[«AUTH_USER»], далее LDAP запросом получаем список групп.

    Теперь предлагаю получить необходимые данные и перейти непосредственно к делу:

    $stmt = $PDO->query("SELECT * FROM Test"); $table = $stmt->fetchAll(); //Получим таблицу из БД $groups = LDAP::getGroups("$login"); //Получим группы ActiveDirectory

    Задача №1

    Необходимо научиться работать с иерархией как с деревом а не списком. Уровень вложенности заранее не известен и может быть любым, следовательно должно быть универсальное средство, позволяющее выполнять проход по дереву как сверху вниз, так и в обратном направлении.

    Задача №2

    Необходимо гибко управлять доступами, то есть, давать права на группы, отдельные документы и т.д., по аналогии с файловой системой NTFS, можно закрыть права на всю папку, но для одного документа в этой папке доступ нарезать - тоже самое должно получиться и у нас.

    Задача №3

    Необходимо скрыть от пользователей ресурсы, к которым у них нет доступа, но самое главное, при наличии прав хотя бы на один документ где то в глубине закрытой для него ветки, делать видимыми элементы ведущие к этому документу (иначе как пользователь до него доберётся?)

    Вот собственно базовая функция:

    $array = array(); //выходной массив function recursive($data, $pid = 0, $level = 0){ global $array; foreach ($data as $row) { //перебираем строки if ($row["pid"] == $pid) { //Начинаем со строк, pid которых передан в функцию, у нас это 0, т.е. корень сайта //Собираем строку в ассоциативный массив $_row["uid"] = $row["uid"]; $_row["pid"] = $row["pid"]; $_row["name"] = $_row["name"] = str_pad("", $level*3, ".").$row["name"]; //Функцией str_pad добавляем точки $_row["level"] = $level; //Добавляем уровень $array = $_row; //Прибавляем каждую строку к выходному массиву //Строка обработана, теперь запустим эту же функцию для текущего uid, то есть //пойдёт обратотка дочерней строки (у которой этот uid является pid-ом) recursive($data, $row["uid"], $level + 1); } } } recursive($table); //Запускаем
    Описание по большей части привёл в комментариях, но если говорить просто - после того как цикл foreach проходит строку и делает что то с данными(в нашем случае просто копирует данные в другой массив, добавляя поле level и точки к имени), он запускает эту же функцию, передав ей uid строки, и поскольку в условии if мы сравниваем его с pid, то следующий запуск однозначно захватит дочерние элементы. Цикл foreach перебирает все строки у которых uid родителя совпадает с переданным значением, поэтому перезапуская саму себя, функция отработает на каждом элементе каждого уровня. Для наглядности, мы так же передаём level увеличивая его на единицу. В итоге мы увидим какой документ какой уровень вложенности имеет.

    Выводим массив $array в браузер:

    Уже не плохо, не так ли?

    А теперь немного усложним нашу функцию:

    $array = array(); //выходной массив $array_idx_lvl = array(); //индекс по полю level function recursive($data, $pid = 0, $level = 0, $path = "", $access_parent = "inherit"){ global $array; global $array_idx_lvl; //Индекс по level global $groups; //доменные группы //перебираем строки foreach ($data as $row) { //Начинаем со строк, pid которых передан в функцию, у нас это 0, т.е. корень сайта if ($row["pid"] == $pid) { //Собираем строку в ассоциативный массив $_row["uid"] = $row["uid"]; $_row["pid"] = $row["pid"]; $_row["name"] = str_pad("", $level*3, ".").$row["name"]; $_row["level"] = $level; //Добавляем уровень $_row["path"] = $path."/".$row["name"]; //Добавляем имя к пути $_row["view"] = ""; //Разруливаем доступы if($row["access"] == "inherit") { $_row["access"] = $access_parent; //Если стоит наследование, делаем как у родителя } else { $_row["access"] = (in_array($row["access"], $groups)) ? "allow" : "deny"; } $array[$row["uid"]] = $_row; //Результирующий массив индексируемый по uid //Для быстрой выборки по level, формируем индекс $array_idx_lvl[$level][$row["uid"]] = $row["uid"]; //Строка обработана, теперь запустим эту же функцию для текущего uid, то есть //пойдёт обратотка дочерней строки (у которой этот uid является pid-ом) recursive($data, $row["uid"], $level + 1, $_row["path"], $_row["access"]); } } } recursive($table); //Запускаем
    Разбираем по порядку:

    1. Добавлено поле path - для формирования пути, добавляем к значению "/" и имя строки, затем полученное значение передаём в функцию, где история повторяется и на выходе получается путь от корня до элемента.

    2. Результирующий массив теперь формируется не по порядку, начиная с нуля а с привязкой к uid - $array[$row["uid"]] = $_row; . В данном случае это ни как не влияет на работу скрипта, но возможность обращаться к строке по индексу, а не перебором в цикле потребуется нам в дальнейшем, когда будем разбирать проход по дереву в обратную сторону.

    3. Добавлен индекс $array_idx_lvl = array(); . Этот индекс нам так же потребуется позже, смысл таков - результирующий набор складывается не в одну кучу, а с разбивкой на массивы индексируемые по level.

    4. Поле Access . Когда функция запускает саму себя, вместе с остальными параметрами она передаёт свою настройку прав $_row["access"] дочерям, а далее происходит следующее, проверяются права - если выставлено наследование (inherit), то применяются права родителя, если нет - через in_array проверяем, есть ли указанная в access доменная группа среди групп зашедшего пользователя. Если есть - добавляем в строку allow (разрешить), иначе deny (запрет).

    Итоговый результат:

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

    Почему здесь нужен проход в обратную сторону? Предположим у пользователя закрыт доступ для всего контента за исключением одного, самого дальнего(на последнем уровне) документа, если подумать, логично было бы брать начало от доступного, и вести его к корню дерева, показывая только нужные элементы.

    Приступим:

    //Функция прохода вверх по дереву на один уроверь от заданного uid, устанавливает //свойство видимости себе и родителю в зависимости от доступа или заданной ранее видимости... function backRecursive($uid, $view = null, $ident = 0) { global $array; //Если поднялись не больше чем на один уровень if($ident <= 1) { //Если видимость уже есть - не меняем текущую строку, иначе //проверяем доступ и то что пришло от дочки if($array[$uid]["view"] != "show") { $array[$uid]["view"] = ($array[$uid]["access"] == "allow" or $view == "show") ? "show" : "hide"; } backRecursive($array[$uid]["pid"], $array[$uid]["view"], $ident+1); } }
    Что делает эта функция - принимает в качестве параметра uid строки, с которой нужно начать действовать, обращается к этой строке и проверяет видимость. Если в поле view не show(т.е. показывать), а что то другое, проверяет что находится в безопасности, и если там стоит allow (доступ открыт), делает элемент видимым, в противном случае скрытым(hide ), затем запускает себя же, передавая свой pid и настройку видимости, а так же переменную $ident увеличенную на 1, тем самым блокируя последующие самозапуски. При втором проходе, по переданному pid находится родительский элемент, выполняется та же проверка, за исключением одного, если от дочернего в переменной $view передано "show ", то не смотря ни на что, текущему элементу так же присвоится show , то есть видимый.

    На мой взгляд, работа с ограничителем - самый оптимальный вариант, ибо представьте ситуацию, на 10 уровне у нас 100 документов, для полного обхода всего дерева, нам нужно запускать эту функцию на каждом элементе, т.к. если на последнем уровне мы запустим функцию 100 раз, то выполняя самозапуски, перебор 100 раз дойдёт до корня. Если умножить на 10 уровней - уже получится 1000 циклов, что не есть хорошо, поэтому подъём нужно осуществлять равномерно, уровень за уровнем.

    Запускает эту функцию следующий код:

    Function startBack(){ global $array_idx_lvl; $levels = array_keys($array_idx_lvl); //получаем массив уровней $maxLevel = max($levels); //Находим самый глубокий уровень дерева //Проходимся циклом по каждому уровню начиная с самого большого for ($i = $maxLevel; $i > 0; $i--) { $uids = array_keys($array_idx_lvl[$i]); //На текущем уровне обходим все элементы и по каждому инициируем обработку и движение на 1лвл foreach ($uids as $uid) { backRecursive($uid); } } }
    Вот тут как раз и потребовался индекс по уровню. Здесь мы движемся от самого дальнего уровня, заходя в каждый, обрабатывая в нём каждый элемент.

    А вот и картинка:

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

    Вот и собственно всё, думаю с задачей мы справились, основа получена, алгоритм работает, можно применить в реальной системе.

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

    Технически рекурсивные функции ничем не отличаются от обычных. Единственное различие заключается в том, что где-то в коде функции находится вызов ее самой. Например, если вы напишете

    Function test() {
    разные операторы
    test();
    разные операторы
    }
    то это и будет рекурсивная функция.

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

    Одна из наиболее употребительных применений рекурсивных функций — это обход деревьев, и сегодня мы попробуем разобрать несколько примеров. Первое, что приходит в голову — это вывод многомерного массива. Разумеется, в PHP есть функция print_r(), но мы попробуем сделать результат более красивым и приспособленным для просмотра через браузер.

    Function print_array($ar) {
    static $count;

    $count = (isset($count)) ? ++$count: 0;

    $colors = array("#FFCB72", "#FFB072", "#FFE972", "#F1FF72",
    "#92FF69", "#6EF6DA", "#72D9FE");

    if ($count > count($colors)) {
    echo "Достигнута максимальная глубина погружения!";
    $count--;
    return;
    }

    if (!is_array($ar)) {

    ";
    return; }

    echo "

    ";
    echo "\n";
    if (is_array($v)) {
    echo "\n";
    }
    }
    echo "
    $k$v
    ";
    print_array($v);
    echo "
    ";
    $count--;
    }

    В этой функции используется статическая переменная $count, в которой будет содержаться "глубина погружения" функции — мы будем использовать ее для того, чтобы раскрашивать вложенные массивы в разные цвета, в зависимости от глубины вложенности. Статические переменные сохраняют свое значение при выходе из функции, и использовать их при рекурсии достаточно удобно. Разумеется, с тем же успехом можно было бы передавать переменную $count в качестве параметра, но статические переменные использовать удобнее...

    Массив $colors содержит список разных цветов, которые будут использоваться для раскрашивания. Заодно мы его используем для ограничения максимального уровня рекурсии — как только все цвета будут исчерпаны (проверка if ($count >= count($colors))), функция будет выводить сообщение о том, что достигнута максимальная глубина.

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

    If (!is_array($ar)) {
    echo "Passed argument is not an array!

    ";
    return;
    }

    Затем мы "открываем" таблицу (echo "

    ";) и начинаем последовательно просматривать переданный в качестве аргумента массив. Конструкция

    While(list($k, $v) = each($ar)) {
    ...
    }

    Является одним из стандартных способов пошагового прохода массива — в каждом проходе цикла переменным $k и $v присваиваются следующие значения индекса (или, как его еще называют, ключа) и значения элемента массива.

    Получив пару "ключ-значение", мы выводим ее в строке таблицы:

    Echo "

    \n";
    Обратите внимание, что если значением этого элемента является массив, то будет напечатано слово «array». Теперь мы проверяем, является ли значение массивом:
    if (is_array($v))
    и если да, то печатаем (не до конца!) еще одну строку таблицы, пропуская индекс (он уже есть на предыдущей строке):
    echo "\n";
    На этом обработка текущей пары "ключ-значение" заканчивается, и цикл while переходит к следующей паре. А когда весь массив пройден, то нам остается только закрыть таблицу:
    echo "
    $k$v
    ";
    и вызываем нашу функцию печати массива, указывая в качестве аргумента вложенный массив:
    print_array($v);
    Затем (после того как рекурсивно вызванная функция закончит свою работу) мы "закрываем" строку таблицы (обратите внимание, что, так как наша функция печатает "полную таблицу" — от до
    , — нам не требуется вложенную таблицу закрывать — функция об этом сама позаботится, — а надо только закрыть ячейку и строку текущей таблицы).
    echo "
    ";
    и уменьшить значение глубины
    $count--; Из написанного выше ясно, что наша функция ничего не знает о том, вызвана она рекурсивно или нет, да ей это и безразлично — главное, чтобы в качестве аргумента был передан массив. Точно так же, при вызове самой себя для обработки вложенного массива, ей безразлично, что это рекурсивный вызов — он ничем не отличается от вызова какой-то другой функции. А заботой программиста является гарантия того, что рекурсивным вызовам где-то придет конец, то есть функция сможет закончить работу, не вызывая больше саму себя. Как только это случится, глубина вложенных вызовов начнет уменьшаться, и в конце концов функция "вынырнет на поверхность".

    Результат работы подобной функции будет выглядеть примерно следующим образом (в примере максимальная глубина ограничена третьим уровнем, для наглядности добавлен вывод переменной $count, а в качестве массива для распечатки задан массив array(1, array(1.1, 1.2, 1.3), 2, 3, array(3.1, 3.2, array(3.21, 3.22, 3.23, array(3.231, 3.232), 3.24)), 4)

    count key value
    0 0 1
    0 1 Array
    0
    count key value
    1 0 1.1
    1 1 1.2
    1 2 1.3
    0 2 2
    0 3 3
    0 4 Array
    0
    0 5 4 Распечатка массива редко бывает нужна "в реальной жизни", разве что для тестирования своих скриптов, но вот рекурсия может пригодиться довольно часто. Например, более жизненная потребность — обработка всех файлов в директории, включая поддиректории. Скажем, для удаления файлов удобно использовать рекурсивную функцию dd() (сокращение от Directory Delete): function dd($file) {
    if (file_exists($file)) {
    chmod($file,0777);
    if (is_dir($file)) {
    $handle = opendir($file);
    while($filename = readdir($handle))
    if ($filename != "." && $filename != "..") dd($file."/".$filename);
    closedir($handle);
    rmdir($file);
    } else {
    unlink($file);
    }
    }
    }

    Работает она по точно такому же принципу — если в качестве аргумента передан файл, то он удаляется (unlink($file);), а если директория — она открывается, последовательно просматривается, и для каждого файла (включая поддиректории) вызывается все та же функция dd()...

    Еще одним примером, где рекурсивные функции использовать очень удобно, является чтение файлов по HTTP-протоколу с отслеживанием редиректов — вам надо открыть соединение, запросить файл (или только заголовок) и проверить ответ сервера — если в нем содержится заголовок

    То надо соединение закрыть и снова вызвать функцию чтения файла, но уже с новым адресом. Написание такой функции можно предложить в качестве "домашнего задания", но учтите, что к ней надо подходить осторожно — следует обязательно сохранять список всех редиректов (удобно использовать для этого статичный массив) и сравнивать адреса, иначе функция очень легко может зациклиться, если редиректы идут "по кругу".

    Вот, пожалуй, и все. Надеюсь, что использование рекурсивных функций сможет пополнить ваш "арсенал программиста" еще одним мощным орудием...

    Согласитесь, что очень часто, когда пишешь сайт нужно, что бы определенные блоки кода повторялись несколько раз (больше, чем один:-))

    Соответственно правило программирования нам гласит: "Если какой-то кусок кода, неважно какой - одна строка или много, повторяется больше чем один раз - ПОД НЕГО НЕОБХОДИМО ПИСАТЬ ФУНКЦИЮ!"

    Т.е. Функция - это "кусок" програмного кода, котрый выполнятся тогда, когда его "попросят". Иными словами вызываем определенный код многократно, когда это нам надо.

    Пример функций - это, как вариант, какой-нибудь графический редактор, ну например корел. Там ведь сама программа весит совсем мало, а программный код не превышает нескольких страниц (возможно:-)). Все остальное в ней и то, что она может раскидано по функциям - нажали одну кнопку, вызвали функцию, она отработала, завершилась. Нажали другую кнопку - другая функция выполнилась и т.д.

    И так, как описывается функция в php?

    Где test - имя функции

    Самый простейший пример:

    Естественно нам нужно как-то этот TEST() вызывать. Делается это очень просто:

    Обратите внимание на то, что наш test() нельзя объявить дважды. Т.е. следующий код приведет к ошибке

    В php существует несколько тысяч встроенных функций и они, естесственно уже объявлены, а значит если мы, вдруг, захотим объявить свою, ну например
    GetType() , то код приведет к ошибке.

    Так вот прежде чем создавать свою функцию, нам нужно проверить, а не существует ли уже такое имя?

    Делается это с помощью встроенной функции так:

    Т.е. если test() существует - возвращается true, если нет - false

    Параметры функции.

    Как видите, после имени функции идут круглые скобки, соответственно они для чего-то да нужны:-)

    Мы можем передавать в нашу функцию определенные параметры. Это простые переменные, например передаем переменную $name:

    Поскольку у нас появился параметр мы должны его как-то в функцию передать. Делается это несколькими способами:

    Аргументов может быть и больше. Они передаются через запятую

    Получим:

    Есть еще один способ вызова функций, который используется крайне редко, НО знать о нем необходимо.

    Например:

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

    Так же есть параметры обязательные и необязательные. Сначала передаются обязательные параметры!

    Помните нашу таблицу умножения?

    Давайте напишем под нее функцию, немножко модернизировав таблицу, что бы ее можно было выделять цветом и жирным:

    На 18-ой строке мы вызываем функцию с параматрами по умолчанию, а потом вызываем с различными параметрами. Смотрим что получилось:

    Вот и все волшебство! :-)

    Возвращение значения функцией.

    Иногда нам нужно, что бы функция не что-то там выполнила, вывела и т.д. А вернула какое-то значение (число), например как встроенная функция strlen() которая возвращала длину строки. Смотрим

    Обратите внимание, что return не просто возвращает значение функции, но и заканчивает ее выполнение - наподобии break в циклах. Т.е. все, что будет ниже return в функции никогда не вполнится!

    Рекурсивный вызов функции.

    Принцип работы в том, что функция вызывает сама себя, т.е. рекурсия. Рассмотрим на примере функции считающей факториал (кто забыл - например факториал пяти это 1*2*3*4*5 и помня, что факториал нуля это 1)

    Надеюсь, что здесь все понятно.

    Что-то такое рекурсия, многие из Вас знают. Но, на всякий случай, рекурсия - это вызов функции внутри самой себя . Однако, это определение все знают, но вот с пониманием возникают неясности. Я решил в этой статье разобрать рекурсию в PHP на достаточно простом и реальном примере из практики.

    Не секрет, что все данные (за редким исключением), приходящие из форм, надо пропускать через функцию htmlspecialchars() , дабы избежать XSS . И я думаю, что Вы знаете, что некоторые входные данные являются массивами (например, при той же загрузке файлов). И наша с Вами задача написать такую функцию, которая принимает массив всех входящих данных, а также учесть, что среди этих данных есть также массивы, внутри которых также могут быть массивы и так далее.

    Привожу код функции:

    function xss($data) {
    if (is_array($data)) { // Если это массив
    $result = array(); // Создаём новый массив
    foreach ($data as $key => $value) { // Перебираем исходный массив
    $result[$key] = xss($value); // Рекурсивно вызываем функцию xss
    }
    return $result; // Возвращаемый "защищённый" массив
    }
    return htmlspecialchars($data, ENT_QUOTES); // Если это не массив, то вызываем htmlspecialchars()
    }
    $data = xss($_REQUEST); // Пример вызова функции
    ?>

    Ключевым моментом здесь является, что внутренних массивов внутри входящего $data может быть очень много, поэтому без рекурсии в PHP здесь не обойтись. А сам алгоритм простой:

    1. Если входящие данные - это НЕ массив, то просто вызываем функцию htmlspecialchars() .
    2. Если входящие данные - это массив, то перебираем все элементы массива и для каждого вызываем эту же функцию. А дальше возвращаемся к пункту 1 .

    Я понимаю, что для новичков рекурсия в PHP - это достаточно сложная вещь, но я рекомендую прямо пройтись по коду, как будто Вы интерпритатор PHP . Тогда станет всё яснее.

    Проблемы