34

The task is trivial. I need to get list of products for particular store view with a flat catalog enabled. The most obvious solution is the following:

$collection = Mage::getResourceModel('catalog/product_collection')
    ->setStore($storeId);

In fact setStore() method is not making any difference here because it is called after the _initSelect() method of Mage_Catalog_Model_Resource_Product_Collection which gets the name of the flat table based on store ID. As the store ID is not yet set it takes the current store ID.

So the obvious workaround would be to set a current store ID before getting a model.

Mage::app()->setCurrentStore($storeId);

$collection = Mage::getResourceModel('catalog/product_collection');

It will work. But only if you need to get a collection once. If you need to get a collection in the loop or you just need two back to back collections you will not be able to set a specific store for them.

The reason is that Mage_Catalog_Model_Resource_Product_Flat class has it's own _storeId property and in the constructor it is set to the current store ID. That's why it will be set first time. Then for some reason (heaven knows I hope there is one) in Mage_Eav_Model_Entity_Collection_Abstract::_init each resource module is fetched as a singleton. So no constructor for 2nd call.

This all looks so wrong that I'm pretty sure I'm wrong and it is not another Magento bug (or two). Hope somebody can shed a light on it.

4
  • Do you have to use getResourceModel() as this gives you the instance? getModel('catalog/resource_product_collection') might just work. Jul 2, 2014 at 23:25
  • No, it's absolutely the same. It is instantiating resource model singleton in any way. Jul 3, 2014 at 4:48
  • Tim, add it as an answer please! Jul 3, 2014 at 12:13
  • @FabianBlechschmidt done. Jul 3, 2014 at 13:06

5 Answers 5

13

What version of Magento is this? These are my results for Magento 1.9:

Flat catalog enabled:

Flat catalog is indexed:

Some data set in a specific store view:

Code used:

<?php

require_once 'app/Mage.php';

Mage::app('admin');

$collection = Mage::getResourceModel('catalog/product_collection')
    ->addAttributeToSelect('*')                                                                                                                                                                                                                                                 
    ->addFieldToFilter('entity_id', array('eq' => 231))
    ->setStore(2);

var_dump($collection->getFirstItem()->getName());

Result is as expected:

string(18) "But I Am Le French"

edit:

Nevermind, flat catalog is specifically prohibited for admin store:

// Flat Data can be used only on frontend
if (Mage::app()->getStore()->isAdmin()) {
    return false;
}

Investigating...

edit2:

It looks like you are right. _initSelect is called before we are able to modify the storeId which is used to generate the table name.

Of course (if we don't want to go the rewrite route) we can:

  • getSelect(), do a reset and set a new from()
  • $collection->getEntity()->setStoreId(123) and then use reflection to call _initSelect again
  • Just create our own resource model and extend from flat, give some way of inserting storeId at the right time (__construct, delaying _initSelect, etc).
  • call setCurrentStore everytime we create the collection.

But these all feel very hacky... Sorry, this may be an unsatisfactory answer :-(

edit3:

So for the sake of providing at least an answer:

// Get collection and update store ID.
$collection = Mage::getResourceModel('catalog/product_collection');
$collection->getEntity()->setStoreId(2);

// Reset the select.
$collection->getSelect()->reset();

// Update table name.
$reflectionMethod = new ReflectionMethod($collection, '_initSelect');
$reflectionMethod->setAccessible(true);
$reflectionMethod->invoke($collection);

// Do any other operations on the collection now.
$collection->addAttributeToSelect('*');

Please don't use that ;-)

4
  • So do you also think it is a bug? Jul 3, 2014 at 5:08
  • 1
    I skimmed over the code, but product_collection's constructor accepts a resource model as argument. So if you create a Product_Resource_Flat, set it's store id, clone it and set a different store id, then pass it to the collection constructor, would that be doable?
    – user4351
    Jul 3, 2014 at 5:30
  • 1
    @Tim: Sorry, only just saw your comment. Yes I think it's a bug. Jul 4, 2014 at 12:37
  • up for great answer, it's working for 1.14.2.0
    – user4531
    Dec 7, 2015 at 5:44
10

So I consider these to be two bugs in Magento.

First one is the fact that you can't set store ID on catalog/product collection. And the second is that you absolutely can't get resource model as non-singleton.

So stupid workaround is to instantiate model twice. The first time the store ID can be set and the second instantiation will use it:

Mage::getResourceModel('catalog/product_collection')->setStore($storeId);

$collection = Mage::getResourceModel('catalog/product_collection')
1
  • I don't know why my set store in Mage::getModel('catalog/category')->getProductCollection()->setStoreId() didn't work and yours worked. by the way thank you
    – Nickool
    Sep 29, 2014 at 17:32
3

Interestingly, the used flat table is set once and never changed which works for EAV since table name doesn't change but not for flat since the table name includes the store ID. A workaround would be to make a helper that would swap out the table in the FROM part of the query. Here's an example of such a helper:

class My_Module_Helper_Data extends Mage_Core_Helper_Abstract
{
    public function getProductCollectionForStore($store)
    {
        $collection = Mage::getResourceModel('catalog/product_collection');

        // Change the store on the entity
        // This doesn't change it in the (already constructed) SQL query
        $collection->setStore($store);

        if (! $collection->isEnabledFlat()) {
            return $collection;
        }

        // Change the used table to the $store we want
        $select = $collection->getSelect();
        $from = $select->getPart('from');

        // Here, getFlatTableName() will pick up the store set above
        $from[$collection::MAIN_TABLE_ALIAS]['table'] =
        $from[$collection::MAIN_TABLE_ALIAS]['tableName'] = 
            $collection->getEntity()->getFlatTableName();

        $select->setPart('from', $from);
        return $collection;
    }
}

Then you can use it simply with:

$collection = Mage::helper('my_module')->getProductCollectionForStore('somestore')
    ->addAttributeToSelect('name');

I imagine this would cause no trouble for the SQL since you're fetching all data from a single flat table but since it's a singleton the last used store would be used everywhere else.

Alternative solution would be to make an observer on catalog_product_collection_load_before which does something like this:

class My_Module_Model_Observer
{
    public function setCorrectFlatStore(Varien_Event_Observer $observer)
    {
        $collection = $observer->getCollection();
        if (! $collection->isEnabledFlat()) {
            return;
        }

        $select = $collection->getSelect();
        $from = $select->getPart('from');

        // If somebody called setStore() on the collection make sure
        // to update the used flat table
        $from[$collection::MAIN_TABLE_ALIAS]['table'] =
        $from[$collection::MAIN_TABLE_ALIAS]['tableName'] =
            $collection->getEntity()->getFlatTableName();

        $select->setPart('from', $from);
    }
}

I agree that Magento guys should fix this in the _beforeLoad() method.

0

Why not use a usual filter?

$collection->addAttributeToFilter('store_id', $store_id);

store_id is given as a regular column in * _eav_entity table, so you can also filter by it. Worked for me.

0

Be my works this solution with core/app_emulation:

$storeId = 3;
$emulationModel = Mage::getModel('core/app_emulation');

// Emulate shop environment to disable using flat model and get collection for specific store
$emulationModel->startEnvironmentEmulation($storeId);
$products = Mage::getModel('catalog/product')->getCollection();

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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