1

I have a category tree array fetched from MySQL Table. I want to revert this Category array tree back into Breadcrumb list using PHP.

PHP Category Tree Building Function

function buildTree(array &$elements, $parentId = 0) 
{
    $branch = array();

    foreach ($elements as $element) {
        if ($element['parent_category_id'] == $parentId) {
            $children = buildTree($elements, $element['category_id']);
            if ($children) {
                $element['children'] = $children;
            }
            $branch[$element['category_id']] = $element;
            unset($elements[$element['category_id']]);
        }
    }
    return $branch;
}

Result Array

[48] => Array
    (
        [category_id] => 48
        [category_name] => Cat 1 
        [parent_category_id] => 0
        [children] => Array
            (
                [957] => Array
                    (
                        [category_id] => 957
                        [category_name] =>  Cat 2
                        [parent_category_id] => 48
                        [children] => Array
                            (
                                [1528] => Array
                                    (
                                        [category_id] => 1528
                                        [category_name] =>  Cat 3
                                        [parent_category_id] => 957
                                    )

                                [1890] => Array
                                    (
                                        [category_id] => 1890
                                        [category_name] =>  Cat 4
                                        [parent_category_id] => 957
                                    )

                                [1570] => Array
                                    (
                                        [category_id] => 1570
                                        [category_name] =>  Cat 5
                                        [parent_category_id] => 957
                                    )

                                [958] => Array
                                    (
                                        [category_id] => 958
                                        [category_name] =>  Cat 6
                                        [parent_category_id] => 957
                                    )

                            )

                    )

Now I want to convert this array tree back into Breadcrumb List using PHP, for example

"Cat 1 > Cat 2 > Cat 3"

"Cat 1 > Cat 2 > Cat 4"

"Cat 1 > Cat 2 > Cat 5"

"Cat 1 > Cat 2 > Cat 6"

Any help would be much appreciated.

Screenshot

Screenshot Reference

PHPDev
  • 23
  • 5
  • 3
    Would the breadcrumb list for `Cat6` not be `Cat1 > Cat2 > Cat6`? – KIKO Software Sep 29 '18 at 07:09
  • @KIKOSoftware Sorry my mistake. The result should be "Cat 1 > Cat 2 > Cat 3". "Cat 1 > Cat 2 > Cat 4". "Cat 1 > Cat 2 > Cat 5". "Cat 1 > Cat 2 > Cat 6" – PHPDev Sep 29 '18 at 09:58
  • So you want all four of them, not just one? Suppose that they are returned in an array as values, what should the keys be? The `category_id`? Should "Cat 1" and "Cat 1 > Cat 2" be left out intentionally? – KIKO Software Sep 29 '18 at 10:04
  • @KIKOSoftware, Yes exactly. I want all the 4 of them. Its just an example. This is a dynamic array. So `category_id` of each node should be the keys. `Example : "48 > 957 > 1528", "48 > 957 > 1890"` – PHPDev Sep 29 '18 at 10:43
  • Should "Cat 1" and "Cat 1 > Cat 2" be left out intentionally? Yes it must be skipped. The array with the highest node should be printed. – PHPDev Sep 29 '18 at 11:01

2 Answers2

1

The key concept is converting your tree into a flat array where each category is indexed by it's ID. From that flat structure, you can walk up the hierarchy until you reach the root for each category, creating an array of the levels. I've created a helper class to encapsulate the basic functionality you might want for breadcrumbs. The recursion happens in the _unwindTree method. The _buildBreadcrumbs method calls this function and uses the resulting flat array to build the breadcrumb "lines" for each category. These are the two functions to look at to understand how you convert a tree into an array of category paths.

There are some public functions that provide access to the breadcrumb data in different ways.

<?php

$tree = [
    48 => [
        'category_id' => 48,
        'category_name' => 'Cat 1',
        'parent_category_id' => 0,
        'children' =>
            [
                957 =>
                    [
                        'category_id' => 957,
                        'category_name' => 'Cat 2',
                        'parent_category_id' => 48,
                        'children' =>
                            [
                                1528 =>
                                    [
                                        'category_id' => 1528,
                                        'category_name' => 'Cat 3',
                                        'parent_category_id' => 957
                                    ],
                                1890 =>
                                    [
                                        'category_id' => 1890,
                                        'category_name' => 'Cat 4',
                                        'parent_category_id' => 957
                                    ],
                                1570 =>
                                    [
                                        'category_id' => 1570,
                                        'category_name' => 'Cat 5',
                                        'parent_category_id' => 957
                                    ],
                                958 =>
                                    [
                                        'category_id' => 958,
                                        'category_name' => 'Cat 6',
                                        'parent_category_id' => 957
                                    ]

                            ]

                    ]
            ]
    ]
];

class BreadcrumbHelper
{

    private $_leafOnly = true;
    private $_defaultBaseUrlPath = '/category/';

    private $_tree        = [];
    private $_idMap       = [];
    private $_leafIds     = [];
    private $_breadcrumbs = [];

    /**
     * BreadcrumbHelper constructor.
     * @param array $tree The tree of category data
     */
    public function __construct($tree)
    {
        $this->_tree = $tree;

        //Build the breadcrumb data structure
        $this->_buildBreadcrumbs();
    }

    /**
     * Return breadcrumbs as an array
     * @param mixed $categoryIds optional, only specified categories will be returned
     * @return array
     */
    public function getBreadcrumbArray($categoryIds = [])
    {
        //If a bare ID is passed, wrap it in an array so we can treat all input the same way
        if (!is_array($categoryIds))
        {
            $categoryIds = [$categoryIds];
        }

        //If we have category input, return a filtered array of the breadcrumbs
        if (!empty($categoryIds))
        {
            return array_intersect_key($this->_breadcrumbs, array_flip($categoryIds));
        }

        //If no input, return the fill array
        return $this->_breadcrumbs;
    }

    /**
     * Return breadcrumbs as an array containing HTML markup
     * You may want to modify this to echo HTML directly, or return markup only instead of an array
     * @param mixed $categoryIds optional, only specified categories will be returned
     * @return array
     */
    public function getBreadcrumbHtml($categoryIds = [], $baseUrlPath = null)
    {
        //If a bare ID is passed, wrap it in an array so we can treat all input the same way
        if (!is_array($categoryIds))
        {
            $categoryIds = [$categoryIds];
        }

        //If a base URL path is provided, use it, otherwise use default
        $baseUrlPath = (empty($baseUrlPath)) ? $this->_defaultBaseUrlPath : $baseUrlPath;

        //Filter breadcrumbs if IDs provided
        $breadcrumbs = (empty($categoryIds)) ? $this->_breadcrumbs : array_intersect_key($this->_breadcrumbs, array_flip($categoryIds));

        $output = [];
        foreach ($breadcrumbs as $currCategoryId => $currLine)
        {
            $currLinkBuffer = [];
            foreach ($currLine as $currCategory)
            {
                //Build the markup - customize the URL for your application
                $currLinkBuffer[] = '<a href="' . $baseUrlPath . $currCategory['category_id'] . '">' . $currCategory['category_name'] . '</a>';
            }

            $output[$currCategoryId] = implode(' &gt; ', $currLinkBuffer);
        }

        return $output;
    }

    /**
     * Print the breadcrumbs
     * @param array $categoryIds optional, only specified categories will be printed
     */
    public function printBreadcrumbs($categoryIds = [])
    {
        //If a bare ID is passed, wrap it in an array so we can treat all input the same way
        if (!is_array($categoryIds))
        {
            $categoryIds = [$categoryIds];
        }

        //Filter breadcrumbs if IDs provided
        $breadcrumbs = (empty($categoryIds)) ? $this->_breadcrumbs : array_intersect_key($this->_breadcrumbs, array_flip($categoryIds));

        foreach ($breadcrumbs as $currLine)
        {
            //Build a buffer of the category names
            $currNameBuffer = [];
            foreach ($currLine as $currCategory)
            {
                $currNameBuffer[] = $currCategory['category_name'];
            }

            //Join the name buffer with a separator and echo the result
            echo implode(' > ', $currNameBuffer) . PHP_EOL;
        }
    }

    /**
     * Create the breadcrumb data structure from the provided tree
     */
    private function _buildBreadcrumbs()
    {
        //Unwind the tree into a flat array
        $this->_unwindTree($this->_tree);

        //Traverse the flat array and build the breadcrumb lines
        $categoryIds = ($this->_leafOnly) ? $this->_leafIds:array_keys($this->_idMap);
        foreach ($categoryIds as $currLeafId)
        {
            $currCategoryId = $currLeafId;

            $currLine = [];

            do
            {
                $currLine[]     = $this->_idMap[$currCategoryId];
                $currCategoryId = $this->_idMap[$currCategoryId]['parent_category_id'];
            } while ($currCategoryId != 0);

            $this->_breadcrumbs[$currLeafId] = array_reverse($currLine);
        }
    }

    /**
     * Recursive function that traverses the tree and builds an associative array of all categories
     * indexed by ID. Categories saved in this structure do not include children.
     * @param $branch
     */
    private function _unwindTree($branch)
    {
        foreach ($branch as $currId => $currData)
        {
            //Set the current category in the ID map, remove the children if present
            $this->_idMap[$currId] = array_diff_key($currData, array_flip(['children']));

            if (!empty($currData['children']))
            {
                //Recursion
                $this->_unwindTree($currData['children']);
            }
            else
            {
                $this->_leafIds[] = $currId;
            }
        }
    }
}

//Instantiate our helper with the tree data
$breadcrumbHelper = new BreadcrumbHelper($tree);

echo 'All breadcrumbs: ' . PHP_EOL;
$breadcrumbHelper->printBreadcrumbs();
echo PHP_EOL;

echo 'Single breadcrumb line by category ID: ' . PHP_EOL;
$breadcrumbHelper->printBreadcrumbs(1570);
echo PHP_EOL;

echo 'Multiple categories: ' . PHP_EOL;
$breadcrumbHelper->printBreadcrumbs([957, 1570]);
echo PHP_EOL;

echo 'Breadcrumb HTML: ' . PHP_EOL;
$breadcrumbMarkup = $breadcrumbHelper->getBreadcrumbHtml();
echo $breadcrumbMarkup[1570] . PHP_EOL;
echo PHP_EOL;

echo 'Breadcrumb HTML with custom base URL: ' . PHP_EOL;
$breadcrumbMarkup = $breadcrumbHelper->getBreadcrumbHtml(1570, '/category.php?id=');
echo $breadcrumbMarkup[1570] . PHP_EOL;
echo PHP_EOL;
Rob Ruchte
  • 3,569
  • 1
  • 16
  • 18
  • Thanks. The answer is really close. Could you please include the below feature too?. I want to skip all the parent nodes & I only need the nodes which doesn't have any children.**bold**I have attached a screenshot in my question. Please refer to it.**bold** I want only the nodes inside green box & Skip nodes inside Red boxes. Hope it makes sense. Awaiting your reply.. – PHPDev Sep 30 '18 at 14:05
  • I added a private variable called _leafOnly that determines whether or not so show all categories. – Rob Ruchte Sep 30 '18 at 22:58
  • Thanks. This is the perfect answer. Thank you @Rob Ruchte – PHPDev Oct 02 '18 at 09:39
  • @PHPDev If the answer is perfect, please [accept it](https://stackoverflow.com/help/someone-answers) rather than saying "thanks". – ggorlen Jan 09 '20 at 19:00
0
function treeToArray($data, &$return_data, $index = '', $sub = 'sub')
{
    if (is_array($data)) {
        foreach ($data as $value) {
            if (isset($value[$sub])) {
                $tmp = $value[$sub];
                unset($value[$sub]);
                if ($index) {
                    $return_data[$value[$index]] = $value;
                } else {
                    $return_data[] = $value;
                }
                treeToArray($tmp, $return_data, $index, $sub);
            } else {
                if ($index) {
                    $return_data[$value[$index]] = $value;
                } else {
                    $return_data[] = $value;
                }
            }
        }
    }
    return $return_data;
}

$tree[48] =  Array
    (
    "category_id" => '48',
    "category_name" => 'Cat 1',
    "parent_category_id" => '0',
    "children" => Array
        (
            '957' => Array
                (
                    "category_id" => "957",
                    "category_name" =>  "Cat",
                    "parent_category_id" => "48",
                    "children" => Array
                        (
                            '1528' => Array
                                (
                                    "category_id" => "1528",
                                    "category_name" =>  "Cat3",
                                    "parent_category_id" => "957",
                                )

                        )

                )
            )
    );

$data = [];
treeToArray($tree, $data, 'category_id', 'children');
print_r($data);

hope it can be helpful

tyloafer
  • 123
  • 7
  • This is not the output I was expecting. The output should be `"Cat 1 > Cat 2 > Cat 3" "Cat 1 > Cat 2 > Cat 4" "Cat 1 > Cat 2 > Cat 5" "Cat 1 > Cat 2 > Cat 6"` – PHPDev Sep 29 '18 at 11:04
  • Do you mean that each branch should be displayed? – tyloafer Sep 30 '18 at 02:28