Find the top level parent - php

I have a MySQL table with id, category_id (parent id), and url.
I have a class that looks something like this. I have removed all unnecessary functions.
class categoriesBuilder
{
var $items = array();
var $html = array();
function fetch_assoc_all( $sql )
{
$result = mysql_query( $sql, $this->conn );
if ( !$result ){
return false;
}
$assoc_all = array();
while( $fetch = mysql_fetch_assoc( $result ) ){
$assoc_all[] = $fetch;
}
mysql_free_result( $result );
return $assoc_all;
}
function get_categories()
{
$sql = 'SELECT id, category_id, url FROM categories ORDER BY category_id, id;';
return $this->fetch_assoc_all( $sql );
}
function get_category_string($root_id=0)
{
$this->html = array();
$this->items = $this->get_categories();
foreach ( $this->items as $item )
$children[$item['category_id']][] = $item;
// loop will be false if the root has no children
$loop = !empty( $children[$root_id] );
// initializing $parent as the root
$parent = $root_id;
$parent_stack = array();
while ( $loop && ( ( $option = each( $children[$parent] ) ) || ( $parent > $root_id ) ) )
{
if ( $option === false )
{
$parent = array_pop( $parent_stack );
}
elseif ( !empty( $children[$option['value']['id']] ) )
{
array_push( $parent_stack, $option['value']['category_id'] );
$parent = $option['value']['id'];
// HTML for menu item containing childrens (open)
$this->html[] = $option['value']['url'] . "/";
}
else
{
$this->html[] = $option['value']['url'] . "/";
}
}
return implode($this->html );
}
}
I need a function that returns only the top level parent. there may be several sub categories.
i.e.
category id 1
category id 3
category id 4
category id 5
category id 2
category id 6
In this example if i entered 3, 4 or 5 into the function the output would be 1. if i entered 6 i would get 2.
I also need a similar function that shows all parent folders.
for example if i entered 3 i would get 1 but if i entered 4 or 5 i would get 1-3
Thanks for your help.

Assuming your DB is like this:
(1, 0, URL1),
(3, 1, URL3),
(4, 3, URL4),
(5, 3, URL5),
(2, 0, URL2),
(6, 2, URL6)
Then you just need to walk up the list
e.g.
function get_top_parent($category_id, $root_id=0)
{
// Grab the id's and category's
$item_list = array();
foreach($this->items as $item) {
$item_list[$item['id']] = $item['category_id'];
}
$current_category = $category_id;
while(TRUE) {
if ($item_list[$current_category] == $root_id) {
// Check to see if we have found the parent category.
return $current_category;
} else {
// update our current category
$current_category = $item_list[$current_category];
}
}
}
function get_parents($category_id, $root_id=0)
{
$parents = array();
// Grab the id's and category's
$item_list = array();
foreach($this->items as $item) {
$item_list[$item['id']] = $item['category_id'];
}
$current_category = $category_id;
while(TRUE) {
// Check to see if we have found the root category.
if ($item_list[$current_category] == $root_id) {
return $parents;
} else {
// update our current category and parents
$current_category = $item_list[$current_category];
array_unshift($parents, $current_category);
}
}
}
reworked to return URL (I did not verify this code but it should work):
function get_top_parent($category_id, $root_id=0)
{
// Grab the id's and category's
$item_list = array();
foreach($this->items as $item) {
$item_list[$item['id']] = array(
'category_id' => $item['category_id'],
'url' => $item['url']
);
}
$current_category = $category_id;
while(TRUE) {
if ($item_list[$current_category]['category_id'] == $root_id) {
// Check to see if we have found the parent category.
return $item_list[$current_category]['url'];
} else {
// update our current category
$current_category = $item_list[$current_category]['category_id'];
}
}
}
function get_parents($category_id, $root_id=0)
{
$parents = array();
// Grab the id's and category's
$item_list = array();
foreach($this->items as $item) {
$item_list[$item['id']] = array(
'category_id' => $item['category_id'],
'url' => $item['url']
);
}
$current_category = $category_id;
while(TRUE) {
// Check to see if we have found the root category.
if ($item_list[$current_category]['category_id'] == $root_id) {
return $parents;
} else {
$temp_array = array(
'category_id' => $current_category
'url' => $item_list[$current_category]['url']
);
// update our current category and parents
$current_category = $item_list[$current_category]['category_id'];
array_unshift($parents, $temp_array);
}
}
}
The first function returns the URL, the second function should return an array of arrays... You will have the standard index, with "category_id" and "url" as nested/sub arrays... (If in doubt, just do a print_r of the return value to see what I mean)
again, i checkced the origional code but not the update...

Related

foreach until find some value

I have a code that get categories from database but I don't know how to get all subcategories(parents).
This my php code :
function get_the_category($allCats,$filter_id = null) {
$re_struct_cat = array();
$filter_id = 10;
$ids = array();
$xx = array();
foreach($allCats as $cat_key=>$cat_val) {
$re_struct_cat[$cat_val["id"]] = array(
"title" => $cat_val["cat_title"],
"parent" => $cat_val["cat_parent"],
);
$ids = array_merge($ids,array($cat_val["id"]));
}
foreach($ids as $k=>$v) {
if($re_struct_cat[$v]["parent"]) {
$xx[] = $re_struct_cat[$re_struct_cat[$v]["parent"]];
}
}
return $xx;
//return $re_struct_cat;
//print_r($re_struct_cat);
}
What I want exactly
I have table with 3 columns [id,title,parent]
ID TITLE PARENT
1 Science 0
2 Math 1
3 Algebra 2
4 Analyse 2
5 Functions 4
So if variable filter_id = 10 I got cat_parent = 4
So I want to take that value and looking for it in array and if find another cat_parent do the same thing until find 0 or null value
It is not the most optimal solution, but you can use iterators.
Firstly, create the custom iterator, that can handle categories:
class AdjacencyListIterator extends RecursiveArrayIterator
{
private $adjacencyList;
public function __construct(
array $adjacencyList,
array $array = null,
$flags = 0
) {
$this->adjacencyList = $adjacencyList;
$array = !is_null($array)
? $array
: array_filter($adjacencyList, function ($node) {
return is_null($node['parent']);
});
parent::__construct($array, $flags);
}
private $children;
public function hasChildren()
{
$children = array_filter($this->adjacencyList, function ($node) {
return $node['parent'] === $this->current()['id'];
});
if (!empty($children)) {
$this->children = $children;
return true;
}
return false;
}
public function getChildren()
{
return new static($this->adjacencyList, $this->children);
}
}
It is taken from my another answer.
Then you can simply loop over this iterator until you find the needed id:
$id = 5;
$categories = [];
$result = null;
foreach ($iterator as $node) {
$depth = $iterator->getDepth();
$categories[$depth] = $node['categoryname'];
if ($node['id'] === $id) {
$result = array_slice($categories, 0, $depth + 1);
break;
}
}
Here is the demo.

PHP function for returning array with unique indexes

I have a simple problem. Lets say I have an array
Array
0
product_id 233
slug "zotac-geforce-gtx-1070-amp-extreme-edition-8gb-zt-p10700b-10p"
1
product_id 227
slug "zotac-geforce-gtx-1060-mini-6gb-gddr5-zt-p10600a-10l"
2
product_id 233
slug "zotac-geforce-gtx-1070-amp-extreme-edition-8gb-zt-p10700b-10p"
Now if you see there are two products having same product_id! I don't want that I am trying to get this array filtered from duplicate products
This is what I tried but it doesn't work
$temp_products = array();
foreach($products as $product)
{
if(count($temp_products) > 0)
{
foreach($temp_products as $temp_product)
{
if($temp_product['product_id'] != $product['product_id'])
{
$temp_products[] = $product;
}
}
}
else
{
$temp_products[] = $product;
}
}
It returns the same array as the original one. and $products is the main array having the data.
Try this! But I would definitely suggest using array_filter or array_unique will post an example later. Try this.
$temp_products = array();
$count = 0;
foreach($products as $product)
{
if(count($temp_products) > 0)
{
//foreach($temp_products as $temp_product)
//{
if($temp_products[$count]['product_id'] != $product['product_id'])
{
$temp_products[] = $product;
}
//}
}
else
{
$temp_products[] = $product;
}
}
Using array_unqiue
foreach($products as $product)
{
$temp_products[] = $product;
}
dd(array_unique($temp_products));
Another way would be to use a helper array to keep track of already present ids.
$temp_products = array();
$already_present = array();
foreach($products as $product)
{
$id = $product['product_id'];
if ( isset($already_present[ $id ] ) ) continue;
$temp_products[] = $product;
$already_present[ $id ] = '';
}
$products = $temp_products;

Reduce amount of queries and increase performance for categories and subcategories queries

EDIT 3: Got it down to 300-500 ms by changing flatten method to only merge arrays if not empty.
EDIT 2: Got it down to 1.6 seconds by only calling array_replace for non empty array. Now all that is left to do is optimize the function sort_categories_and_sub_categories. That is NOW the bottleneck. If I remove that I am down to 300ms. Any ideas?
get_all_categories_and_sub_categories
foreach(array_keys($categories) as $id)
{
$subcategories = $this->get_all_categories_and_sub_categories($id, $depth + 1);
if (!empty($subcategories))
{
$categories = array_replace($categories, $subcategories);
}
}
EDIT
I improved performance by over 50% (6 seconds --> 2.5 seconds) by doing a cache in the get_all method. It reduces the amount of queries to 1 from 3000. I am still wondering why it is slow.
I have the following method for getting categories and nested sub categories. If a user has a couple hundred (or thousand) top level categories it does a bunch of queries for each category to find the children. In one case I have 3000 categories and it did 3000 queries. Is there a way to optimize this to do less queries? OR should I just check to see if they have a lot of categories NOT to try to show nested too.
function get_all_categories_and_sub_categories($parent_id = NULL, $depth = 0)
{
$categories = $this->get_all($parent_id);
if (!empty($categories))
{
foreach($categories as $id => $value)
{
$categories[$id]['depth'] = $depth;
}
foreach(array_keys($categories) as $id)
{
$categories = array_replace($categories, $this->get_all_categories_and_sub_categories($id, $depth + 1));
}
return $categories;
}
else
{
return $categories;
}
}
function get_all($parent_id = NULL, $limit=10000, $offset=0,$col='name',$order='asc')
{
static $cache = array();
if (!$cache)
{
$this->db->from('categories');
$this->db->where('deleted',0);
if (!$this->config->item('speed_up_search_queries'))
{
$this->db->order_by($col, $order);
}
$this->db->limit($limit);
$this->db->offset($offset);
foreach($this->db->get()->result_array() as $result)
{
$cache[$result['parent_id'] ? $result['parent_id'] : 0][] = array('name' => $result['name'], 'parent_id' => $result['parent_id'], 'id' => $result['id']);
}
}
$return = array();
$key = $parent_id == NULL ? 0 : $parent_id;
if (isset($cache[$key]))
{
foreach($cache[$key] as $row)
{
$return[$row['id']] = array('name' => $row['name'], 'parent_id' => $row['parent_id']);
}
return $return;
}
return $return;
}
function sort_categories_and_sub_categories($categories)
{
$objects = array();
// turn to array of objects to make sure our elements are passed by reference
foreach ($categories as $k => $v)
{
$node = new StdClass();
$node->id = $k;
$node->parent_id = $v['parent_id'];
$node->name = $v['name'];
$node->depth = $v['depth'];
$node->children = array();
$objects[$k] = $node;
}
// list dependencies parent -> children
foreach ($objects as $node)
{
$parent_id = $node->parent_id;
if ($parent_id !== null)
{
$objects[$parent_id]->children[] = $node;
}
}
// clean the object list to make kind of a tree (we keep only root elements)
$sorted = array_filter($objects, array('Category','_filter_to_root'));
// flatten recursively
$categories = self::_flatten($sorted);
$return = array();
foreach($categories as $category)
{
$return[$category->id] = array('depth' => $category->depth, 'name' => $category->name, 'parent_id' => $category->parent_id);
}
return $return;
}
static function _filter_to_root($node)
{
return $node->depth === 0;
}
static function _flatten($elements)
{
$result = array();
foreach ($elements as $element)
{
if (property_exists($element, 'children'))
{
$children = $element->children;
unset($element->children);
}
else
{
$children = null;
}
$result[] = $element;
if (isset($children))
{
$flatened = self::_flatten($children);
if (!empty($flatened))
{
$result = array_merge($result, $flatened);
}
}
}
return $result;
}

