PHP Reverse Category Tree array to Breadcrumb List

2020-07-29 23:14发布

问题:

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

回答1:

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



回答2:

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;