Create simple array with category tree structure - php

I would like to create a category-tree-array, with unlimited subcategories. Without having to nest foreach loops in foreach loops.
The goal is to have an array of categories in the right order, that I can use for multiple other functions. I need to be able to easily go through this array to find more underlying data. For instance to fetch and later display the (blog, page or product) items that reside in these categories.
It should look something like this:
Array
(
[0] => Array
(
[url] => url_to_cat
[title] => Top Category 1
[depth] => 0
)
[1] => Array
(
[url] => url_to_cat
[title] => Top Category 2
[depth] => 0
)
[2] => Array
(
[url] => url_to_cat
[title] => Sub Category 1 of Category 2
[depth] => 1
)
[3] => Array
(
[url] => url_to_cat
[title] => Sub Category 2 of Category 2
[depth] => 1
)
[4] => Array
(
[url] => url_to_cat
[title] => Sub Category 1 of Sub Category 2
[depth] => 2
)
)
With some help from this and other sites I have come to this function below, wich is in the right direction, but it gets me a multi-dimensional array. That will be difficult for displaying.
The category table has these fields: cat_id, parent_id, title.
The url comes from another table, is not really relevant here.
function category_list($category_parent_id = 0) {
static $cats;
if (!is_array($cats)) {
$sql = "SELECT * FROM category";
$result = $db->query($sql);
while ($record = $result->fetch_array()) {
$cats[] = $record;
}
}
foreach ($cats as $cat) {
// if not a match, move on
if ((int) $cat['parent'] !== (int) $category_parent_id) {
continue;
}
$item[$i]['url'] = 'url';
$item[$i]['title'] = $cat['title'];
$item[$i]['children'] = category_list($cat['cat_id']);
$list_items[] = $item;
}
return $list_items;
}
The initial $cats array:
Array
(
[0] => Array
(
[title] => Top Category 1
[parent] => 0
[cat_id] => 1
)
[1] => Array
(
[title] => Top Category 2
[parent] => 0
[cat_id] => 2
)
[2] => Array
(
[title] => Sub Category 1 of Category 2
[parent] => 2
[cat_id] => 3
)
[3] => Array
(
[title] => Sub Category 2 of Category 2
[parent] => 2
[cat_id] => 4
)
[4] => Array
(
[title] => Sub Sub Category 1 of Sub Category 2
[parent] => 4
[cat_id] => 5
)
[5] => Array
(
[title] => Sub Sub Sub Category 1 of Sub Sub Category 1
[parent] => 5
[cat_id] => 6
)
)
I can not get my head arround how to get the children included in the main array instead of them being a nested array.
I have searched but can not find the right solution for this on here, so I am sorry if it turns out to be a duplicate. Then I would like to get the link to the original question.

I have found it!
The solution was to make the array global that I use to store the list items in.
Also I have added $level to the function, so that I can display a specific class style per depth.
And finally the recursive use of the function is not stored in the array as a nested "children array", but gets passed on to the global array that I return in the end.
This gives me exactly the right array:
function category_list($category_parent_id = 0, $level = 0) {
// build our category list only once
static $cats;
global $list_items
if (!is_array($cats)) {
$sql = "SELECT * FROM category";
$result = $db->query($sql);
while ($record = $result->fetch_array()) {
$cats[] = $record;
}
}
foreach ($cats as $cat) {
// if not a match, move on
if ((int) $cat['parent'] !== (int) $category_parent_id) {
continue;
}
$list_items[] = array (
'title' => $cat['title'],
'id' => $cat['cat_id'],
'level'=> $level
);
category_list($cat['cat_id'], $level + 1);
}
return $list_items;
}
Note: Url is not used in this second test, but that is not relevant for the example.
Pfew. Finally.

Related

How to get top parent id from a child id using php?

