🎉 New Super Feature! Elementor Integration Added to our eCommerce Mobile App.

Learn PrestaShop Symfony Basics With an Example

If you do not know what are Symfony container, Symfony services and the dependency injection concepts, I recommend reading this tiny article to gain a tangible understanding of these concepts. You need to know more and read more, but it can be a good start. Here is a simple example to introduce you the concept.

Scenario

The goal is to remove the two statistic blocks on the Order page of PrestaShop BO.
After uninstalling analytical and statistical modules (probably, because of performance) you may see that the Conversion rate and Net profit show an infinite value (or you may have noticed accidentally). That's because Conversion rate and Net profit KPIs get their data from those modules.

How to Fix?

To solve this issue we should either install those modules again, or remove the widgets from Order page in Back Office. I'm going to show you a different way to do that (you might remove the HTML completely in twig, or you might use KpiRowModifier hook.).

What we are going to do in this tutorial?

In this tiny article, I'm going to to show you how Symfony works in PrestaShop with a simple example: removing the above mentioned blocks from Orders page in Back-office. Before going further we need to know a little more about new PrestaShop controllers and Symfony concepts. Additionally, you can also see how I traced PrestaShop code to find the target classes and files.

Symfony and New Controllers

Unlike the front-office, most of the back-office pages have been migrated to a new way of coding in the past years. I'm talking about Symfony and dependency injection, you probably knew that.

Say bye to the "new" keyword

We always use software design patterns, best practices and software design principles to make our codes understandable, flexible and maintainable. One of these principles in SOLID is Dependency Inversion. Dependency injection (DI) frameworks help us building loosely-coupled components by depending on abstractions (interfaces) not concrete classes. It means we do not need to directly create new instance of classes, we request IoC container and it provides us with a new object (it can be a newly created object, or an old object which is kept in IoC container).

Dependency Injection in PrestaShop

Most of the back-office pages are backed by the modern controllers, which have Symfony support. When Symfony comes in, we expect to see dependency injection (DI) and an object pool/container (we usually call it IoC container), and also a way to inject (to bind to wire) those objects (in Symfony framework we call them services) in together at run-time, through a file (XML or Yaml) or through annotations.

In the most programming languages like java the functionality of adding metadata to classes, fields or methods is called annotation. FYI and to research more, the process of reading these metadata information in run-time will be done by the reflection technology.

As far as I know, Symfony 5.2 introduced Autowiring with Attributes (attributes introduced in PHP 8, a new way to annotate), however it already has supported annotations (Doctrine Annotations).

Nevertheless, in PrestaShop there are yaml files to put the wiring information to bind and connect objects (services). But why we need these info?

Because, PrestaShop back-office is changing more and more to support Symfony-style. So, if we want to modify the existing features and to develop new features we need to know about the concepts.

Oh! what about the widgets in BO Order page..

Okay! Let's remove those two KPIs!

Now, we know that most of the PrestaShop back office pages have Symfony components. Let's go to Orders BO controller and indextAction method and take a look at the first line:

public function indexAction(Request $request, OrderFilters $filters)
  {
    $orderKpiFactory = $this->get('prestashop.core.kpi_row.factory.orders');
    $orderGrid = $this->get('prestashop.core.grid.factory.order')->getGrid($filters);

    $changeOrderStatusesForm = $this->createForm(ChangeOrdersStatusType::class);
.
.

This line of code:

    $orderKpiFactory = $this->get('prestashop.core.kpi_row.factory.orders');

is responsible to build the top KPI elements (the four default widgets) of the Order page in back-office. Actually, we are requesting the container to give us an object with this line of code. $this->get is a reference to our container, and gets an argument which is a key to our registered service.

The container gets the key and returns back an instance of the requested object (creates an object from that concrete class). Simply, think of the container a pool of objects that holds the application's objects within it. You can read more about these concepts if you search for these keywords, dependency injection, object pooling, IoC container, inversion of control, factory design pattern, single tone and prototype objects. You also need to have a good understanding of interface usages, abstractions and specifications..

The container creates an instance of this concrete class src/Core/Kpi/Row/HookableKpiRowFactory.php and the returns back the object. Now, the class will handle the rest of the work for UI rendering. The class HookableKpiRowFactory has an instance variable named $kpis, this array keeps the UI elements that should be placed in KPI section (Do you remember that I mentioned that the proper way to remove KPIs is to use hooks? This arrays is the one that we should change it through KpiRowModifier hook). But how they are being populated?

The $kpis array comes from outside of the class. Again, here our container comes in and injects the array into our instance (this type of injection is called constructor based dependency injection, you can read more about other methods like setter-based and autowiring).

How the container detects which class should be injected?

The answer is the definition file. Take a look at the file located in this directory: src/PrestaShopBundle/Resources/config/services/core/kpi.yml. Within the file, the services related to the KPI are defined.

    prestashop.core.kpi_row.factory.orders:
        class: PrestaShop\PrestaShop\Core\Kpi\Row\HookableKpiRowFactory
        arguments:
          -
            - '@prestashop.adapter.kpi.conversion_rate'
            - '@prestashop.adapter.kpi.abandoned_cart'
            - '@prestashop.adapter.kpi.average_order_value'
            - '@prestashop.adapter.kpi.net_profit_per_visit'
          - '@prestashop.core.hook.dispatcher'
          - 'orders'

This way, the container detects which class and arguments should be injected. As you see, the arguments are the keys to KPI classes which are responsible to render their logic. For example, the class src/Adapter/Kpi/ConversionRateKpi.php is the concrete class responsible for conversion rate logic. Seems that we found it..

Let's remove the two KPI Blocks

Go to the class: src/Adapter/Kpi/ConversionRateKpi.php and try to remove these two lines:

            - '@prestashop.adapter.kpi.conversion_rate'
            - '@prestashop.adapter.kpi.net_profit_per_visit'

Now, try to do a clear cache in your PrestaShop BO, to remove Symfony cache files. And finished! You should see just two KPIs in your Order page.

That was a quick overview of how Symfony container works with an example in PrestaShop context and to have a better understanding of these concepts. If you want to know more about Symfony services and how you can use them, you can check this article.

A better way to manipulate KPIs in Back-office Order page..

If we want to modify KPI elements in Order page, there is a better way to do that. We can also override KPI items with a hook named KpiRowModifier through a module. But in this article I use the first method just to explain Symfony concepts. (actually the new hookable class created to add the dynamicity to the KPI section in BO)

Take a look at other topics in this category: Modern PrestaShop and Symfony related topics

Posted 1 year ago by Sam Berry