Skip to main content
page last edited on 5 October 2022

Module System

Version: 5.5

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 1000sorting 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.