magento attributes - fresh view

Post on 12-May-2015

2.697 Views

Category:

Technology

5 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Magento attributes Fresh view

Monday, October 14, 13

Alex Gotgelf

Senior Software Developer at Eltrino

Contacts:

alex@eltrino.com

ramzes3988

http://www.linkedin.com/in/gotgelf

Monday, October 14, 13

‣ Magento Attributes EAV concepts

‣ Not trivial problems and solutions

Lecture Overview

Monday, October 14, 13

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.

EAV History

Monday, October 14, 13

EAV conception

‣ E - Entity

‣ A - Attribute

‣ V - Value

Monday, October 14, 13

EAV conception

‣ 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

Eav becomes especially useful when the following conditions are presented:

Monday, October 14, 13

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

Weakness of EAV

‣ Performance (complex join structures etc...)

Monday, October 14, 13

Attributes Bottlenecksin Magento

Monday, October 14, 13

Problem #1

Product Select Attribute with Large Options Set (several thousands of options) at the admin side

Problem #1

Monday, October 14, 13

Let’s create select attribute with 5 options

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

PHP

Problem #1

Monday, October 14, 13

• Usability

• Performance

Problem #1

Monday, October 14, 13

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

PHP

Let’s create select attribute with 10000 optionsProblem #1

Monday, October 14, 13

• Usability

• Performance

Problem #1Problem #1

Monday, October 14, 13

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

PHP

Let’s create select attribute with 35000 optionsProblem #1

Monday, October 14, 13

• Usability

• Performance

Fatal error : Allowed memory size of 268435456 bytes exhausted (tried to allocate 32 bytes

memory_limit: 1024M

Problem #1Problem #1

Monday, October 14, 13

As ResultProblem #1

Monday, October 14, 13

Solution for Problem #1- AutoComplete

Problem #1

Monday, October 14, 13

Select2 - jquery based

replacement for

Select Boxes

Problem #1 - SolutionProblem #1

Monday, October 14, 13

http://ivaynberg.github.io/select2/

Problem #1

Problem #1 - Solution

Monday, October 14, 13

‣ Enhancing native selects with search

‣ Loading data from JavaScript: easily load items via ajax and have them searchable.

‣ 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.

‣ Working with large, remote datasets: ability to partially load a dataset based on the search term.

‣ etc ...

Select2 Advantages

Problem #1

Monday, October 14, 13

Magento Renderers

‣ Button

‣ Checkbox

‣ Select

‣ Text

‣ Radio

‣ ...

Varien_Data_Form_Element

Problem #1

Monday, October 14, 13

<?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'));

PHP

Step 1 - create test attribute with custom renderer

custom renderer

Problem #1

Monday, October 14, 13

<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>

XML

Problem #1

Step 2 - create layout with select2 js and css files

Monday, October 14, 13

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

PHP

Problem #1

Step 3 - create custom renderer block

Monday, October 14, 13

...$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 }; } },...

PHP

Problem #1

Step 3 - create custom renderer block

Monday, October 14, 13

...initSelection : function (element, callback) { var data = {id: '{$this->getValue()}', text: '{$defaultOptionText}'}; callback(data); } }); });</script>EOF;

$html .= $js; return $html;}

PHP

Problem #1

Step 3 - create custom renderer block

Monday, October 14, 13

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

PHP

Problem #1

Step 4 - create admin controller

Monday, October 14, 13

... $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 .'%');

...

loading array data using ajax

PHP

Problem #1

Step 5 - create model and resource model

Monday, October 14, 13

https://github.com/gotgelf/magento-examples

Customselect Module

Problem #1

Problem #1 - Solution

Monday, October 14, 13

Saving Attribute via admin side with a Large Number of Options

Problem #2

Problem #2

Monday, October 14, 13

Problem #2

Monday, October 14, 13

...

Problem #2

Monday, October 14, 13

• Change Opt1, change Opt300

• Click “Save Attribute” button

• As result Opt1 will be changed and Opt300 will not

Problem #2

Problem #2

Monday, October 14, 13

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

Problem #2

Problem #2

Monday, October 14, 13

Product Select Attribute with Large Options Set (several thousands of options) at the Frontend

Problem #3

Problem #3

Monday, October 14, 13

$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, ));

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

} important !

Let’s create select attribute with 10000 optionsProblem #3

Monday, October 14, 13

• Crete test product and assign to it some option

• Go to Frontend

Problem #3Problem #3

Monday, October 14, 13

Option load takes about 0.9 sec !!!

Problem #3

Problem #3

Monday, October 14, 13

Mage_Catalog_Block_Product_View_Attribtues

Mage_Eav_ModelEntity_Attribute_Frontend_Abstract

getAdditionalData()

getValue()

getOption()

getOptionText()

Mage_Eav_ModelEntity_Attribute_Source_Table

getAllOptions()

Problem #3

Problem #3

Monday, October 14, 13

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; }

Problem #3

PHP

We load all options to retrieve selected option text !!!

Monday, October 14, 13

Attribute Custom Frontend

Model

Problem #3

Problem #3 - Solution

Monday, October 14, 13

...

$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

Problem #3

PHP

Step 1 - create test attribute with custom frontend model

Monday, October 14, 13

Examples_Сustomselect_Model_Catalog_Product_Attribute_Frontend_Select

getAdditionalData()getValue()

Mage_Catalog_Block_Product_View_Attribtues

Problem #3

Problem #3

Monday, October 14, 13

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; }

Problem #3

PHP

Step 2 - create getValue() method at our custom model

Monday, October 14, 13

Option load takes about 0.0014 sec !!! instead 0.9

Problem #3Problem #3

Monday, October 14, 13

Layer Navigation Issues

(a lot of attributes or a lot of options)

Problem #4

Problem #4

Monday, October 14, 13

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 !!!

Problem #4Problem #4

Monday, October 14, 13

Problem #4

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

PHP

Problem #4

Monday, October 14, 13

Create custom getAllOptions method

which will load all values for all

attributes at once

(using static variable)

Problem #4 - SolutionProblem #4

Monday, October 14, 13

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']; } ... }

PHP

Problem #4

Problem #4 - Solution

Monday, October 14, 13

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]; }

}

Problem #4

PHP

Problem #4 - Solution

Monday, October 14, 13

Preconditions: one dropdown attribute with 10000 options, used at layer navigation. Cache is disabled.

Problem #4

Problem #4

Monday, October 14, 13

Category Page, loading time

Problem #4

Monday, October 14, 13

Problem #5

Usage of attribute created via install script at Layer Navigation

Problem #5

Monday, October 14, 13

Problem #5...

$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,));

...

Problem #5

PHP

Monday, October 14, 13

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

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.

Problem #5

PHP

Monday, October 14, 13

Load all category collection (getStoreCategories() method) to add

needed attributes for each item in the collection.

Problem #6Problem #6

Monday, October 14, 13

PHP

Mage_Catalog_Model_Resource_Category_Tree

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

... }

Problem #6

Monday, October 14, 13

<category> <collection> <attributes> <name/> <url_key/> <is_active/> </attributes> </collection> </category>

XML

Catalog/etc/config.xml

Problem #6

Monday, October 14, 13

Additional Example

Change Product Attribute Set (for simple products) on the Fly

Monday, October 14, 13

Monday, October 14, 13

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

PHP

Step 1 - add Observer to create new mass action

Monday, October 14, 13

$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 ) ) )); } }

PHP

Step 1 - add Observer to create new mass action

Monday, October 14, 13

...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);}...

PHP

Step 2 - create custom Controller

Monday, October 14, 13

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;}

PHP

Step 3 - remove old attribute’s values

Monday, October 14, 13

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

https://twitter.com/gotgelf

Monday, October 14, 13

Thank You !

Questions

Monday, October 14, 13

top related