📜 ⬆️ ⬇️

Magento step by step

Magento is an online shopping management system. According to Alexa, Magento is the most popular online shopping management system in the world for February 2013.

Currently, of all e-commerce solutions, I prefer Magento:


On the Internet there are a lot of articles on this topic, but on Habré this section is still filling, so I will try to share my knowledge in this area.

Any development of modules in Magento should be conducted in local (or community, if you plan to upload your module to connect)
So, the basic module consists of:

')
Loading modules is quite simple: Magento first loads the app / etc / local.xml with the settings of the database, sessions, cache, etc., then loads the modules bootstraps from the app / etc / modules / *. Xml , sorts them according to dependencies in depends> , loads app / code / * / * / * / etc / config.xml , merges all XML into one global XML, and loads app / etc / local.xml again into global XML to avoid losing data from local.xml .

In fact, the meaning of all development in Magento is to interfere with the source code of kernel modules (or any other module) to a minimum, for this Magento has several approaches:
1) and, perhaps, the most basic - events. If you need to add / change some existing functionality, in most cases it is enough to intercept the event.
2) Rewrite class
3) Local override

Developments

Events in Magento is the most correct approach when changing existing functionality. When developing your own functionality, do not forget to use events, especially since it is not so difficult, for example
Mage::dispatchEvent('namespace_module_something', array('model' => $this, 'something' => $that)) 

Further, when intercepting an event:
 // app/code/local/Namespace/Module/Observer.php public function someName(Varien_Event_Observer $observer) { $model = $observer->getModel(); // getData('model') $something = $observer->getSomething(); // getData('something') } 

The definition of interception itself is described in config.xml in the events node:
 <config> <global> <events> <namespace_module_something> <observers> <some_name> <class>namespace_module/observer</class> <method>someName</method> </some_name> </observers> </namespace_module_something> </events> </global> </config> 

Also, intercepting events globally is not always necessary, so events can be prescribed, in addition to the global one, also in frontend / adminhtml / crontab

Rewrite class

Models, helpers, and blocks almost everywhere in code are called via factory methods:

All of these methods use getModelClassName / getHelperClassName / getBlockClassName respectively in Mage_Core_Model_Config
The first parameter in all methods is the model / helper / block alias: for example, catalog / product
You can also use the name of the class directly, but it will be wrong, because kill the rewrite system completely - essentially the same thing as doing new Namespace_Module_Model_Something () - you can’t override this class through the config, so try not to use direct class names in your modules, for example:
 fgetcsv($handle, 0, Namespace_Module_Model_Something::DELIMITER, Namespace_Module_Model_Something::ENCLOSURE, Namespace_Module_Model_Something::ESCAPE ) 

In fact, it will force all users of your module to use only the DELIMITER / ENCLOSURE / ESCAPE specified in it. Such a problem, for example, several versions ago was in the Mage_ImportExport module, and people like it are still found sometimes in code.
So, consider the configuration file:

 <config> <modules> <Namespace_Module> </Namespace_Module> </modules> <global> <models> <namespace_module> <class>Namespace_Module_Model</class> </namespace_module> </models> <helpers> <namespace_module> <class>Namespace_Module_Helper</class> </namespace_module> </helpers> <blocks> <namespace_module> <class>Namespace_Module_Block</class> </namespace_module> </blocks> </global> </config> 

It indicates that models, helpers, and blocks will be available under the namespace_module alias.
Thus, if you need to reach Namespace_Module_Model_Modelname, it is enough to use Mage :: getModel ('namespace_module / modelname') (or Mage :: getSingleton if you need a singleton). With blocks and helpers, the same situation, with only one addition: Mage :: helper ('namespace_module') will call the main helper of the module: Namespace_Module_Helper_Data
Also this helper will use blocks and controllers when calling the translation function ($ this -> __ (“Somestring”)), so the helper must be inherited from Mage_Core_Helper_Abstract.
For a revright, it is enough to specify the following:
 ... <models> <catalog> <rewrite> <product>Namespace_Module_Model_Product</product> </rewrite> </catalog> </models> ... 

For each model (block, helper) you need to specify your rewright. In the XML above, we override the product model on Namespace_Module_Model_Product, so Mage :: getModel ('catalog / product') will absolutely everywhere return Namespace_Module_Model_Product

Local override

It is needed only if there is no possibility of correcting errors in the file, for example, if there is an error in the Abstract class, which cannot be redefined in any way (especially if this abstract class is used by a dozen other classes).
By default, include_path = app / code / local; app / code / community; app / code / core; lib , so if an error occurs in the community or core class, it can be copied to the same place as the file, only to local: for example, local / Mage / Catalog / Model / Abstract.php, and the file will be loaded from local instead of core.
Not the best way, of course, because When updating Magento, the file will need to be updated (if the problem is not fixed), but it has the right to life, especially when it comes to optimization.

Practical application


Task: add the “is_exported” field for orders and display it in the list of orders in the admin panel.
First of all, create a bootstrap:
app / etc / modules / Easy_Interfacing.xml
 <?xml version="1.0"?> <config> <modules> <Easy_Interfacing> <active>true</active> <codePool>local</codePool> <depends> <Mage_Sales/> </depends> </Easy_Interfacing> </modules> </config> 

In dependencies, you must set Mage_Sales, since we are going to change the structure of the table created by the Mage_Sales module. If this is not installed, everything will pass without problems on the already existing Magento, but when deploying from scratch, installation scripts can “fall” because there is no sales_flat_order table if your script runs first.
app / code / local / Easy / Interfacing / etc / config.xml
 <?xml version="1.0"?> <config> <modules> <Easy_Interfacing> <version>0.0.1</version> </Easy_Interfacing> </modules> <global> <resources> <easy_interfacing_setup> <setup> <module>Easy_Interfacing</module> <class>Mage_Sales_Model_Resource_Setup</class> </setup> </easy_interfacing_setup> </resources> <helpers> <easy_interfacing> <class>Easy_Interfacing_Helper</class> </easy_interfacing> </helpers> <blocks> <easy_interfacing> <class>Easy_Interfacing_Block</class> </easy_interfacing> </blocks> </global> </config> 

in the “resources” node, it is essentially specified that the module has an installation script and that it will be of the class Mage_Sales_Model_Resource_Setup
create app / code / local / Easy / Interfacing / sql / easy_interfacing_setup / install-0.0.1.php
 /* @var $this Mage_Sales_Model_Resource_Setup */ $this->addAttribute('order', 'is_exported', array('type' => 'int', 'grid' => true)); 

Here we add an int attribute to the order table, grid => true also indicates that the grid table needs to be updated with this attribute. It is important not to use capital letters in the field name - getters and setters will not be able to work with them correctly - (get) setSomeValue is equivalent only to (get) setData ('some_value'), but not (get) setData ('SomeValue')

Initially, orders were implemented in the EAV pattern, however, due to the peculiarity of EAV - to write one object with several child objects, totally with half a thousand attributes in a dozen tables, only to write one order is unprofitable from the point of view of performance, therefore orders and everything, what is connected with them - invoices, deliveries, quotas, addresses - these are flat tables that use a duplicate grid table.
Good practice is to use DDL methods, since it is safe to use $ this-> run ($ sql) only for newly created tables, using ALTER TABLE in the run method does not clear the Zend table cache and can be stuck for a long time due to misunderstanding of the reasons for field non-saving.
Now let's try to make a small modification of the admin in the list of orders.
A common mistake in community modules is the JOINs in the order list — instead of simply writing data to the grid table, people attach data to the 'sales / order_grid_collection' collection by redefining the Mage_Adminhtml_Block_Sales_Order_Grid block, which is not quite true in terms of performance : if the data is already recorded in the grid table, why not just add one or more fields there?
So, let's override the Mage_Adminhtml_Block_Sales_Order_Grid block. His alias is adminhtml / sales_order_grid:
 .... <blocks> <adminhtml> <rewrite> <sales_order_grid>Easy_Interfacing_Block_Adminhtml_Sales_Order_Grid</sales_order_grid> </rewrite> </adminhtml> </blocks> .... 

Please note that I added Adminhtml at the beginning of the block name - it is more convenient to distinguish blocks from the admin panel and from the front - because Sales_Order_Grid is in the Adminhtml module, not Sales, and if you need to override also sales / order_history, this will result in the Block directory / Order next to Grid.php is also History.php, which will lead to confusion.

Create the file app / code / local / Easy / Interfacing / Block / Sales / Order / Grid.php :
 <?php class Easy_Interfacing_Block_Adminhtml_Sales_Order_Grid extends Mage_Adminhtml_Block_Sales_Order_Grid { protected function _prepareColumns() { parent::_prepareColumns(); $options = array( null => $this->helper('eav')->__('No'), 1 => $this->helper('eav')->__('Yes'), ); $this->addColumnAfter( 'is_exported', array( 'header' => $this->__('Exported'), 'index' => 'is_exported', 'type' => 'options', 'width' => '70px', 'options' => $options ), 'status' ); $this->sortColumnsByOrder(); } } 

It's simple, they added an Exported column to the grid, after the status. Since type = options, you need to specify options as an array of id => value.
It is impossible to use eav / entity_attribute_source_boolean directly, since there is No = 0, and not NULL, as in our case.

To test this functionality, I use a simple console script:
 <?php require 'app/Mage.php'; Mage::app('admin'); Mage::getModel('sales/order')->load(194)->setIsExported(1)->save(); 

Since the is_exported attribute exists in both tables, when you save the order object, or rather, in the Mage_Sales_Model_Abstract :: _ afterCommitCallback method, updateGridRecords is executed from the order model resource, which copies the field data from the model to the grid table.

Now, it is enough to implement any export we are interested in through the event (do not forget to add a description of the model):
 <config> <global> ... <models> <easy_interfacing> <class>Easy_Interfacing_Model</class> </easy_interfacing> </models> <events> <sales_order_save_commit_after> <observers> <easy_interfacing_order> <class>easy_interfacing/observer</class> <method>exportOrder</method> </easy_interfacing_order> </observers> </sales_order_save_commit_after> </events> ... </global> 

How to find the name of the event? It's very simple - open app / Mage.php, find the dispatchEvent method and add logging (temporarily, of course):
 public static function dispatchEvent($name, array $data = array()) { Mage::log($name, LOG_DEBUG, 'events.log', true); 

Then perform the required action and see the list of running events (file "<MAGENTO_ROOT> /var/log/events.log"). It would be more correct to choose afterCommit, since this will be exactly the event that will be triggered when the order object is successfully saved. If you do this afterSave, then if any of the modules throws an exception, it may turn out that your module could already export data, which is wrong, because the entire transaction will be canceled and the order will return to its original state (which may even be non-existent, in case of a rollback of INSERT INTO).

Create the Observer app / code / local / Easy / Interfacing / Model / Observer.php class itself:
 <?php class Easy_Interfacing_Model_Observer { public function exportOrder(Varien_Event_Observer $observer) { $order = $observer->getOrder(); /* @var $order Mage_Sales_Model_Order */ if (!$order->getIsExported() && $order->getState() == Mage_Sales_Model_Order::STATE_PROCESSING) { try { Mage::getModel('easy_interfacing/order')->export($order); $order->setIsExported(1)->addStatusHistoryComment('Exported order'); } catch (Exception $ex) { $order->addStatusHistoryComment('Failed exporting order: ' . $ex->getMessage())->save(); } } } } 

Nothing complicated, a simple check whether the order has already been exported and whether it is possible to export it at all - only orders in the PROCESSING state are suitable for export - these are paid orders.

And finally, easy_interfacing / order app / code / local / Easy / Interfacing / Model / Order.php
 <?php class Easy_Interfacing_Model_Order { public function export(Mage_Sales_Model_Order $order) { Mage::throwException('Not implemented'); } } 


To test the export functionality, we will change our test script to:
 <?php require 'app/Mage.php'; Mage::app('admin'); Mage::getModel('sales/order')->load(188)->setDummyValue(1)->save(); 


Setting a non-existent value is necessary in order for the order to be saved - saving the model in Magento does not happen if _origData = _data, so the events of beforeSave / afterSave / afterCommit will not be called. After launching this script, a new comment will appear in the order comment history:
22/07/2014 6:43:43 AM|Processing
Customer Not Notified
Failed exporting order: Not implemented

Final config.xml
 <?xml version="1.0"?> <config> <modules> <Easy_Interfacing> <version>0.0.1</version> </Easy_Interfacing> </modules> <global> <resources> <easy_interfacing_setup> <setup> <module>Easy_Interfacing</module> <class>Mage_Sales_Model_Resource_Setup</class> </setup> </easy_interfacing_setup> </resources> <models> <easy_interfacing> <class>Easy_Interfacing_Model</class> </easy_interfacing> </models> <helpers> <easy_interfacing> <class>Easy_Interfacing_Helper</class> </easy_interfacing> </helpers> <blocks> <easy_interfacing> <class>Easy_Interfacing_Block</class> </easy_interfacing> <adminhtml> <rewrite> <sales_order_grid>Easy_Interfacing_Block_Adminhtml_Sales_Order_Grid</sales_order_grid> </rewrite> </adminhtml> </blocks> <events> <sales_order_save_commit_after> <observers> <easy_interfacing_order> <class>easy_interfacing/observer</class> <method>exportOrder</method> </easy_interfacing_order> </observers> </sales_order_save_commit_after> </events> </global> </config> 



This is how an uncomplicated ~ 12 kilobyte image is realized for the skeleton to be exported to Magento when creating an order. It remains only to implement the export algorithm you need in the Easy_Interfacing_Model_Order model.

Source: https://habr.com/ru/post/230957/


All Articles