Эффективный сбор вызовов, фильтрация и загрузка

15

Сейчас я повторно использую множество коллекций, которые вложены в циклы foreach. Можно ли поднять эти вещи на несколько уровней? В настоящее время я вынужден многократно перезагружать коллекции, в которых есть 51 тыс. Объектов, что сильно замедляет работу. В частности, коллекции инвентаря.

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

РЕДАКТИРОВАТЬ: это текущая функциональность, я придумал, я все еще думаю, что есть лучший способ обрабатывать эти коллекции.

easymoden00b
источник
Какая коллекция передается functionOne($collection)? В каком порядке это будет размер / количество предметов? Нужно ли зацикливаться на нем, чтобы получить SKU?
7очм
@ 7ochem Это специальная коллекция, созданная на основе новых данных инвентаризации, которые мы получаем из нашей системы управления запасами. он содержит название предмета, количество предмета под рукой и номер предмета. Он может содержать более 60 тыс. Элементов.
easymoden00b

Ответы:

9

Есть несколько вещей, над которыми вы можете работать;

  • не передавая по ссылке, поэтому используя дополнительную память, вы можете передавать объекты, но по умолчанию массивы не могут передаваться по ссылке. Или добавьте &в объявление параметра функции, какfunction hello(array &$world)
  • недействительные проверки, если что-то не там, немедленно вернуться. Не пытайтесь найти то, чего там нет
  • удобочитаемость иногда бывает сложной
    • добавить документ (чтобы вы могли понять, если увидите его через несколько дней, месяцев, лет)
    • умные ifзаявления, чтобы получить меньше отступов
  • Функции должны иметь только одну цель - обновление запаса или связанное обновление, но не оба, поэтому, возможно, даже обрезать некоторые функции в еще меньших функциях. Попытайтесь создать такую ​​логику в уме и переделать оттуда.
  • Взгляните ->cleanModelCache()->clearInstance()с, Mage_Core_Model_Model_Abstractчтобы очистить базовые данные для некоторых объектов, может ускорить процесс.
  • о грубом всем остальном, что уже было сказано.

Добавил обновленную версию вашего кода с некоторыми встроенными рекомендациями по вашему текущему коду, я мог бы продолжить, но в настоящее время он не добавил бы к нему больше.

Функция 1: цель ходит по коллекции

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

        // ...
    }

Функция 2: цель обновления запаса, если изменилось

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

Функция 3: Цель обновления связанных товарных позиций

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

Функция 4: Нужно было сделать некоторые удачные (или неудачные) догадки, потому что теперь это бесполезная функция, может быть добавлена, как в функции 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();
        }        

        // ...

    }
}
Йерун
источник
Ты мужчина. Я относительно уверен, что это сработает, и, если это покажет явное улучшение во времени обработки, возможно, это мой справочник по работе с коллекциями!
easymoden00b
Несколько небольших ошибок, но это гораздо лучше, чем моя собственная.
easymoden00b
5

Я хотел добавить это в качестве комментария, но у меня еще недостаточно представителей. Посмотрите, как основные сетки Magento объединяют количество продуктов с каталогом / коллекцией продуктов здесь: https://github.com/OpenMage/magento-mirror/blob/magento-1.9/app/code/core/Mage/Adminhtml /Block/Catalog/Product/Grid.php#L65

Если вы присоединяетесь к таблице, чтобы получить кол-во, вам не нужно вызывать это в цикле: 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.
}

Другой вариант - посмотреть, сможете ли вы кешировать результаты этого интенсивного процесса. Возможно, вы могли бы создать вторую таблицу базы данных для хранения результатов и обновить ее, как это сделал бы индекс magento.

Эрик Систранд
источник
Хотите поделиться, как я мог тогда назвать эту информацию способом, подобным коду выше? Кроме того, если я сгенерирую коллекции продуктов и назначу их переменной класса, смогу ли я вызывать эту коллекцию по всему коду? Будет ли фильтрация (как показано в коде) влиять на переменную класса или она останется неизменной, если я назначу эту отфильтрованную коллекцию другой переменной?
easymoden00b
Я добавил пример того, как присоединиться к полю, но это только небольшая оптимизация. Да: вы можете сохранить результаты как переменные класса и вызвать их в другом месте. Но: я думаю, вам придется создавать новую модель каждый раз, когда вы захотите изменить фильтрацию (может победить цель).
Эрик Сеастранд,
Спасибо, похоже, как я боялся. Спасибо за этот пример оптимизации. Какие-нибудь другие, о которых ты можешь думать? Я немного поясню использование каждой коллекции в примере кода выше.
easymoden00b
3

Вам не нужно перезагружать модель снова и снова, Mage::getModel()достаточно справки, не зная, как настроены ваши модели ресурсов, трудно сказать, повторяется ли она каждый раз в памяти, и в этих циклах вы заканчиваете утечкой / исчерпанием Возможно, произошла перестановка памяти.

Одна коллекция, чтобы править ими всеми. Рефакторинг функций для ссылки только на одну коллекцию. Это то же самое со стандартным SQL и процедурным программированием. Потратьте немного больше времени на изучение своих коллекций и моделей ресурсов на предмет того, как вы можете получить все необходимые данные из SQL один раз, возможно, дважды, а затем иметь достаточно памяти, а также ссылки на данные для циклического отображения для отображения / манипуляции. Кроме того, проще хранить один результат в кэше, чем во многих, это то же самое относится и к встроенным механизмам кэширования MySQL, поскольку частые запросы, которые достаточно велики, вызовут ту же проблему подкачки диска.

Сохранить ввод / вывод

Vinai имеет хороший пример реализации того же подхода:

Рекомендации :

B00MER
источник