Introduction
This article aims to show developers how to give priority to one class over another during the decoration process. It also shows how one module can require another one for proper work.
Understanding the problem
Imagine the situation: you are writing a module that correlates with another one. A typical case - you want to override a customer menu on the storefront.
The top menu is defined in the core class \XLite\View\Menu\Customer\Top
(see the article about class names):
By default, top menu is empty and the SimpleCMS module overrides the \XLite\View\Menu\Customer\Top
class by \XLite\Module\CDev\SimpleCMS\View\Menu\Customer\Top
one and actually creates the top menu we see.
If we want to replace existing menu and show our items no matter whether the module SimpleCMS is enabled or not, we must be sure that our module decorates the core class \XLite\View\Menu\Customer\Top
after the SimpleCMS' class, otherwise SimpleCMS will just ignore our implementation of the menu.
Solution
Create a module. We create it with developer ID XCExample and module ID OverridingTopMenu.
To define our version of top menu in store-front, we need to decorate the method
defineItems()
of the class\XLite\View\Menu\Customer\Top
.We create the file
classes/XLite/Module/XCExample/OverridingTopMenu/View/Menu/Customer/Top.php
with the following content:<?php
namespace XLite\Module\XCExample\OverridingTopMenu\View\Menu\Customer;
class Top extends \XLite\View\Menu\Customer\Top implements \XLite\Base\IDecorator
{
protected function getMyItems()
{
$return = array();
$return[] = array (
'url' => 'http://google.com',
'label' => 'Google menu',
'controller' => false,
);
return $return;
}
protected function defineItems()
{
return $this->getMyItems();
}
}We decorate the method
defineItems()
, so it would return items declared in the methodgetMyItems()
. This is straightforward.If we leave this code as is, our module will work properly with the module SimpleCMS disabled, but if it is enabled, SimpleCMS will still override our change.
In order to overcome the situation, we create the file
classes/XLite/Module/Tony/OverridingTopMenu/View/Menu/Customer/TopAfterSimpleCMS.php
with the following content:<?php
namespace XLite\Module\XCExample\OverridingTopMenu\View\Menu\Customer;
/**
* @Decorator\Depend ("CDev\SimpleCMS")
*/
class TopAfterSimpleCMS extends \XLite\View\Menu\Customer\Top implements \XLite\Base\IDecorator
{
protected function defineItems()
{
return $this->getMyItems();
}
}Note: The viewer class name can be whatever you want – it does not have to be TopAfterSimpleCMS, but its name has to be the same as the .php filename it is declared in.
As you can see, its implementation is similar, we are overriding the method
defineItems()
that calls the methodgetMyItems()
which was declared in our first viewer class. However, there is the directive/**
* @Decorator\Depend ("CDev\SimpleCMS")
*/which tells X-Cart that this class (TopAfterSimpleCMS) must decorate the
\XLite\View\Menu\Customer\Top
one only after all viewer classes of the SimpleCMS module. On the other hand, if there is no SimpleCMS module enabled in the system, then this decoration will never happen, but our first class will still apply the needed change. The directive@Decorator\Depend ()
has to be put into PHP comments according to DocBlock standard; in other words, it must start with the/**
construction, end with the*/
construction, and every line between those must start with the*
symbol. If the@Decorator\Depend
directive is put in any other format, X-Cart will not be able to fetch and use it.Check results in store-front. You should only see the Google menu item in the storefront, no matter if the module SimpleCMS is enabled or not.
All available directives
X-Cart has three directives that can be used in order to define an order of class decorators:
@Decorator\Depend
directive means that decoration will happen only if such module is enabled.@Decorator\After
directive means that decoration will happen after decorations of such module. If the module is not enabled, such directive will be ignored and our decorator will work as regular one. Essentially, we could have written the same mod using@Decorator\After
directive and using only one class, instead of two.@Decorator\Before
is similar to@Decorator\After
, but X-Cart will put your decorators before decorators of the given module. If there is no given module, the directive will be ignored.
Examples of usage:
@Decorator\Before ("CDev\SimpleCMS")
;@Decorator\After ({"CDev\SimpleCMS", "CDev\Coupons"})
, if you need to specify dependency on several modules;@Decorator\Depend("!CDev\SimpleCMS")
, if you need to decorate a class in case the module is NOT in the system.
Module pack
You can download this sample module from here: XCExample-OverridingTopMenu-v5_3_0.tar
What if one module requires another one to work properly
In this case, you need to make the entire module to be dependent on that other one.
For example, we want to write a module that can work only if the module CDev\SimpleCMS is installed and enabled. It can be true if our module is an adjustment of the SimpleCMS.
Here is how we can achieve that.
We create the module with developer ID XCExample and module ID DependenciesDemo.
We add the following method to the file
Main.php
of your module:public static function getDependencies()
{
return array('CDev\SimpleCMS');
}This method tells X-Cart that our 'DependenciesDemo' module cannot work without the module SimpleCMS enabled. X-Cart will not even allow to enable it, if SimpleCMS is not active.
Note that if the 'DependenciesDemo' module is active, you cannot disable the 'SimpleCMS' module either.
Second module pack
Еxample of the DependenciesDemo module can be downloaded from here: