Как установить идентификатор магазина на Mage_Catalog_Model_Resource_Product_Collection?

34

Задача тривиальная. Мне нужно получить список товаров для конкретного магазина с включенным плоским каталогом. Наиболее очевидным решением является следующее:

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

На самом деле setStore()метод здесь не имеет никакого значения, потому что он вызывается после того, как _initSelect()метод Mage_Catalog_Model_Resource_Product_Collectionполучает имя плоской таблицы на основе идентификатора магазина. Поскольку идентификатор магазина еще не установлен, он принимает текущий идентификатор магазина.

Таким образом, очевидным обходным путем будет установка текущего идентификатора магазина перед получением модели.

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

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

Это сработает. Но только если вам нужно получить коллекцию один раз. Если вам нужно получить коллекцию в цикле или вам просто нужны две коллекции подряд, вы не сможете установить для них конкретное хранилище.

Причина в том, что Mage_Catalog_Model_Resource_Product_Flatкласс имеет свое собственное _storeIdсвойство и в конструкторе ему присваивается идентификатор текущего хранилища. Вот почему он будет установлен в первый раз. Затем по какой-то причине (небеса знают, я надеюсь, что таковой имеется) в Mage_Eav_Model_Entity_Collection_Abstract::_initкаждом ресурсном модуле извлекается как одиночка. Так что нет конструктора для 2-го вызова.

Все это выглядит так неправильно, что я почти уверен, что ошибаюсь, и это не очередной баг Magento (или два). Надеюсь, что кто-то может пролить свет на это.

user487772
источник
Вы должны использовать getResourceModel (), поскольку это дает вам экземпляр? getModel ('catalog / resource_product_collection') может просто работать.
Кристоф на Фуман
Нет, это абсолютно то же самое. Это создание модели ресурса синглтона в любом случае.
user487772
Тим, добавь это как ответ пожалуйста!
Фабиан Блехшмидт
@FabianBlechschmidt сделано.
user487772

Ответы:

13

Какая версия Magento это? Вот мои результаты для Magento 1.9:

Плоский каталог включен:

Плоский каталог проиндексирован:

Некоторый набор данных в определенном представлении магазина:

Используемый код:

<?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());

Результат, как и ожидалось:

string(18) "But I Am Le French"

редактировать:

Неважно, плоский каталог специально запрещен для администратора магазина:

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

Исследуя ...

edit2:

Похоже, ты прав. _initSelectвызывается до того, как мы сможем изменить storeId, который используется для генерации имени таблицы.

Конечно (если мы не хотим идти по пути переписывания), мы можем:

  • getSelect(), сделать сброс и установить новый из ()
  • $collection->getEntity()->setStoreId(123)а затем использовать отражение, чтобы позвонить _initSelectснова
  • Просто создайте нашу собственную модель ресурсов и продолжайте с простого, дайте некоторый способ вставки storeId в нужное время (с __constructзадержкой _initSelectи т. Д.).
  • звоните setCurrentStoreкаждый раз, когда мы создаем коллекцию.

Но все они чувствуют себя очень нахально ... Извините, это может быть неудовлетворительным ответом :-(

Edit3:

Таким образом , для обеспечения , по меньшей мере , в ответ:

// 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('*');

Пожалуйста, не используйте это ;-)

Дэниел Слооф
источник
Так вы тоже думаете, что это ошибка?
user487772
1
Я пролистал код, но product_collectionконструктор принимает модель ресурса в качестве аргумента. Так что если вы создадите Product_Resource_Flat, зададите идентификатор хранилища, клонируете его и зададите другой идентификатор хранилища, а затем передадите его конструктору коллекции, это будет выполнимо?
Мелвин
1
@Tim: Извините, только что увидел ваш комментарий. Да, я думаю, что это ошибка.
Даниэль Слооф
за отличный ответ, он работает для 1.14.2.0
user4531
10

Поэтому я считаю, что это две ошибки в Magento.

Во-первых, вы не можете установить идентификатор магазина в catalog/productколлекции. И второе: вы абсолютно не можете получить модель ресурсов как не-синглтон.

Так что глупый обходной путь - дважды создавать экземпляр модели. Первый раз, когда идентификатор магазина может быть установлен, и второй экземпляр будет использовать его:

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

$collection = Mage::getResourceModel('catalog/product_collection')
user487772
источник
Я не знаю, почему мой набор хранилищ в Mage :: getModel ('catalog / category') -> getProductCollection () -> setStoreId () не работал, а ваш работал. кстати спасибо
Nickool
3

Интересно, что используемая плоская таблица устанавливается один раз и никогда не изменяется, что работает для EAV, поскольку имя таблицы не меняется, но не для плоской, поскольку имя таблицы включает в себя идентификатор магазина. Обходной путь может заключаться в создании помощника, который будет заменять таблицу в части запроса FROM. Вот пример такого помощника:

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;
    }
}

Тогда вы можете использовать его просто с:

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

Я предполагаю, что это не вызовет проблем для SQL, поскольку вы извлекаете все данные из одной плоской таблицы, но, поскольку это одноэлементное хранилище, последнее использованное хранилище будет использоваться везде.

Альтернативное решение было бы сделать наблюдателя, catalog_product_collection_load_beforeкоторый делает что-то вроде этого:

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);
    }
}

Я согласен, что ребята из Magento должны исправить это в _beforeLoad()методе.

adioe3
источник
0

Почему бы не использовать обычный фильтр?

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

store_id указан как обычный столбец в таблице * _eav_entity , так что вы также можете фильтровать по нему. Работал на меня.

nakajuice
источник
0

Будь моим решением это решение с 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();
томик
источник