Legacy architecture
In the old architecture that is used in the GX2 and GX3 versions of the shop software, the MainFactory
class acted
as the only component which provided all essential classes/services that were needed to execute the shop software.
Compared to the new architecture, there are some similarities to the DI Container but without describing how
components should be built and how they interact or depend on each other.
The focus of the MainFactory was to allow overloading classes and to provide a powerful way of extending the system. The main problem of it was that any incorrect usage of it would lead to issues that could break the whole system and would be hard to debug.
Notice
This tutorial explains the old overloading and extender system that we used in the old architecture. The new GX4 architecture does not support this. There are still some parts of the shop software that are using the old architecture and that's why we still provide this information.
There are two possibilities to change existing PHP classes in the shop system. The first possibility is class-overloading and allows you to overload the behaviour of an existing class with your logic. The second possibility, the extending system, can hook into existing program logic and execute your own logic.
Class-Overloading
Class-Overloading increases the update-security of code changes. Class-Overloading allows you to change the behaviour of PHP classes without modifying the class itself. Since the changes aren't made in the original class, they are not lost when original files get replaced with new ones.
Changes made by Class-Overloading can be, e.g. extensions of existing methods, adding new methods, replacing methods (or even the whole class). Thus, all possibilities are available to change the class's behaviour to your wishes.
How to overload a class
To overload an existing class, a new class following some specific rules needs to be created. You need to create a
subdirectory inside the GXModules/<Vendor>/<Module>/Admin/Overloads
or GXModules/<Vendor>/<Module>/Shop/Overloads
directory with the name of the class you want to overload (e.g. 'breadcrumb'). Then you create a PHP file
inside this directory with a new unique class name (e.g. CustomizedBreadcrumb.inc.php
). The naming convention
for these files is: <ClassName>.inc.php
.
The following example shows an overload CustomizedBreadcrumb
, which extends the trail
method of the original
breadcrumb
class. The entries in the breadcrumb will be enclosed in square brackets and the separator replaced
with a pipe, unless the given separator differs from the standard. For this purpose a file
GXModules/<Vendor>/<Module>/Shop/Overloads/breadcrumb/CustomizedBreadcrumb.inc.php
is created with the following
content:
class CustomizedBreadcrumb extends CustomizedBreadcrumb_parent
{
/**
* Returns a modified breadcrumb as a string. The items are in brackets.
*
* They are separated by a pipe if the standard separator is a dash. Otherwise
* the given separator is used.
*
* @param string $separator Optional, the breadcrump separator string.
*
* @return string Returns the modified breadcrump string.
*/
public function trail($separator = ' - ')
{
foreach($this->_trail as &$item)
{
$item['title'] = '[' . $item['title'] . ']';
}
if($separator === ' - ')
{
$separator = ' | '; // Replace the original separator.
}
return parent::trail($separator);
}
}
In the example above, you can see that the CustomizedBreadcrumb
inherits classes from CustomizedBreadcrumb_parent
,
which is very important for the overloading system. The parent class always follows the naming convention:
<ClassName>_parent
. The MainFactory starts the inheritance for overloading based on the folder name breadcrumb
,
in which the overload class is located. Overloading the same class multiple times is possible. To do this, place
all overloads in the same specific directory.
Technically the overloading corresponds to an inheritance chain. The classes all inherit from each other in the order they are located in the file system.
Which classes can be overloaded?
All classes created by the MainFactory can be overloaded. This covers almost all classes of the old architecture. The overloadable classes are located in the following directories including their subdirectories:
admin/includes/classes
admin/includes/gm/classes
gm/classes
GXEngine
GXMainComponents
includes/classes
(exceptincludes/classes/scssphp
)includes/modules/order_total
includes/modules/payment
includes/modules/shipping
system/classes
system/core
system/extender
system/overloads
Excluded are classes that are only used for type validation (TypeHints) and therefore are not created via the
MainFactory. These classes are located in the directory GXEngine/Shared/Types
.
Especially worth mentioning are the extenders, which can be used by Class-Overloading.
You can find some sample overloads to illustrate the functionality in the
GXMainComponents/overloads/_samples
directory. They can be activated by moving the respective folder one level up.
Overloading a class with namespaces
If you want to overload a class within a namespace, the namespace must also be mapped into the folder structure of
the overload. If, for example, you want to overload the class Customer
in the namespace Gambio\Orders\ValueObjects
with a new overload called OverloadedCustomer
, then this class must be placed like this:
GXModules/<Vendor>/<Module>/Shop/Overloads/Gambio/Orders/ValueObjects/Customer/OverloadCustomer.inc.php
What else should be considered
- A few classes occur twice in the system, for example the
order
, which can be found both in theincludes/classes
andadmin/includes/classes
directory. If you want to overload the class from theadmin/includes/classes
directory, you need to useadmin-order
as the directory name, instead oforder
. Thisadmin-
prefix works exclusively for the following duplicate classes:language
messageStack
order
shoppingCart
splitPageResults
- Since the same class can be overloaded several times, you have to be careful not to interrupt the inheritance chains. So when extending a method, make sure to call the parent method.
- After creating an overload, the cache for module information must be cleared in Gambios Admin panel (Toolbox > Cache). Only then a new overload becomes effective.
- Changes of original classes, e.g. caused by shop updates, may influence the functionality of an overload. The behaviour may no longer be as expected, or errors may occur if, for example, methods or entire classes no longer exist. If original classes change, it is necessary to check whether the overload still works.
- The class name of the overload must be chosen uniquely throughout the system. Otherwise, the MainFactory cannot instantiate the overload, since class names must be unique in PHP. Namespaces are taken into account. Identical class names in different namespaces are distinguished and are therefore allowed.
- The class name must match the file name.
- The same namespace must be specified in the overload and in the class to be overloaded.
Extender system
The extender system is a hook-point system and is used for convenient and update-safe adaptation and expansion of the
shop system. It enables module developers to inject content or behaviour at predefined points. Using the
OrderExtender
, it's possible to add additional information to the order detail view. An extender not only allows
content to be placed at specific locations but also allows functions to be executed at particular points in the code.
For example, if you have added another input field to the product processing, you need to store this inputs content.
This can be achieved by overloading the AdminCategoriesExtender
.
The example below shows how to use the extender system. The example adds some order details to the confirmation page that is displayed after a successful order.
Choosing the right extender
The currently existing extenders can be found in this list.
For our example, a suitable extender already exists: CheckoutSuccessExtenderComponent
. With this extender, you can
execute source code after a successful order and place additional HTML code into the confirmation page.
Page manipulation by overloading the extender
The extenders are based on the Class-Overloading system. For this purpose, many extenders have individual specifications as to the form in which they expect content or provide data.
To display your content on the order confirmation page, follow these steps.
Create a subdirectory for the overload
First of all, a directory with the same name as the extender class must be created in your Overloads
directory: GXModules/<Vendor>/<Module>/Shop/Overloads/CheckoutSuccessExtenderComponent
. Then the file
MyOrderDetails.inc.php
can be created inside this directory.
Create your overload and implement the proceed
method
Again, the class name must correspond to the name of the file. The parent class name for each overload follows the
convention: <ClassName>_parent
.
class MyOrderDetails extends MyOrderDetails_parent {
}
Every extender uses the proceed
method. This method executes the whole logic of the overload. When using extenders
it's important that you call the proceed
method of the parent class using parent::proceed()
, otherwise the overload
system will not work correctly.
Using this extender, the ID of the completed order can be taken from the variable $this->v_data_array['orders_id']
.
To access the data of the order later on an object of the type IDType
must be instantiated using
new IdType($this->v_data_array['orders_id'])
, because the OrderReadService
expects an object of this type.
The proceed
method should look like this:
public function proceed()
{
parent::proceed();
$orderId = new IdType($this->v_data_array['orders_id']);
}
There are two different ways to display HTML code.
The first option allows you to add content at any position. For this, a Smarty variable (e.g. {$myOrderDetails}
)
must be added to the checkout_success
template. Then, $this->v_output_buffer['<variable_name>'] = '<html_code>';
can be used to assign a Smarty variable.
The second option is to add HTML code in a fixed position. With $this->v_output_buffer[] = '<html_code>';
the HTML
code is added directly below the actual confirmation message.
For simplicity's sake, we will use the second option for this example and use helper method
createOrderDetails(IdType $orderId)
, to generate the HTML code to be added. Adding the order details should look
like this:
$this->html_output_array[] = $this->createOrderDetails($orderId);
In the example code below, you can see the whole MyOrderDetails
class. Within the createOrderDetails
method, the
OrderReadService
is used to read the order information from the database, and the HTML code generated.
class MyOrderDetails extends MyOrderDetails_parent {
public function proceed()
{
parent::proceed();
$orderId = new IdType($this->v_data_array['orders_id']);
$this->html_output_array[] = $this->createOrderDetails($orderId);
}
private function createOrderDetails(IdType $orderId)
{
$orderReadService = StaticGXCoreLoader::getService('OrderRead');
$order = $orderReadService->getOrderById($orderId);
$customerAddressBlock = $order->getCustomerAddress();
$billingAddressBlock = $order->getBillingAddress();
$deliveryAddressBlock = $order->getDeliveryAddress();
$html = '<h2>Bestelldetails (#' . $order->getOrderId() . ')</h2>'
. '<div class="row">'
. '<div class="col-sm-4"><b>Kundenadresse:</b><br /> '
. $customerAddressBlock->getFirstname() . ' '
. $customerAddressBlock->getLastname() . '<br />'
. $customerAddressBlock->getStreet() . ' '
. $customerAddressBlock->getHouseNumber() . '<br />'
. $customerAddressBlock->getPostcode() . ' '
. $customerAddressBlock->getCity() . '<br />'
. '</div>'
. '<div class="col-sm-4"><b>Rechnungsadresse:</b><br /> '
. $billingAddressBlock->getFirstname() . ' '
. $billingAddressBlock->getLastname() . '<br />'
. $billingAddressBlock->getStreet() . ' '
. $billingAddressBlock->getHouseNumber() . '<br />'
. $billingAddressBlock->getPostcode() . ' '
. $billingAddressBlock->getCity() . '<br />'
. '</div>'
. '<div class="col-sm-4"><b>Lieferadresse:</b><br /> '
. $deliveryAddressBlock->getFirstname() . ' '
. $deliveryAddressBlock->getLastname() . '<br />'
. $deliveryAddressBlock->getStreet() . ' '
. $deliveryAddressBlock->getHouseNumber() . '<br />'
. $deliveryAddressBlock->getPostcode() . ' '
. $deliveryAddressBlock->getCity() . '<br />'
. '</div>'
. '</div>';
return $html;
}
}
The complete overload/extender can be found here.
Clearing the module cache
Because the extender system also uses the overloading system, you need to clear the module cache after adding or implementing an extender. This can be done in the Gambio Admin (Toolbox > Cache).
Result of this example
Using this sample extender the result should look like this:
Additional examples
Some additional example can be downloaded here.