The question I propose actually has several parts so I will explain with examples and end with two questions who's answers should answer the topic question.

The file / method in question and instructions to repeat the tests.

app\code\core\Mage\Catalog\Model\Product\Type\Configurable.php (Line 494)

    public function getProductByAttributes($attributesInfo, $product = null)
  1. Create a custom attribute such as "Water Type" with the possible values of, "Clear Water, Opaque Water".
  2. Create a configurable product that uses the previously created attribute.
  3. Create two associated simple products, each having one of the possible values.
  4. Get the product id that you create for simplicity purposes and setup a test environment/extension however you please.

Now let's get some information for this product. I'm going to make some assumptions for testing purposes, such as the product being configurable.

//load up our created product.
$product = Mage::getModel('catalog/product')->load($ourProductId);

//get the attributes for the configurable product and dump them in the magento log.
$attributes = Mage::getModel('catalog/product_type_configurable')->getConfigurableAttributesAsArray($product);

//log the attributes for the product to the log so we can see what's available.
Mage::log(print_r($attributes,true));

//you should get a result similar to this
2014-02-25T21:21:25+00:00 DEBUG (7): Array
(
    [0] => Array
        (
            [id] => 204
            [label] => Water Type
            [use_default] => 0
            [position] => 0
            [values] => Array
                (
                    [0] => Array
                        (
                            [product_super_attribute_id] => 204
                            [value_index] => 165
                            [label] => Clear Water
                            [default_label] => Clear Water
                            [store_label] => Clear Water
                            [is_percent] => 0
                            [pricing_value] =>
                            [use_default_value] => 1
                        )
                    [1] => Array
                        (
                            [product_super_attribute_id] => 204
                            [value_index] => 166
                            [label] => Opaque Water
                            [default_label] => Opaque Water
                            [store_label] => Opaque Water
                            [is_percent] => 0
                            [pricing_value] =>
                            [use_default_value] => 1
                        )
                )
            [attribute_id] => 226
            [attribute_code] => water_type
            [frontend_label] => Water Type
            [store_label] => Water Type
        )
)

Now, the end result is that we want to get the associated product model by an array of attributes. Looking at the attributes that are available let's manually create an array for the sake of testing and pass it to getProductByAttributes() and see if we can do that.

We want to get the associated product that has the 'Opaque Water' attribute set, so we'll put that into our array and pass it on. According to the comments above the getProductByAttributes() method it requires an array with $attributeId => $attributeValue. This is what I will put into my first array to pass through based on the information I dumped into my log.

$filterAttributes = array(
    '226' => 'Opaque Water'
);

$associatedProduct = Mage::getModel('catalog/product_type_configurable')->getProductByAttributes($filterAttributes,$product);

//let's see if it worked
if ($associatedProduct !== NULL) 
{ 
    Mage::log('We found the ' . $associatedProduct->getTypeId() . ' associated product, yay');
} else {
    Mage::log('No product found, its null, boo');
} 

The result you should/will receive is sadly null. Let's do some logging on the method and try to figure out why it's null! Let's count the collection within the getProductByAttributes before it returns anything so that we're sure it's grabbing the associated products, it should return 2 products before the filtering is done.

Add this line of code inside of the getProductByAttributes method in this file, app\code\core\Mage\Catalog\Model\Product\Type\Configurable.php.

//add this directly after the 'getUsedProductCollection' call and before the attributes are added to filter by. This will give us the count of that collection before it filters it.
Mage::log('Product collection count: ' . $productCollection->count());

If done correctly, your log will show some magic just happened. Your product was found and the result from the previous code is no longer null. This brings me to the first question.

Question #1 Why does the count change the fact that the filter previously didn't work?

Now let's remove the $productCollection->count() and test again to verify you still get null. This should definitely be the case. Let's now change the $filterAttributes array to use the value_index instead of the name of the value.

//change from 'Opaque Water' to '165', the value_index from the attributes array we dumped.
$filterAttributes = array(
    '226' => '165'
);

Run it all again and get the outcome. It works fine, without needing to add in the $productCollection->count().

Question #2 Why does using value_index work without needing to force the collection to load prior to adding the filters?

Possible Answer / Guess

I have some theories of why, but I don't know if they are correct or not. I will provide them either way.

