Llamadas, filtrado y carga de colecciones eficientes

15

En este momento estoy reutilizando muchas colecciones que están anidadas dentro de los bucles foreach. ¿Es posible subir estas cosas algunos niveles? Actualmente me veo obligado a recargar colecciones que tienen más de 51k entidades una y otra vez, lo que ralentiza enormemente las cosas. Específicamente las colecciones 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();
    ...
}

EDITAR: esta es la funcionalidad actual que se me ocurrió, todavía creo que hay una mejor manera de manejar estas colecciones.

easymoden00b
fuente
¿A qué tipo de colección se pasa functionOne($collection)? ¿En qué orden sería el tamaño / número de artículos? ¿Es necesario recorrerlo para obtener los SKU?
7ochem
@ 7ochem Es una colección personalizada construida a partir de nuevos datos de inventario que obtenemos de nuestro sistema de gestión de inventario. contiene el nombre del artículo, la cantidad del artículo disponible y el número de artículo. Potencialmente puede contener más de 60k elementos.
easymoden00b

Respuestas:

9

Hay algunas cosas en las que puedes trabajar;

  • no pasa por referencia, por lo que con memoria adicional, puede pasar objetos, pero las matrices no se pueden pasar por referencia de forma predeterminada. O agregue un &en la declaración de parámetro de función comofunction hello(array &$world)
  • comprobaciones no válidas, si algo no está allí, devuelva inmediatamente No intentes encontrar algo que no esté allí.
  • la legibilidad puede ser difícil a veces
    • agregue un documento (para que pueda entender si lo ve en unos días, meses, años)
    • ifdeclaraciones más inteligentes para obtener menos sangría
  • Las funciones deben tener un solo propósito, actualizar el stock o actualizar, no ambos, por lo que tal vez incluso cortar algunas funciones en funciones aún más pequeñas. Intenta crear esa lógica en tu mente y vuelve a trabajar desde allí.
  • Eche un vistazo ->cleanModelCache()->clearInstance()desde Mage_Core_Model_Model_Abstractpara borrar los datos subyacentes de algunos objetos, puede acelerar las cosas.
  • de grosero todas las demás cosas que ya se han dicho.

Agregué una versión actualizada de su código con algunas recomendaciones en línea sobre su código actual, podría continuar un poco, pero actualmente no agregaría más.

Función 1: el propósito es caminar por la colección

    /**
     * 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
        }

        // ...
    }

Función 2: el propósito es actualizar el stock si se modifica

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

Función 3: Propósito actualizar artículos de stock relacionados

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

Función 4: Tuve que hacer algunas conjeturas afortunadas (o desafortunadas), por ahora es una función inútil, podría agregarse como está en la Función 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
fuente
Tu eres el hombre. Estoy relativamente seguro de que esto funcionará, y si esto muestra una clara mejora en el tiempo de procesamiento, ¡puede ser mi referencia para manipular colecciones!
easymoden00b
Algunos pequeños errores, pero está mucho mejor construido que el mío.
easymoden00b
5

Quería agregar esto como un comentario, pero aún no tengo suficiente representante. Eche un vistazo a cómo las cuadrículas principales de Magento unen la cantidad de productos al catálogo / colección de productos aquí: https://github.com/OpenMage/magento-mirror/blob/magento-1.9/app/code/core/Mage/Adminhtml /Block/Catalog/Product/Grid.php#L65

Si te unes a la tabla para obtener la cantidad, no tienes que llamar a esto en un bucle: 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.
}

La otra alternativa es ver si puede almacenar en caché los resultados de este proceso intensivo del sistema. Tal vez podría hacer una segunda tabla de base de datos para almacenar los resultados y hacer que se actualice como lo haría un índice de magento.

Eric Seastrand
fuente
¿me gustaría compartir cómo podría llamar a esta información de manera similar al código anterior? Además, si genero las colecciones de productos y las asigno a una variable de clase, ¿podría llamar a esa colección en todo el código? ¿El filtrado (como se muestra en el código) afectaría la variable de clase o permanecería sin cambios si asigno esta colección filtrada a otra variable?
easymoden00b
Agregué un ejemplo de cómo unirse al campo, pero esto es solo una pequeña optimización. Sí: puede guardar los resultados como variables de clase y llamarlos en otro lugar. Pero: creo que tendrá que crear una instancia de un nuevo modelo cada vez que desee cambiar el filtrado (puede que no
cumpla
Gracias, parece que he temido. Gracias por este ejemplo de optimización. ¿Alguna otra que se te ocurra? Explicaré un poco los usos de cada colección en el ejemplo de código anterior.
easymoden00b
3

No es necesario volver a cargar el modelo una y otra vez, Mage::getModel()con una referencia es suficiente sin saber cómo están configurados sus modelos de recursos, es difícil decir si se reinicia cada vez en la memoria y en esos bucles termina goteando / quedando sin memoria causando el intercambio de disco posiblemente.

Una colección para gobernarlos a todos. Refactorizando las funciones para hacer referencia solo a una colección. Esto es lo mismo con SQL estándar y programación de procedimientos. Dedique un poco más de tiempo a investigar sus colecciones y modelos de recursos sobre cómo puede obtener todos los datos que necesita de SQL una vez, tal vez dos veces y luego tener suficiente memoria en su lugar, así como hacer referencia a los datos para realizar un bucle para su visualización / manipulación. También es más fácil almacenar un resultado en caché frente a muchos, este es el mismo caso para los mecanismos de almacenamiento en caché integrados de MySQL también, ya que las solicitudes frecuentes que son lo suficientemente grandes causarán el mismo problema de intercambio de disco.

Guarde la E / S

Vinai tiene un buen ejemplo de implementación del mismo enfoque:

referencias :

B00MER
fuente