Appel, filtrage et chargement efficaces des collections

15

En ce moment, je réutilise un grand nombre de collections imbriquées dans des boucles foreach. Est-il possible de déplacer ces choses à plusieurs niveaux? Actuellement, je suis obligé de recharger des collections qui ont 51k + entités encore et encore, ce qui ralentit considérablement les choses. Plus précisément les collections kitinventory.

<?php
class Codespace_Module_Helper_Item extends other_one{

function functionOne($collection){
    ...
    $data = $collection->getData();
    foreach($data as $item){
        $this->_functionTwo($item);
    }
    ...
}

function _functionTwo($item){
    $model = Mage::getModel('catalog/product');
    $id = $model->getIdBySku($item['sku']);
    $inventoryStatus = Mage::getResourceSingleton('catalog/product')->getAttributeRawValue($id, 'product_inventory_status', 1);
    $invStatus = $model->getResource()->getAttribute('product_inventory_status')->getSource()->getOptionText($inventoryStatus);
    if ($invStatus && $id) {
        if ($invStatus !== 'Z') {
            $stockItem = Mage::getModel('cataloginventory/stock_item');
            $stockItem->setData(array());
            $stockItem->loadByProduct($id);
            if ($stockItem->getQty() != $item['quantity']) {
                $stockItem->setQty(item['quantity']);
                $stockItem->save();
                $this->functionThree($item['sku']);
            }
        }
    }
}

function functionThree($sku){
    $collectionOfKits = Mage::getModel('kitinventory/kitinventory')->getCollection()->addFieldToFilter('related_sku',$sku);
    if($collectionOfKits->getSize()){
        foreach($collectionOfKits as $kit){
            $kitSku = $kit->getSku();
            $kitCollection = Mage::getModel('kitinventory/kitinventory')->getCollection()->addFieldToFilter('kit_sku',$kitSku)->setOrder('related_sku','ASC');
            ...
            foreach($kitCollection as $component){
                $componentSkus[] = $component->getRelatedSku();
                $componentRequiredQuantity[] = $component->getRequiredQuantity();
            }
            $componentProductCollection = Mage::getModel('catalog/product')->getCollection();
            $componentProductCollection->joinField('qty',
                'cataloginventory/stock_item',
                'qty',
                'product_id=entity_id',
                '{{table}}.stock_id=1',
                'left');
            $componentProductCollection->addAttributeToFilter('sku', array('in' => $componentSkus));
            foreach($componentProductCollection as $component){
                $quantity = $component->getQty();
                ...
            }
            $kitId= Mage::getModel('catalog/product')->getIdBySku($kitSku)
            $kitStockItem = Mage::getModel('cataloginventory/stock_item')->loadByProduct($kitId);
            $this->functionFour($kitStockItem,$kitSku,$amountOfKitsPossible);
        }
    }
}

function functionFour($kitStockItem,$kitSku,$amountOfKitsPossible){
    ...
    $kitStockItem->setQty($quantity);
    $kitStockItem->save();
    ...
}

EDIT: c'est la fonctionnalité actuelle que j'ai proposée, je pense toujours qu'il existe une meilleure façon de gérer ces collections.

easymoden00b
la source
À quel type de collection est transmise functionOne($collection)? Dans quel ordre serait sa taille / nombre d'articles? Est-il nécessaire de boucler dessus pour obtenir les SKU?
7ochem
@ 7ochem C'est une collection personnalisée construite à partir de nouvelles données d'inventaire que nous obtenons de notre système de gestion des stocks. il contient le nom de l'article, la quantité de l'article en main et le sku de l'article. Il peut potentiellement contenir 60k + éléments.
easymoden00b

Réponses:

9

Il y a quelques choses sur lesquelles vous pouvez travailler;

  • ne passant pas par référence, donc en utilisant de la mémoire supplémentaire, vous pouvez passer des objets, mais les tableaux ne peuvent pas être passés par référence par défaut. Ou ajoutez un &dans la déclaration de paramètre de fonction commefunction hello(array &$world)
  • chèques invalides, si quelque chose n'est pas là, revenez immédiatement. N'essayez pas de trouver quelque chose qui n'est pas là
  • la lisibilité peut parfois être difficile
    • ajoutez un peu de doc (afin que vous puissiez comprendre si vous le voyez dans quelques jours, mois, années)
    • des ifdéclarations plus intelligentes pour obtenir moins d'indentation
  • Les fonctions ne devraient avoir qu'un seul objectif, mettre à jour le stock ou les mises à jour, pas les deux, donc peut-être même couper certaines fonctions en fonctions encore plus petites. Essayez de créer une telle logique dans votre esprit et retravaillez à partir de là.
  • Jetez un oeil à ->cleanModelCache()->clearInstance()partir Mage_Core_Model_Model_Abstractpour effacer les données sous - jacentes pour certains objets, peuvent accélérer les choses.
  • de grossier toutes les autres choses qui ont déjà été dites.

Ajout d'une version mise à jour de votre code avec quelques recommandations en ligne sur votre code actuel, je pourrais continuer un peu, mais il n'y ajouterait actuellement rien de plus.

Fonction 1: Le but est de parcourir la collection

