Wydajne wywoływanie, filtrowanie i ładowanie kolekcji

15

W tej chwili ponownie używam wielu kolekcji zagnieżdżonych w pętlach foreach. Czy można przenieść te rzeczy o kilka poziomów wyżej? Obecnie jestem zmuszony ponownie ładować kolekcje, które mają ponad 51 tys. Podmiotów, co znacznie spowalnia. W szczególności kolekcje 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();
    ...
}

EDYCJA: to jest obecna funkcjonalność, którą wymyśliłem, wciąż uważam, że istnieje lepszy sposób na obsługę tych kolekcji.

easymoden00b
źródło
Jakiego rodzaju kolekcja jest przekazywana functionOne($collection)? W jakiej kolejności byłby rozmiar / liczba elementów? Czy konieczne jest zapętlenie go, aby uzyskać jednostki SKU?
7ochem
@ 7ochem Jest to niestandardowa kolekcja zbudowana z nowych danych o zapasach, które otrzymujemy z naszego systemu zarządzania zapasami. zawiera nazwę przedmiotu, ilość przedmiotu w ręku i SKU przedmiotu. Może potencjalnie zawierać ponad 60 000 elementów.
easymoden00b

Odpowiedzi:

9

Jest kilka rzeczy, nad którymi możesz popracować;

  • nieprzekazywanie przez referencję, więc używając dodatkowej pamięci, możesz przekazywać obiekty, ale tablice domyślnie nie mogą być przekazywane przez referencję. Lub dodaj &deklarację parametru funkcji, npfunction hello(array &$world)
  • nieprawidłowe kontrole, jeśli czegoś tam nie ma, natychmiast zwróć. Nie próbuj znaleźć czegoś, czego tam nie ma
  • czytelność może być czasami trudna
    • dodaj dokument (abyś zrozumiał, jeśli zobaczysz go za kilka dni, miesięcy, lat)
    • mądrzejsze ifstwierdzenia, aby uzyskać mniej wcięć
  • funkcje powinny mieć tylko jeden cel, aktualizować zasoby lub związane z nimi aktualizacje, a nie oba, więc może nawet wyciąć niektóre funkcje w jeszcze mniejszych funkcjach. Spróbuj stworzyć taką logikę w swoim umyśle i stamtąd przerób.
  • Przyjrzeć się ->cleanModelCache()->clearInstance()zMage_Core_Model_Model_Abstract wyczyścić dane źródłowe dla niektórych obiektów, może przyspieszyć rzeczy.
  • zgrubne wszystkie inne rzeczy, które już zostały powiedziane.

Dodano zaktualizowaną wersję twojego kodu z kilkoma wbudowanymi zaleceniami na temat twojego obecnego kodu, mógłbym przejść trochę dalej, ale obecnie nie dodałby więcej.

Funkcja 1: Celem jest chodzenie po kolekcji

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

        // ...
    }

Funkcja 2: Celem jest aktualizacja zapasów, jeśli zostaną zmienione

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

Funkcja 3: Cel aktualizacji powiązanych pozycji magazynowych

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

Funkcja 4: Musiałem zgadywać (lub pecha), na razie jest to funkcja bezużyteczna, można ją dodać, tak jak w Funkcji 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
źródło
Jesteś meżczyzną. Jestem względnie pewien, że to zadziała, a jeśli wykaże to wyraźną poprawę czasu przetwarzania, może być moim odniesieniem do manipulowania kolekcjami!
easymoden00b
Kilka drobnych błędów, ale jest znacznie lepiej skonstruowany niż mój własny.
easymoden00b
5

Chciałem dodać to jako komentarz, ale nie mam jeszcze wystarczającej liczby przedstawicieli. Zobacz, jak siatki podstawowe Magento łączą ilość produktów z katalogiem / kolekcją produktów tutaj: https://github.com/OpenMage/magento-mirror/blob/magento-1.9/app/code/core/Mage/Adminhtml /Block/Catalog/Product/Grid.php#L65

Jeśli dołączysz do tabeli, aby uzyskać ilość, nie musisz wywoływać tego w pętli: 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.
}

Inną alternatywą jest sprawdzenie, czy można buforować wyniki tego intensywnego systemu. Być może mógłbyś stworzyć drugą tabelę bazy danych do przechowywania wyników i sprawić, by odświeżyła się tak jak indeks magento.

Eric Seastrand
źródło
chcesz się podzielić, jak mogę wywołać te informacje w podobny sposób jak powyższy kod? Ponadto, jeśli wygeneruję kolekcje produktów i przypiszę je do zmiennej klasy, czy będę w stanie wywołać tę kolekcję w całym kodzie? Czy filtrowanie (jak pokazano w kodzie) wpłynęłoby na zmienną klasy, czy też pozostałoby niezmienione, jeśli przypiszę tę filtrowaną kolekcję do innej zmiennej?
easymoden00b
Dodałem przykład, jak dołączyć do pola, ale to tylko niewielka optymalizacja. Tak: Możesz zapisać wyniki jako zmienne klas i wywołać je gdzie indziej. Ale: Myślę, że będziesz musiał utworzyć nowy model za każdym razem, gdy chcesz zmienić filtrowanie (może to
uniemożliwić
Dziękuję, wygląda na to, czego się obawiałem. Dzięki za ten przykład optymalizacji. Jakieś inne, o których możesz pomyśleć? Wyjaśnię nieco zastosowania każdej kolekcji w powyższym przykładzie kodu.
easymoden00b
3

Nie musisz ciągle ładować modelu, Mage::getModel() wystarczy odniesienie, nie wiedząc, w jaki sposób skonfigurowane są modele zasobów, trudno powiedzieć, czy jest on ponownie inicjowany za każdym razem w pamięci i w tych pętlach kończy się wyciek / wyczerpanie pamięć powodująca możliwość wymiany dysku.

Jedna kolekcja, by rządzić nimi wszystkimi. Refaktoryzacja funkcji tak, aby odwoływała się tylko do jednej kolekcji. To samo dotyczy standardowego programowania SQL i proceduralnych. Poświęć nieco więcej czasu na sprawdzenie swoich kolekcji i modeli zasobów, w jaki sposób możesz uzyskać wszystkie potrzebne dane z SQL raz, może dwa razy, a następnie mieć wystarczającą ilość pamięci, a także odwoływać się do danych do zapętlenia w celu wyświetlenia / manipulacji. Łatwiej jest również zapisać jeden wynik w pamięci podręcznej niż wiele, to samo dotyczy wbudowanych mechanizmów buforowania MySQL, ponieważ częste żądania, które są wystarczająco duże, spowodują ten sam problem z wymianą dysku.

Zapisz I / O

Vinai ma dobry przykład wdrożenia tego samego podejścia:

Referencje :

B00MER
źródło