Filtered Collection – How to Select the First Item Correctly

categorycollection;

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

Best Answer

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();