I have created rest api for getting categories and sub-categories using recursion technique. Here is my code:-
public function getNodeChildrenData(Varien_Data_Tree_Node $node)
{
$data = array(
'title' => $node->getData('name'),
'url' => $node->getData('url_key'),
);
foreach ($node->getChildren() as $childNode) {
if (!array_key_exists('children', $data)) {
$data['children'] = array();
}
$data['children'][$childNode->getData('entity_id')] = $this->getNodeChildrenData($childNode);
}
return $data;
}
public function getCategoryTree($recursionLevel, $storeId = 1)
{
$parent = Mage::app()->getStore()->getRootCategoryId();
$tree = Mage::getResourceModel('catalog/category_tree');
/* #var $tree Mage_Catalog_Model_Resource_Category_Tree */
$nodes = $tree->loadNode($parent)
->loadChildren($recursionLevel)
->getChildren();
$tree->addCollectionData(null, false, $parent);
$categoryTreeData = array();
foreach ($nodes as $node) {
$categoryTreeData[$node->getData('entity_id')] = $this->getNodeChildrenData($node);
}
return $categoryTreeData;
}
public function _retrieveCollection()
{
$cur_category = array();
$cur_category[] = $this->getCategoryTree(2);
return json_encode($cur_category);
}
The result which I am getting is in the following format:-
"[{
\"1\": {
\"title\":\"Root Catalog\",\"url\":null,\"children\":{
\"2\":{
\"title\":\"Electronics\",\"url\":null, \"children\":{
\"3\":{\"title\":\"Laptops\",\"url\":\"laptops\"}
}
},
\"4\":{
\"title\":\"Men\",\"url\":\"apparels\",\"children\":{
\"9\":{\"title\":\"Formal Mens Jeans\",\"url\":\"formal-mens-jeans\"}
}
},
\"5\":{\"title\":\"Women\",\"url\":\"women\"},
\"6\":{\"title\":\"Baby & Kids\",\"url\":\"baby-kids\"},
\"7\":{\"title\":\"Home & Furniture\",\"url\":\"home-furniture\"}
}
}
}]"
Can you please tell me where I am getting wrong.
Related
I have a structure table like this
I want to change record data field goid = 1, but I as you can see my code bellow, there's limit looping. Is there a better way to do it?
public function actionShareGoid($folder)
{
$one = DokumenFolder::findOne($folder);
$one->goid = '1';
$one->update(false);
$models_1 = DokumenFolder::find()->where(['parent_id'=>$one->id])->all();
foreach ($models_1 as $key_1 => $model_1) {
$model_1->goid = '1';
$model_1->update(false);
$models_2 = DokumenFolder::find()->where(['parent_id'=>$model_1->id])->all();
foreach ($models_2 as $key_2 => $model_2) {
$model_2->goid = '1';
$model_2->update(false);
$models_3 = DokumenFolder::find()->where(['parent_id'=>$model_2->id])->all();
foreach ($models_3 as $key_3 => $model_3) {
$model_3->goid = '1';
$model_3->update(false);
$models_4 = DokumenFolder::find()->where(['parent_id'=>$model_3->id])->all();
foreach ($models_4 as $key_4 => $model_4) {
$model_4->goid = '1';
$model_4->update(false);
}
}
}
}
return $this->redirect(Yii::$app->request->referrer);
}
Thanks to someone, I got the answer. ^_^
So here it is...
public function folderParent($id)
{
$models = DokumenFolder::find()->where(['parent_id'=>$id])->all();
foreach ($models as $key => $model) {
$model->goid = '1';
$model->update(false);
}
}
public function actionShareGoid($id)
{
$models = DokumenFolder::find()->where(['parent_id'=>$id])->all();
foreach ($models as $key => $model) {
$this->folderParent($model->id);
}
return $this->redirect(Yii::$app->request->referrer);
}
I would to create a recursive function in order to retrieve data from an array and to organize then.
However I have some difficulties to create the right logic. The principle must be apply to any sub level until it ends or nothing is found.
I want to prevent this kind of code by repeating a foreach inside a foreach...:
$cats = get_categories($args);
$categories = array();
foreach($cats as $cat){
$parent = $cat->category_parent;
if ($parent) {
$categories['child'][$parent][$cat->cat_ID] = $cat->name;
} else {
$categories['parent'][$cat->cat_ID] = $cat->name;
}
}
}
if (isset($categories['parent']) && !empty($categories['parent'])) {
foreach($categories['parent'] as $id => $cat){
$new_cats[$id]['title'] = $cat;
if (isset($categories['child'][$id])) {
foreach($categories['child'][$id] as $child_id => $child_cat){
$new_cats[$child_id]['title'] = $child_cat;
$new_cats[$child_id]['parent_id'] = $id;
if (isset($categories['child'][$child_id])) {
foreach($categories['child'][$child_id] as $sub_child_id => $sub_child_cat){
$new_cats[$sub_child_id]['title'] = $sub_child_cat;
$new_cats[$sub_child_id]['parent_id'] = $child_id;
}
}
}
}
}
}
}
May be this code help you to get idea for formatting your desired array format.
<?php
// Main function
function BuildArr($arr)
{
$formattedArr = array();
if(!empty($arr))
{
foreach($arr as $val)
{
if($val['has_children'])
{
$returnArr = SubBuildArr($val['children']); // call recursive function
if(!empty($rs))
{
$formattedArr[] = $returnArr;
}
}
else
{
$formattedArr[] = $val;
}
}
}
return $formattedArr;
}
// Recursive Function( Build child )
function SubBuildArr($arr)
{
$sub_fortmattedArr = array();
if(!empty($arr))
{
foreach($arr as $val)
{
if($val['has_children'])
{
$response = SubBuildArr($val['children']); // call recursive
if(!empty($response))
{
$sub_fortmattedArr[] = $response;
}
}
else
{
$sub_fortmattedArr[] = $arr;
}
}
return $sub_fortmattedArr;
}
}
?>
I use this code for my previous project for generating categories that upto n-th level
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;
}
I'm trying to generate a JSON string that includes the id and url for all my categories, subcategories, and sub-subcategories. The code below only gets me as far as the top-level category, but I also need the second and third as well. My goal is to create a dynamically updating sitemap that decodes the string and makes an unordered list of every category level.
public function createCategoryTree() {
$_categories = Mage::getModel('catalog/category')->getCollection()->addAttributeToSelect('*')->addIsActiveFilter()->addLevelFilter(2)->addOrderField('position'); // load all categories
$arr = array();
if ($_categories){ // if there are categories
foreach ($_categories as $cat){
$cat_name = $cat->getName(); // name as key
if (strlen(stristr($cat_name,'default')) < 3) {
$cat_id = $cat->getId(); // id
$cat_url = $cat->getUrl(); // url
$arr[$cat_id] = array('title'=>$cat_name,'url'=>$cat_url);
}
}
}
print_r (json_encode($arr));
die;
}
I get something like this:
{
"637": {
"title": "bikes",
"url": "http:www.123.com/shop/bicycles "
}
}
But I want something like this:
{
"637": {
"title": "bikes",
"url": "http:www.123.com/shop/bicycles",
"children": {
"658": {
"title":"wheels",
"url":"http:www.123.com/shop/bicycles/wheels"
},
"489": {
"title": "pumps",
"url":"http:www.123.com/shop/bicycles/pumps"
}
}
}
Appreciate your time and help!
You can use some functionality provided by Mage_Catalog_Model_Resource_Category_Tree for this. See if this script works for you:
<?php
require_once('../app/Mage.php');
Mage::app();
function getCategoryTree($recursionLevel, $storeId = 1)
{
$parent = Mage::app()->getStore()->getRootCategoryId();
$tree = Mage::getResourceModel('catalog/category_tree');
/* #var $tree Mage_Catalog_Model_Resource_Category_Tree */
$nodes = $tree->loadNode($parent)
->loadChildren($recursionLevel)
->getChildren();
$tree->addCollectionData(null, false, $parent);
$categoryTreeData = array();
foreach ($nodes as $node) {
$categoryTreeData[$node->getData('entity_id')] = getNodeChildrenData($node);
}
return $categoryTreeData;
}
function getNodeChildrenData(Varien_Data_Tree_Node $node)
{
$data = array(
'title' => $node->getData('name'),
'url' => $node->getData('url_key'),
);
foreach ($node->getChildren() as $childNode) {
if (!array_key_exists('children', $data)) {
$data['children'] = array();
}
$data['children'][$childNode->getData('entity_id')] = getNodeChildrenData($childNode);
}
return $data;
}
print_r(json_encode(getCategoryTree(3)));
I ceated a function to convert a list of parent child to an object of class "city" :
public static function createCity(array $config)
{
$city= new City();
$id=key($config);
$label= $config[$id]['label'];
$city->setId($id);
$city->setLabel($label);
$children = $config[$id]['childrens'];
if(!empty($children)){
foreach($children as $key_child => $child) {
$children = array($key_child => $child);
$city->addChild(self::createCity($children));
}
}
return $city;
}
Now I would to creat a function to do the opposite => convert an object of type Class City to an array,so I do like that :
public function getCityArray(City$rootCity)
{
$result = array();
$result['id'] = $rootCity->getId();
$result['label']= $rootCity->getLabel();
$children = $rootCity->getChildren();
if ( !empty($children)) {
foreach ($children as $key_child => $child) {
$result['childrens'] = array($key_child => $child );
$result[] = $this->getCityArray($child);
}
}
return $result;
}
But it doesn't work because when I do var_dump('$result') so I have a list with no end and the loop does not stop?
Try this. Since I don't know have the full code, not sure if it will work. The class variable $result will contain the results.
$result = array();
public function getCityArray(City $rootCity) {
$result['id'] = $rootCity->getId();
$result['label']= $rootCity->getLabel();
$children = $rootCity->getChildren();
if ( !empty($children)) {
$result['childrens'] = $children;
$this->result[] = $result;
foreach ($children as $key_child => $child) {
$this->getCityArray($child);
}
}
}