I am an amateur in PHP. I tried a lot of suggestions from here but I didn't get the result I need. I want to get the top parent id of a child. ex. For id=10 top parent is 8. This is the array I get from the database. Thanks in advance.
$events = mysqli_query($dbtmx,"SELECT * FROM tmetrix_events WHERE status='1'");
while($re=mysqli_fetch_assoc($events)){
$array[] = array('id'=>$re['id'],'parent_id'=>$re['parent_id']);
}
And the array I am receiving is
Array
(
[0] => Array
(
[id] => 5
[parent_id] => 0
)
[1] => Array
(
[id] => 6
[parent_id] => 5
)
[2] => Array
(
[id] => 7
[parent_id] => 5
)
[3] => Array
(
[id] => 8
[parent_id] => 0
)
[4] => Array
(
[id] => 9
[parent_id] => 8
)
[5] => Array
(
[id] => 10
[parent_id] => 9
)
)
Now I want to find the top parent or root parent id of the child.
I tried Steverst1 and AymDev's code. But I am getting the result.
You can create a recursive function which will run through all parents until it finds 0 which I assume means there is no parent:
function get_top_parent(int $id, array $list): ?array
{
// Find current item
$key = array_search($id, array_column($list, 'id'));
$item = $list[$key] ?? null;
// Check if item has parent
if (null !== $item && $item['parent_id'] !== 0) {
// recursive call
return get_top_parent($item['parent_id'], $list);
}
// No parent (or no item)
return $item;
}
Tested on 3v4l.org: https://3v4l.org/PPm3K
Assumption that 0 in parent_id means that it is the parent we are looking for. So get the id of it.
$child_id = 10;
$parent = 0;
while ($child_id != 0) {
foreach ($array as $key => $inner_arr) {
$id = $inner_arr['id'];
$parent_id = $inner_arr['parent_id'];
if($id === $child_id){
$child_id = $parent_id;
$parent = $id;
}
}
}
echo $parent; // 8
Demo

duplicate an existing tree branch and store it into the database

Hallöschen everyone,
i'm having a hierarchical array which i'm getting from jsTree when I copy+paste a node and I want to insert the new tree branch into database.
My table has an adjacency list structure with the three fields 'id','text','parent_id'.
The structure can be like below or more nested. I did't figured out how to keep track of the parent so to know which children belong to which parent as I walk the array recursively.
What could be the cleanest approach?
Array(
[id] => 1
[text] => 'a'
[children]
=>Array(
[0] => Array
(
[id] => 2
[text] => 'b'
[children] => []
)
[1] => Array
(
[id] => 3
[text] => 'c'
[children] => Array(
[0] => Array(
[id] => 4
[text] =>''
[children]=>[]
)
)
)
)
)
So the final sollution that works for me is this one
function insertRows($data, $parent_id)
{
foreach ($data as $key => $value) {
if(isset($value['text']))
// here insert the the $value['text'] and get the generated $id
if (isset($value['children'])) {
insertRows($value['children'], $id);
}
}
}
To duplicate the dataset try this:
function insertRows($array, $parentId = null)
{
/** #var PDO $pdo */
global $pdo;
foreach ($array as $key => $value) {
// Add text and parent id to table.
$pdo->query('INSERT INTO table SET text = "' . $value['text'] . '", parent_id = ' . $parentId);
if (is_array($value['children'])) {
insertRows($value['children'], $pdo->lastInsertId());
}
}
}
Its untested an similar to pseudocode. Its more a hint.
You can have third index as parent_id and for root element make parent_id as 0 and in thier respective children put parent_id as id of parent.
for root element :
Array(
[id] => 1,
[text] => 'a',
[parent_id] => 0
);
for 1st child it would be like
Array(
[id] => 2,
[text] => 'a',
[parent_id] => 1
);

Get all children items from flat array with nested data structure

