X-Cart core can be extended with modules that introduce new and customize existing functionality.
Each module has an Author ID (AuthorId) and Module ID (ModulesId). AuthorId and ModuleId are letter-first alpha-numeric sequences and use the UpperCamelCase notation.
Directory Structure
The module code and resources are located in the <XCart>/modules/{AuthorId}/{ModuleId}
folder.
The module directory structure generally adheres to the Symfony Bundle Structure.
Additional folders:
docs
- changelog;public
- regular location for resources storage with a specific folder structure inside;templates
- regular location for templates storage with a specific folder structure inside.
Public and Templates
Templates and resources are divided into interfaces and zones. Generally, there are three interfaces (web
, mail
, pdf
) and two zones (admin
and customer
). The common
zone is a parent for all other zones. The mail
and pdf
zones override the web
zone.
Manifest
The <XCart>/modules/{AuthorId}/{ModuleId}/config/main.yaml
file is required for all modules:
version: 5.5.0.0
type: common # the module type, "common" is the mostly used type, it can also be "skin", "payment", or "shipping".
authorName: 'X-Cart team' # a readable author-name
moduleName: 'Sample Module' # a readable module name
description: 'The sample module' # module's brief description
minorRequiredCoreVersion: 0 # the module minor core version (the 3rd number)
dependsOn: { } # a list of module IDs (in the '{AuthorId}-{ModuleId}' format) with dependencies on this module
incompatibleWith: { } # a list of module IDs (in the '{AuthorId}-{ModuleId}' format) incompatible with this module
showSettingsForm: false # the module settings page availability
Modules can also use the following images as interface icons.
<XCart>/modules/{AuthorId}/{ModuleId}/config/images/icon.png
<XCart>/modules/{AuthorId}/{ModuleId}/config/images/iconAlt.png
Module-Core Interaction
General ways of the module's interaction with the core are as follows:
- Code injection through code generation
- Symfony DI (decoration, event-dispatcher, etc.)
- ViewLists
Code Injection through Code Generation
The executed code is generated base on the core code from the <XCart>/classes
folder and the modules code from <XCart>/modules/{AuthorId}/{ModuleId}/src
.
Suppose the core contains a <XCart>/classes/XLite/Example.php
file with the following class defined:
<?php
namespace XLite;
class Example
{
public function someMethod()
{
return 'SomeMethodResult';
}
}
To change the someMethod
method behavior, create an undefined module class and specify a specific annotation there (but better copy the original class FQCN changing XLite
to {AuthorId}/{ModuleId}
).
For example, we have the XCExample\Module1
module with the <XCart>/modules/XCExample/Module1/src/Example.php
file.
<?php
namespace XCExample\Module1;
use XCart\Extender\Mapping\Extender;
/**
* @Extender\Mixin
*/
class Example extends \XLite\Example
{
public function someMethod()
{
return parent::someMethod() . ' someAdditionalResult';
}
}
As a result of the code generation, three classes will be created:
<?php
namespace XLite;
class Example extends \XCExample\Module1\Example
{
}
<?php
namespace XCExample\Module1;
use XCart\Extender\Mapping\Extender;
/**
* @Extender\Mixin
*/
class Example extends \XLite\ExampleAncestor
{
public function someMethod()
{
return parent::someMethod() . ' someAdditionalResult';
}
}
<?php
namespace XLite;
class ExampleAncestor
{
public function someMethod()
{
return 'SomeMethodResult';
}
}
This way, the XCExample\Module1\Example
class overrides the original implementation, but the rest of the code will still use the original class name.
Symfony DI
To interact with Symfony DI, you must declare a module as a Symfony Bundle. For this purpose, create the {ModuleId}Bundle
class and store it in the <XCart>/modules/{AuthorId}/{ModuleId}/src/{ModuleId}Bundle.php
file.
For example, we have the XCExample\Module1
module with the <XCart>/modules/XCExample/Module1/src/ModuleBundle.php
file.
<?php
namespace XCExample\Module1;
use Symfony\Component\HttpKernel\Bundle\Bundle;
final class Module1Bundle extends Bundle
{
}
As a result, the module will become an efficient Symfony bundle. You can upload the <XCart>/modules/XCExample/Module1/config/service.yaml
file to the core to extend basic Symfony facilities after X-Cart configuration. That is not typical for Symfony and is used to allow modules to adjust X-Cart configuration.
ViewLists
Besides adding templates and widgets to lists (see Rendering), you can also use the module to adjust the templates and widgets lists. For this purpose, create a module as a Symfony Bundle and then create one of the following event handlers:
xcart.service.view-list.collect-mutation.before
xcart.service.view-list.collect-mutation
xcart.service.view-list.collect-mutation.after
xcart.service.view-list.apply-mutation.before
The \XCart\Event\Service\ViewListMutationEvent
object used to adjust the lists arrives in the event handler.
public function addMutations(array $mutations): void
{
foreach ($mutations as $subject => $mutation) {
$this->addMutation($subject, $mutation);
}
}
/**
* The mutation can be in the following formats:
* 1. To remove from a single list:
* [
* {remove_definition}
* ]
* 2. To remove from the single list and insert to the single list (move)
* [
* {remove_definition},
* {insert_definition}
* ]
* 3. To remove from several lists or/and insert to several lists
* [
* 'to_remove' => [{remove_definition}, ...],
* 'to_insert' => [{insert_definition}, ...]
* ]
* {remove_definition}: 'list_name' | ['list_name'{, 'interface'}]
* {insert_definition}: 'list_name' | ['list_name'{, (int) weight{, 'interface'}}]
*
* @param string $subject FQCN or Template relative path
* @param array $mutation Mutation definition
*/
public function addMutation(string $subject, array $mutation): void
For example:
Handler registration (<XCart>/modules/XCExample/Module1/config/services.yaml
)
services:
XCExample\Module1\Core\EventListener:
tags:
- { name: kernel.event_listener, event: xcart.service.view-list.collect-mutation, method: onCollectViewListMutations }
Handler:
namespace XCExample\Module1\Core;
use XLite;
use XCart\Event\Service\ViewListMutationEvent;
final class EventListener
{
public function onCollectViewListMutations(ViewListMutationEvent $event): void
{
$event->addMutations([
'layout/content/main.location.twig' => [
ViewListMutationEvent::TO_REMOVE => [
['layout.main', XLite::INTERFACE_WEB, XLite::ZONE_CUSTOMER ],
],
ViewListMutationEvent::TO_INSERT => [
['center.top', 1000, XLite::INTERFACE_WEB, XLite::ZONE_CUSTOMER ],
],
]
])
}
}
As a result, the layout/content/main.location.twig
template will be deleted from the layout.main
list and added to the center.top
list with the 1000
sorting weight.
To check what modules are enabled, use the XCart\Domain\ModuleManagerDomain
service.
services:
XCExample\Module1\Core\EventListener:
arguments:
$moduleManagerDomain: '@XCart\Domain\ModuleManagerDomain'
tags:
- { name: kernel.event_listener, event: xcart.service.view-list.collect-mutation, method: onCollectViewListMutations }
namespace XCExample\Module1\Core;
use XCart\Domain\ModuleManagerDomain;
use XCart\Event\Service\ViewListMutationEvent;
use XLite;
final class EventListener
{
private ModuleManagerDomain $moduleManagerDomain;
public function __construct(
ModuleManagerDomain $moduleManagerDomain
) {
$this->moduleManagerDomain = $moduleManagerDomain;
}
if ($this->moduleManagerDomain->isEnabled('CDev-GoSocial')) {
$event->addMutations([
'modules/CDev/GoSocial/product/details/parts/common.share.twig' => [
ViewListMutationEvent::TO_REMOVE => [
['product.details.page.info', XLite::INTERFACE_WEB, XLite::ZONE_CUSTOMER ],
],
ViewListMutationEvent::TO_INSERT => [
['product.details.page.image', 20, XLite::INTERFACE_WEB, XLite::ZONE_CUSTOMER ],
],
],
]);
}
}
As a result, the changes will apply only if the CDev-GoSocial
module is enabled.