If you want to extend the behavior of a Back Office page in PrestaShop, you have multiple options.
Most of standard extension needs can be fulfilled using one hook.
If there is no hook available for your need, and you only need to modify the visual appearance of the page, you might want to override a template.
But sometimes you want to modify the page in a deeper way. In this case, you need to modify the controller’s behavior.
If the Back Office page you want to modify is powered by Symfony, you have 3 options:
In Symfony, routes such as /sell/orders/orders
are mapped to controllers by YAML configuration files such as this one:
# src/PrestaShopBundle/Resources/config/routing/admin/sell/orders/orders.yml
admin_orders_index:
path: /sell/orders/orders/
methods: [GET]
defaults:
_controller: PrestaShopBundle:Admin/Sell/Order/Order:index
_legacy_controller: AdminOrders
_legacy_link: AdminOrders
In your module, you have the possibility to replace this configuration item by your own.
So for example, if you want that people browsing the Back Office on URL /sell/orders/orders/
to hit your own controller MyModule\Controller\DemoController
instead of the Core’s, you can do this:
# modules/your-module/config/routes.yml
admin_orders_index:
path: /sell/orders/orders/
methods: [GET]
defaults:
_controller: 'MyModule\Controller\DemoController::demoAction'
_disable_module_prefix: true
path
has not been changed, but you can change it to whatever you want (eg. /demo/orders
). However, keep in mind that while this will re-route internal links, external or hardcoded links will keep targeting the old path.Keep the item _legacy_controller
if your controller relies on it to configure a AdminSecurity annotation such as @AdminSecurity("is_granted('read', request.get('_legacy_controller'))")
Keep the items _legacy_controller
and _legacy_link
if you want to reroute internal links and legacy URLs like index.php?controller=AdminOrders
as well.
Thanks to this, whenever an HTTP request is matched to the route admin_orders_index
, then your controller demoAction()
will be executed.
Thanks to option _disable_module_prefix: true
(available from PS 1.7.7.0) the route path is /sell/orders/orders
, just like the original route.
Routing is computed and cached by Symfony. You will need to clear this cache for Symfony to acknowledge your updated routing.
You can do it by using php bin/console cache:clear
.
If you have trouble writing the right routing configuration for your controller, you can use Symfony debugger to dump the routes with php bin/console debug:router
.
Since 1.7.7, PrestaShop Back Office controllers are registered as services, and like all public services, they can be overridden by modules.
For example, the controller responsible for the page “Improve > Design > CMS Pages” is registered with service ID PrestaShopBundle\Controller\Admin\Improve\Design\CmsPageController
.
With the following configuration item, we can override this configuration to make it target our custom module:
# modules/your-module/config/services.yml
'PrestaShopBundle\Controller\Admin\Improve\Design\CmsPageController':
class: MyModule\Controller\DemoController
Thanks to this, whenever Symfony forwards a request to the Core controller PrestaShopBundle\Controller\Admin\Improve\Design\CmsPageController
it will be forwarded to DemoController
instead.
php bin/console debug:container
. It can also be helpful to find the service ID of the controller.Instead of replacing the whole controller, we recommend extending its behavior using service decoration. By implementing the decorator pattern, you can keep most or all of the original behavior of the decorated controller, and only customize the parts you want.
Back to the controller responsible for the page “Improve > Design > CMS Pages” which is registered with service ID PrestaShopBundle\Controller\Admin\Improve\Design\CmsPageController
.
With the following configuration item, we can decorate it with a custom controller in our module:
# modules/your-module/config/services.yml
custom_controller:
class: MyModule\Controller\DemoController
decorates: PrestaShopBundle\Controller\Admin\Improve\Design\CmsPageController
arguments: ['@custom_controller.inner']
or if you are using autowiring:
# modules/your-module/config/services.yml
custom_controller:
autowire: true
class: MyModule\Controller\DemoController
decorates: PrestaShopBundle\Controller\Admin\Improve\Design\CmsPageController
bin/console cache:clear
). If (for some reason) you still need to modify the routing of your decorated controller, then you will need to add public: true
to your decorated controller definition in module services.yml, or else you will get error complaining about your controller being private.Thanks to this, whenever Symfony forwards a request to the Core controller PrestaShopBundle\Controller\Admin\Improve\Design\CmsPageController
it will be forwarded to DemoController
instead. But what is different with overriding is that the decorated CmsPageController is injected into the constructor of DemoController, and we can use it:
<?php
namespace MyModule\Controller;
use PrestaShop\PrestaShop\Core\Search\Filters\CmsPageCategoryFilters;
use PrestaShop\PrestaShop\Core\Search\Filters\CmsPageFilters;
use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
use PrestaShopBundle\Controller\Admin\Improve\Design\CmsPageController;
use Symfony\Component\HttpFoundation\Request;
class DemoController extends FrameworkBundleAdminController
{
/**
* @var CmsPageController
*/
private $decoratedController;
public function __construct(CmsPageController $decoratedController)
{
$this->decoratedController = $decoratedController;
}
public function indexAction(CmsPageCategoryFilters $categoryFilters, CmsPageFilters $cmsFilters, Request $request)
{
return $this->decoratedController->indexAction($categoryFilters, $cmsFilters, $request);
}
}
In this example we do nothing more than returning the exact output from the decorated controller. However we could modify the input request or the output given by decorated controller before returning it. Example:
<?php
public function indexAction(CmsPageCategoryFilters $categoryFilters, CmsPageFilters $cmsFilters, Request $request)
{
$output = $this->decoratedController->indexAction($categoryFilters, $cmsFilters, $request);
$myService = $this->getMyPaymentService();
$output = $this->injectMyData($myService, $output);
return $output;
}
php bin/console debug:container
. It can also be helpful to find the service ID of the controller.