Magento step by step

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

Currently, of all e-commerce solutions, I prefer Magento:
  • EAV-database model, allows you to easily manipulate the attributes of products, categories, users
  • A large number of paid and free modules on Connect (albeit not always well written)
  • Open source
  • Easy to deploy and upgrade out-of-the-box online store
  • Great and powerful admin panel
  • Compared to the rest, perhaps the most powerful basic functionality for ecommerce


There are a lot of articles on the Internet on this topic, but this section is still being filled on the hub, so I'll try to share my knowledge in this area.

Any development of modules in Magento should be done in local (or community, if you plan to lay out your module in connect)
So, the basic module consists of:
  • app / etc / modules / [Namespace] _ [Module] .xml - bootstrap module file. It contains the location of the module ( core | community | local ) and the dependencies (depends)
  • app / code / local / [Namespace] / [Module] - the main directory of the module
    • etc directory with the module configuration files ( config.xml | system.xml | adminhtml.xml | api.xml | api2.xml ... )
    • controllers - directory of module controllers
    • data - directory of installation scripts of the module, only data
    • sql - module installation scripts directory, only table structures
    • Block - directory of block classes
    • Helper - helper class directory
    • Model - model class directory



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

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

Events

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 :
namespace_module/observersomeName

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

Rewrite class

Models, helpers and blocks almost everywhere in the code are called through factory methods:
  • Mage :: getModel () / Mage :: getSingleton ()
  • Mage :: helper ()
  • Mage :: getBlockSingleton / Mage_Core_Model_Layout :: createBlock

All these methods use getModelClassName / getHelperClassName / getBlockClassName in Mage_Core_Model_Config, respectively.
The first parameter in all methods is the model / helper / block alias: for example, catalog / product
You can also use the class name directly, but this will not be true, because the rewrite system will be completely killed - essentially the same as 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 DELIMITER / ENCLOSURE / ESCAPE specified in it. Such a problem, for example, a few versions ago was in the Mage_ImportExport module, and similar ones are still sometimes found in code.
So, consider the configuration file:

Namespace_Module_ModelNamespace_Module_HelperNamespace_Module_Block

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

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

Local override

It is necessary only if there is no possibility of fixing the error in the file, for example, when there is an error in the Abstract class, which cannot be overridden 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 in local: for example local / Mage / Catalog / Model / Abstract.php, and the file will be downloaded 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 field "is_exported" for orders and display it in the list of orders in the admin panel.
First of all, create bootstrap:
app / etc / modules / Easy_Interfacing.xml
truelocal

You must set Mage_Sales in the dependencies, since we are going to change the structure of the table created by the Mage_Sales module. If this is not installed, everything will go smoothly on an existing Magento, however, when deployed from scratch, installation scripts may “crash” due to the absence of the sales_flat_order table if your script starts first.
app / code / local / Easy / Interfacing / etc / config.xml
0.0.1Easy_InterfacingMage_Sales_Model_Resource_SetupEasy_Interfacing_HelperEasy_Interfacing_Block

in the "resources" node it is essentially indicated that the module has an installation script and that it will be of the Mage_Sales_Model_Resource_Setup class,
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 you need to update the grid table 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 at all (get) setData ('SomeValue')

Initially, orders were implemented in the EAV pattern, however, due to the peculiarity of EAV - recording one object with several child objects, with a total of half a thousand attributes in a dozen tables, only to record one order - is unprofitable in terms of performance, so orders and everything with them connected - invoices, deliveries, quotas, addresses - these are flat tabs Persons who use a duplicate grid table.
It’s good practice to use DDL methods because$ this-> run ($ sql) can only be safely used 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 if you don’t understand the reasons for not saving the field.
Now let's try to make a small modification of the admin panel in the list of orders.
A common mistake in community modules - JOINs in the order list - instead of just writing data to the grid table, people attach data to the 'sales / order_grid_collection' collection by overriding the Mage_Adminhtml_Block_Sales_Order_Grid block, which is not entirely true in terms of performance : if the data is already being written to the grid table, why not just add one or more fields there?
So, we will redefine the block Mage_Adminhtml_Block_Sales_Order_Grid. His alias is adminhtml / sales_order_grid:
....
        Easy_Interfacing_Block_Adminhtml_Sales_Order_Grid
....

Please note that I added Adminhtml at the beginning of the block name - it’s more convenient to distinguish the blocks from the admin and the front - after all, Sales_Order_Grid is in the Adminhtml module, not Sales, and if you need to redefine also sales / order_history - this will lead to Block appearing in the 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 :

 $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, we added the Exported column to the grid, after the status. Since type = options, you need to specify options in the form of an array id => value.
You cannot use eav / entity_attribute_source_boolean directly, as there is No = 0 and not NULL, as in our case.

To test this functionality, I use a simple console script:

load(194)->setIsExported(1)->save();

Since the is_exported attribute exists in both tables, when saving the order object, or rather, the Mage_Sales_Model_Abstract :: _ afterCommitCallback method updatesGridRecords from the resource model of the order, which copies the field data from the model to the grid table.

Now it’s enough to implement any export of interest to us through the event (do not forget to add a description of the model):

...
        Easy_Interfacing_Modeleasy_interfacing/observerexportOrder
...
    

How to find out the name of the event? Everything is 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 triggered events (file "/var/log/events.log "). It is most correct to select afterCommit, because it will be the event that will be triggered when the order object is successfully saved. If you do this afterSave, then if one of the modules throws an exception , it may turn out that your module could already export data, which is wrong, because the whole transaction will be canceled and the order will return to its original state (which may even be non-existent if INSERT INTO is rolled back).

Let's create the Observer class app / code /local/Easy/Interfacing/Model/Observer.php :

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 can be exported 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


Для тестирования функционала экспорта изменим наш тестовый скрипт на:

load(188)->setDummyValue(1)->save();


Установка несуществуюего значения нужно для того, чтобы пошло сохранение заказа — сохранение модели в Magento не происходит, если _origData = _data, поэтому события beforeSave/afterSave/afterCommit не будут вызваны. После запуска этого скрипта, в истории комментариев заказа появится новый комментарий:
22/07/2014 6:43:43 AM|Processing
Customer Not Notified
Failed exporting order: Not implemented

Финальный config.xml
0.0.1Easy_InterfacingMage_Sales_Model_Resource_SetupEasy_Interfacing_ModelEasy_Interfacing_HelperEasy_Interfacing_BlockEasy_Interfacing_Block_Adminhtml_Sales_Order_Grideasy_interfacing/observerexportOrder



Вот таким вот нехитрым образом в ~12 килобайт реализуется скелет для экспорта в Magento при создании заказа. Дальше остается всего лишь реализовать нужный Вам алгоритм экспорта в модели Easy_Interfacing_Model_Order.

Also popular now: