Services

Symfony Services

You have the possibility to define your own Symfony services from your modules. But first we strongly advise you to use namespaces in your module, this can be done thanks to composer.

Setup composer

You need setup composer in your module before create the services. Create the file yourmodule/composer.json and paste:

{
    "name": "<your name>/<nmodule name>",
    "description": "<module description>",
    "authors": [
        {
            "name": "<your name>",
            "email": "<your email>"
        }
    ],
    "require": {
        "php": ">=5.6.0"
    },
    "autoload": {
        "psr-4": {
            "<YourNamespace>\\": "src/"
        },
        "exclude-from-classmap": []
    },
    "config": {
        "preferred-install": "dist",
        "prepend-autoloader": false
    },
    "type": "prestashop-module",
    "author": "<???>",
    "license": "<???>"
}

In YourNamespace add your namespace. Then in console in your module root run command composer dump-autoload. This will generate a vendor folder contain an autoload.php file which allows the use of your namespace.

The convention for namespaces used by PrestaShop is PrestaShop\\Module\\ModuleName as our base namespace, you can either follow this convention or adapt it to your business YourCompany\\YourModuleName.

You can also use composer to include some dependencies in your module, you can find more information about composer on Composer page.

Disable prepend-autoloader

It is required for you to disable the prepend autoloader feature. By default the module dependencies would be defined before the core ones, which would result in overriding them. If you want the PrestaShop core system to work correctly you must not override its dependencies. Which is why you need to always add in your composer.json file:

    "config": {
        "prepend-autoloader": false
    }

Don’t forget to publish your vendor folder

When using composer the autoload file generated by composer is required for your module to work correctly. So before creating your module archive and releasing it don’t forget to run composer dump-autoload (or composer install if you have included dependencies) so that they will be included in your module.

Define your services

At first you will need to create a class for your service of course:

// modules/yourmodule/src/YourService.php
namespace YourCompany\YourModule;

use Symfony\Component\Translation\TranslatorInterface;

class YourService {
    /** @var TranslatorInterface */
    private $translator;

    /** @var string */
    private $customMessage;

    /**
     * @param string $customMessage
     */
    public function __construct(
        TranslatorInterface $translator,
        $customMessage
    ) {
        $this->translator = $translator;
        $this->customMessage = $customMessage;
    }

    /**
     * @return string
     */
    public function getTranslatedCustomMessage() {
        return $this->translator->trans($this->customMessage, [], 'Modules.YourModule');
    }
}

Now that your namespace is setup, you can define your services in the config/services.yml file of your module.

# yourmodule/config/services.yml
services:
  _defaults:
    public: true

  your_company.your_module.your_service:
    class: YourCompany\YourModule\YourService
    arguments:
      - "@translator"
      - "My custom message"

This will then allow you to get your service from the Symfony container, like in your modern controllers:

// modules/yourmodule/src/Controller/DemoController.php
namespace YourCompany\YourModule\Controller;

use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;

class DemoController extends FrameworkBundleAdminController
{
    public function demoAction()
    {
        $yourService = $this->get('your_company.your_module.your_service');

        return $this->render('@Modules/yourmodule/templates/admin/demo.html.twig', [
            'customMessage' => $yourService->getTranslatedCustomMessage(),
        ]);
    }
}

If you need more details about dependency injection and how services work in the Symfony environment we recommend you to read their documentation about the Service Container.

Services in Legacy environment

Being able to declare services for Symfony environment is a nice feature when you use modern controllers, however when you are on front office or in a legacy page in the back office (meaning a page that has not been migrated yet with Symfony) you can’t access the Symfony container or your services.

Since the version 1.7.6 you can now define your services and access them in the legacy environment. We manage a light container for this environment (PrestaShop\PrestaShop\Adapter\ContainerBuilder) which is accessible from legacy containers.

To define your services you need to follow the same principle as Symfony services, but this time you need to place your definition files in sub folders:

  • config/admin/services.yml will define the services accessible in the back office (in legacy environment AND Symfony environment)
  • config/front/services.yml will define the services accessible in the front office

Accessing your services

You can then access your services from any legacy controllers (in which the container is automatically injected):

// modules/yourmodule/controllers/front/Demo.php
class YourModuleDemoModuleFrontController extends ModuleFrontController {
    public function display()
    {
        ...
        $yourService = $this->get('your_company.your_module.front.your_service');
        ...
    }
}
// modules/yourmodule/controllers/admin/demo.php
// Legacy controllers have no namespace
class YourModuleDemoModuleAdminController extends ModuleAdminController {
    public function display()
    {
        ...
        $yourService = $this->get('your_company.your_module.admin.your_service');
        ...
    }
}

But you can also access them from your module, to display its content or in hooks:

// modules/yourmodule/yourmodule.php
class yourmodule {
    public function getContent()
    {
        ...
        // The controller here is the ADMIN one so only admin services are accessible
        $yourService = $this->context->controller->getContainer()->get('your_company.your_module.admin.your_service');
        ...
    }

    public function hookDisplayFooterProduct($params)
    {
        ...
        // The controller here is the FRONT one so only front services are accessible
        $yourService = $this->context->controller->getContainer()->get('your_company.your_module.front.your_service');
        ...
    }
}

Environments

Keep in mind that the legacy container is a light version of the full Symfony container so you won’t have access to all the Symfony components. But you will be able to use the Doctrine service as well as a few few core services from PrestaShop.

For more details about available services you can check in <PS_ROOT_DIR>/config/services/ folder which services are available in admin or front. Be careful and always keep in mind in which context/environment you are calling your service.

Here is a quick summary so that you know where you should define your services:

Definition file Symfony Container Front Legacy Container Admin Legacy Container Available services
config/services.yml Yes No No All symfony components and PrestaShopBundle services
config/admin/services.yml Yes No Yes Doctrine, services defined in <PS_ROOT_DIR>/config/services/admin folder
config/front/services.yml No Yes Yes Doctrine, services defined in <PS_ROOT_DIR>/config/services/front folder

Define a service on both front and admin

Sometimes services are only useful in a particular context (back-office or front-office), but sometime you also need them on both (a Doctrine repository is a good example). You could easily define the same services in both environment but it’s very modular and can create errors in case of modifications.

An easy trick is to create a common definition file which will then be included by each environment:

# yourmodule/config/common.yml
services:
  _defaults:
    public: true

  your_company.your_module.common.open_service:
    class: YourCompany\YourModule\YourService
    arguments:
      - '@your_company.your_module.common.open_dependency'

  your_company.your_module.common.open_dependency:
    class: YourCompany\YourModule\YourServiceDependency

Then you can include this file in the environment you wish (front, admin, Symfony);

# yourmodule/config/services.yml
imports:
    - { resource: ./common.yml }
# yourmodule/config/admin/services.yml
imports:
    - { resource: ../common.yml }
# yourmodule/config/front/services.yml
imports:
    - { resource: ../common.yml }