Large number of input variables in Magento

UPDATE: The issue described in the following text has been taken care of in Magento EE 1.14 / CE 1.9

Recently, a pretty strange issue occurred on one of our projects. Our client reported that when trying to assign products to a category, only a thousand of products got saved while the other ones were ignored. To deal with this issue, I decided to jump to the category save action and check for any problems with the code that may cause this behaviour.

Here’s how Magento assigns products to a category:

class Mage_Adminhtml_Catalog_CategoryController extends Mage_Adminhtml_Controller_Action
{
public function saveAction()
{
. . .
 
if (isset($data['category_products']) && !$category->getProductsReadonly()) {
$products = array();
parse_str($data['category_products'], $products);
$category->setPostedProducts($products);
}
. . .
}
}

 It takes a string generated by a serializer, passes it to the parse_str function which makes an array out of it and assigns the resulting array to a category. Nothing wrong with it, right?

Since examining the code didn’t give me any answers I decided to check the error log. Here’s what I found in it:

Warning: parse_str(): Input variables exceeded 1000. To increase the limit change max_input_vars in php.ini.

As you can see, the problem is being caused by php’s configuration parameter max_input_vars which limits the number of input variables that may be accepted. It was introduced in PHP version 5.3.9. in order to deal with denial of service attacks which use hash collisions. Parse_str function seems to take that parameter into account when generating array from string. Resulting array’s length is equal to the value of max_input_vars parameter which is by default set to 1000. This is why only first thousand products got saved and the rest were not.

To deal with this issue we can do two things. Increase the value of max_input_vars parameter or try to solve the problem programmatically.

  • Increase the max_input_vars value

Max_input_vars value can be changed in two ways, through .htaccess or php.ini. To change it through .htaccess add this line to your .htaccess file:

php_value max_input_vars 2000

and adjust the value to your needs.

Depending on your hosting  you may not have privileges to edit php.ini file. In that case just contact your host to increase the value for you. Otherwise, if you are allowed to edit php.ini or are working under local development environment, open it and locate the following line:

;max_input_vars = 1000

If this line is commented out, uncomment it and adjust the value to you needs, for example:

max_input_vars = 2000.


In order for the changes to take effect you need to restart the apache.

  • Programmatic solution

 Changing max_input_vars value is fast and simple solution which will probably solve your problems for quite some time. But if you add lots of products on a daily basis  and your category product count raises quickly, than you would always need to keep an eye on max_input_vars and adjust it’s value every now and then. To avoid this we can come up with the programmatic solution which will work no matter how many products we try to assign to one category. You can find one solution below:

require_once 'Mage/Adminhtml/controllers/Catalog/CategoryController.php';
class Inchoo_Smile_Adminhtml_Catalog_CategoryController extends Mage_Adminhtml_Catalog_CategoryController
{
public function saveAction()
{
...
 
if (isset($data['category_products']) && !$category->getProductsReadonly()) {
$products = array();
$exploded = explode('&', $data['category_products']);
 
foreach ($exploded as $row) {
$temp = array();
parse_str($row, $temp);
list($key, $value) = each($temp);
if(!empty($key)) {
$products[$key] = $value;
}
}
 
$category->setPostedProducts($products);
}
 
...
}
}

Instead of overriding the category controller we could also solve this problem with a more elegant solution. In the save action of Mage_Adminhtml_Catalog_CategoryController, right after product data is assigned to a category, Magento dispatches an event catalog_category_prepare_save. Using the observer we can hook to that event and change category products data like so:

class Inchoo_Smile_Model_Observer
{
// event: catalog_category_prepare_save
public function assignCategoryProducts($observer)
{
$category = $observer->getCategory();
$request = $observer->getRequest();
 
$products = array();
$exploded = explode('&', $request->getParam('category_products'));
 
foreach ($exploded as $row) {
$temp = array();
parse_str($row, $temp);
list($key, $value) = each($temp);
 
if(!empty($key)) {
$products[$key] = $value;
}
}
 
$category->setPostedProducts($products);
}
 
}

As you can see I didn’t go through the process of module creation or file rewrites since there are lots of articles out there covering the subject. If you decide to go with the one of the programmatic solutions make sure you create your own module and do the coding there. Editing core files directly is never a good practice, besides “Magento Boogieman” will get you if you do! 🙂

Happy coding!