Comment définir un identifiant de magasin sur Mage_Catalog_Model_Resource_Product_Collection?

34

La tâche est triviale. Je dois obtenir la liste des produits pour une vue de magasin particulière avec un catalogue à plat activé. La solution la plus évidente est la suivante:

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

En fait, setStore()méthode ne fait aucune différence ici car elle est appelée après la _initSelect()méthode Mage_Catalog_Model_Resource_Product_Collectionqui obtient le nom de la table à plat en fonction de l'ID de magasin. L'ID de magasin n'étant pas encore défini, il prend l'identifiant de magasin actuel.

La solution évidente serait donc de définir un ID de magasin actuel avant d’obtenir un modèle.

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

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

Ça va marcher. Mais seulement si vous devez obtenir une collection une fois. Si vous avez besoin d'une collection dans la boucle ou si vous avez juste besoin de deux collections consécutives, vous ne pourrez pas définir de magasin spécifique pour elles.

La raison en est que cette Mage_Catalog_Model_Resource_Product_Flatclasse a sa propre _storeIdpropriété et que, dans le constructeur, elle est définie sur l'ID de magasin actuel. C'est pourquoi il sera réglé pour la première fois. Ensuite, pour une raison quelconque (Dieu sait que j'espère qu'il y en a un), Mage_Eav_Model_Entity_Collection_Abstract::_initchaque module de ressource est récupéré en tant que singleton. Donc, pas de constructeur pour le 2e appel.

Tout cela a l'air tellement faux que je suis presque sûr que je me trompe et que ce n'est pas un autre bogue de Magento (ou deux). J'espère que quelqu'un pourra nous éclairer.

utilisateur487772
la source
Devez-vous utiliser getResourceModel () car cela vous donne l'instance? getModel ('catalog / resource_product_collection') pourrait tout simplement fonctionner.
Kristof à Fooman
Non, c'est absolument pareil. Il instancie le modèle de ressource singleton de quelque manière que ce soit.
user487772
Tim, ajoute-le comme réponse s'il te plaît!
Fabian Blechschmidt
@FabianBlechschmidt terminé.
user487772

Réponses:

13

De quelle version de Magento s'agit-il? Voici mes résultats pour Magento 1.9:

Catalogue plat activé:

Le catalogue plat est indexé:

Certaines données définies dans une vue de magasin spécifique:

Code utilisé:

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

Le résultat est comme prévu:

string(18) "But I Am Le French"

modifier:

Nevermind, le catalogue plat est spécifiquement interdit pour admin store:

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

Enquête ...

edit2:

On dirait que tu as raison. _initSelectest appelé avant que nous puissions modifier le storeId qui est utilisé pour générer le nom de la table.

Bien sûr (si nous ne voulons pas emprunter la voie de la réécriture), nous pouvons:

  • getSelect(), réinitialiser et définir un nouveau élément from ()
  • $collection->getEntity()->setStoreId(123)puis utiliser la réflexion pour appeler à _initSelectnouveau
  • Il suffit de créer notre propre modèle de ressources et d’en étendre une à plat, de donner un moyen d’insérer storeId au bon moment ( __constructen retardant _initSelect, etc.).
  • appel à setCurrentStorechaque fois que nous créons la collection.

Mais tous se sentent très mal en point ... Désolé, cela peut être une réponse peu satisfaisante :-(

edit3:

Donc, dans le but de fournir au moins une réponse:

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

S'il vous plaît, n'utilisez pas ça ;-)

Daniel Sloof
la source
Alors pensez-vous aussi que c'est un bug?
user487772
1
J'ai parcouru le code, mais product_collectionle constructeur accepte un modèle de ressource comme argument. Ainsi, si vous créez un Product_Resource_Flat, définissez son identifiant de magasin, le clonez et définissez un autre identifiant de magasin, puis transmettez-le au constructeur de la collection, est-ce faisable?
Melvyn
1
@ Tim: Désolé, je viens juste de voir votre commentaire. Oui je pense que c'est un bug.
Daniel Sloof
pour une bonne réponse, ça marche pour 1.14.2.0
user4531
10

Je considère donc qu'il s'agit de deux bogues dans Magento.

Le premier est le fait que vous ne pouvez pas définir d'ID de magasin lors de la catalog/productcollecte. Et le second est que vous ne pouvez absolument pas obtenir un modèle de ressource non singleton.

La solution de rechange si stupide consiste à instancier deux fois le modèle. La première fois que l'ID de magasin peut être défini et que la deuxième instanciation l'utilisera:

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

$collection = Mage::getResourceModel('catalog/product_collection')
utilisateur487772
la source
Je ne sais pas pourquoi mon magasin de jeux dans Mage :: getModel ('catalogue / catégorie') -> getProductCollection () -> setStoreId () n'a pas fonctionné et le vôtre a fonctionné. en passant merci
Nickool
3

Fait intéressant, la table plate utilisée est définie une fois et n’a jamais changé, ce qui fonctionne pour EAV puisque le nom de la table ne change pas, mais pas pour plate car le nom de la table inclut l’ID de magasin. Une solution de contournement consisterait à créer un assistant qui permuterait la table dans la partie FROM de la requête. Voici un exemple d'un tel assistant:

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

Ensuite, vous pouvez l'utiliser simplement avec:

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

J'imagine que cela ne poserait aucun problème pour le SQL puisque vous récupérez toutes les données d'une seule table plate, mais comme il s'agit d'un singleton, le dernier magasin utilisé sera utilisé partout ailleurs.

La solution alternative serait de faire un observateur sur catalog_product_collection_load_beforelequel fait quelque chose comme ceci:

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

Je suis d'accord que les gars de Magento devraient résoudre ce problème dans la _beforeLoad()méthode.

adioe3
la source
0

Pourquoi ne pas utiliser un filtre habituel?

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

store_id est donné sous forme de colonne normale dans la table * _eav_entity , vous pouvez donc également filtrer en fonction de celle-ci. Travaillé pour moi

Nakajuice
la source
0

Be my works cette solution avec 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();
tomik
la source