1

I have a category with more than 50K products therefore whenever I click on it in admin "Manage Categories" it gives me memory exhaust error, now I want to only load products when I click on "Category Product" tab or a button in phtml file which loads product in the grid.

Can someone guide through steps to load grid from phtml with working filters and selection?

  • What is your PHP Memory set to? – Steven J Jan 5 '18 at 22:56
1

I'm not sure that what you're asking for will solve the problem described. As I read the symptom, my first diagnosis pointed to the following roots:

  1. Most admin content tabs will preload everything at runtime by default.
  2. The collection process for assigned products under a category uses an inefficient query.

Supporting the Diagnosis:

Mage_Adminhtml_Block_Catalog_Category_Tabs::_prepareLayout ~lines 148-154:

...
$this->addTab('products', array(
    'label'     => Mage::helper('catalog')->__('Category Products'),
    'content'   => $this->getLayout()->createBlock(
        'adminhtml/catalog_category_tab_product',
        'category.product.grid'
    )->toHtml(),
));
...
  • The block is rendered instantly upon layout preparation.
  • IMO this is the laziest of available content load mechanisms available.
  • Magento offers grid serialization components and deferred (AJAX) load tooling (more on this later).

Mage_Adminhtml_Block_Catalog_Category_Tab_Product::_prepareCollection ~lines 71-98:

...
protected function _prepareCollection()
{
    if ($this->getCategory()->getId()) {
        $this->setDefaultFilter(array('in_category'=>1));
    }
    $collection = Mage::getModel('catalog/product')->getCollection()
        ->addAttributeToSelect('name')
        ->addAttributeToSelect('sku')
        ->addAttributeToSelect('price')
        ->addStoreFilter($this->getRequest()->getParam('store'))
        ->joinField('position',
            'catalog/category_product',
            'position',
            'product_id=entity_id',
            'category_id='.(int) $this->getRequest()->getParam('id', 0),
            'left');
    $this->setCollection($collection);

    if ($this->getCategory()->getProductsReadonly()) {
        $productIds = $this->_getSelectedProducts();
        if (empty($productIds)) {
            $productIds = 0;
        }
        $this->getCollection()->addFieldToFilter('entity_id', array('in'=>$productIds));
    }

    return parent::_prepareCollection();
}
...
  • The call to $this->_getSelectedProducts() appends 50K IDs to an IN clause in your case.
  • The call to parent::_prepareCollection() instantly loads the collection, but not before applying a limit to the query (see commentary below).

Deeper Commentary:

Knowing that the parent widget-grid block [see parent::_prepareCollection()] applies a limit to the results returned through the assembled query, the most loaded at once should be no greater than 200. I say this because 200 is the highest default limit given by the toolbar class. So unless you have a modification to your code that would increase the limit beyond 200, I am not certain that the memory exhaustion is caused by 50K assigned products.

Therefore, it seems to me that deferred loading won't solve your problem. Ask yourself some questions that might seem unrelated, like:

  • Do you have any observers registered on events like catalog_product_collection_load_before or catalog_product_collection_load_after?
  • Do you have extensions that add additional content tabs to the area?
  • Are you adding additional columns to the Category Products grid that would make the select query more complex?

I would bet that something else is getting in the way. You could try some brute force guesses by stripping away customizations until the problem goes away. Or else, this is a worthwhile exercise in using a profiler like XDebug to pinpoint when the memory exhaustion occurs.

Deferred Tab Content Loading

If you've followed along so far, you learned that the content is loaded at runtime (or more precisely, layout generation time) for the Category Products tab, and also that Magento offers some unique features for deferring the loading of contents. The easiest solution would be to do the following:

Register a rewrite on block adminhtml/catalog_category_tabs:

<blocks>
    ...
    <adminhtml>
        <rewrite>
            <catalog_category_tabs>Vendor_Module_Block_Adminhtml_Catalog_Category_Tabs</catalog_category_tabs>
        </rewrite>
    </adminhtml>
    ...

In this rewrite, copy the original _prepareLayout method, modifying only the section of code that adds the Category Products tab, so that it reads like this:

$this->addTab('products', array(
    'label'     => Mage::helper('catalog')->__('Category Products'),
    'url'       => $this->getUrl('*/*/grid', array('_current' => true)),
    'class'     => 'ajax',
));
  • Notice that we exchanged the contents property for a custom url and class property, effectively changing the behavior to load contents from an external source when the tab is selected.
  • Interestingly, there is already an endpoint for doing exactly what you need, at Mage_Adminhtml_Catalog_CategoryController::gridAction. I haven't researched this, but it may have been an abandoned implementation by the original developer that was intended to load the grid exactly as you are looking for today.
  • Sadly, we have to replace the entire method because there is no other way to avoid preloading of the product grid which, presumably, you believe to be causing your memory exhaustion problems.

Give it some thought, a try, and feedback. I'm sure it can be resolved by branching off from one of these suggestions above.

| improve this answer | |
  • kudos, I was looking for exactly the same, there is a lot of customization done to that project I will check the other options too. – Haris Jan 7 '18 at 3:26
  • Hi, Rick, you were right, products load was not the cause of the issue rather a third party badly rewritten code was, for performance improvements, I also added the second option (replacing contents by url, ajax based loading) but it caused a lot, this method doesn't insert values into category_product element due to which when I save a category, it looses all of its product. – Haris Feb 2 '18 at 10:42
  • I should have tested my own solution better @Haris. Some additional info: Mage_Adminhtml_Catalog_CategoryController ~ line 316 will force save an empty data set because catalog/category/edit/form.phtml inserts a hidden field which the product grid uses for tracking assigned products (~line 51). That assignment happens in the same template ~ line 193. So in effect the entire category form area is dependent on the product grid at run-time. You would have to modify this template to fit the deferred-load approach, so that it doesn't set the hidden input until the grid is ready. – Rick Buczynski Feb 2 '18 at 16:41
0

You could try increasing your memory limit via the php.ini file:

PHP measures memory in M (e.g. 4096M is 4gb). Add or modify these lines on your php.ini file:

max_execution_time = 240
memory_limit = 4096M
post_max_size = 32M
max_input_vars = 6400

These are the settings I use on my 5 site multi-store Magento 1.9.3.x install. Adjust according to your specs and needs.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.