How To Get Sub Categories in Magento ?` - php

I am playing with magento's home page where I have created a tab Which Shows all the categories including Root, categories, and Sub Categories (In One Tab). Now I want to Show Only Main Categories( Whose Parent Is Root) in main Tab And under Each Category I want to List Their respective Sub Categories. I wrote The following Code to achieve a part of this,
MODEL CLASS
public function getsubCategory($parent)
{
$subcategoryCollection = Mage::getModel('catalog/category')
->getCollection()
->addAttributeToFilter('parent_id', $parent);
return $subcategoryCollection;
BLOCK CLASS
protected function b4Html_subcategory($parent)
{
$catModel = Mage::getModel('Pragtech_Sweet/category');
$mysubCategory = $catModel->getsubCategory($parent);
$this->mysubCategory = $myCategory;
return $mysubCategory;
}
TEMPLATE FILE
$obj = new Pragtech_Sweet_Block_Category();
$collection = $obj->b4Html();
foreach ($collection as $category)
{
$name = $category->getName();
$parent = $category->getParent_id();
foreach ($obj->b4Html_subcategory($parent) as $subcategory)
{
$subname = $subcategory->getName();
//Here Will Go Ther Code For Sub Categories
}
but it doesn't work.. I am unable to understand where I am doing wrong... Can anyone help me out

Do this instead :
Mage::getModel('catalog/category')->load('23')->getChildrenCategories();
and iterate over the result.
and that's how i found it out:
$object = Mage::getModel('catalog/category');
print_r(get_class_methods($object));
print_r($object->load('23')->getChildrenCategories()->toArray());

Here's another way to do this, if you don't want to mess with the treeModel stuff, or want more control how categories are loaded:
function getCategoryTree($root_category_name)
{
$categories = Mage::getModel('catalog/category')->getCollection()
->addAttributeToSelect("*")
->addFieldToFilter("Name",array("eq"=>$root_category_name));
$stack = array();
$category = $categories->getFirstItem()->getData();
$categories=array();
array_push($stack, $category);
//regular recursion is boring, let's do a stack
while(count($stack) > 0)
{
$category = array_pop($stack);
array_push($categories, $category);
$children = Mage::getModel('catalog/category')->getCollection()
->addAttributeToSelect("*")
->addFieldToFilter("parent_id",array("eq"=>$category['entity_id']))
->addAttributeToSort("position","desc");
foreach ($children as $child)
{
array_push($stack, $child->getData());
}
}
return $categories;
}
This will give you an array of categories representing the full category tree rooted at the top category with the name "Some Category Name", in order, with all of the category data. Tune this to only select the specific fields you need, instead of "*".
This technique can give you finer grained control of the loading of the category tree, and with how you want the data structured afterwards. For example, you could trivially modify this to do a hierarchical tree instead of a flat array and then serialize that to a JSON object to send to the client.
Add whatever filters you like (active, etc...) at whatever level you like to get even more control.

iterate through this object
Mage::getModel('catalog/category')
->getCollection()
->addAttributeToSelect('*')
->getItems();

Related

How to send relational data from multiple foreach to view in Laravel

I made a Laravel project where Category, Subcategory and Child category working using same table relation.
I want to show all products from childcategory when click to Main Category.
So for that I need to use multiple foreach loop like this,
foreach ($categories->subcategories as $subcat)
{
foreach ($subcat->childcategories as $childcat )
{
$products = $childcat->products;
}
}
Now I want to send $products to a view. How can I do it.
I don't want to use array. How can I send It. Please help.
Use Array()
$products = array()
foreach ($categories->subcategories as $subcat)
{
foreach ($subcat->childcategories as $childcat )
{
$products[] = $childcat->products;
}
}
return view(....);
In Laravel you can pass your Category Object to the view. In Blade you have access to the relationships of Category. You can testet by :
dd($categories->subcategories) or dd($categories->subcategories[]->childcat).

Symfony2, PHP decrease query weight when counting many objects from database

So I have 4 tables in my database. Product, Category, ProductCategory and ProductSubcategory.
These tables are in a ManyToMany relationship.
All products are saved in table: Product.
All Categories and Subcategories are saved in one table: Category.
A Product can have a Subcategory without the Category, can have both, or can have a Category without any Subcategory.
ProductCategory and ProductSubcategory is holding all the relationships.(product_id and category_id/subcategory_id)
Now in my twig I want to count how many products does the category and subcategory hold. For that I am using Twig Extensions.
For Subcategory:
Counting how many products are in a subcategory is easy and it works.
In Twig:
{% for subcategory in categories.children %}
[{{ getProductsBySubcategory(subcategory.id) }}]
{% endfor %}
The function:
public function getProductsBySubcategory($id)
{
$products = 0;
$subcategories = $this->doctrine->getRepository('MpShopBundle:ProductSubcategory')->findBy(array('subcategory' => $id));
foreach ($subcategories as $subcategory) {
$products++;
}
return $products;
}
For Category:
For category its a little bit different. Here I want to count all the products in the Category and all the products in Subcategories that this category has. First I check if the category has products and if it has I add the to the array. Then I check if this category has subcategories. If it does i get all the products from the subcategories and add them in to the same array. Now if the product is in both the category and subcategory i dont want to double count. Thats why I am storing everything in an array(to do array_unique later).
My Twig:
[{{ getProductsByCategory(categories.id)|length }}]
The function:
public function getProductsByCategory($id)
{
$category = $this->doctrine->getRepository('ApplicationSonataClassificationBundle:Category')->find($id);
$productCategories = $this->doctrine->getRepository('MpShopBundle:ProductCategory')->findBy(array('category' => $id ));
$productSubcategories = $this->doctrine->getRepository('MpShopBundle:ProductSubcategory')->findAll();
$products = array();
foreach($productCategories as $productCategory) {
array_push($products, $productCategory->getProduct()->getId());
}
foreach($productSubcategories as $productSubcategory) {
$subcategory = $productSubcategory->getSubcategory();
if($subcategory->getparent() == $category) {
array_push($products, $productSubcategory->getProduct()->getId());
}
}
$result = array_unique($products);
return $result;
}
THE PROBLEM:
Everything works in local. I am getting the correct number. However when I switch to Live I get error:500. I am thinking that this is probably because Live has thousands of products and allot of categories. So the foreach loops are breaking the server. Or maybe its because i am saving everything in array?
What should I do? Is it the server problem and I need to increase something? Or maybe there is a way to count without using arrays and decreasing the loop count? But how to check for duplicates then?
The logs show this:
Allowed memory size of 536870912 bytes exhausted (tried to allocate 77 bytes) in /var/www/kd_cms/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php on line 40
Dql is faster than foreach with large db objects
// .../ProductCatRepository.php
public function findDistinctProductsByCat($catId)
{
$query = $this->createQueryBuilder('entity')
->leftjoin('entity.category', 'cat')
->andWhere('cat.id = :cat')
->setParameter('cat', $catId)
;
return $query->getQuery()->getResult();
}
// .../ProductSubCatRepository.php
public function findDistinctProductsByCatSubCat($catId)
{
$query = $this->createQueryBuilder('entity')
->leftjoin('entity.subcategory', 'subcat')
// maybe missing a leftJoin with cat idk your mapping
// ->leftJoin('subcat.category', 'cat')
// ->andWhere('cat.parent = :cat')
->andWhere('subcat.parent = :cat')
->setParameter('cat', $catId)
;
return $query->getQuery()->getResult();
}
public function getProductsByCategory($id)
{
$categoryId = $id;
$productsCat = $this->doctrine->getRepository('MpShopBundle:ProductCat')->findDistinctProductsByCat($categoryId);
$productsSubCat = $this->doctrine->getRepository('MpShopBundle:ProductSubCat')->findDistinctProductsByCatSubCat($categoryId);
// ArrayCollection::count()
return array('count_cat' => $productsCat->count(), 'product_subcat' => $productsSubCat->count(), 'result' => array_unique(array_merge($productsSubCat, $productsCat)));
}

TYPO3 extbase - ObjectStorage and 1:n relation

I have an Item object which has a 1:n relation to categories. My Item Model contains:
setCategories(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $categories)
getCategories()
addCategory(VENDX\Items\Domain\Model\Category $category)
removeCategory(VENDX\Items\Domain\Model\Category $category)
but I am not able to add multiple categories to an itemobject.
i tried:
$category = $this->objectManager->get('VENDX\Items\Domain\Model\Category');
$category->setCatName('Cat1'); //First category
$item->addCatgeory($category);
$category->setCatName('Cat2'); //Second category
$item->addCategory($category);
after adding $item to my $itemrepository it just saves the last category "Cat2" into the db. What am i missing??
also tried that:
$categories = $this->objectManager->get('TYPO3\CMS\Extbase\Persistence\ObjectStorage');
$category = $this->objectManager->get('VENDX\Items\Domain\Model\Category');
$category->setCatName('Cat1'); //First category
$categories->attach($category);
$category->setCatName('Cat2'); //Second category
$categories->attach($category);
$item->setCategories($categories);
same issue with the above code. It just saves the last (second) category. How can i add multiple categories to my item-object?
Well i made a fatal error when using the SAME category-object. In fact i just changed its CatName value. In ORM we need one object for each "value". Means we can't use the same object for multiple "object-allocations" like i did above. So the correct way of achieving my purpose is:
$categories = $this->objectManager->get('TYPO3\CMS\Extbase\Persistence\ObjectStorage');
$category1 = $this->objectManager->get('VENDX\Items\Domain\Model\Category'); //1st catobj
$category1->setCatName('Cat1'); //First category
$categories->attach($category1);
$category2 = $this->objectManager->get('VENDX\Items\Domain\Model\Category'); //2nd catobj
$category2->setCatName('Cat2'); //Second category
$categories->attach($category2);
$item->setCategories($categories);
another "mistake" was using the objectManager for entities-instantiation. I was told to construct them via "new" instead of "overheading" the extension with the objectManager.
so my final solution is:
$categories = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage;
$category1 = new \VENDX\Items\Domain\Model\Category; //1st catobj
$category1->setCatName('Cat1'); //First category
$categories->attach($category1);
$category2 = new \VENDX\Items\Domain\Model\Category; //2nd catobj
$category2->setCatName('Cat2'); //Second category
$categories->attach($category2);
$item->setCategories($categories);

sorting collections by subcategory, an attribute and by productname

I been working on a problem for hours now and it seems I can not find a way to get the above sorting to work.
In an magento project (im relatively new to magento) I have to sort the collection first by subcategory, then an attribute and last by the product name. is Anchor is set to ture with all categories, so parent categories also show subcategory products.
I came across an idea of using the ReflectionObject using the usort() PHP function with an comparator function, like this:
private static function cmp($a, $b) {
$a_product_geschmack = Mage::getModel('catalog/product')
->load($a->getId())->getAttributeText('ws_geschmack');
$b_product_geschmack = Mage::getModel('catalog/product')
->load($b->getId())->getAttributeText('ws_geschmack');
$r = strcmp(get_category($a), get_category($b));
if ($r != 0)
return $r;
$r = strcmp($a_product_geschmack, $b_product_geschmack);
if ($r != 0)
return $r;
return strcmp($a->getName(), $b->getName());
}
The helper function to get the subcategory looks like this:
function get_category($product) {
$categoryModel = Mage::getModel( 'catalog/category' );
// Get Array of Category Id's with Last as First (Reversed)
$_categories = array_reverse( $product->getCategoryIds() );
// Get Parent Category Id
$_parentId = $categoryModel->load($_categories[0])->getParentId();
// Load Parent Category
$_category = $categoryModel->load($_parentId);
return $_category->getName();
}
Using the above comparator with usort and the ReflectionObject in the _getProductCollection() method of Mage_Catalog_Block_Product_List:
// ...
$collection = $this->_productCollection;
$collectionReflection = new ReflectionObject($collection);
$itemsPropertyReflection = $collectionReflection->getProperty('_items');
$itemsPropertyReflection->setAccessible(true); // Make it accessible
$collectionItems = $itemsPropertyReflection->getValue($collection);
usort($collectionItems, array('Mage_Catalog_Block_Product_List', 'cmp'));
$itemsPropertyReflection->setValue($collectionReflection, $collectionItems);
$itemsPropertyReflection->setAccessible(false); // Return restriction back
$this->_productCollection = $collection;
// ...
All above I set up as a test (to see if it is working) in the Mage_Catalog_Block_Product_List class. For savety I commented out the default sorting setting with setOrder in Toolbar.php
Above code I found at https://magento.stackexchange.com/questions/5438/on-search-result-group-products-by-category and it seemed promising (even if this is more a hack than OO).
When I print $collectionItems the order is as expected. In the Frontend it is not as expected, for example the attribute ws_geschmack is not sorted and also the subcategory is not "properly" sorted.
I also ask myself if the way passing back $collection to the _productCollection member is the way to go (as it is in the sample code found in the stackexchange answer). I found out, there is also a method called setValue in the ReflectionObject class, but also does not work.
Well, the problem would be not a real problem, if the subcategory would be an attribute, in this case I could just use the setOrder with an array of fields to sort in ASC order.
Also the order of the categories in the backend (if I order them alphabetically) seem to have no effect. If this would work, I could drop the sorting for the subcategory.
The sorting has to work in the category product listing and in the search results list, the latter is not so important, but the category browsing is.
Another questions is alo similar to mine (https://magento.stackexchange.com/questions/2889/sorting-product-list-by-more-than-one-attribute), but that guy had only attributes and that can be solved with an setOrder Call using an array.
So I am out of ideas. Anybody has an idea how to get this issue solved? Any help is greatly appreciated!
I am using magento version 1.7.0.2 for this project.
Update
Just to clarify what I am looking for: I implemented this iterative code which does exactly what I need. It first gets the sub-categories of the current category, then it queries all the products in these subcategories. The result is sorted list of categories, and the product results / sublists are sorted by the attribute ws_geschmack and name:
$categories = Mage::getModel('catalog/category')->getCollection()
->addAttributeToSelect('name')
->addFieldToFilter('parent_id',
array(
'eq' => Mage::getModel('catalog/layer')->getCurrentCategory()->getId()))
->addFieldToFilter('include_in_menu',array('eq' => '1'))
->addFieldToFilter('is_active', array('eq' => '1'))
->addAttributeToFilter('is_active', 1)
->addAttributeToSort('name', 'asc');
foreach($categories as $category) {
// Check if there are products for sale in this category
if (Mage::getModel('catalog/category')->load($category->getId())
->getProductCollection()
->addAttributeToSelect('entity_id')
->addAttributeToFilter('status', 1)
->addAttributeToFilter('visibility', 4)
->count() == 0) continue;
print "-> " . $category->getId() .': '. $category->getName() . "<br />";
// Get all child categories below this current category
$_subcategory_ids = get_categories(Mage::getModel('catalog/category')->getCategories($category->getId()));
// Build 'finset' query for category_id filter
$_subcategory_finset_ids = array_map(
function($elem) {
return array('finset' => $elem);
},
$_subcategory_ids);
$_products = Mage::getModel('catalog/product')
->getCollection()
->joinField('category_id', 'catalog/category_product', 'category_id', 'product_id = entity_id', null, 'left')
->addAttributeToSelect('*')
->addAttributeToFilter('status', 1)
->addAttributeToFilter('visibility', 4)
->addAttributeToFilter('is_saleable', array('like' => '1'))
->addAttributeToFilter('category_id', $_subcategory_finset_ids)
->addAttributeToSort('ws_geschmack', 'ASC')
->addAttributeToSort('name', 'ASC');
if ($_products->count() != 0) {
foreach ($_products as $_product) {
$prod = Mage::getModel('catalog/product')->load($_product->getId());
echo $prod->getName() . ": " . $prod->getAttributeText('ws_geschmack') . "<br />";
}
}
}
This is just a demo code, I can not use it as is. All that I would need as the return value from getLoadedProductCollection() for example. I guess it will be no easy task to implement that functionality.
I'm not sure if this is the expected result, but given your criteria, will the following below set the collection in the way you need it to?
$products = Mage::getModel( 'catalog/product' )
->getCollection()
->addAttributeToSelect( '*' )
->addFieldToFilter( 'status', Mage_Catalog_Model_Product_Status::STATUS_ENABLED )
->addFieldToFilter( 'visibility', Mage_Catalog_Model_Product_Visibility::VISIBILITY_BOTH );
->addAttributeToFilter( 'category_id', array( 'in' => array( 'finset' => '[CATEGORY_ID_HERE]' ) ) );
->addAttributeToSort( 'category_id', ASC )
->addAttributeToSort( 'ws_geschmack', ASC )
->addAttributeToSort( 'name', ASC );
You can use similar methods to narrow down your collection and simplify things. Also know that if you are customizing the built in product collection methods that the sorter on the front end may be modifying your front-end, so you may want to make sure it's not affecting it.

Separating SQL, PHP and the view when listing categories and products in Kohana

I want to migrate to Kohana with my small websites and I'm trying to separate the SQL, PHP and the view, but I've some problems with this one.
I have to tables. Every category can have multiple products.
Categories table
id
category
Products table
id
category_id
product
This was my previous code (converted to Kohana's query builder):
$categories = DB::select('id', 'category')
->from('categories')
->as_object()
->execute();
foreach ($categories as $category)
{
echo '<b>'.$category->category.'</b><br />';
$products = DB::select('product')
->from('products')
->where('category_id', '=', $category->id)
->as_object()
->execute();
foreach($products as $product)
{
echo $product->product.'<br />';
}
echo '<hr />';
}
I want to do the same, just in the view file I don't want to use other than echoing out the variables.
Update:
I would prefer a solution without using Kohana's ORM module.
Btw I'm using Kohana 3.0
Update 2:
I've accepted Lukasz's last solution, but a few modifications are needed to do exactly what I wanted to (note that this is for Kohana 3.0, while Lukasz was working with an older version):
SQL code:
$products = DB::select(array('categories.category', 'cat'), array('products.product', 'prod'))
->from('categories')
->join('products','RIGHT')
->on('products.category_id','category.id')
->as_object()
->execute();
Code in the view file (see comments for explanation):
// Let's define a new variable which will hold the current category in the foreach loop
$current_cat = '';
//We loop through the SQL results
foreach ($products as $product)
{
// We're displaying the category names only if the $current_cat differs from the category that is currently retrieved from the SQL results - this is needed for avoiding the category to be displayed multiple times
// At the begining of the loop the $current_cat is empty, so it will display the first category name
if($curren_cat !== $product->cat)
{
// We are displaying a separator between the categories and we need to do it here, because if we display it at the end of the loop it will separate multiple products in a category
// We avoid displaying the separator at the top of the first category by an if statement
if($current_cat !== '')
{
//Separator
echo '<hr />';
}
// We echo out the category
echo '<b>'.$product->cat.'</b><br />';
// This is the point where set the $current_cat variable to the category that is currently being displayed, so if there's more than 1 product in the category the category name won't be displayed again
$current_cat = $product->cat;
}
// We echo out the products
echo $product->prod.'<br />';
}
I hope this was helpful to others as well, if anybody has a better solution go on, share it!
This should work:
$categories = ORM::factory('category')->find_all();
foreach ($categories as $category)
{
echo '<b>'.$category->category.'</b><br />';
$products = ORM::factory('product')->where('category_id',$category->id)->find();
foreach($products as $product)
{
echo $product->product.'<br />';
}
echo '<hr />';
}
I assume that you created models for each table? If not, read here.
Better if you separate data and view layers. And create relations between categories and products in Model files. Then in controller you should call only this:
$categories = ORM::factory('category')->find_all();
$view = new View('yourView');
$viem->categories = $categories;
And in view file:
foreach ($categories as $category)
{
echo '<b>'.$category->category.'</b><br />';
foreach($category->products as $product)
{
echo $product->product.'<br />';
}
echo '<hr />';
}
You will not have to call 2nd query for every row. Kohana ORM will do it for you. All you have to do is to create appropriate models:
class Category_Model extends ORM {
protected $has_many = array('products');
}
class Product_Model extends ORM {
protected $belongs_to = array('category');
}
Another approach: you can join tables categories and products
$products = DB::select('categories.category AS cat','products.product AS prod')
->from('categories')
->join('products','products.category_id','category.id')
->as_object()
->execute();
And then:
$current_cat = '';
foreach($products as $product)
{
if($current_cat != $products->cat)
{
echo '<b>'.$products->cat.'</b><br />';
$current_cat = $products->cat;
}
echo $product->prod.'<br />';
}
Just a quick thought:
When using the ORM-approach, I think you could achieve the same thing as a join-query using the with() method:
$products = ORM::factory('product')
->with('category')
->find_all();
$view = new View('yourView');
$viem->products = $products;
I haven't had enough time to mess around with it, but this is what I would try.

Categories