Im doing a project in php CodeIgniter which has a table where all attributes_values can be kept and it is designed such that it can have its child in same tbl. the database structure is
fld_id fld_value fld_attribute_id fld_parent_id
1 att-1 2 0
2 att-2 2 0
3 att-1_1 2 1
4 att-1_2 2 1
5 att-1_1_1 2 3
here above att-1 is the attribute value of any attribute and it has two child att-1_1 and att-1_2 with parent id 1. and att-1_1 has too its child att-1_1_1 with parent_id 3. fld_parent_id is the fld_id of the same table and denotes the child of its. Now i want to show this in tree structure like this
Level1 level2 level3 ..... level n
att-1
+------att-1_1
| +------att-1_1_1
+------att-1_2
att-2
and this tree structure can vary upto n level. the attribute values with parent id are on level one and i extracted the values from level one now i have to check the child of its and if it has further child and display its child as above. i used a helper and tired to make it recursive but it didnt happen. So how could i do it such: the code is below
foreach($attributes_values->result() as $attribute_values){
if($attribute_values->fld_parent_id==0 && $attribute_values->fld_attribute_id==$attribute->fld_id){
echo $attribute_values->fld_value.'<br/>';
$children = get_children_by_par_id($attribute_values->fld_id); //helper function
echo '<pre>';
print_r($children);
echo '</pre>';
}
}
and the helper code is below:
function get_children_by_par_id($id){ //parent id
$children = get_children($id);
if($children->num_rows()!=0){
foreach($children->result() as $child){
get_children_by_par_id($child->fld_id);
return $child;
}
}
}
function get_children($id){
$CI = get_instance();
$CI->db->where('fld_parent_id',$id);
return $CI->db->get('tbl_attribute_values');
}
please help me...............
The key of recursion is an "endless" call. This can be done with a function that calls it self.
So
function get_children($parent_id)
{
// database retrieve all stuff with the parent id.
$children = Array();
foreach($results as $result)
{
$result['children'] = get_children($result['id']);
$children[] = $result;
}
return $children;
}
Or use the SPL library built into PHP already PHP recursive iterator
Related
I'm building a website that contains a product database. Each product belongs to a category. The structure of categories is multi-tiered and can contain any number of tiers, for example:
Electronics > Games Consoles > Xbox > Xbox One > Games > etc..
Fashion > Mens > Shirts > Long Sleeved
I always assign the product to the "last" category in the tier.
Here is the structure of my category table:
id name parent_id
================================
1 Fashion NULL
2 Mens 1
3 Shirts 2
4 Long Sleeved 3
5 Short Sleeved 3
I'm using Yii2 as my application framework, but the same concepts should apply to most MVC Frameworks, or at least those that implement an ORM like ActiveRecord.
What I want to do is:
For any category level, get the "master" parent. I.e. for Shirts it would be Fashion
For any category level, get all "last" level categories in the tier. I.e. for Mens it would be Long Sleeved and Short Sleeved.
(more advanced) For any category level, find out the number of children / parents it has.
I have the following default relations in my model:
public function getParent()
{
return $this->hasOne(Category::className(), ['id' => 'parent_id']);
}
public function getParent()
{
return $this->hasMany(Category::className(), ['parent_id' => 'id']);
}
The following is a function I have created which outputs the "tree" for any given category:
public function getParentTree()
{
$array = [];
// $this->parent refers to the 'getParent()' relation above
if(!empty($this->parent))
{
$array[] = $this->parent->name;
if(!empty($this->parent->parent))
$array[] = $this->parent->parent->name;
if(!empty($this->parent->parent->parent))
$array[] = $this->parent->parent->parent->name;
}
else
$array[] = "(none)";
$output = implode(" --> ", array_reverse($array));
return $output;
}
But there is a lot of repetition here and it looks ugly. But it is also leading me to be believe perhaps I have taken the wrong approach and need to restructure the database itself?
Bill I think I have resolved this issue in YII2 -> Models.
Below is my code.
public static function getSubCategories($parent_id = NULL, $level = 0)
{
// Get the Category from table
// Here you can use caching Yii::$app->cache->get to avoid multiple queries
$categories = Category::find()->select(['id', 'parent_id', 'name'])->where(['parent_id' => $parent_id])->asArray()->all();
// Logic of Nth level to return
self::$max_down_level += 1;
if($level != 0 && self::$max_down_level > $level) return $categories;
// Now for each sub categories find and return chidren as Array
foreach($categories as $key => $category)
{
$categories[$key]['children'] = self::getSubCategories($category['id'], $level);
}
return $categories;
}
Also do not forget to declare public static $max_down_level = 0; variable in your model class. and now call the function like below.
To get all the children of Parent category self::getSubCategories(NULL)
To get all the children up to 2nd level self::getSubCategories(NULL, 2)
Same way above you can declare recursive function for getting parent Categories.
public static function getParentCategories($parent_id, $level = 0)
{
// Get the Category from table
// Here you can use caching Yii::$app->cache->get to avoid multiple queries
$categories = Category::find()->select(['id', 'parent_id', 'name'])->where(['id' => $parent_id])->asArray()->all();
// Logic of Nth level to return
self::$max_up_level += 1;
if($level != 0 && self::$max_up_level > $level) return $categories;
foreach($categories as $key => $category)
{
$categories[$key]['parent'] = self::getParentCategories($category['parent_id'], $level);
}
return $categories;
}
Also do not forget to declare public static $max_up_level = 0; variable in your model class. and now call the function like below.
To get all the children of Parent category self::getParentCategories(16, 0)
To get all the children up to 2nd level self::getParentCategories(16, 2)
You can use your own class name instead of self
Hope this helps.
how can I show the whole tree. I have a 3 Level Navigation (First is root). With that code, I will only see the 2nd Level.
$tree = \App\Category::where('identifier', 'soccer')->first();
foreach($tree->getDescendants()->toHierarchy() as $descendant) {
echo "{$descendant->name} <br>";
}
You can get the whole tree including root by doing:
$root = Table::where('id', '=', $id)->first();
$tree = $root->getDescendantsAndSelf()->toHierarchy();
Now as $tree is Tree structure you need to traverse it recursively or with a queue data structure (DFS or BFS). Each item on the tree will have a children property with its children
Some pseudo for traversing would be:
function traverseBFS(tree) {
q = Queue()
q.push(tree[0]);
while (!q.empty()) {
item = q.top(); q.pop();
// do what you need with item
foreach (item->children as child) {
q.push(child);
}
}
}
Using CActiveRecord my table looks like this:
A column parent_id has relation many to id, and it works properly.
id | parent_id
---+----------
1 1 <- top
2 1 <- means parent_id with 1 has parent with id=1
3 1
4 2 <- parent for is id=2
5 2
6 2 and so many nested levels....
A goal is how to properly get nested as PHP classically way nested arrays data (arrays inside arrays).
array(1,1) {
array(2,1) {
array(4,2) ....
}
}
Problem is Yii. I didn't find properly way how to pick up a data as nested array using properly CActiveRecord.
What is best way to make nested array results? A main goal is to easy forward to render view so I don't separate with too many functions and calling many models outside from modules or models.
A good is one function to get a result.
Solved using this: Recursive function to generate multidimensional array from database result
You need get a data as arrays from model:
$somemodel = MyModel::model()->findAll();
Then put all in array rather then Yii objects model or what you need:
foreach ($somemodel as $k => $v)
{
$arrays[$k] = array('id' => $v->id, 'parent_id' => $v->parent_id, 'somedata' => 'Your data');
}
Then call a function:
function buildTree(array $elements, $parentId = 0) {
$branch = array();
foreach ($elements as $element) {
if ($element['parent_id'] == $parentId) {
$children = buildTree($elements, $element['id']);
if ($children) {
$element['children'] = $children;
}
$branch[] = $element;
}
}
return $branch;
}
Then call put all $arrays data into buildTree function to rebuild nesting arrays data.
$tree = buildTree($arrays);
Now your $tree is nested arrays data.
Note: there aren't depth into function but in convient way you can add using like this sample: Create nested list from Multidimensional Array
Is it possible to remove the parent node from a Tree using CakePHP Tree Behavior?. Let's say for example I have a node like this:
<Node A>
- child 1 node A
- child 2 node A
- child 3 node A
- <Node B> (which is also a child 4 of Node A)
- child 1 node B
- child 2 node B
Is it possible to get all the chidren of Node A (using the chidren() or any other function of the Tree behavior in cakePHP), but exclude a node which has children from the result (in our case Node B)?
Any idea please?
Thanks in advance
You can but you'll need to get your hands a bit dirty because I don't think the behavior allows anything like this.
The key is that all nodes that do not have children should have a left and right value that are in sequence. You'll need to whip up a query like this:
SELECT * FROM items WHERE left > (parent's left) AND right < (parent's right) AND right = left + 1 AND parent_id = (parent's ID)
That way we're asking that all returned values are children of our parent and that their left and right values are in sequence, which they won't be if a node has children.
Looking at specifications there is no specific method for this, so you must build your own function for that using children() and childCount(). Here's the code template (I don't use Cake PHP):
$children = <call TreeBehavior children() method with $id = id of Node A and $direct = true>;
$children_without_children = array();
foreach ($children as $child) {
if (<call TreeBehavior childCount() method with $id = $child->id and $direct = true> === 0) {
$children_without_children[] = $child;
}
}
Then $children_without_children should contain what you want.
you can use this code:
$this->Node->removeFromTree($id, true);
here is code from my cakephp 2.x project:
public function delete($id = null) {
$this->ProductCategory->id = $id;
if (!$this->ProductCategory->exists()) {
throw new NotFoundException(__('Invalid product category'));
}
$this->request->allowMethod('post', 'delete');
if ($this->ProductCategory->removeFromTree($id, TRUE)) {
$this->Session->setFlash(__('The product category has been deleted.'));
} else {
$this->Session->setFlash(__('The product category could not be deleted. Please, try again.'));
}
return $this->redirect(array('action' => 'index'));
}
Using this method (e.i. removeFromTree ()) will either delete or move a node but retain its sub-tree, which will be reparented one level higher. It offers more control than delete, which for a model using the tree behavior will remove the specified node and all of its children.
I am working on a data driven menu system in PHP /MySQL. I can't figure out how to delete menu items without leaving some of them orphaned.
All top level menu items have a zero (0) parent id value indicating that they are top level. My gridview displays all menus, top level and sub menu items and it allows multiple selection for delete.
The problem is that if one of the items selected in the gridview for delete is a top level menu item, all sub menus under it will become orphaned.
What is the general logic I need to implement?
Simply delete the child items when you delete some item. If you only have a 2 levels of depth this shouldn't be too much of a problem. If you can have X levels, then you'll have to recursively delete every child element for every element you delete.
The below class will work with as many childs as you can create(infinit)... so considering your mysql tabel is structered as(id,parent,name), all you need is a function that gets all items from current level, loop through each item and call recursively the function again to get child items for current loop id, each time keeping the ids found in an array to delete later, below is the full code in which I accomplished it using a class, but it can be done with a global array and function also.
//full class below comprising of 2 methods(functions)
class menu_manager{
//this is the function called with id, to initiate the recursive function below
private function remove_menu_item($id){
$ids_to_delete ="";
//Zero global arrays for more than one call for this function get child menus id first in a an array
$this->child_items_ids = array(0 => $id);
$this->incrementby_one=0;
//call recursive function with $id provided
$this->get_array_of_child_ids($id);
//then createw ids for mysql IN Statment, foreach will create - 1,10,25,65,32,45,
foreach($this->child_items_ids as $k=>$v ) $ids_to_delete.=$v.",";
//Then we wrap it in around "(" and ")" and remove last coma to provide - (1,10,25,65,32,45)
$ids_to_delete="(".substr($ids_to_delete, 0, -1).")";
//then we Delete all id in one query only
$remove = $this->db->query("DELETE FROM menu WHERE id IN $ids_to_delete ");
if(!$remove) return false;
else return true;
}
/*this is the function that will be called as many times as a child is found,
this function is called inside of itself in the query loop*/
private function get_array_of_child_ids($id){
$query = $this->db->query("SELECT id,label,parent FROM menu WHERE parent='".$id."' ");
if($query){
if($query->num_rows > 0) { // if found any items
while($list = $query->fetch_assoc()){ // we loop through each item
//increments array index by 1
$this->incrementby_one += 1;
//place current id in the array
$this->child_items_ids[$this->incrementby_one] = intval($list["id"]);
//and we call this function again for the current id
$this->get_array_of_child_ids($list["id"]);
} // while closing
} // second if closing
} //first if closing
} // recursive function closing
} // class closing
//to call the class you need:
$delete_items = new menu_manager;
$delete_items->remove_menu_item($id); //$id is the id for the item to be removed