Theory #1 The attribute value name isn't available until the collection is loaded, which is why loading it with the count makes it work with the filter is applied. This doesn't make sense to me though because wouldn't the filter use those attributes during the select statements in the database and get the same result without needing for the collection to be force loaded?

Theory #2 Bad variable naming and comments. Whether or not it works after force loading the collection with count, the $attributeValue should be something other than what it implies such as value_index that works. This implies that what I passed first 'Opaque Water' is an actual value. I've also considered that it's not technically a value and that because we're dealing with a select, value_index is actually the value regardless of what it's called when you dump the attributes.

up vote 1 down vote accepted

We've been going back and forth on this for the better part of the afternoon on IRC and I think it just comes down to your calling the getProductByAttributes method incorrectly. It expects the following:

// 92 = EAV attribute_id from the eav_attribute table
// 17 = EAV value_id from the eav_attribute_option_value table
$attributes = [92 => 17];

/* @var $product Mage_Catalog_Model_Product */
$product = Mage::getModel('catalog/product')->load(3);

/* @var $simpleProduct Mage_Catalog_Model_Product_Type_Configurable */
$simpleProduct = Mage::getModel('catalog/product_type_configurable');

$result = $simpleProduct->getProductByAttributes($attributes, $product);
var_dump($result);

Doing this generates a query (regardless of weather you insert a ->load() statement inside the getProductByAttributes method to debug it) that looks like this...

SELECT `e`.*, `link_table`.`parent_id`, `at_color`.`value` AS `color`
FROM `catalog_product_entity` AS `e`
INNER JOIN `catalog_product_super_link` AS `link_table`
    ON link_table.product_id = e.entity_id
INNER JOIN `catalog_product_entity_int` AS `at_color`
    ON (`at_color`.`entity_id` = `e`.`entity_id`)
    AND (`at_color`.`attribute_id` = '92')
    AND (`at_color`.`store_id` = 0)
WHERE (link_table.parent_id = 3)
AND (at_color.value = '17')

When you structure your $filterAttributes array as you do, the query comes out like this (notice the last line)...

SELECT `e`.*, `link_table`.`parent_id`, `at_color`.`value` AS `color`
FROM `catalog_product_entity` AS `e`
INNER JOIN `catalog_product_super_link` AS `link_table`
    ON link_table.product_id = e.entity_id
INNER JOIN `catalog_product_entity_int` AS `at_color`
    ON (`at_color`.`entity_id` = `e`.`entity_id`)
    AND (`at_color`.`attribute_id` = '92')
    AND (`at_color`.`store_id` = 0)
WHERE (link_table.parent_id = 3)
AND (at_color.value = 'Opaque Water')

So, that's why it doesn't work initially. The question I think you (and I for a bit) is why it seems to work when you go into the getProductByAttributes method and do this...

if (is_array($attributesInfo) && !empty($attributesInfo)) {
    $productCollection = $this->getUsedProductCollection($product)->addAttributeToSelect('name');
    $productCollection->load(); // This here...
    foreach ($attributesInfo as $attributeId => $attributeValue) {
        $productCollection->addAttributeToFilter($attributeId, $attributeValue);
    }
    // ... more code follows
}

The reason is, I'm seeing is that by loading the collection before it has a chance to add those malformed attributes, it actually fully loads the child products associated with the parent configurable product... all of them... see, this is the query that gets executed when you call ->load() without any attribute filtering...

SELECT `e`.*, `link_table`.`parent_id`
FROM `catalog_product_entity` AS `e`
INNER JOIN `catalog_product_super_link` AS `link_table`
    ON link_table.product_id = e.entity_id
WHERE (link_table.parent_id = 3)

It's not returning to you a collection filtered by your $filterAttributes array... it's returning ALL constituent products of that configurable product.

Bottom line, reformat that $filterAttributes array!

  • Thanks for looking over this with me today. To clarify for myself, the first method of using "Opaque Water" in the $filterAttributes never worked. It only seemed to work because using count() or load() before the filter simply loaded all of the associated products and then grabbed the first one from the collection and returned it. – jesseconnr Feb 25 '14 at 23:35
  • Furthermore, value_index is the value_id in the eav_attribute_option_value table you mentioned, making that the correct value to use when building your array to filter by. – jesseconnr Feb 25 '14 at 23:38

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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