Anzeige

Making Magento flying like a rocket! (A set of valuable tips for developers)

Independent Technical Consultant & Trainer um EcomDev BV
17. Sep 2013
Anzeige

Más contenido relacionado

Anzeige

Making Magento flying like a rocket! (A set of valuable tips for developers)

  1. Making Magento flying like a rocket! A set of valuable tips for developers Ivan Chepurnyi Co Owner / Trainer EcomDev BV
  2. Ivan Chepurnyi About me Developers Paradise • Technical Consultant, Co-owner at EcomDev B.V. • Was one of the first five developers in original Magento core team • Providing trainings and coaching Magento Developers in Europe • Main areas of my expertise: – System Architecture – Performance Optimization – Test Driven Development – Complex Customizations
  3. Ivan Chepurnyi Lecture Overview Developers Paradise • Common Developer Mistakes • Tips for regular Magento Stores • Dealing with high loaded projects
  4. Ivan Chepurnyi Mistake #0 Developers Paradise Developing a store without turning on flat version of products and categories
  5. Ivan Chepurnyi Mistake #1 Developers Paradise Using of model load in the template to access property that is not available in the collection
  6. Ivan Chepurnyi Mistake #1 Developers Paradise public getMySuperProductAttribute($product) { $myProduct = Mage::getModel(„catalog/product‟)->load($product- >getId()); return $myProduct->getMySuperProductAttribute(); } Some Helper
  7. Ivan Chepurnyi Mistake #1 Developers Paradise <p class=“my-attribute”> <?php echo $this->helper(„my_helper‟) ->getMySuperProductAttribute($product);?> </p> Product List Template
  8. Ivan Chepurnyi Mistake #1 Developers Paradise • Usually Affected Models – Product Model • Caused by need to access some custom values from product in lists – Category Model • Caused by need to retrieve some custom text attribute in menu or layered navigation – Stock Item • Need to access available stock qty in product listings
  9. Ivan Chepurnyi Solution for Product Listing Developers Paradise // In case of new attribute $this->addAttribute(„catalog_product‟, „custom_attribute_code‟, array( // …. your attribute options “used_in_product_listing” => true )); // In case of updating existing attribute $this->updateAttribute( „catalog_product‟, „customer_attribute_code‟, „used_in_product_listing‟, 1 ); Your install script
  10. Ivan Chepurnyi Solution for Category Collection Developers Paradise <frontend> <category> <collection> <your_custom_attribute_code /> </collection> </category> </frontend> Your config.xml file
  11. Ivan Chepurnyi Solution for Category Tree (Flat) Developers Paradise <frontend> <events> <catalog_category_flat_loadnodes_before> <class>your_module/observer</class> <method>addCustomAttribute</method> </catalog_category_flat_loadnodes_before> </events> </frontend> In your config.xml
  12. Ivan Chepurnyi Solution for Category Tree (Flat) Developers Paradise public function addCustomAttribute($observer) { $select = $observer->getSelect(); $attributes = array(„attribute_code1‟, „attribute_code2‟, „attribute_code3‟); Mage::getResourceSingleton(„your_module/observer) ->addAttributesToSelect($select, $attributes); } In your Observer
  13. Ivan Chepurnyi Solution for Category Tree (Flat) Developers Paradise public function addAttributesToSelect ($select, $attributes) { $select->columns($attributes, „main_table‟); } In your Observer Resource
  14. Ivan Chepurnyi Solution for Stock Item Developers Paradise <frontend> <events> <catalog_product_collection_load_after> <class>your_module/observer</class> <method>addStockItemData</method> </catalog_product_collection_load_after> </events> </frontend> In your config.xml
  15. Ivan Chepurnyi Solution for Stock Item Developers Paradise public function addStockItemData($observer) { $collection = $observer->getCollection(); Mage::getSingleton('cataloginventory/stock') ->addItemsToProducts($collection); } In your Observer
  16. Ivan Chepurnyi Mistake #2 Developers Paradise Using of full collection load where it is not needed.
  17. Ivan Chepurnyi Mistake #2 Developers Paradise public function isCategoryHasSaleableProducts($category) { $collection = Mage::getResourceModel('catalog/product_collection'); $collection ->setStoreId(Mage::app()->getStore()->getId()) ->addCategoryFilter($category); Mage::getSingleton('catalog/product_status‟) ->addVisibleFilterToCollection($collection); Mage::getSingleton('catalog/product_visibility') ->addVisibleInCatalogFilterToCollection($collection); return $collection->getFirstItem()->isSaleable(); } Code in block
  18. Ivan Chepurnyi Solutions Developers Paradise • Write a resource model for making query to find this data in stock and product status tables. • Write an indexer that will contain information about salable categories and add this as column to all categories select.
  19. Ivan Chepurnyi Summary Developers Paradise • Always develop with flat version of catalog turned on • Never use model load on every collection item, there is always an option to add your custom attribute • Never use collection to check for something, they are created to load subset of the data for loading, not for checking.
  20. Ivan Chepurnyi If you write your code correctly… Developers Paradise It is time to get your hands dirty in core optimizations!
  21. Ivan Chepurnyi Regular Store Bottlenecks Developers Paradise • Overall Problems • Category Pages • Product Pages • Checkout Pages
  22. Ivan Chepurnyi Developers Paradise Overall Problems
  23. Ivan Chepurnyi Problem #1 Developers Paradise Non optimized layout handles usage Magento adds on every category and product page a layout handle with its entity id, so you have layout cache unique for each product or category!
  24. Ivan Chepurnyi Solution Developers Paradise Add an observer to controller_action_layout_load_before, check loaded handles and remove items that match starts with CATEGORY_ and PRODUCT_, except PRODUCT_TYPE.
  25. Ivan Chepurnyi Solution Developers Paradise public function optimizeLayout($observer) { $update = $observer->getLayout()->getUpdate(); foreach ($update->getHandles() as $handle) { if (strpos($handle, 'CATEGORY_') === 0 || (strpos($handle, 'PRODUCT_') === 0 && strpos($handle, 'PRODUCT_TYPE_') === false)) { $update->removeHandle($handle); } } } Example of Observer
  26. Ivan Chepurnyi Problem #2 Developers Paradise No cache for CMS Blocks Magento doesn‟t cache them by default. On average projects you have up to 10-20 CMS blocks that can be edited by customer on every page.
  27. Ivan Chepurnyi Solution Developers Paradise Add an observer to core_block_abstract_to_html_before, and specify required cache information for the block, like cache key (that is a block identifier) and cache tags (Mage_Cms_Model_Block::CACHE_TAG).
  28. Ivan Chepurnyi Solution Developers Paradise public function optimizeCmsBlocksCache($observer) { if ($block instanceof Mage_Cms_Block_Widget_Block || $block instanceof Mage_Cms_Block_Block) { $cacheKeyData = array( Mage_Cms_Model_Block::CACHE_TAG, $block->getBlockId(), Mage::app()->getStore()->getId() ); $block->setCacheKey(implode('_', $cacheKeyData)); $block->setCacheTags( array(Mage_Cms_Model_Block::CACHE_TAG) ); $block->setCacheLifetime(false); } } Example of Observer
  29. Ivan Chepurnyi Problem #3 Developers Paradise Magento top menu Since CE 1.7 version of Magento, they moved menu to another block, but forget to add caching to it. So now each page load spends from 100-300mson building top menu! And the more categories you have, the more time is spent!
  30. Ivan Chepurnyi Solution Developers Paradise 1. Add a cache tags via observer similar to optimization of CMS blocks. The difference only, that you will need to add category cache tag instead: Mage_Catalog_Model_Category::CACHE_TAG 2. Rewrite Mage_Page_Block_Html_Topmenu to add category id into the menu node class name. Yes, there is no event for this :( 3. Add JS on the page that will use current category id for highlighting top menu item.
  31. Ivan Chepurnyi Solution Developers Paradise public function optimizeNavigationCache($observer) { if ($block instanceof Mage_Page_Block_Html_Topmenu) { $cacheKeyData = array( Mage_Catalog_Model_Category::CACHE_TAG, 'NAVIGATION', Mage::app()->getStore()->getCode() ); $block->setCacheKey(implode('_', $cacheKeyData)); $block->setCacheTags( array(Mage_Catalog_Model_Category::CACHE_TAG) ); $block->setCacheLifetime(false); } } Example of Observer
  32. Ivan Chepurnyi Solution Developers Paradise protected function _getMenuItemClasses(Varien_Data_Tree_Node $item) { $classes = parent::_getMenuItemClasses($item); // Will be something like category-node-#id $classes[] = $item->getId(); return $classes; } Overridden Block Method
  33. Ivan Chepurnyi Solution Developers Paradise JS implementation is up to you. I believe you can do that yourself :)
  34. Ivan Chepurnyi Developers Paradise Category Pages & Product Pages
  35. Ivan Chepurnyi Problem #1 Developers Paradise Layered Navigation For each attribute you have in Magento and that is marked as filtrable, it will make a call to getAllOptions() of attribute source model. Even if there is no filter results for it, it will invoke attribute option collection load. For merchants who have a lot of attributes, it is a huge performance bottleneck.
  36. Ivan Chepurnyi Solution Developers Paradise • Remove collection usage usage for each item by rewriting Mage_Eav_Model_Entity_Attribute_Source_Table class. • Implement a static method, that will load values for all attributes at once. If you will not use objects, it won‟t take too much memory. • Override getAllOptions() method to use your static method. • You can find class by this url: http://bit.ly/mageoption
  37. Ivan Chepurnyi What it does? Developers Paradise // This one will use collection getData() method // to retrieve array items instead of collection of objects. Mage::getResourceModel('eav/entity_attribute_option_collection') ->setPositionOrder('asc') ->setStoreFilter($storeId) ->getData(); On the first call to getAllOptions() it will preload all attribute values for current store in array format.
  38. Ivan Chepurnyi Problem #2 Developers Paradise Dropdown Options on Product Page The problem is the same as with layered navigation, but not that much visible if you don‟t have too much dropdown attributes to show. Product getOptionText() uses the same getAllOptions() method call. This one is automatically fixed by fixing previous one.
  39. Ivan Chepurnyi Problem #3 Developers Paradise Configurable Products Magento is not using flat version of products for configurable products children retrieval. So every configurable product page is a bottleneck, especially for fashion retailers.
  40. Ivan Chepurnyi Solution Developers Paradise 1. Rewrite the class with this long name: Mage_Catalog_Model_Resource_Product_Type_Configurable_Product_Collection 2. Overriden isEnabledFlat() method that should return the real information about flat availability. 1. Make sure that attributes, that your customer is using, are included into the flat version of catalog. Mark them as “used_in_product_listing”.
  41. Ivan Chepurnyi Solution Developers Paradise public function isEnabledFlat() { return Mage_Catalog_Model_Resource_Product_Collection::isEnabledFlat(); } Overriden Collection Method
  42. Ivan Chepurnyi Developers Paradise Checkout Pages
  43. Ivan Chepurnyi Problem #1 Developers Paradise Catalog Price Rules Each time when Magento calls collectTotals() method on quote object, it walks though all items in the quote and invoked getFinalPrice() method on your product. This method dispatches catalog_product_get_final_price event, that is observed by Mage_CatalogRule module.
  44. Ivan Chepurnyi Solution Developers Paradise 1. Rewrite Mage_CatalogRule_Model_Observer class and create a new method that will preload rule prices for quote. 2. Define $_preloadedPrices property to cache results per quote object. 3. Add an observer in configuration for sales_quote_collect_totals_before event, for original core class alias, but with the method you‟ve created. Full observer class can be found at this url: http://bit.ly/magerule
  45. Ivan Chepurnyi Solution Developers Paradise public function beforeCollectTotals(Varien_Event_Observer $observer) { // … Omited retrieval of product ids and customer group with website $cacheKey = spl_object_hash($quote); if (!isset($this->_preloadedPrices[$cacheKey])) { $this->_preloadedPrices[$cacheKey] = Mage::getResourceSingleton('catalogrule/rule') ->getRulePrices($date, $websiteId, $groupId, $productIds); } foreach ($this->_preloadedPrices[$cacheKey] as $productId => $price) { $key = implode('|', array($date, $websiteId, $groupId, $productId)); $this->_rulePrices[$key] = $price; } } Created method code
  46. Ivan Chepurnyi There are more issues… Developers Paradise But it is too much for one presentation :) Let‟s better talk about high loaded projects!
  47. Ivan Chepurnyi Developers Paradise Are you using Varnish for your projects?
  48. Ivan Chepurnyi Varnish Issues Developers Paradise Common issues caused by developers, when they use Varnish on the project • Developers usually hide poor code quality behind front cache server • Doing backend callbacks for functionality that can be fully done by modern browser in JavaScript. • Caching static files by Varnish
  49. Ivan Chepurnyi ESI include is an evil for Magento Developers Paradise Only when the content for ESI include can be cached by Varnish. It doesn‟t make to make ESI includes for shopping cart, compare and recently viewed reports.
  50. Ivan Chepurnyi AJAX Callbacks Issue Developers Paradise AJAX call should be done only when needed. Do not perform a bunch of calls just because the data should be loaded from the server. It is always possible to decrease amount of calls by using your frontend skills.
  51. Ivan Chepurnyi Use Cookies & HTML5! Developers Paradise • You can always set a custom cookie in Magento when customer: – Adds a product to a cart – Logs in or logs out – Adds a product to a compare list • You always can store up to 2Mb of data into sessionStorage of your visitor browser! Only IE7 doesn‟t support it.
  52. Ivan Chepurnyi How it can help you? Developers Paradise • You can decrease amount of AJAX calls to the number of real user actions. – Customer adds product to cart, you make an ajax call to update session storage – Customer logs in, you make and ajax call to update the it again – Customer adds product to wishlist or compare products list, you update a session storage. So in the end it should be1 action – 1 AJAX call, and NOT 1 page view – 1 AJAX call!
  53. Ivan Chepurnyi Recently Viewed Products Developers Paradise For recently viewed products, you even don‟t need to make any AJAX calls – Render a hidden block on the product page – When customer views a product, add this hidden block to session storage – On any other page, just render data by JavaScript!
  54. Ivan Chepurnyi Conclusion Developers Paradise Be smart and use Varnish correctly!
  55. Ivan Chepurnyi There is more to come… Developers Paradise EcomDev is currently working on replacing of Magento flat catalog with Sphinx search, that will gain incredibly fast response time for non-cached pages. Want to learn more about it? Visit our website: http://www.ecomdev.org/contact-us Or email us: info@ecodmev.org
  56. Ivan Chepurnyi Developers Paradise Thank You!
  57. Questions?
Anzeige