📜 ⬆️ ⬇️

Phased creation of extensions for Magento using the example of a debug console

Hello.
It is noticeable that Habr is not spoiled by articles about Magento, despite the fact that the platform is quite popular and at the same time - not easy. The article will show the way to create a real extension available for download. This is not a hello world, rather the desire to provide the community with a free alternative to Commercebug (if you worked with Magento - you probably heard about it).

So, what should be a ready extension? This should be a plot on the page with data on the currently used blocks, templates, handles and other useful information. It seemed to me that it would be most convenient to do this in the form of a collapsible "console" - a thing that we used to see in many games like Half-Life. Naturally, no command line will be there because it is simply not needed there.

The first thing to start with is the creation of a file structure. Since I am describing a real extension, I ask permission to use in the article the real names of the namespace and classes.
We will define our namespace (namespace) - this is the very first part of the name of any class, it is identified with the developer of the extension. For example, all internal Magento classes developed internally have a namespace of "Mage." We will have this “Linker”. Create a directory with this name in app / code / community.
Let me remind you that the platform has three standard code pools (code pool) - core, community and local. The first one contains only code developed internally, the second one is what the community develops (it can be both paid and free extensions), and the third one contains changes for this particular site that you are not going to share.
')
Next, create inside a directory with the name of the module (Insider), and in it - the standard directory structure:

Insider
|-- Block
|-- controllers
|-- etc
|-- — config.xml
|-- Helper
|-- — Data.php
|-- Model


Data.php and config.xml are mandatory extension files, without them the code simply will not run.

Add to config.xml the following things:

<? xml version ="1.0" ? > <!-- , - XML! -->
< config > <!-- root node -->
< modules >
< Linker_Insider > <!-- -->
< version > 0.1.0.0 </ version >
</ Linker_Insider >
</ modules >
</ config >


Here we simply informed the system that I am such a module and I have such a version. So far, nothing substantial. Here are the contents of Data.php:

Copy Source | Copy HTML
  1. <? php
  2. class Linker_Insider_Helper_Data extends Mage_Core_Helper_Data {}


This is a stub. We are simply not going to use helper, but this file is necessary for the system.
A bit about class naming conventions: it basically fits with Zend's, that is, by looking at the name of the class you can tell exactly where it is. In fact, the name corresponds to the location in the file system if you replace "_" with "/".
Now let's go and turn on our module in the general list. To do this, open etc / modules / Mage_All.xml and add the following lines between the description of the last module and </ config>:

< Linker_Insider >
< active > true </ active >
< codePool > community </ codePool >
</ Linker_Insider >


Thus, we reported that we want to enable (<active> true </ active>) a module called Linker_Insider, which is located in the community code pool (app / code / community).
Great, turned on! But we still can not see it on the site, because it still does nothing. Let's make it happen.
For this you need to create a template file. Templates for frontend are stored in app / design / frontend / base / default / template. There we create a directory with the name of our module (insider), and in it - the file disclose.phtml. We write in it, for example “hello” (for now we just need to see if the module works). Now you need to tell the system where to insert it - this already applies to markup. Layout templates are XML files and are located in app / design / frontend / base / default / layout. Create a file named insider.xml there with the following content:

<? xml version ="1.0" ? >
< layout >
< default > <!-- -->
< block type ="core/template" name ="insider" template ="insider/disclose.phtml" />
</ default >
</ layout >


The 'type' attribute tells which block will control the rendering of the 'template' template. In this case, it will be a block with the name of the class Mage_Core_Block_Template. type = "modulename / blockname" stands for Namespace_modulename_Block_blockname. It is assumed that the namespace is already registered in the system and she knows where to look for blocks starting with modulename. Mage, of course, is registered, and when we need to use our own block, we will need to add something to our configuration file, but more on that later.
The attribute 'name' is needed so that other blocks could “cling” to it using <reference> - this will be clearer, too, a little further.

Insider.xml by itself does not catch up, we need to explicitly indicate that our module is going to make changes to the markup - for this we will create another node in the config.xml:

< frontend > <!-- , -->
< layout > <!-- - -->
< updates > <!-- , - -->
< insider > <!-- -->
< file > insider.xml </ file > <!-- , -->
</ insider >
</ updates >
</ layout >
</ frontend >


Now run back to the site and press F5 - our “hello” should appear somewhere below on any page of the frontend. He has not appeared? Are you sure you don't use the cache? Let me remind you that you can disable it in the admin area: System -> Cache Management.
I don’t know about you, but I, for example, don’t like that our record dangles at the very bottom. Let's throw it higher? In order to do this, you need to tell a little about the mechanism for creating markup - before displaying the page, Magento parses all the .xml files from the layout folder (of course, only those that directly affect the display of the current page) and collects one of them, but it is large. Using the attributes 'before' and 'after' we can tell the parser, respectively before or after which block to insert ours. If you place the block inside the <reference> node, then you can “cling” to a specific block — insert yourself inside. Virtually every block has an attribute 'name' - this is how it defines itself in the general markup and it can be asked to “move over” or “let it sleep”.
In order to know which block in which place determines how itself it is necessary to study the markup files of other modules. For example, if we look at page.xml, we will see there a block with the name 'after_body_start' with the label “Page Top”. It seems that this is what you need! Perhaps we will cling to him. To do this, put the block description in the insider.xml file inside the <reference> node:

< reference name ="after_body_start" > <!-- after_body_start, ! -->
< block type ="core/template" name ="insider" template ="insider/disclose.phtml" />
</ reference >


Well, that's better. But something our “hello” stopped inspiring me. Let's add some stupid <div> to our template, maybe we’ll add some class, and we’ll describe the style in .css, and then add some more dynamics via .js. I leave the code to your taste, I describe only how to make it work. Create a file in the skin / frontend / base / default / css / insider / insider.css folder and write some code there that should affect the display style disclose.phtml. In order to add this file to the page, you need to edit the markup file (insider.xml) and add the following inside the <default> node:

< reference name ="head" > <!-- HTML <head> -->
< action method ="addCss" >< stylesheet > css/insider/insider.css </ stylesheet ></ action >
</ reference >


What is this action? The <action> node allows you to call the block method and pass parameters to it directly from the markup file. This is a very powerful thing. If you return to page.xml you will see that the block “page / html_head” has been signed by the name “head”, and this, in turn, is the class called Mage_Core_Page_Block_Html_Head. If you look at its implementation, you can easily find the addCss () method. Now you understand what a wild thing this is - <action>?
Let's get JavaScript done. Create a js / insider / insider.js file and spice it up with code to taste. You need to add almost the same way as CSS - in the same <reference> node that refers to the “head” create the following entry:

< action method ="addJs" >< script > insider/insider.js </ script ></ action >


I think there is nothing to explain here - by analogy everything should be clear.

All this, of course, is good, but our extension still does not make a damn good thing. It's time to fix this situation! In order to influence the behavior of the template and transfer information to it, we need our own block. Create a file Linker / Insider / Block / Disclose.php. Fill it with the following content:

Copy Source | Copy HTML<br/> <?php <br/> class Linker_Insider_Block_Disclose extends Mage_Core_Block_Template<br/>{<br/> // Magento __construct() ( "_") <br/> function _construct()<br/> {<br/> // ( Zend Framework) <br/> $request = $this ->getRequest();<br/> // , <br/> $this ->module = $request ->getModuleName();<br/> $this ->controller = $request ->getControllerName();<br/> $this ->action = $request ->getActionName();<br/> }<br/>}<br/> <br/>


In order for it to be able to pass this information into a template, we need to change the block type in the insider.xml file:

< block type ="insider/disclose" name ="insider" template ="insider/disclose.phtml" />


But there is one caveat: it just does not work. Remember, we have our own namespace, and we haven’t described it anywhere else? You need to do this, otherwise the system will not be able to find the class of the block. Make changes to config.xml:

< global > <!-- , -->
< blocks > <!-- -->
< insider > <!-- , Mage::getModel('insider/') -->
< class > Linker_Insider_Block </ class > <!-- Linker_Insider_Block_ -->
</ insider >
</ blocks >
</ global >


The template, in fact, is rendered by the method of the block itself, which means it has access to all the fields and methods of this block, even to the protected and private ones. You can display the contents of variables in this way (write in disclose.phtml):

Copy Source | Copy HTML<br/> <br/>Module: <?php echo $this ->module; ?> <br/>Controller: <?php echo $this ->controller; ?> <br/>Action : <?php echo $this ->action; ?> <br/> <br/>


Now imagine that we suddenly wanted to beautifully display and fill our block with data. Basically, we will have a name = value scheme here, so the easiest way to do this is through the form. For the form relies a separate file, it will be Linker / Insider / Block / Disclose / Form / Controller.php. Let the name not confuse you - it will be just a form with information about the current controller / module / action, here is the file contents:

Copy Source | Copy HTML<br/> <br/> <?php <br/> class Linker_Insider_Block_Disclose_Form_Controller extends Varien_Data_Form<br/>{<br/> public function __construct()<br/> {<br/> // HTML id <br/> parent::__construct( array ( 'id' => 'insider-form-controller' ));<br/> <br/> // HTML id , ( Varien_Data_Form_Element_*), <br/> // (ZF- ) <br/> $this ->addField( 'module' , 'text' , array ( 'label' => 'Module' ));<br/> $this ->addField( 'controller' , 'text' , array ( 'label' => 'Controller' ));<br/> $this ->addField( 'action' , 'text' , array ( 'label' => 'Action' ));<br/> }<br/>}<br/> <br/>


Done, now let's fill it with data. According to the MVC concept, the data should come from the model, so we will have to move the contents of our block into the model. Create a file Linker / Insider / Model / Insider.php:

Copy Source | Copy HTML<br/> <br/> <?php <br/> class Linker_Insider_Model_Insider extends Mage_Core_Model_Abstract<br/>{<br/> public function _construct()<br/> {<br/> // , getRequest(), : <br/> $request = Mage::app()->getRequest();<br/> $module = $request ->getModuleName();<br/> $controller = $request ->getControllerName();<br/> $action = $request ->getActionName();<br/> <br/> // setData() protected $_data <br/> // , id , <br/> // , <br/> $this ->setData( 'controller' , array (<br/> 'module' => $module ,<br/> 'controller' => $controller ,<br/> 'action' => $action ,<br/> ));<br/> }<br/>} <br/>


Now we can fill the form with data from the model:

Copy Source | Copy HTML<br/>...<br/> $this ->addField( 'action' , 'text' , array ( 'label' => 'Action' ));<br/> $model = Mage::getSingleton( 'insider/insider' );<br/> $this ->addValues( $model ->getData( 'controller' ));<br/> <br/>


But this will not work either. The system, although it knows where to look for blocks starting at 'insider', but does not know where to look for such models. We explain to it in the config.xml file, the <global> node:

<!-- -->
< models >
< insider >
< class > Linker_Insider_Model </ class >
</ insider >
</ models >



Now we can turn to the model, but now we need to bring our form to the eyes. To do this, change the contents of Disclose.php as follows:

Copy Source | Copy HTML<br/> <br/> <?php <br/> class Linker_Insider_Block_Disclose extends Mage_Core_Block_Template<br/>{<br/> public $forms = array ();<br/> <br/> protected function _construct()<br/> {<br/> $this ->forms = array (<br/> 'controller' => new Linker_Insider_Block_Disclose_Form_Controller(),<br/> );<br/> }<br/>}<br/> <br/>


And in the template, delete the three lines that we added earlier and replace it with something simpler:

Copy Source | Copy HTML<br/> <br/> <?php echo $this ->forms[ 'controller' ]->toHtml(); ?> <br/>


Okay, everything is ready. In order to give the form a "console" view, you can write your renderer. This is not a very difficult task and everything should be clear from the source code, so I’ll skip this step here.
Now let's do something more serious - it would be useful to get a list of all the blocks that are involved in displaying the current page. You can do this from the model like this:

Copy Source | Copy HTML<br/> // , <br/> $layout = Mage::app()->getLayout();<br/> foreach ( $layout ->getAllBlocks() as $name => $block ) {<br/> $class = get_class( $block );<br/> // , , Mage_Core_Block_Abstract, <br/> if (method_exists( $block , 'getTemplate' )) {<br/> /* @var $block Mage_Core_Block_Template */ <br/> $template = $block ->getTemplate();<br/> } else {<br/> $template = false ;<br/> }<br/> <br/> $blocks [] = array (<br/> // eg Mage_Catalog_Product_List <br/> 'class' => $class ,<br/> // eg catalog/product_list.phtml <br/> 'template' => $template ,<br/> // eg "head" <br/> 'name' => $name ,<br/> // eg /home/user/magento/app/code/core/Mage/Catalog/Product/List.php <br/> 'blockFile' => mageFindClassFile( $class ),<br/> // eg /home/user/magento/app/design/frontend/base/default/template/catalog/product_list.phtml <br/> 'templateFile' => $template ? $block ->getTemplateFile() : '' ,<br/> );<br/>}<br/> <br/>


Create a form, fill it with this data and render it in the template I trust you. Well, that is, it's all available in the source code. Why then is this piece here? In order for you to see the inconsistency of this approach. In the list of blocks we will get ourselves (ok, not great sadness), but also we will not get it all (stop-stop, this is why?). At least because our block is one of the first to render, almost immediately after the <body> tag. Consequently, the model will be able to pull out information only about blocks that are already there, and this is nothing at all. How to overcome this obstacle? The first thing that comes to mind is to see which block is rendered last, and follow it with the help of 'after'. But if you think about it, it will not work, if only because another block may appear - which will be rendered even later, or it will simply cling to us with the help of 'after' - and we will not see it. No, another level is needed here. During the processing of a request, Magento generates a sufficiently large number of events for which it is also possible to “catch hold of” (almost like blocks). What about the event that is reported literally before the page is sent to the browser? Sounds great! All blocks are rendered, they have already done everything they could and in our hands, in fact, ready HTML. It remains only to pull out (now completely accurate) information about all the blocks involved and add your HTML to the final output. What is needed for this? First, to prohibit the rendering automatically (in the list of blocks we are somehow in a row, but we are standing there - and the system will definitely try to render us properly at some stage). The easiest way is to insert a stub instead of the method that is called to generate and return HTML code. Update our Disclose.php with a new method:

Copy Source | Copy HTML<br/> <br/> public function renderView()<br/>{<br/> return '' ;<br/>}<br/> <br/>


And now we will create our own, which we will be able to call upon request - exactly when we need:

Copy Source | Copy HTML<br/> <br/> public function renderSelf()<br/>{<br/> return parent::renderView();<br/>}<br/> <br/>


Well, now we will make it to be called at a strictly specific moment - according to the event about which we spoke. The event has a name that sounds like 'controller_front_send_response_before'. In order to “cling” to it, we need to create our own observer. Create a file Linker / Insider / Model / Observer.php with the following contents:

Copy Source | Copy HTML<br/> <br/> class Linker_Insider_Model_Observer<br/>{<br/> public function renderSelf(Varien_Event_Observer $observer )<br/> {<br/> // , - - ? <br/> // , ( - fire') <br/> if ( $block = Mage::app()->getLayout()->getBlock( 'insider' )) {<br/> // HTML- <br/> $insiderHtml = $block ->renderSelf();<br/> // front controller, HTML <br/> $front = $observer ->getData( 'front' );<br/> // <br/> $front ->getResponse()->append( 'insider' , $insiderHtml );<br/> }<br/> }<br/>}<br/> <br/>


Now we have to describe what our observer should respond to. This is done in config.xml, the <global> node:

< events > <!-- -->
< controller_front_send_response_before > <!-- - -->
< observers > <!-- ! -->
< insider_renderself > <!-- -->
< class > insider/observer </ class > <!-- ... -->
< method > renderSelf </ method > <!-- -->
</ insider_renderself >
</ observers >
</ controller_front_send_response_before >
</ events >


That's all I wanted to talk about. I hope this will help someone to understand some of the internal schemes of the work of Magento.
In order to avoid reproaches in samopiare, I do not cite any references. Those who want to evaluate the usefulness of the extension can find it on Magento Connect.

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


All Articles