Esta semana me deparei com um problema aparentemente simples: precisava fazer um script que fazia um loop em todos os produtos de uma loja para remover algumas imagens duplicadas.
O problema é que a collection de produtos possuía 100.000 registros, mesmo após vários filtros. Ao fazer o ->count() da collection no Magento, me deparei com estouro de memória (Exhausted X bytes in ..), antes mesmo de chegar no usual foreach.
$products = Mage::getModel('catalog/product')->getCollection(); $total = $products->count();
O Magento carrega a collection ao acionarmos oo método count(), jogando seus valores em array, ou seja, colocando em memória como podemos ver abaixo:
//Varien_Data_Collection::count public function count() { $this->load(); return count($this->_items); }
O problema do count() é conhecido e de simples solução. Basta usar o método getSize() em seu lugar.
$total = $products->getSize();
O getSize() é bem menos custoso, pois invocará uma chamada apenas com o count de registros. Veja:
//Varien_Data_Collection_Db::getSize public function getSize() { if (is_null($this->_totalRecords)) { $sql = $this->getSelectCountSql(); $this->_totalRecords = $this->getConnection()->fetchOne($sql, $this->_bindParams); } return intval($this->_totalRecords); }
Problema resolvido né? Não. Ainda temos o foreach.
foreach($products as $product) { //F...! }
Ao chamar o foreach a coleção é carregada no load() novamente em memória, estourando novamente o erro classico de memória do PHP.
A solução foi utilizar o Resource Iterator do Magento. Ele fará a leitura de cada linha do meu SQL chamando um método de callback.
Funcionou mais ou menos assim:
Mage::getSingleton('core/resource_iterator') ->walk($products->getSelect(), array('meuMetodo') ); function meuMetodo($args){ Zend_Debug::dump($args, 'Detalhes da linha atual'); }
Desta forma os 100 mil registros da minha collection não são carregados de uma vez na memória, e consigo fazer o loop normalmente, registro por registro, sem me preocupar (muito) com a quantidade de registros.
Em resumo, aprendemos que:
- É melhor getSize() do que count()
- É melhor Mage::getSingleton(‘core/resource_iterator’) do que foreach
Ahh se eu soubesse disso antes…
Ninguém comentou sobre isso ainda. Seja o primeiro.