Magento Attributes - Fresh View
Upcoming SlideShare
Loading in...5
×

Like this? Share it with your network

Share
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
2,737
On Slideshare
2,729
From Embeds
8
Number of Embeds
2

Actions

Shares
Downloads
34
Comments
0
Likes
4

Embeds 8

https://twitter.com 6
http://www.linkedin.com 2

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Magento attributes Fresh view Monday, October 14, 13
  • 2. Alex Gotgelf Senior Software Developer at Eltrino Contacts: alex@eltrino.com ramzes3988 http://www.linkedin.com/in/gotgelf Monday, October 14, 13
  • 3. Lecture Overview ‣ Magento Attributes EAV concepts ‣ Not trivial problems and solutions Monday, October 14, 13
  • 4. EAV History EAV has quite a long history. Some of its earliest applications were storage systems for clinical data back in 1970s. As a storage method EAV borrows from early object-oriented languages. SIMULA 67 is cited as one such influence. Functional languages such as LISP are also known to have contributed to the development of EAV. They contain storage structures that record object information in attribute-value pairs – a principle fundamental to EAV. Monday, October 14, 13
  • 5. EAV conception ‣ E - Entity ‣ A - Attribute ‣ V - Value Monday, October 14, 13
  • 6. EAV conception Eav becomes especially useful when the following conditions are presented: ‣ Entity attributes vary significantly in terms of data type ‣ The number of possible entity types is large and entity types have individual sets of attributes Monday, October 14, 13
  • 7. Advantages of EAV ‣ Scalability - you can change your data without changing the structure of db ‣ Flexible mechanism to work with attributes associated to entity Monday, October 14, 13
  • 8. Weakness of EAV ‣ Performance (complex join structures etc...) Monday, October 14, 13
  • 9. Attributes Bottlenecks in Magento Monday, October 14, 13
  • 10. Problem #1 Problem #1 Product Select Attribute with Large Options Set (several thousands of options) at the admin side Monday, October 14, 13
  • 11. Problem #1 Let’s create select attribute with 5 options PHP $installer = $this; $installer->addAttribute(Mage_Catalog_Model_Product::ENTITY, 'test_select', array( 'group' => 'General', 'type' => 'int', 'input' => 'select', 'label' => 'Test Select', 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL, 'required' => true, )); $attributeId = $installer ->getAttributeId(Mage_Catalog_Model_Product::ENTITY, 'test_select'); for ($i = 1; $i <= 5; $i++) { $data[] = 'Opt' . $i; } $option = array ( 'attribute_id' => $attributeId, 'values' => $data ); $this->addAttributeOption($option); $installer->endSetup(); Monday, October 14, 13
  • 12. Problem #1 • • Monday, October 14, 13 Usability Performance
  • 13. Problem #1 Let’s create select attribute with 10000 options PHP $installer = $this; $installer->addAttribute(Mage_Catalog_Model_Product::ENTITY, 'test_select', array( 'group' => 'General', 'type' => 'int', 'input' => 'select', 'label' => 'Test Select', 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL, 'required' => true, )); $attributeId = $installer ->getAttributeId(Mage_Catalog_Model_Product::ENTITY, 'test_select'); for ($i = 1; $i <= 10000; $i++) { $data[] = 'Opt' . $i; } $option = array ( 'attribute_id' => $attributeId, 'values' => $data ); $this->addAttributeOption($option); $installer->endSetup(); Monday, October 14, 13
  • 14. Problem #1 Problem #1 • • Monday, October 14, 13 Usability Performance
  • 15. Problem #1 Let’s create select attribute with 35000 options PHP $installer = $this; $installer->addAttribute(Mage_Catalog_Model_Product::ENTITY, 'test_select', array( 'group' => 'General', 'type' => 'int', 'input' => 'select', 'label' => 'Test Select', 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL, 'required' => true, )); $attributeId = $installer ->getAttributeId(Mage_Catalog_Model_Product::ENTITY, 'test_select'); for ($i = 1; $i <= 35000; $i++) { $data[] = 'Opt' . $i; } $option = array ( 'attribute_id' => $attributeId, 'values' => $data ); $this->addAttributeOption($option); $installer->endSetup(); Monday, October 14, 13
  • 16. Problem #1 Problem #1 memory_limit: 1024M • • Usability Performance Monday, October 14, 13 Fatal error : Allowed memory size of 268435456 bytes exhausted (tried to allocate 32 bytes
  • 17. Problem #1 As Result Monday, October 14, 13
  • 18. Problem #1 Solution for Problem #1- AutoComplete Monday, October 14, 13
  • 19. Problem #1 Problem #1 - Solution y based 2 - jquer Select ent for e placem r Select Boxes Monday, October 14, 13
  • 20. Problem #1 Problem #1 - Solution http://ivaynberg.github.io/select2/ Monday, October 14, 13
  • 21. Problem #1 Select2 Advantages ‣ ‣ Enhancing native selects with search ‣ Nesting opt-groups: native selects only support one level of nested. Select2 does not have this restriction. ‣ ‣ Tagging: ability to add new items on the fly. ‣ etc ... Loading data from JavaScript: easily load items via ajax and have them searchable. Working with large, remote datasets: ability to partially load a dataset based on the search term. Monday, October 14, 13
  • 22. Problem #1 Magento Renderers Varien_Data_Form_Element ‣ ‣ ‣ ‣ ‣ ‣ Monday, October 14, 13 Button Checkbox Select Text Radio ...
  • 23. Problem #1 Step 1 - create test attribute with custom renderer PHP <?php $installer = $this; $installer->addAttribute(Mage_Catalog_Model_Product::ENTITY, 'test_select', array( 'group' => 'General', 'type' => 'int', 'input' => 'select2', 'label' => 'Test Select', 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL, 'required' => true, 'input_renderer' => 'examples_customselect/renderer_select' )); custom renderer Monday, October 14, 13
  • 24. Problem #1 Step 2 - create layout with select2 js and css files XML <reference name="head"> <action method="addItem"><type>js_css</type> <name>jquery/select2/select2.css</name><params/> </action> <action method="addJs"> <file>jquery/jquery-1.9.min.js</file> <params><![CDATA[name="js_001_first"]]></params> </action> <action method="addJs"> <file>jquery/select2/select2.min.js</file> <params><![CDATA[name="js_002_second"]]></params> </action> </reference> Monday, October 14, 13
  • 25. Problem #1 Step 3 - create custom renderer block PHP public function getElementHtml() { $html = ' <p> <input type="hidden" id = "test_attribute" name = "product[test_attribute]" value = ' . $this->getValue() . ' data-placeholder="-- Please select --" style="width:300px"/> </p>'; $defaultOptionText = $this->getDefaultOptionText(); ... Monday, October 14, 13
  • 26. Problem #1 Step 3 - create custom renderer block PHP ... $js = <<<EOF <script type="text/javascript"> jQuery.noConflict()(document).ready(function() { jQuery.noConflict()('#test_attribute').select2({ minimumInputLength: 4, placeholder: 'Search', ajax: { url: '{$this->getLink()}', dataType: 'json', data: function(term, page) { return { search: term, attribute_code: '{$this->getId()}' }; }, results: function (data, page) { return { results: data }; } }, ... Monday, October 14, 13
  • 27. Problem #1 Step 3 - create custom renderer block PHP ... initSelection : function (element, callback) { var data = {id: '{$this->getValue()}', text: '{$defaultOptionText}'}; callback(data); } }); }); </script> EOF; $html .= $js; return $html; } Monday, October 14, 13
  • 28. Problem #1 Step 4 - create admin controller PHP public function testAction() { if (!$this->getRequest()->isAjax()) { $this->_forward('noRoute'); return; } $search = $this->getRequest()->getParam('search'); $attributeCode = $this->getRequest()->getParam('attribute_code'); $attribute = Mage::getModel('eav/entity_attribute')-> loadByCode(Mage_Catalog_Model_Product::ENTITY, $attributeCode); $options = Mage::getModel('examples_customselect/select')-> getAttributesLikeSearch($attribute->getId(), $search); $this->getResponse()->setBody(Mage::helper('core')-> jsonEncode($options)); } Monday, October 14, 13
  • 29. Problem #1 Step 5 - create model and resource model PHP loading array data using ajax ... $select = $this->_getReadAdapter()->select()->from(array('a' => $this>getTable('eav/attribute_option_value'))) ->joinLeft( array('b' => $this->getTable('eav/attribute_option')), 'a.option_id = b.option_id', array() ) ->where('a.store_id = ?', $store->getId()) ->where('b.attribute_id = ?', $attributeId) ->where('a.value LIKE ?', $search .'%'); ... Monday, October 14, 13
  • 30. Problem #1 Problem #1 - Solution https://github.com/gotgelf/ magento-examples Customselect Module Monday, October 14, 13
  • 31. Problem #2 Problem #2 Saving Attribute via admin side with a Large Number of Options Monday, October 14, 13
  • 32. Problem #2 Monday, October 14, 13
  • 33. Problem #2 ... Monday, October 14, 13
  • 34. Problem #2 Problem #2 • Change Opt1, change Opt300 • Click “Save Attribute” button • As result Opt1 will be changed and Opt300 will not Monday, October 14, 13
  • 35. Problem #2 Problem #2 Problem: POST request is truncated, so we need to increase it’s size Solution: increase max_input_vars (since PHP 5.3.9 ) variable, by default it’s 1000 Monday, October 14, 13
  • 36. Problem #3 Problem #3 Product Select Attribute with Large Options Set (several thousands of options) at the Frontend Monday, October 14, 13
  • 37. Problem #3 Let’s create select attribute with 10000 options $installer = $this; $installer->addAttribute(Mage_Catalog_Model_Product::ENTITY, 'frontend_select', array( 'group' => 'General', 'type' => 'varchar', 'input' => 'int', 'label' => 'Frontend Select', 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL, 'required' => true, 'visible_on_front' => true, )); } important ! $attributeId = $installer ->getAttributeId(Mage_Catalog_Model_Product::ENTITY, 'frontend_select'); for ($i = 1; $i <= 10000; $i++) { $data[] = 'Opt' . $i; } $option = array ( 'attribute_id' => $attributeId, 'values' => $data ); $this->addAttributeOption($option); $installer->endSetup(); Monday, October 14, 13
  • 38. Problem #3 Problem #3 • Crete test product and assign to it some option • Go to Frontend Monday, October 14, 13
  • 39. Problem #3 Problem #3 Option load takes about 0.9 sec !!! Monday, October 14, 13
  • 40. Problem #3 Problem #3 Mage_Catalog_Block_Product_ View_Attribtues Mage_Eav_ModelEntity_Attribute _Frontend_Abstract Mage_Eav_ModelEntity_Attribute _Source_Table getAdditionalData() getValue() getOption() getOptionText() getAllOptions() Monday, October 14, 13
  • 41. Problem #3 We load all options to retrieve selected option text !!! PHP public function getAllOptions($withEmpty = true, $defaultValues = false) { $storeId = $this->getAttribute()->getStoreId(); if (!is_array($this->_options)) { $this->_options = array(); } if (!is_array($this->_optionsDefault)) { $this->_optionsDefault = array(); } if (!isset($this->_options[$storeId])) { $collection = Mage::getResourceModel('eav/ entity_attribute_option_collection') ->setPositionOrder('asc') ->setAttributeFilter($this->getAttribute()->getId()) ->setStoreFilter($this->getAttribute()->getStoreId()) ->load(); $this->_options[$storeId] = $collection->toOptionArray(); $this->_optionsDefault[$storeId] = $collection>toOptionArray('default_value'); } $options = ($defaultValues ? $this->_optionsDefault[$storeId] : $this>_options[$storeId]); if ($withEmpty) { array_unshift($options, array('label' => '', 'value' => '')); } return $options; } Monday, October 14, 13
  • 42. Problem #3 Problem #3 - Solution ttribute Custom A ontend Model Fr Monday, October 14, 13
  • 43. Problem #3 Step 1 - create test attribute with custom frontend model PHP ... $installer = $this; $installer->addAttribute(Mage_Catalog_Model_Product::ENTITY, 'frontend_select', array( 'group' => 'General', 'type' => 'varchar', 'input' => 'int', 'label' => 'Frontend Select', 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL, 'required' => true, 'frontend' => 'examples_customselect/catalog_product_attribute_frontend_select', 'visible_on_front' => true, )); ... Сustom Frontend Model Monday, October 14, 13
  • 44. Problem #3 Problem #3 Mage_Catalog_Block_Product_ View_Attribtues Examples_Сustomselect_Model_Catalog_Pro duct_Attribute_Frontend_Select getAdditionalData() getValue() Monday, October 14, 13
  • 45. Problem #3 Step 2 - create getValue() method at our custom model PHP public function getValue(Varien_Object $object) { $optionId = $object->getData($this->getAttribute() ->getAttributeCode()); $storeId = $this->getAttribute()->getStoreId(); $data = Mage::getModel('examples_customselect/select') ->getOptionText($optionId, $storeId); $value = $data['value']; return $value; } Monday, October 14, 13
  • 46. Problem #3 Problem #3 Option load takes about 0.0014 sec !!! instead 0.9 Monday, October 14, 13
  • 47. Problem #4 Problem #4 Layer Navigation Issues (a lot of attributes or a lot of options) Monday, October 14, 13
  • 48. Problem #4 Problem #4 For each attribute which is used for Layer Navigation, it will make a call to getAllOptions() method at Mage_Eav_Model_Entity_Attribute_Source_Table model and during this call we will load all attribute option collection !!! Monday, October 14, 13
  • 49. Problem #4 Problem #4 PHP public function getAllOptions($withEmpty = true, $defaultValues = false) { ... $collection = Mage::getResourceModel('eav/ entity_attribute_option_collection') ->setPositionOrder('asc') ->setAttributeFilter($this->getAttribute()->getId()) ->setStoreFilter($this->getAttribute()->getStoreId()) ->load(); ... } Monday, October 14, 13
  • 50. Problem #4 Problem #4 - Solution n s m e t h od e t Al l O p t i o e custom g reat es for all C ad all valu hich will lo w s at once attribute variable) sing static (u Monday, October 14, 13
  • 51. Problem #4 Problem #4 - Solution PHP public function getAllOptions($withEmpty = true, $defaultValues = false) { ... if (!isset($this->_options[$storeId])) { $options = $this->_getAttributeOptionsByStore($storeId, $this->getAttribute()->getId()); $this->_options[$storeId] = $options['store']; $this->_optionsDefault[$storeId] = $options['default']; } ... } Monday, October 14, 13
  • 52. Problem #4 Problem #4 - Solution PHP protected static $_predefinedOptions = array(); protected static function _getAttributeOptionsByStore($storeId, $attributeId) { if (!isset(self::$_predefinedOptions[$storeId])) { // load options collection } if (isset(self::$_predefinedOptions[$storeId][$attributeId])) { return self:: $_predefinedOptions[$storeId][$attributeId]; } } Monday, October 14, 13
  • 53. Problem #4 Problem #4 Preconditions: one dropdown attribute with 10000 options, used at layer navigation. Cache is disabled. Monday, October 14, 13
  • 54. Problem #4 Category Page, loading time Monday, October 14, 13
  • 55. Problem #5 Problem #5 Usage of attribute created via install script at Layer Navigation Monday, October 14, 13
  • 56. Problem #5 Problem #5 PHP ... $installer->addAttribute(Mage_Catalog_Model_Product::ENTITY, 'test_select', array( 'group' => 'General', 'type' => 'int', // int or varchar type ? 'input' => 'select', 'label' => 'Test Select', 'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL, 'required' => true, )); ... Monday, October 14, 13
  • 57. Problem #5 if backend_type is varchar, our attribute will not work correctly at Layer Navigation (as example - default Magento attribute ‘manufacture’), use int backend type instead. PHP protected function _getIndexableAttributes($multiSelect) { $select = ... if ($multiSelect == true) { $select->where('ea.backend_type = ?', 'varchar') ->where('ea.frontend_input = ?', 'multiselect'); } else { $select->where('ea.backend_type = ?', 'int') ->where('ea.frontend_input = ?', 'select'); } return $this->_getReadAdapter()->fetchCol($select); } Monday, October 14, 13
  • 58. Problem #6 Problem #6 Load all category collection (getStoreCategories() method) to add needed attributes for each item in the collection. Monday, October 14, 13
  • 59. Problem #6 Mage_Catalog_Model_Resource_Category_Tree PHP protected function _getDefaultCollection($sorted = false) { $this->_joinUrlRewriteIntoCollection = true; $collection = Mage::getModel('catalog/category')->getCollection(); $attributes = Mage::getConfig()->getNode('frontend/category/ collection/attributes'); if ($attributes) { $attributes = $attributes->asArray(); $attributes = array_keys($attributes); } $collection->addAttributeToSelect($attributes); ... } Monday, October 14, 13
  • 60. Problem #6 Catalog/etc/config.xml XML <category> <collection> <attributes> <name/> <url_key/> <is_active/> </attributes> </collection> </category> Monday, October 14, 13
  • 61. Additional Example Change Product Attribute Set (for simple products) on the Fly Monday, October 14, 13
  • 62. Monday, October 14, 13
  • 63. Step 1 - add Observer to create new mass action PHP public function addNewActionToProductGrid($observer) { $block = $observer->getBlock(); if ($block instanceof Mage_Adminhtml_Block_Catalog_Product_Grid){ $attributeSets = Mage::getResourceModel('eav/ entity_attribute_set_collection') ->setEntityTypeFilter(Mage::getModel('catalog/product')>getResource()->getTypeId()) ->load() ->toOptionHash(); ... Monday, October 14, 13
  • 64. Step 1 - add Observer to create new mass action PHP $block->getMassactionBlock()->addItem('attr_set', array( 'label'=> Mage::helper('catalog')->__('Change Attribute Set'), 'url' => $block->getUrl('*/index/test', array('_current'=>true)), 'additional' => array( 'visibility' => array( 'name' => 'attribute_set', 'type' => 'select', 'class' => 'required-entry', 'label' => Mage::helper('catalog')->__('Select Attribute Set'), 'values' => $attributeSets ) ) )); } } Monday, October 14, 13
  • 65. Step 2 - create custom Controller PHP ... foreach ($productIds as $productId) { $product = Mage::getSingleton('catalog/product')->load($productId); $attributes = Mage::getModel('catalog/ product_attribute_api')->items($product->getAttributeSetId()); $product ->setStoreId($storeId) ->setAttributeSetId($this->getRequest()->getParam('attribute_set')) ->setIsMassupdate(true) ->save(); $this->_cleanOldAttributeValues($product, $attributes); } ... Monday, October 14, 13
  • 66. Step 3 - remove old attribute’s values PHP protected function _cleanOldAttributeValues($product, $attributes) { $productResource = $product->getResource(); foreach ($attributes as $attribute) { if (!in_array($attribute['code'], $this->_fixAttributes)) { $product->setData($attribute['code'], null); $productResource->saveAttribute($product, $attribute['code']); } } return $this; } Monday, October 14, 13
  • 67. There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult. (Tony Hoare) Monday, October 14, 13
  • 68. https://twitter.com/gotgelf Monday, October 14, 13
  • 69. ou ! nk Y Th a ions uest Q Monday, October 14, 13