Trying to get child of a specific category which is active. Please help. I am having trouble doing it. I'm currently able to show them all but not specifically. Would appreciate any help.
$category = Mage::getModel('catalog/category')->load(2);
$category->getChildCategories();
$tree = $category->getTreeModel();
$tree->load();
$ids = $tree->getCollection()->getAllIds();
here is code to load active category
/* Load category by id*/
$cat = Mage::getModel('catalog/category')->load($id);
/*Returns comma separated ids*/
$subcats = $cat->getChildren();
//Print out categories string
#print_r($subcats);
foreach(explode(',',$subcats) as $subCatid)
{
$_category = Mage::getModel('catalog/category')->load($subCatid);
if($_category->getIsActive())
{
$caturl = $_category->getURL();
$catname = $_category->getName();
if($_category->getImageUrl())
{
$catimg = $_category->getImageUrl();
}
echo '<h2><img src="'.$catimg.'" alt="" />'.$catname.'</h2>';
}
}
?>
hope this is sure help you.
As mentioned by mhaupt, it is faster to load a collection rather than each category in a loop. But, as far as I am concerned, there is no need to manually load the child categories. Basically this is what $category->getChildrenCategories() already does.
There is also a filter to get active categories only. Just call addIsActiveFilter() on the collection.
a.) Load active child categories via getChildren()
// 1. Get a list of all child category ids (e.g "12,23,11,42")
$subcategoryIds = $category->getChildren();
// 2. Create collection
$categoryCollection = Mage::getModel('catalog/category')->getCollection();
// 3. Add all attributes to select, otherwise you can not
// access things like $cat->getName() etc.
$categoryCollection->addAttributeToSelect('*');
// 4. Filter by ids
$categoryCollection->addIdFilter($subcategoryIds);
// 5. Add filter to collection to get active categories only
$categoryCollection->addIsActiveFilter();
b.) Load active child categories with getChildrenCategories()
// 1. Load collection
$categoryCollection= $category->getChildrenCategories();
// 2. Add filter to collection to get active categories only
$categoryCollection->addIsActiveFilter();
The collection will be loaded form the database as soon as it is accessed. If the collection is not loaded and $subcategories->count() is called only a "SELECT count(*)" will be fired against the database (in contrast to count($subcategories) which will force the collection to load itself).
Iterating the collection
foreach($categoryCollection as $category) {
echo $category->getName();
}
If you add more filters to the collection after accessing it, the collection will not load itself again automatically. To apply changes to the collection, just call $categoryCollection->load() to reload the collection from the database.
Those who are saying to use getAllChildren() instead of getChildren() are simply wrong.
Both methods return the exact same thing, with one difference, getAllChildren(true) will return an array instead of a comma delimited string. getAllChildren($bool asArray) defaults to false. My point being that either way you're going to have to use
Mage::getModel('catalog/category')->load($catId);
inside of a loop unless you use the function below.
private function fetchCatsById($onlyThese)
{
$cats = Mage::getModel('catalog/category')
->getCollection(true)
->addAttributeToSelect('*')
->addIdFilter($onlyThese)
->addAttributeToFilter('level','2')
->addIsActiveFilter();
return $cats;
}
$cats = $this->fetchCatsById($onlyThese);
The one answer liyakat wrote, should not be used in professional shops, because it raises a performance issue, because of the multiple n time loads of the category object, rather use the collection of categories for that, get all children
$cat->getAllChildren()
, then limit the category collection by the needed category ids like
$coll->addIdFilter($idFilter);
then you won't have to load n times against the database.
Please do keep in mind that loads within loops are one of the most often used bad code examples in any Magento projects and to avoid them!
Hello you will see below code
$category_model = Mage::getModel('catalog/category');
$_category = $category_model->load(13);
$all_child_categories = $category_model->getResource()->getAllChildren($_category);
print_r($all_child_categories);
If you want any number of subcategories of parent category than Click here http://magentoo.blogspot.com/2014/01/get-all-subcategories-of-parent-category-magento.html
Related
How does one get the parent category ID of a category ID in Magento 2?
In Magento 1, I did it with the following:
$product_id = 101; //for example
$product = Mage::getModel('catalog/product')->load($product_id); //get product object by product ID
$category_ids = $product->getCategoryIds(); //array of all categories that the product is in
foreach ($category_ids as $cat_ids) {
$parent_id = Mage::getModel('catalog/category')->load($cat_id)->getParentId(); //
echo $parent_id; //outputs an int ID of parent category
}
In Magento 2, I've been attempting the same with the following:
$product_id = 101; //again, for example
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$productRepository = $objectManager->create('\Magento\Catalog\Model\ProductRepository');
$product = $productRepository->getById($product_id); //get product object by product ID
$category_ids = $product->getCategoryIds(); //array of all categories that the product is in
foreach ($category_ids as $cat_ids) {
echo $cat_ids;
}
Up to here, my code is working perfectly and the $category_ids is an array of all the categories that the product is in. However I cannot figure out how to get the parent category IDs of each child category ID in the $category_ids array.
NOTICE* I'm aware that I'm not officially supposed to directly used the ObjectManager, so please save this from your answer. I am seeking to specifically use the ObjectManager in this manner to iterate over $category_ids and load the parent category IDs for each child category ID.
Like so often, there are multiple ways to achieve this.
The CategoryFactory route
To load a category directly, you load it via the Factory Singleton responsible for the \Magento\Catalog\Model\Category class. This is the \Magento\Catalog\Model\CategoryFactory class. From each instance of Category, you can simple call the method getParentId() to get the parent ID.
foreach ($categoryIds as $categoryId) {
try {
$category = $this->_categoryFactory->create()->load($categoryId);
} catch (\Exception $e) {
/* Handle this appropriately... */
}
echo 'Parent Category ID: ', $category->getParentId(), PHP_EOL;
}
In this example, $categoryIds is the array of Category IDs you extracted from your \Magento\Catalog\Model\Product instance.
The CategoryRepository route
Or preferably you can use a Singleton instance of the \Magento\Catalog\Model\CategoryRepository class as a wrapper around the Factory. It will handle all the loading with some added error handling and it will also store a reference to the loaded category for later reuse. So if you are doing this multiple times during one execution, or suspect that you will load the same category later on, using the Repository will optimize your performance.
foreach ($categoryIds as $categoryId) {
try {
$category = $this->_categoryRepository->get($categoryId);
} catch (\Exception $e) {
/* Handle this appropriately... */
}
echo 'Parent Category ID: ', $category->getParentId(), PHP_EOL;
}
The Collection route
This should be a much faster route, as you (1) load all categories once from database instead of using several multiple sql calls in the backend and (2) you have some control over what is populated in the Category, and what is left out. Please be aware, that pretty much only what you put in addAttributeToSelect() will be populated in the Collection. But if you're only after the parent_id this should not be an issue.
First, make sure you are familiar with collections, then acquire a CollectionFactory Singleton for Magento\Catalog\Model\ResourceModel\Category\CollectionFactory and then populate it like so:
/** #var \Magento\Catalog\Model\ResourceModel\Category */
$collection = $this->_categoryCollectionFactory->create();
# Specifically select the parent_id attribute
$collection->addAttributeToSelect('parent_id');
# Only select categories with certain entity_ids (category ids)
$collection->addFieldToFilter('entity_id', ['in' => $categoryIds])
# Iterate over results and print them out!
foreach ($collection as $category) {
echo 'Parent Category ID: ', $category->getParentId(), PHP_EOL;
}
With great powers comes great risk, however. This above code will have no error correction whatsoever. If there is a logical database error, such as a product which points to a missing category, this category will just be omitted from the collection and it will be up to you as a programmer to spot that and deal with it. Also, you will have to decide for yourself on how you are handling store view and active/inactive categories via filters to the collection.
The Direct Database route
Ok, I would not recommend this route unless you know exactly what you are doing, and are in desperate need for performance.
This will be crazy-fast, but there are all sorts of problems, like relying on the underlying data storage and data structure, not to mention that you are open to (very unlikely, to be fair) future updates to the underlying database structure, either directly via Magento upgrades or via (nasty) 3rd party modules. Not the mention the dangers of SQL injections or XSS attacks. (Though, you should always keep this in mind, with all 4 methods.)
As you are using the ObjectManager directly, I assume you won't mind these drawbacks, however, so I though I'd give you this option as well.
The basic pseudo-sql is:
select parent_id from <name of catalog_category_entity table> where entity_id in (<sanitized, comma-separated list of category ids);
First, acquire an instance of the \Magento\Framework\App\ResourceConnection class. You will use this to get the necessary table name for catalog_category_entity, as well as getting the database connection. Then you should sanitize your data and finally, the bind and execute the query and fetch your data.
/** #var \Magento\Framework\App\Connection */
$connection = $this->_resourceConnection->getConnection();
# Get prefixed table name of catalog_category_entity
$categoryEntityTableName = $this->_resourceConnection->getTableName('catalog_category_entity');
# Sanitize the $categoryIds array using a bit of overkill
array_walk_recursive($categoryIds, function(&$value, $key){
$value = filter_var($value, FILTER_SANITIZE_NUMBER_INT);
});
# Prepare a sql statement which fetches entity_id and parent_id
$preparedStatement = $this->connection->prepare('select entity_id, parent_id from ' . $categoryEntityTableName . ' where entity_id in (' . implode(',', array_fill(0, sizeof($categoryIds), '?')) . ')');
# Bind sanitized $categoryIds array to statement and execute said statement in one single step
$preparedStatement->execute($categoryIds);
# fetch result as a key-value pair array of entity_id=>parent_id
$parentIds = $preparedStatement->fetchAll(\PDO::FETCH_KEY_PAIR);
# Iterate over results and print them out!
foreach ($parentIds as $categoryId => $parentId) {
echo 'Parent Category ID: ', (int)$parentId, PHP_EOL;
}
Footnote
I assume you are well aware of the pros and cons of using the ObjectManager directly, so I'll spare you the lecture ;-). However, for future reference I'll also have to state to future readers stumbling upon this answer that if they are unaware on how to acquire instances of the CategoryFactory, CategoryRepository, CollectionFactory or ResourceConnection classes, I highly recommend them to do so via the intended Dependency Injection mechanism.
We are using Magento 1.
We have used this code:
Mage::getResourceSingleton('catalog/category_tree')->load();
$tree = Mage::getResourceSingleton('catalog/category_tree')->load();
$root = $tree->getNodeById($rootCategoryId);
if($root && $root->getId() == 1) {
$root->setName(Mage::helper('catalog')->__('Root'));
}
$collection = Mage::getModel('catalog/category')->getCollection()
->addAttributeToSelect('name')
->addAttributeToFilter('is_active','1');
//->addAttributeToFilter('display_mode',array('nlike'=>'PAGE'))
//->setLoadProductCount(true)
//->setProductStoreId($store)
//->addAttributeToFilter('include_in_menu','1');
$tree->addCollectionData($collection, true);
We have created a api, and calling this in widget categories as well as category listing, but its only loading for last one only, not resulting for all calls.
kindly suggest a proper way
that is the way a singleton works, use model instead.
Mage::getResourceModel('catalog/category_tree')
I have an Entity Category, which is linked to itself in order to form a tree (a category can have a category as a parent and a category can have a bunch of categories as children). These are marked as private inside the Entity and not exposed to the serializer.
When I do $category->getChildren()->toArray(), I get an array of the children, but when I do $this->getDoctrine()->getRepsitory('PmbLicensing:Category')->findByParent($category)->toArray(), I get an error that toArray() is not defined. I need to use the latter because the top level categories have their parent set to null, so I cannot use the former method. How do I convert the collection of categories obtained in the latter method to an array?
Also, when trying to trouble shoot, I often would like to print out variables, but when I do something like print_r($categories);, print_r((array)$categories); or var_dump($categories); the call just runs for about two minutes and then returns null. I assume it is because of the relational mapping that goes into an infinate loop, but how do I stop this from happening?
Edit: I want to convert the object (or collection of objects) to an array, because I want to build a recursive function where the children categories of the supplied category can be retrieved up to n-depth. If the supplied category can be null, in order to retrieve from the main level of categories (with parent set to null). Here is my function:
private function getRecursiveChildren(Category $category = null, $depth, $iteration)
{
$children = $this->getDoctrine()->getRepository('PmbLicensingBundle:Category')->findByParent($category);
// \Doctrine\Common\Util\Debug::dump($children); die();
if ($depth > $iteration)
foreach ($children as $child) {
$child['children'] = $this->getRecursiveChildren($child, $depth, $iteration+1);
}
return $children;
}
On the line that has $child['children'], is says that I cannot use an object as an array.
If you need the results as an array you can return them from the database as arrays.
In your CategoryRepository class:
public function findArrayByParent($categoryId)
{
// it's a good adivce from #i.am.michiel to pass only the `id` here.
// You don't need the whole category object.
$query = $this->getEntityManager()->createQuery('...')
->setParameters(array('categoryId' => $categroyId));
return $query->getArrayResult();
}
They are never converted to objects after being retrieved from the DB so you also save time and memory.
Actually your
$children = $this->getDoctrine()->getRepository('PmbLicensingBundle:Category')
->findByParent($category);`
already returns an array so you don't have to (and can't) use ->toArray().
When you loop through your categories with
foreach ($children as $child)
$child["..."] = ...
You are treating an obect $child like an array with ["..."]. That's what your error message is about.
If you can, you should probably use doctrine and let it fill the related child and parent categories. See the Doctrine Documentation on this. Then you have automatically all your Children and can access them like $category->getChildren() (This one will return an ArrayCollection). This will save you a lot of work.
Your call simply returns no categories. Btw, I think you should pass the id of the category, not the entity.
$this->getDoctrine()
->getRepository('PmbLicensing:Category')
->findByParent($category->getId());
And why use the toArray() function? ArrayCollection already are arrays with a few additionnal methods? You should be able to use an ArrayCollection whenever you used an array.
What I am trying to do is write a "search" class that can search for a list of products and store them in an array.
I already have a "product" class that can be used to get the details of a specific product.
Here is my code:
class Product {
public $name;
public $price;
public $description;
public function getProductById ($id) {
$sql = 'SELECT name, price, description FROM product WHERE id = ' . $id;
$row = /* MySQL functions here to execute SQL statement and get a matching row */
$this->name = $row['name'];
$this->price = $row['price'];
$this->description = $row['description'];
return TRUE;
}
}
class Search {
public $results;
public $totalResults;
function __construct() {
$this->results = array ();
$this->totalResults = 0;
}
public function doSearch ($name) {
$sql = 'SELECT id FROM product WHERE name LIKE "%' . $name . '%"';
$rows = /* MySQL functions here to execute SQL statement and get a list of matching product ID's */
foreach ($rows as $row) {
$product = new Product;
$product->getProductById ($row['productid']);
$this->results[] = $product;
}
return TRUE;
}
}
$search = new Search;
$search->doSearch ('Fresh Flowers');
The problem with the above is that every matching record in the doSearch method will execute a query in the getProductById method. If there are 100 matching products, there will be 100 individual queries carried out in the Product class.
However, if I get the products directly in the doSearch method using a single query, this will then bypass the Product class altogether.
When a "product" is an object, what's the most appropriate way to write a search class that can return a list of "product" objects without the overhead of what I'm doing above?
Add a constructor to the Product class which takes name, price and description as parameters (or an assoziative array), to populate the object with the necessary values, decoupled of the database query.
Within doSearch, you can then create a SELECT which not only gets the ID but all relevant fields from the products table, and create the populated product objects immediately with new Product($name, $price, $description) or new Product($row), without calling getProductById for each product.
Create a class that populates instances of Product with data from the database.
The class can then create one or multiple instances of the Product class depending on how much data is being fetched.
Conclusion: Extract the getProductById from your Product class and put it somewhere else. It is a specialised method that only populates one instance.
Just grab what you want in the first place.
public function doSearch ($name) {
$sql = 'SELECT id, name, price, description FROM product
WHERE name LIKE "%' . $name . '%"';
// now just return the array
}
Or use PDO to return result sets as objects.
$result->setFetchMode(PDO::FETCH_INTO, new animals);
as discussed here: How can I simply return objects in PDO?
Your Product class shouldn't know anything about a database. It should contain the values representing a product, nothing more. Extract all stuff dealing with the database out of this class.
Searching for products is one way to access a list of products. Your next class should be a list of products then. Only when accessing one single product you'd not have to deal with a list, but this is probably less often than you think.
Ok, you have the product and the list of products, you now can go one step forward and add database access. You need a class that deals with giving you both one product (when searching by id) and lists of products (when searching by some text or other stuff). Only this class allows you to deal with the queries needed to access the database. The result sets of each query may directly be used inside the "list of products" class, probably by inheriting all the stuff that is defined in the general "list of products" class and adding dealing with database results.
So in the end you'll end up having:
Product -> ListOfProducts -> ProductDatabase -> DatabaseAccessLayer
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