Примеры построения многоуровневых выпадающих списков (select option) и базы данных с применением рекурсии PHP.
В скриптах используется MySQL-таблица `category`
с полями `id`
, `parent`
, `name`
, где поле `parent`
содержит id родителя.
Обычный пробел в начале текста элемента <option>...<option>
не выводится, поэтому нужно использовать мнемонику  
– широкий пробел.
- В начале получаем все записи из БД в виде ассоциативного массива.
- С помощью функции
array_to_tree()
преобразуем его в древовидный, к элементам массива добавляется элемент «children» в который перемещаются все дочерние элементы. - С помощью функции
out_options()
рекурсивно выводятся все элементы массива. - Во втором аргументе функции
out_options()
указывается id элемента, которому нужно установитьselected
.
<?php
$dbh = new PDO('mysql:dbname=таблица;host=localhost', 'логин', 'пароль');
$sth = $dbh->prepare("SELECT * FROM `category` ORDER BY `name`");
$sth->execute();
$category = $sth->fetchAll(PDO::FETCH_ASSOC);
$category = array_to_tree($category);
function array_to_tree($array, $sub = 0)
{
$a = array();
foreach($array as $v) {
if($sub == $v['parent']) {
$b = array_to_tree($array, $v['id']);
if(!empty($b)) {
$a[$v['id']] = $v;
$a[$v['id']]['children'] = $b;
} else {
$a[$v['id']] = $v;
}
}
}
return $a;
}
function out_options($array, $selected_id = 0, $level = 0)
{
$level++;
$out = '';
foreach ($array as $i => $row) {
$out .= '<option value="' . $row['id'] . '"';
if ($row['id'] == $selected_id) {
$out .= ' selected';
}
$out .= '>';
if ($level > 1) {
$out .= str_repeat('#emsp;', $level - 1);
}
$out .= $row['name'] . '</option>';
if (!empty($row['children'])) {
$out .= out_options($row['children'], $selected_id, $level);
}
}
return $out;
}
?>
<select name="category">
<option></option>
<?php echo out_options($category, 0); ?>
</select>
Результат:
Оформление ветвей дерева с помощью символов ├ и └:
<?php
$dbh = new PDO('mysql:dbname=таблица;host=localhost', 'логин', 'пароль');
$sth = $dbh->prepare("SELECT * FROM `category` ORDER BY `name`");
$sth->execute();
$category = $sth->fetchAll(PDO::FETCH_ASSOC);
$category = array_to_tree($category);
function array_to_tree($array, $sub = 0)
{
$a = array();
foreach($array as $v) {
if($sub == $v['parent']) {
$b = array_to_tree($array, $v['id']);
if(!empty($b)) {
$a[$v['id']] = $v;
$a[$v['id']]['children'] = $b;
} else {
$a[$v['id']] = $v;
}
}
}
return $a;
}
function out_options($array, $selected_id = 0, $level = 0)
{
$level++;
$out = '';
foreach ($array as $i => $row) {
$out .= '<option value="' . $row['id'] . '"';
if ($row['id'] == $selected_id) {
$out .= ' selected';
}
$out .= '>';
if ($level > 1) {
if ($level > 2) {
$out .= str_repeat(' ', $level - 1);
}
$keys = array_keys($array);
$last_keys = $keys[count($array) - 1];
if ($last_keys != $i) {
$out .= '├';
} else {
$out .= '└';
}
}
$out .= $row['name'] . '</option>';
if (!empty($row['children'])) {
$out .= out_options($row['children'], $selected_id, $level);
}
}
return $out;
}
?>
<select name="category">
<option></option>
<?php echo out_options($category, 0); ?>
</select>
Результат:
Использование <optgroup label="">...</optgroup>
оправдано если необходимо выбрать только крайнюю категорию в дереве, но optgroup не поддерживает вложенность и никакие пробельные символы в начале label="..."
. Поэтому в примере используется —
– широкое тире.
<?php
$dbh = new PDO('mysql:dbname=таблица;host=localhost', 'логин', 'пароль');
$sth = $dbh->prepare("SELECT * FROM `category` ORDER BY `name`");
$sth->execute();
$category = $sth->fetchAll(PDO::FETCH_ASSOC);
$category = array_to_tree($category);
function array_to_tree($array, $sub = 0)
{
$a = array();
foreach($array as $v) {
if($sub == $v['parent']) {
$b = array_to_tree($array, $v['id']);
if(!empty($b)) {
$a[$v['id']] = $v;
$a[$v['id']]['children'] = $b;
} else {
$a[$v['id']] = $v;
}
}
}
return $a;
}
function out_optgroup_options($array, $selected_id = 0, $level = 0)
{
$level++;
$out = '';
foreach ($array as $i => $row) {
if (empty($row['children'])) {
$out .= '<option value="' . $row['id'] . '"';
if ($row['id'] == $selected_id) {
$out .= ' selected';
}
$out .= '>';
if ($level > 1) {
$out .= str_repeat('#emsp;', $level - 1);
}
$out .= $row['name'] . '</option>';
} else {
$out .= '<optgroup label="';
if ($level > 1) {
$out .= str_repeat('#mdash;', $level - 1);
}
$out .= $row['name'] . '"></optgroup>';
$out .= out_optgroup_options($row['children'], $selected_id, $level);
}
}
return $out;
}
?>
<select name="category">
<option></option>
<?php echo out_optgroup_options($category, 0); ?>
</select>
если выбираешь родителя, то появляется следующий select с его дочерним элементом и так пока дочерний элемент не будет конечным?