    /**
     * Walk collection
     * 
     * @param Mage_Core_Model_Resource_Db_Collection_Abstract $collection
     * @return void
     */
    public function functionOne($collection)
    {
        // ...

        // Walk collection, create references instead of passing array data
        foreach ($collection as $item) {

            // Update stock for product
            if (!$this->_functionTwo($item)) {
                // Not updated, continue next
                continue;
            }

            // Update related products
            $this->_functionThree($item); // Use same object again, no extra memory is used
        }

        // ...
    }

Fonction 2: le but est de mettre à jour le stock en cas de changement

    /**
     * Update stock item if changed, returns true if updated
     * 
     * @param Mage_Core_Model_Model_Abstract $item
     * @return bool
     */
    function _functionTwo($item)
    {
        $model = Mage::getModel('catalog/product');
        /** @var $model Mage_Catalog_Model_Product */

        $id = $model->getIdBySku($item->getData('sku'));

        if (!$id) {
            // no id found, so stop looking nothing up
            return false;
        }

        // Get option value for store 1
        $inventoryStatus = $model->getResource()
                ->getAttributeRawValue($id, 'product_inventory_status', 1);

        if (!$inventoryStatus) {
            // No need for another lookup in db, because the status isn't set
            return false;
        }

        $invStatus = $model->getResource()
                ->getAttribute('product_inventory_status')
                ->setStoreId(0) // Get admin value
                ->getSource()
                ->getOptionText($inventoryStatus);

        if (!$invStatus) {
            // No need for another lookup in db, because the status has no text
            return false;
        }

        if ($invStatus === 'Z') {
            // Inventory status to not change something
            return false;
        }

        $stockItem = Mage::getModel('cataloginventory/stock_item');
        /** @var $stockItem Mage_CatalogInventory_Model_Stock_Item */

        // $stockItem->setData(array()); // unneeded piece of code
        $stockItem->loadByProduct($id);

        if ($stockItem->getQty() == $item->getData('quantity')) {
            // Valid stock
            return false;
        }

        // Update stock
        $stockItem->setQty($item->getData('quantity'));
        $stockItem->save();

        // End function and call function three separately, does something else
        return true;
    }

Fonction 3: Objectif de la mise à jour des articles en stock associés

    /**
     * Update related stock items, return false if no related items are found
     * 
     * @param Mage_Core_Model_Model_Abstract $item
     * @return bool
     */
    function functionThree($item)
    {

        $collectionOfKits = Mage::getModel('kitinventory/kitinventory')
                ->getCollection()
                ->addFieldToFilter('related_sku', $item->getData('sku')); // Check if your indexes are set on these columns

        if (!$collectionOfKits->getSize()) {
            // Nothing found to relate to
            return false;
        }

        $connection = Mage::getSingleton('core/resource')
                ->getConnection('core_write');

        // Walk kits
        foreach ($collectionOfKits as $kit) {

            // getData is slightly faster then getSku(unless you've implemented it in your model)
            // getSku -> __call('getSku') -> get -> lowercase('sku') -> getData('sku') | note, Magento has some internal caching in this 
            $kitSku = $kit->getData('sku');

            $kitCollection = Mage::getModel('kitinventory/kitinventory')
                    ->getCollection()
                    ->addFieldToFilter('kit_sku', $kitSku)
                    ->setOrder('related_sku', 'ASC');

            // Use just a fetchAll to create a fast db query
            $select = $kitCollection->getSelect();

            $select->reset(Zend_Db_Select::COLUMNS)
                    ->distinct()
                    ->columns('related_sku')
                    ->columns('required_quantity');

            // Fetch component sku
            $componentSkus = $connection->fetchAll($select, 0);

            // Fetch required quantity
            $componentRequiredQuantity = $connection->fetchCol($select, 1);

            // ...

            $componentProductCollection = Mage::getModel('catalog/product')
                    ->getCollection()
                    ->joinField('qty',
                    'cataloginventory/stock_item',
                    'qty',
                    'product_id = entity_id',
                    '{{table}}.stock_id = 1',
                    'left');
            $componentProductCollection->addAttributeToFilter('sku', array('in' => $componentSkus));

            // Next line will invoke a load on the product collection
            foreach ($componentProductCollection as $component) {
                $quantity = $component->getQty();

                // ...

            }
            // You could choose to do a fetchAll here instead to get just the data you need
            $connection = $componentProductCollection->getConnection();

            foreach ($connection->fetchAll($componentProductCollection->getSelect()) as $row) {
                // Will have a array here
                $quantity = $row['quantity'];

                // ... -- do not not which funky magic happens here
            }


            $kitId = Mage::getModel('catalog/product')
                    ->getIdBySku($kitSku);
            if (!$kitId) {
                // No id
                continue;
            }

            // You could also take a look if you can sum the stock and do a single update instead
            $kitStockItem = Mage::getModel('cataloginventory/stock_item')
                    ->loadByProduct($kitId);
            $this->functionFour($kitStockItem, $kitSku, $amountOfKitsPossible);

            // Or something like this, update single field
            $connection->update($kitStockItem->getResource()->getMainTable(), array('qty' => $quantity), 'item_id = ' . $kitStockItem->getId());
        }

        return true;
    }

Fonction 4: J'ai dû faire des suppositions chanceuses (ou malchanceuses), pour l'instant c'est une fonction inutile, pourrait être ajoutée telle quelle dans la fonction 3.

    /**
     * Save stock item if changed and something else, rather not say ;-)
     * 
     * @param Mage_Catalog_Inventory_Model_Stock_Item $kitStockItem
     * @param string $kitSku
     * @param int $amountOfKitsPossible Guessed it
     */
    function functionFour($kitStockItem, $kitSku, $amountOfKitsPossible)
    {

        // ...

        // Do not know the rest of the code, so I wouldn't know which I could optimize here
        // If it isn't to serious, you could look at a single query and not hitting extra functions

        // Check if changed
        if ($quantity !=$kitStockItem->getData('qty')) {
            $kitStockItem->setQty($quantity);
            $kitStockItem->save();
        }        

        // ...

    }
}
Jeroen
la source
Tu es l'homme. Je suis relativement certain que cela fonctionnera, et si cela montre une nette amélioration du temps de traitement, c'est peut-être ma référence pour manipuler les collections!
easymoden00b
Quelques petites erreurs, mais c'est beaucoup mieux construit que le mien.
easymoden00b
5

Je voulais ajouter ceci en tant que commentaire mais je n'ai pas encore assez de représentants. Jetez un œil à la façon dont les grilles de base Magento joignent la quantité de produits à la collection de produits / catalogue ici: https://github.com/OpenMage/magento-mirror/blob/magento-1.9/app/code/core/Mage/Adminhtml /Block/Catalog/Product/Grid.php#L65

Si vous rejoignez la table pour obtenir la quantité, vous n'avez pas besoin d'appeler cela en boucle: Mage::getModel('cataloginventory/stock_item')->loadByProduct($product)->getQty();

$productCollection = Mage::getModel('catalog/product')->getCollection();
$productCollection->joinField('qty',
    'cataloginventory/stock_item',
    'qty',
    'product_id=entity_id',
    '{{table}}.stock_id=1',
    'left');
$productCollection->addAttributeToFilter('sku',array('in' => $relatedSkus));
foreach($productCollection as $product){
    $quantity = $product->getQty();
    ...// now you have your qty without having to load the product model.
}

L'autre alternative consiste à voir si vous pouvez mettre en cache les résultats de ce processus intensif du système. Peut-être pourriez-vous créer une deuxième table de base de données pour stocker les résultats et l'actualiser comme le ferait un index magento.

Eric Seastrand
la source
voulez-vous partager comment je pourrais alors appeler ces informations d'une manière similaire au code ci-dessus? De plus, si je génère les collections de produits et les affecte à une variable de classe, serais-je capable d'appeler cette collection dans le code? Le filtrage (comme indiqué dans le code) affecterait-il la variable de classe ou resterait-il inchangé si j'attribue cette collection filtrée à une autre variable?
easymoden00b
J'ai ajouté un exemple de comment rejoindre le terrain, mais ce n'est qu'une petite optimisation. Oui: vous pouvez enregistrer les résultats en tant que variables de classe et les appeler ailleurs. Mais: je pense que vous devrez instancier un nouveau modèle chaque fois que vous voulez changer le filtrage (cela peut aller à l'encontre du but.)
Eric Seastrand
Merci, il apparaît comme je le craignais. Merci pour cet exemple d'optimisation. En pensez-vous d'autres? Je vais expliquer un peu les utilisations de chaque collection dans l'exemple de code ci-dessus.
easymoden00b
3

Vous n'avez pas besoin de recharger le modèle encore et encore, Mage::getModel()avec une référence suffit sans savoir comment vos modèles de ressources sont configurés, il est difficile de dire s'il est réinitialisé à chaque fois en mémoire et dans ces boucles, vous finissez par fuir / manquer de mémoire entraînant éventuellement un échange de disque.

Une collection pour les gouverner tous. Refactorisation des fonctions pour ne référencer que la seule collection. C'est la même chose avec la programmation SQL et procédurale standard. Prenez un peu plus de temps pour enquêter sur vos collections et modèles de ressources sur la façon dont vous pouvez obtenir toutes les données dont vous avez besoin à partir de SQL une fois, peut-être deux fois, puis avoir suffisamment de mémoire en place ainsi que référencer les données pour boucler pour l'affichage / la manipulation. Il est également plus facile de stocker un résultat dans le cache contre plusieurs, c'est également le cas pour les mécanismes de mise en cache intégrés de MySQL, car les demandes fréquentes qui sont suffisamment importantes entraîneront le même problème d'échange de disque.

Enregistrez les E / S

Vinai a un bon exemple de mise en œuvre de la même approche:

Les références :

B00MER
la source