Save Tree array in mysql

Array
(
[Root] => Array
(
[Parent0] => Array
(
[Child0] => Child0
)
[Parent1] => Array
(
[Child1] => Child1
)
)
)
Above tree array need to be save in mysql with parent id so results should be like below:
id parent_id name
1 0 Root
2 1 Parent0
3 2 Child0
4 1 Parent1
5 4 Child1
Any one please let me know how can i save above results in mysql using php.
Thanks in advance for quick response.
Try this:EDITED
$c=0;
$x;
foreach($array1 as $key=>$val){
if(is_array($val)){
echo "insert $key with parent id $c<br>";
$c++;
$x=$c;
foreach($val as $key1=>$val1){
echo " insert $key1 with parent id $x<br>";
$c++;
if(is_array($val1)){
foreach($val1 as $key2=>$val2){
echo "insert $key2 with parent id $c<br>";
$c++;
getlevel($val2,$c);
}
}/* else{
echo "else insert $key1 with parent id $c<br>";
$c++; */
}
}
}
function getlevel($value,$c1){
if(is_array($value)){
foreach($value as $keyV=>$Value){
echo " insert $keyV with parent id $c1<br>";
$c1++;
if(is_array($Value)){
getlevel($Value,$c1);
}
}
}
}
where ever i have written echo replace with your sql.Hope it helps.here $array is your array.
Below the perfect working script in any depth level tree.
$root_id;
$parent_id = 0;
foreach ( $tree_array as $root_key => $root_value ) {
$parent_id = insertRecord($root_key, $parent_id);
$root_id = $parent_id;
if ( is_array($root_value) ) {
foreach ( $root_value as $parent_key => $parent_value ) {
$parent_id = insertRecord($parent_key, $root_id);
$keep_parent_id = $parent_id;
if ( is_array($parent_value) ) {
foreach ( $parent_value as $child_key => $child_value ) {
$parent_id = insertRecord($child_key, $keep_parent_id);
getlevel($child_value, $parent_id);
}
}
}
}
}
function getlevel($sub_childs, $new_parent_id) {
$keep_new_parent_id = $new_parent_id;
if ( is_array($sub_childs) ) {
foreach ( $sub_childs as $sub_child => $sub_child_sub ) {
$new_parent_id = insertRecord($sub_child, $keep_new_parent_id);
if ( is_array($sub_child_sub) ) {
getlevel($sub_child_sub, $new_parent_id);
}
}
}
}
function insertRecord($name, $parent_id) {
$q = "insert into xtable set name = '".$name."', parent_id = '".$parent_id."'";
mysql_query($q);
$folder_id = mysql_insert_id();
return $folder_id;
}
Thanks to everyone for your efforts.
If you want to achieve parent child tree you can go for the below code with n depth
Automobile
Fuel
Gasoline
Diesel
Maintenance
Food
Fish
Pork
First create a database table named “categories” that has fields.
- category_id (PK int)
- parent_id (int)
- title (varchar)
<?php
$connect = mysql_connect("localhost", "root", "") or die ( mysql_error() );
mysql_select_db("test");
$nav_query = mysql_query("SELECT * FROM `categories` ORDER BY `category_id`") or die( mysql_error() );
$tree = ""; // Clear the directory tree
$depth = 1; // Child level depth.
$top_level_on = 1; // What top-level category are we on?
$exclude = array(); // Define the exclusion array
array_push($exclude, 0); // Put a starting value in it
while ( $nav_row = mysql_fetch_array($nav_query) )
{
$goOn = 1; // Resets variable to allow us to continue building out the tree.
for($x = 0; $x < count($exclude); $x++ ) // Check to see if the new item has been used
{
if ( $exclude[$x] == $nav_row['category_id'] )
{
$goOn = 0;
break; // Stop looking b/c we already found that it's in the exclusion list and we can't continue to process this node
}
}
if ( $goOn == 1 )
{
$tree .= $nav_row['title'] . "<br>"; // Process the main tree node
array_push($exclude, $nav_row['category_id']); // Add to the exclusion list
if ( $nav_row['category_id'] < 6 )
{ $top_level_on = $nav_row['category_id']; }
$tree .= build_child($nav_row['category_id']); // Start the recursive function of building the child tree
}
}
function build_child($oldID) // Recursive function to get all of the children...unlimited depth
{
global $exclude, $depth; // Refer to the global array defined at the top of this script
$tempTree = "";
$child_query = mysql_query("SELECT * FROM `categories` WHERE parent_id=" . $oldID);
while ( $child = mysql_fetch_array($child_query) )
{
if ( $child['category_id'] != $child['parent_id'] )
{
for ( $c=0;$c<$depth;$c++ ) // Indent over so that there is distinction between levels
{ $tempTree .= " "; }
$tempTree .= "- " . $child['title'] . "<br>";
$depth++; // Incriment depth b/c we're building this child's child tree (complicated yet???)
$tempTree .= build_child($child['category_id']); // Add to the temporary local tree
$depth--; // Decrement depth b/c we're done building the child's child tree.
array_push($exclude, $child['category_id']); // Add the item to the exclusion list
}
}
return $tempTree; // Return the entire child tree
}
echo $tree;
?>
You can copy paste the above code and you are done :)

