Лучший способ генерировать текстовые хлебные крошки из массива PHP, имеющего 3 столбца (идентификатор, путь, имя)?

Для одного возможного решения, см. Мой второй пост ниже.

Наличие массива PHP, хранящего данные из древовидной структуры, с

  • первый столбец, хранящий идентификатор узла,
  • второй столбец, хранящий путь родительского узла в виде объединения значений идентификаторов,
  • третьи столбцы, хранящие имя узла,

Каков наилучший способ для создания пути текста (хлебные крошки) из пути, сделанного из идентификаторов?

Пример записи:

id  |  path  |  name
---------------------
1   | 0       | edible

14  | 1       | fruits

53  | 1.14    | apples
54  | 1.14    | pears

122 | 1.14.53 | red apples
123 | 1.14.53 | green apples
124 | 1.14.54 | yellow pears

Введите идентификатор: 122

Соответствующий входной путь: 1.14.53

Выходная строка: edible > fruits > apples

Идея состоит в том, чтобы достичь чего-то вроде:

foreach($cats as $cat) { // for each category

    foreach(explode('.', $cat['path']) as $id) { // explode the path into chunks
      /*
       1) get the name matching the value of $id
       2) append label to breadcrumbs string
      */
      }
  // 3) output breadcrumbs for the given category
  // [4) list leaf nodes under the breadcrumbs for the current category]
}

Примечание: сам массив генерируется этим запросом MySQL/MariaDB:

$req = "SELECT c.id,p.path,c.name FROM `".$database['database']."`.`".$database['prefix']."productcategories` c
        LEFT OUTER JOIN `".$database['database']."`.`".$database['prefix']."prodcat_path` p
        ON c.id = p.id
        WHERE c.isparent AND (c.id=".$id." OR (p.path=".$id." OR p.path LIKE('".$id.".%') OR p.path LIKE('%.".$id.".%') OR p.path LIKE('%.".$id."'))) ORDER BY p.path ASC";
    $res = mysql_query($req) or die();

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

2 ответа

Вы также можете создать новую функцию на MYSQL, чтобы получить имена из идентификаторов и использовать, как показано ниже:

SELECT p.id,YOURFUNCTION(p.path),.....

https://dev.mysql.com/doc/refman/5.7/en/adding-functions.html

Это мое домашнее решение, написанное на PHP, которое хорошо работает:

function generateBreadcrumbsForNodeAndDescendants($id) {

define ('FOLDER_PATH',dirname(__FILE__).'/');
include(FOLDER_PATH.'db_ajax_connection.inc.php');

// Select node and all subnodes, excepted leafs
// This query works, but is possibly not optimized.
$req = "SELECT c.id,p.path,c.name FROM `".$database['database']."`.`".$database['prefix']."categories` c
        LEFT OUTER JOIN `".$database['database']."`.`".$database['prefix']."paths` p
        ON c.id = p.id
        WHERE c.isparent AND (c.id=".$id." OR (p.path=".$id." OR p.path LIKE('".$id.".%') OR p.path LIKE('%.".$id.".%'))) ORDER BY p.path ASC";

    // We would add the following line to the WHERE clause if we wanted to retrieve leaf nodes too:
    // OR p.path LIKE('%.".$id."')

$res = mysql_query($req) or die();
$descendants = array();
while($descendant = mysql_fetch_assoc($res)) {
  $descendants[] = $descendant;
}

$path = '';
// Get the path to the current node.
// Because the records from the query are ordered by path, this is the first record.
$path = str_replace('.', ',', $descendants[0]['path']);


// This is because there is no record stored in the path table for the first-level nodes
if ($path=='')
  $path = '0';

// Find ancestors of the current node   
$req = "SELECT c.id,p.path,c.name FROM `".$database['database']."`.`".$database['prefix']."categories` c
      LEFT OUTER JOIN `".$database['database']."`.`".$database['prefix']."paths` p
      ON c.id = p.id
      WHERE FIND_IN_SET(c.id,'".$path."')";

$res = mysql_query($req) or die('');
$ancestors = array();
while($ancestor = mysql_fetch_assoc($res)) {
  $ancestors[] = $ancestor;
}

// Build a list of all ancestors and descendants of the current node, i.e. concatenate arrays
$nodes = array_merge($ancestors,$descendants); 

$paths = array();
// Build associative key => value pairs: (id => path) and (id => name)
foreach ($nodes as $node) {
  $paths[$node['id']]=$node['path'];
  $names[$node['id']]=$node['name'];
}    

$html='';
// for each "descendant" node (including custom "root" node), translate numeric path into breadcrumbs
foreach ($descendants as $descendant) {
  $html .= '<p>';
  $path = $paths[$descendant['id']];
  if ($path) { // because no path is stored for the 1st level nodes, we must test that $path != ''
    $i = 0;
    foreach(explode('.', $path) as $id) {
      if ($i)
        $html .= ' > ';
      $html .= $names[$id]; // These nodes can also be encapsulated in html anchors <a href="">...</a> to become links.
      $i++;
    }
    $html .= ' > '; // optional if we want to add the element to the path of its parent nodes (see below)
  }
  $html .= $names[$descendant['id']]; // optional if we want to add the element to the path of its parent nodes
                               // else, we should do some post-processing to remove duplicate paths,
                               // as several nodes may have the same parent path.
  $html .= '</p>';   
}
echo $html;   
}

generateBreadcrumbsForNodeAndDescendants((int) $_POST['key']);

Примечание: добавив OR p.path LIKE('%.".$id."') в конце предложения WHERE в первом запросе можно также извлечь конечные узлы, однако в этом случае в строке возникает неопределенная ошибка смещения $path = str_replace('.', ',', $descendants[0]['path']); для листовых узлов, так как они не имеют потомков. Следовательно, некоторое улучшение кода остается возможным.

Другие вопросы по тегам