I have a flat array of categories. All categories have ID, Parent_id and Name.
The root categories have Parent_id equal to null. They have subcategories that might have them as their parents and their might be subsubcategories that have subcategories as parents. (their might be a lot of levels).
I can get all categories with a single query into a flat array. What I need is - If I take a category (or a subcaegory), how can I get a list of all nested categories (subcategories, subsubcategories) that are included in this category? Got stack with that problem :(
Array looks like this:
Array
(
[0] => Array
(
[pk_i_id] => 2
[fk_i_parent_id] =>
[i_expiration_days] => 30
[i_position] => 0
[b_enabled] => 1
[b_price_enabled] => 1
[s_icon] =>
)
[1] => Array
(
[pk_i_id] => 4
[fk_i_parent_id] =>
[i_expiration_days] => 30
[i_position] => 6
[b_enabled] => 1
[b_price_enabled] => 1
[s_icon] =>
)
[2] => Array
(
[pk_i_id] => 12
[fk_i_parent_id] =>
[i_expiration_days] => 60
[i_position] => 11
[b_enabled] => 1
[b_price_enabled] => 1
[s_icon] =>
)
[3] => Array
(
[pk_i_id] => 13
[fk_i_parent_id] => 108
[i_expiration_days] => 30
[i_position] => 0
[b_enabled] => 1
[b_price_enabled] => 1
[s_icon] =>
)
You can use recursion. It will be looks like this:
function outTree(array $tree, $parentId = null) {
echo '<ul>';
foreach ($tree as $row) {
if ($row['fk_i_parent_id'] == $parent_id) {
echo '<li>' . $row['pk_i_id'];
echo outTree($tree, $row['pk_i_id']);
echo '</li>';
}
}
echo '</ul>';
}

dropdown output in codeigniter with multidimensional array [duplicate]

I'd like some help please. I have created dynamic a menu navbar that displays the menu items accoridning to the order that I've set them. I'm using this nestedsortable plugin, to order my menu items, but currently my menu has only 2 levels, so basicly it goes like this:
Item1
Item2
> Subitem2.1
> Subitem2.2
Item3
etc etc.
What I'd like to do is make it with n-levels, so basicly something like this:
Item1
Item2
> Subitem2.1
>> Subitem2.1.1
> Subitem2.2
Item3
etc etc.
and each item can go n-level deep. The problem is that if I set a new order to my menu items that is more than 2 levels deep I get an error and the order is not stored in the database. How can I fix this please ???
The database structure is this:
table: Menu
id (pk)
menu_item
parent_id // it is the id of the parent menu item
order
Here are my main (model) functions:
// save the order of the menu items
public function save_order($items){
if (count($items)>0) {
foreach ($items as $order => $item) {
if ($item['item_id'] != '') {
$data = array(
'parent_id' => (int)$item['parent_id'],
'order' => $order
);
$this->db->set($data)
->where($this->_primary_key, $item['item_id'])
->update($this->_table_name);
}
}
}
}
// fetch the menu items (parents & children) from the last order set
public function get_menu(){
$this->db->select('id, menu_item, parent_id');
$this->db->order_by('parent_id, order');
$menu_items = $this->db->get('menu')->result_array();
$arr = array();
foreach ($menu_items as $item) {
// the item has no parent
if (!$item['parent_id']) {
$arr[$item['id']] = $item; // e.g. $arr(4 => array())
} // the item is a child
else {
// e.g. $arr(4 => array('children' => array()))
$arr[$item['parent_id']]['children'][] = $item;
}
}
return $arr;
}
Update
For additional help: I did a test and dumped the array of the items on the screen in both cases:
1st case: with 2 levels (as it is currently): I set the items with this order
Item1
Item2
Item4
Item3
Item5
and the result looks like this, as expected:
Array
(
[1] => Array
(
[id] => 1
[menu_item] => Item1
[parent_id] => 0
)
[2] => Array
(
[id] => 2
[menu_item] => Item2
[parent_id] => 0
[children] => Array
(
[0] => Array
(
[id] => 4
[menu_item] => Item4
[parent_id] => 2
)
)
)
[3] => Array
(
[id] => 3
[menu_item] => Item3
[parent_id] => 0
)
[5] => Array
(
[id] => 5
[menu_item] => Item5
[parent_id] => 0
)
)
2nd case: with n-levels: I tried to set the menu items with this order:
Item1
Item2
Item5
Item4
Item3
and the result looks like this:
Array
(
[1] => Array
(
[id] => 1
[menu_item] => Item1
[parent_id] => 0
)
[2] => Array
(
[id] => 2
[menu_item] => Item2
[parent_id] => 0
[children] => Array
(
[0] => Array
(
[id] => 5
[menu_item] => Item5
[parent_id] => 2
)
)
)
[3] => Array
(
[id] => 3
[menu_item] => Item3
[parent_id] => 0
)
[4] => Array
(
[children] => Array
(
[0] => Array
(
[id] => 4
[menu_item] => Item4
[parent_id] => 4
)
)
)
)
This is the case where I get the error and not working. The errors I get are:
Message: Undefined index: page_id
Message: Undefined index: menu_item
in my view file:
function nav($menu_items, $child = false){
$output = '';
if (count($array)) {
$output .= ($child === false) ? '<ol class="sortable">' : '<ol>' ;
foreach ($menu_items as $item) {
$output .= '<li id="list_' . $item['id'] . '">'; // here is the line of the 1st error
$output .= '<div>' . $item['menu_item'] . '</div>'; // 2nd error
//check if there are any children
if (isset($item['children']) && count($item['children'])) {
$output .= nav($item['children'], true);
}
$output .= '</li>';
}
$output .= '</ol>';
}
return $output;
}
echo nav($menu_items);
Considering the database output, it seems the items are stored in the database correctly. And the problem belongs to the get_menu() method and its algorithm to create the output.
In order to create a n-level deep menu, you should iterate through the items recursively.
Here we go:
function prepareList(array $items, $pid = 0)
{
$output = array();
# loop through the items
foreach ($items as $item) {
# Whether the parent_id of the item matches the current $pid
if ((int) $item['parent_id'] == $pid) {
# Call the function recursively, use the item's id as the parent's id
# The function returns the list of children or an empty array()
if ($children = prepareList($items, $item['id'])) {
# Store all children of the current item
$item['children'] = $children;
}
# Fill the output
$output[] = $item;
}
}
return $output;
}
You could use the above logic as a helper function (in CodeIgniter) or a private method within your Controller class.
Then call that function/method inside get_menu() method as follows:
public function get_menu()
{
$this->db->select('id, menu_item, parent_id');
$this->db->order_by('parent_id, order');
$menu_items = $this->db->get('menu')->result_array();
return prepareList($menu_items);
}
Note: I used the prepareList() as a helper (global) function. If you decide to use that as a private method, you should replace the function name by $this->prepareList() everywhere (even inside the function itself).
Here is the Online Demo.

CodeIgniter create n-level deep navigation

I'd like some help please. I have created dynamic a menu navbar that displays the menu items accoridning to the order that I've set them. I'm using this nestedsortable plugin, to order my menu items, but currently my menu has only 2 levels, so basicly it goes like this:
Item1
Item2
> Subitem2.1
> Subitem2.2
Item3
etc etc.
What I'd like to do is make it with n-levels, so basicly something like this:
Item1
Item2
> Subitem2.1
>> Subitem2.1.1
> Subitem2.2
Item3
etc etc.
and each item can go n-level deep. The problem is that if I set a new order to my menu items that is more than 2 levels deep I get an error and the order is not stored in the database. How can I fix this please ???
The database structure is this:
table: Menu
id (pk)
menu_item
parent_id // it is the id of the parent menu item
order
Here are my main (model) functions:
// save the order of the menu items
public function save_order($items){
if (count($items)>0) {
foreach ($items as $order => $item) {
if ($item['item_id'] != '') {
$data = array(
'parent_id' => (int)$item['parent_id'],
'order' => $order
);
$this->db->set($data)
->where($this->_primary_key, $item['item_id'])
->update($this->_table_name);
}
}
}
}
// fetch the menu items (parents & children) from the last order set
public function get_menu(){
$this->db->select('id, menu_item, parent_id');
$this->db->order_by('parent_id, order');
$menu_items = $this->db->get('menu')->result_array();
$arr = array();
foreach ($menu_items as $item) {
// the item has no parent
if (!$item['parent_id']) {
$arr[$item['id']] = $item; // e.g. $arr(4 => array())
} // the item is a child
else {
// e.g. $arr(4 => array('children' => array()))
$arr[$item['parent_id']]['children'][] = $item;
}
}
return $arr;
}
Update
For additional help: I did a test and dumped the array of the items on the screen in both cases:
1st case: with 2 levels (as it is currently): I set the items with this order
Item1
Item2
Item4
Item3
Item5
and the result looks like this, as expected:
Array
(
[1] => Array
(
[id] => 1
[menu_item] => Item1
[parent_id] => 0
)
[2] => Array
(
[id] => 2
[menu_item] => Item2
[parent_id] => 0
[children] => Array
(
[0] => Array
(
[id] => 4
[menu_item] => Item4
[parent_id] => 2
)
)
)
[3] => Array
(
[id] => 3
[menu_item] => Item3
[parent_id] => 0
)
[5] => Array
(
[id] => 5
[menu_item] => Item5
[parent_id] => 0
)
)
2nd case: with n-levels: I tried to set the menu items with this order:
Item1
Item2
Item5
Item4
Item3
and the result looks like this:
Array
(
[1] => Array
(
[id] => 1
[menu_item] => Item1
[parent_id] => 0
)
[2] => Array
(
[id] => 2
[menu_item] => Item2
[parent_id] => 0
[children] => Array
(
[0] => Array
(
[id] => 5
[menu_item] => Item5
[parent_id] => 2
)
)
)
[3] => Array
(
[id] => 3
[menu_item] => Item3
[parent_id] => 0
)
[4] => Array
(
[children] => Array
(
[0] => Array
(
[id] => 4
[menu_item] => Item4
[parent_id] => 4
)
)
)
)
This is the case where I get the error and not working. The errors I get are:
Message: Undefined index: page_id
Message: Undefined index: menu_item
in my view file:
function nav($menu_items, $child = false){
$output = '';
if (count($array)) {
$output .= ($child === false) ? '<ol class="sortable">' : '<ol>' ;
foreach ($menu_items as $item) {
$output .= '<li id="list_' . $item['id'] . '">'; // here is the line of the 1st error
$output .= '<div>' . $item['menu_item'] . '</div>'; // 2nd error
//check if there are any children
if (isset($item['children']) && count($item['children'])) {
$output .= nav($item['children'], true);
}
$output .= '</li>';
}
$output .= '</ol>';
}
return $output;
}
echo nav($menu_items);
Considering the database output, it seems the items are stored in the database correctly. And the problem belongs to the get_menu() method and its algorithm to create the output.
In order to create a n-level deep menu, you should iterate through the items recursively.
Here we go:
function prepareList(array $items, $pid = 0)
{
$output = array();
# loop through the items
foreach ($items as $item) {
# Whether the parent_id of the item matches the current $pid
if ((int) $item['parent_id'] == $pid) {
# Call the function recursively, use the item's id as the parent's id
# The function returns the list of children or an empty array()
if ($children = prepareList($items, $item['id'])) {
# Store all children of the current item
$item['children'] = $children;
}
# Fill the output
$output[] = $item;
}
}
return $output;
}
You could use the above logic as a helper function (in CodeIgniter) or a private method within your Controller class.
Then call that function/method inside get_menu() method as follows:
public function get_menu()
{
$this->db->select('id, menu_item, parent_id');
$this->db->order_by('parent_id, order');
$menu_items = $this->db->get('menu')->result_array();
return prepareList($menu_items);
}
Note: I used the prepareList() as a helper (global) function. If you decide to use that as a private method, you should replace the function name by $this->prepareList() everywhere (even inside the function itself).
Here is the Online Demo.

Categories