Creating Structured array by Recursive function

I have a table
Which I want show recursively like below picture
I am using a recursive function in php
function reccall($cat_id)
{
global $no,$recArray;
$sql = "SELECT a.*
FROM cat_master
WHERE
parent_id = $cat_id
ORDER BY
id ASC
";
$result = mysql_query($sql) or die("Could not fetech Recursively");
while($row = mysql_fetch_object($result))
{
$recArray[$no]['value'] = mysql_real_escape_string($row->value);
$recArray[$no]['id'] = $row->id;
++$no;
reccall($row->id);
}
return $recArray;
}
but I am not able to generate a structured array like how the order is not the picture. A simple array is created all the time. Can anyone help me with creating the structured array like the order shown above.
<?
// I identified this function separately because it is performed only once, for preparing data
// It's collect an array of all parents in the correct order for each id
function dest($array) {
foreach($array as $key=>$value) {
if($value['pid']==0) continue;
$pid = $key;
$array[$key]['dest'] = array();
while ( $pid = $array[$pid]['pid'] ) {
if($key == $pid) exit("this tree is broken");
$array[$key]['dest'][] = $pid;
}
}
return $array;
}
// Recursive function that puts the items in the correct tree. removes the parameter dest.
function tree($array) {
foreach($array as $key=>$value) {
if( is_array($value['dest']) && !empty($value['dest']) ) {
$pid = array_pop($value['dest']);
if( empty($value['dest']) ) unset($value['dest']);
$array[$pid]['childrens'][$key] = $value;
$array[$pid]['childrens'] = tree($array[$pid]['childrens']);
unset($array[$key]);
}
}
return $array;
}
$array = array(
1 => array(
'title'=>'q',
'pid'=>0,
),
2 => array(
'title'=>'w',
'pid'=>1,
),
3 => array(
'title'=>'e',
'pid'=>0,
),
4 => array(
'title'=>'r',
'pid'=>2,
),
5 => array(
'title'=>'t',
'pid'=>1,
),
);
$tree = tree( dest($array) );
echo '<pre>';
print_r($array);
print_r($tree);
?>
By the way, I should note that these arrays are not very useful. Better to use the result of the function dest().
use this function instead of your function and your problem will be solved I hope
function reccall($cat_id)
{
$sql = "SELECT a.*
FROM cat_master
WHERE
parent_id = $cat_id
ORDER BY
id ASC
";
$result = mysql_query($sql) or die("Could not fetech Recursively");
while($row = mysql_fetch_object($result))
{
$recArray[$no]['main']['value'] = mysql_real_escape_string($row->value);
$recArray[$no]['main']['id'] = $row->id;
$recArray[$no]['child'] = reccall($row->id);
++$no;
}
return $recArray;
}

Categories