6

I'm trying to write a script to get a category given its path, which seems like it should be easy to do. As a test, I have a base category with an ID of 2, and it has 2 children, 'Foo' and 'Bar'. I'm using this code to try to get the 'Foo' category:

// Get the 'base' category.
$category = Mage::getModel('catalog/category')->load(2);
// Try to filter it out so that I only get 'Foo'
$category = $category->getChildrenCategories()->addFieldToFilter('name', 'Foo');

At this point, calling print_r($category->getData()); shows that it should be doing what I want:

Array
(
    [0] => Array
        (
            [entity_id] => 16
            [entity_type_id] => 3
            [attribute_set_id] => 0
            [parent_id] => 2
            [created_at] => 2016-06-25 22:47:23
            [updated_at] => 2016-06-25 22:47:23
            [path] => 1/2/16
            [position] => 2
            [level] => 2
            [children_count] => 28
            [request_path] =>
            [is_active] => 1
            [name] => Foo
        )

)

There is 1 item in the collection, and it's the one I'm looking for. However, then I do:

$category = $category->getFirstItem();
print_r($category->getData());

I expect it to select the 'Foo' category, but it's selecting the 'Bar' category instead:

Array
(
    [entity_id] => 5
    [entity_type_id] => 3
    [attribute_set_id] => 0
    [parent_id] => 2
    [created_at] => 2016-06-25 22:42:19
    [updated_at] => 2016-06-25 22:42:19
    [path] => 1/2/5
    [position] => 1
    [level] => 2
    [children_count] => 87
    [request_path] => 
    [is_active] => 1
    [name] => Bar
    [url_key] => bar
    [is_anchor] => 1
)

I'm assuming at this point that getFirstItem() just gets the first item in the whole collection and ignores the filters, but I can't seem to find any documentation for it (although it's used in just about every example I've found). This method doesn't seem to exist in the collection class.

Am I missing a step somewhere? Where can I find the documentation for this method?

Update: I forgot to mention, but this is in Magento 1.9

8

getFirstItem() is defined in Varien_Data_Collection which is the data source independent base class for all collections:

/**
 * Retrieve collection first item
 *
 * @return Varien_Object
 */
public function getFirstItem()
{
    $this->load();

    if (count($this->_items)) {
        reset($this->_items);
        return current($this->_items);
    }

    return new $this->_itemObjectClass();
}

It does what you think it does, loads the collection (if not already loaded) and returns the first item. getFirstItem() is not your problem.


Although you should always call setPageSize(1) before getFirstItem(), if you only need the first item, otherwise all items that match the filters are loaded which can easily hurt performance.


The problem is that getChildrenCategories() immediately loads the children collection, so filters that you add afterwards do not have any effect.

Now there is one additional trap, that you fell into: getData() on collections works different from other methods. It does not check if the collection is loaded, instead it checks if the _data property is empty. When you load a collection, _data is not populated, only when you call getData()!

public function getData()
{
    if ($this->_data === null) {


        $this->_renderFilters()
             ->_renderOrders()
             ->_renderLimit();
        /**
         * Prepare select for execute
         * @var string $query
         */
        $query       = $this->_prepareSelect($this->getSelect());
        $this->_data = $this->_fetchAll($query, $this->_bindParams);
        $this->_afterLoadData();
    }
    return $this->_data;
}

This results in a second database query as soon as you call getData() and this time, your new filter is applied. But it does not change the _items property, so your previously loaded result is still used for everything else.

By the way, I still did not come across any reasonable use case for this method. It's used internally by load() but probably should not have been public. The core doesn't use it from outside the collection class anywhere as far as I see.

Solution

Clear the collection (which resets _items and marks it as not loaded):

$category->getChildrenCategories()->clear()->setPageSize(1)->getFirstItem();
  • 2
    "you should always call setPageSize(1) before, if you only need the first item" <-- That should highlighted, emphasized, and bolded. I have seen, more than once, entire product catalogs loaded in memory because this was neglected. – Kevin Schroeder Jun 28 '16 at 12:14
  • @KevinSchroeder you're right, I highlighted it a little. – Fabian Schmengler Jun 28 '16 at 12:25
  • 1
    Needs more cowbell – Kevin Schroeder Jun 28 '16 at 20:39
  • Absolutely effing ridiculous. – Spencer Williams Feb 5 '17 at 1:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.