Notice: You are browsing the documentation for PrestaShop 9, which is currently in development.
You might want to look at the current version, PrestaShop 8. Read the current version of the documentation
This section provides a list of the most significant changes in PrestaShop 9.0. While this list is not exhaustive, it aims to give developers a clear overview of the important updates. If you notice any missing or incorrect information, please help us improve by creating an issue on our GitHub repository.
PrestaShop 9.0 now requires PHP 8.1 minimum, with support added for PHP 8.2 and 8.3.
The minimum version to build PrestaShop assets is now the version 20.
PrestaShop 9.0 has been upgraded to rely on Symfony 6.4. This is a significant leap from the previous version, 8.x, which was based on Symfony 4.4. As a result, version 9 includes all the breaking changes related to the Symfony Framework. For more details about these changes in the core, you can refer to the PrestaShop GitHub issue. Additionally, you can consult the Symfony migration guides (see links below).
Symfony 6.4 is the latest LTS version and will receive bug fixes until November 2026, as well as security fixes until November 2027.
We have followed Symfony’s recommendations and made changes to our dependencies. Instead of using the symfony/symfony
dependency, we now manually include each sub-package. Additionally, due to the minimum PHP version requirement of 8.1, we had to upgrade or remove certain dependencies.
During this process, we took the opportunity to clean up dependencies that are no longer used in the core, regardless of whether they are from Symfony or not. If you rely on these dependencies in your modules, you will need to integrate them into your module’s dependencies.
Library name | Reason for removal/Replacement |
---|---|
guzzlehttp/guzzle | Replaced by Symfony HTTP client in the core |
league/tactician-bundle | Replaced by Symfony Messenger component |
pear/archive_tar | No longer used |
sensio/framework-extra-bundle | We now favor annotations in the core |
soundasleep/html2text | No longer used |
swiftmailer/swiftmailer | Replaced by Symfony Mailer component in the core |
symfony/inflector | No longer used |
symfony/notifier | No longer used |
symfony/rate-limiter | No longer used |
symfony/semaphore | No longer used |
symfony/uid | No longer used |
symfony/workflow | No longer used |
We have migrated from Swift Mailer to Symfony Mailer. This change includes several improvements and aligns with Symfony’s latest recommendations.
Mail SSL encryption was dropped
When we migrated from Swift Mailer to Symfony Mailer, we noticed that SSL support was not an option in ESMTP transport. SSL is an old and outdated encryption type, and for security reasons, it will no longer be allowed.
The remaining choices are “TLS encryption” or “No encryption”.
Some dependencies are still present but were upgraded which comes with their own breaking changes, please refer to each dependency changelog to understand them in details if you depend on these dependencies:
Name | Old version | New version |
---|---|---|
api-platform/core | 2.7.6 | 3.2.13 |
composer/installers | 1.12.0 | 2.2.0 |
friendsofsymfony/jsrouting-bundle | 2.8.0 | 3.2.1 |
lcobucci/jwt | 3.4.6 (special patch from https://github.com/PrestaShop/jwt.git) | 5.0.0 (no need for fork version anymore) |
mobiledetect/mobiledetectlib | 2.8.41 | 3.74.0 |
pelago/emogrifier | 5.0.1 | 7.0.0 |
Symfony dependencies | 4.4 | 6.4 |
twig/twig | 3.4.3 | 3.8.0 |
doctrine/dbal | 2.13.8 | 3.6.5 |
doctrine/lexer | 1.2.3 | 2.1.1 |
doctrine/orm | 2.12.1 | 2.15.5 |
doctrine/deprecations | 0.5.3 | 1.1.3 |
egulias/email-validator | 3.2.6 | 4.0.1 |
In Symfony 6.4, the controllers must now be defined as services. The impact on the existing controllers is mainly around the concept of Dependency Injection and how services are injected or accessed in the controllers. The container passed to the controllers is no longer the “global container” that contains all the existing services in the application. Instead, it injects a dedicated container optimized for the controller based on the services that are injected into it.
The issue is that most of PrestaShop’s Symfony controllers do not rely on injection but instead use the $this->get('service_name')
method to access any service. This is no longer possible in modern controllers because of the mentioned optimization related to container build (and the get
method was even removed).
To avoid a very big breaking change, we modified the PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController
, so that it can fetch services from the controller’s container and from the global one, but this is a little hack that goes against Symfony recommendations, as such this base class is already deprecated and will be removed in PrestaShop 10.0.
This gives developers some time to refacto their controllers. They should now rely on the new PrestaShopBundle\Controller\Admin\PrestaShopAdminController
, which doesn’t implement the mentioned hack in order to force a proper implementation of the controllers.
You can read more details about Controllers as services, Service Subscribers & Locators or Service container in general.
As an example, you have three main possibilities to inject a service into your controller:
<?php
use PrestaShopBundle\Controller\Admin\PrestaShopAdminController;
class MyController extends PrestaShopAdminController
{
/**
* You can inject your service systematically in the constructor, useful if the service is used by several actions of your controller.
*
* @param MyCustomService $myCustomService
*/
public function __construct(
private readonly MyCustomService $myCustomService,
) {
}
/**
* You can inject a service only on one method, useful if the service is used only in one action. It is more optimized than injecting it all the time and never using it.
*
* @param MySpecificService $specificService
* @return JsonResponse
*/
public function indexAction(MySpecificService $specificService): JsonResponse
{
$generalData = $this->myCustomerService->getData();
$specificData = $specifiService->getSpecificData();
// You can use the get method to fetch a service that was added in the getSubscribedServices method
$toolingService = $this->container->get(ToolingService::class);
return new JsonResponse($toolingService->format($generalData + $specificData));
}
/**
* You can define a list of registered services, they will be accessible via the $this->container->get method, this
* way of injecting service is interesting if implemented on a base controller class for generic services that
* may be used by many controllers (like the router, the translator, ...) so you don't need to inject them manually
* in each controller class.
*
* @return array
*/
public static function getSubscribedServices(): array
{
return parent::getSubscribedServices() + [
ToolingService::class => ToolingService::class,
];
}
}
Define your controller as a service
You can already anticipate and define your controllers as services, it is also compatible with previous versions of Symfony and PrestaShop:
services:
MyNamespace\MyController:
# Using autowire will inject services in the constructors automatically
autowire: true
# Using autoconfigure with controllers that extend AbstractController will handle method parameter injection and subscribed services
autoconfigure: true
# If your controller class doesn't extend AbstractController you should add this tag manually so you benefit from method parameters injection
tags: ['controller.service_arguments']
PrestaShopAdminController::getSubscribedServices
The new base controller class uses the getSubscribedServices
method to give easy access to some commonly used PrestaShop services, like the ConfigurationInterface
, HookDispatcherInterface
, TranslatorInterface
, …
You can see the full list of subscribed services in the class, this list will probably grow piece by piece but only for common generic services.
Thanks to this, you can use $this->container->get(ConfigurationInterface::class)->get('my_config')
to access the configuration service, or alternatively use the helper method $this->getConfiguration()->get('my_config')
.
The PrestaShopAdminController
class comes with other helpful helper methods that are commonly used in controllers.
Until PrestaShop 8, we relied on one Kernel used for the back office. In PrestaShop 9.0, we expanded the usage of the Symfony framework, especially for two new features:
Those two new environments have mechanisms and configuration different from those in the back office. To separate these configurations cleanly, they each have their dedicated kernel class and their own configuration. This allows to cleanly define different routing, security configurations, dedicated services, listeners, etc.
Each kernel has a unique applicationId
that allows to dynamize its configuration and cache folder (since the services are not the same, each kernel needs its own cache folder). The appId
parameter has also been added globally to the bin/console
tool. They all share the common app/config/config.yml
configuration file, but each one extends it in its own config folder.
Environment | Kernel class | Config folder | Cache folder | Endpoint | App ID |
---|---|---|---|---|---|
Back office | AdminKernel |
app/config/admin |
var/cache/{dev,prod,test}/admin |
/admin-dev/index.php |
admin |
Admin API | AdminAPIKernel |
app/config/admin-api |
var/cache/{dev,prod,test}/admin-api |
/admin-api/index.php |
admin-api |
Front office (experimental) | FrontKernel |
app/config/front |
var/cache/{dev,prod,test}/front |
/index.php |
front |
Console usage examples
To clear the cache of the Admin API for its prod
environment:
php bin/console cache:clear --env=prod --app-id=admin-api
–
To display event listeners for the back office in dev
environment (default value of app-id
is admin
for retro compatibility):
php bin/console debug:event-dispatcher kernel.request --env=dev
Related PRs
All the back office pages share a common layout, composed of a few elements:
<head>
element that includes all the CSS and JS (among other things)displayBackOfficeFooter
display hook)Until PrestaShop 8.1, all these common elements were handled by legacy code, so even on the migrated pages, there was always a background legacy controller based on AdminController
in charge of building the layout data and rendering it. Once the layout was rendered the central content of Symfony pages was included in the middle of it. It means no page was completely free from the legacy controllers, which would ultimately block the end of migration, while they are intended to disappear completely.
In PrestaShop 9.0 all this layout part is now fully handled by Symfony, we use Twig components to render each element independently. The code is, therefore, easier to understand, a component class is responsible for fetching/building the data, while the actual rendering is based on Twig. See our Layout components for more details.
On legacy pages, we follow the same principle. Symfony is now in charge of rendering all the layout, and we use some Legacy layout components that follow the same architecture, but render different Twig templates to fit with the old default theme (that is based on old Bootstrap and includes old legacy helpers like HelperForm
and HelperList
).
What changes for my module pages?
We refactored this layout with maximum backward compatibility in mind. The HTML layout itself had minimum changes (in both migrated and legacy pages). We tried to keep the same hooks in both the PHP code and the Twig templates. We even introduced a fake legacy controller in migrated pages that has no logic but is kept mostly as a DTO to contain things like CSS files and JS files, because that’s where most modules add their content.
We still had to change the controller workflow. Many of the functions in AdminController
are no longer useful as they are used to initialize the layout variables (now handled by Twig components). Some methods also render or write output directly, which we prevent as we need to get the rendered content as a string to integrate it correctly inside the new layout. This means some internal methods had to be split to avoid unwanted usage.
Legacy workflow:
Here you’ll find the execution workflow of legacy controllers
Symfony workflow:
The two workflows should render the same result. Many methods from the legacy workflow are not executed anymore because they lost their purpose, but in case you override one of those methods, be aware that they are no longer called directly, so there are breaking changes:
AdminController:run
AdminController::initHeader
AdminController::initContent
AdminController::initFooter
AdminController:display
Also, be aware that many Smarty variables are no longer defined because they were only used to render the layout.
Despite the changes in the workflow and the fact we no longer depend on the Dispatcher
class, we maintained these hooks:
actionDispatcher
actionDispatcherBefore
actionDispatcherAfter
Related PRs:
For more details about the changes, you can check the content of the Symfony layout Epic.
The back office login page has been migrated to Symfony. Along with this change, the authorization system in the back office is now also based on Symfony, which implies several things:
Context::$cookie
, the session is kept on the server side, and very few data are kept on the browser side. For retro-compatibility, we still populate the legacy cookie so that you can read it. While you can use Context::$cookie
for your custom data, making changes to the data previously used for PrestaShop authorization will no longer have the desired effect and may even result in instability, so it’s not recommendedPrestaShopBundle\Security\Admin\EmployeeProvider
and PrestaShopBundle\Service\DataProvider\UserProvider
now return a PrestaShopBundle\Entity\Employee
instance, their responsibility is to return a Symfony\Component\Security\Core\User\UserInterface
anyway (and they still do) but in case you depended on the child class know that PrestaShopBundle\Security\Admin\Employee
no longer existSymfony\Bundle\SecurityBundle\Security
service, but we recommend you use the EmployeeContext
internal PrestaShop service (see explanation about new Contexts)Storing custom data in a Session
If you need to persist some custom Employee data, we no longer recommend using the Context::$cookie
. Instead, you can use the Session
from Symfony and update its attributes:
// In Symfony controllers, you can get the session from the Request parameter injected by Symfony
$session = $request->getSession();
// In other services you will need to get the current request via the RequestStack service
$request = $requestStack->getCurrentRequest();
if ($request) {
$session = $request->getSession();
}
// You can then get/set a custom attribute, but you should always check that the session object is indeed available.
// Some requests do not rely on the session, and it may never be created
if ($session) {
// Be careful to use a custom and UNIQUE attribute name
$myData = $session->getAttribute('my_custom_data');
// The session object will be automatically serialized at the end of the request,
// and automatically unserialized at the beginning of the next requests
$session->setAttribute('my_custom_data', 'updated value');
}
Removed hooks:
actionAdminLoginControllerBefore
actionAdminLoginControllerLoginBefore
actionAdminLoginControllerLoginAfter
actionAdminLoginControllerForgotBefore
actionAdminLoginControllerForgotAfter
actionAdminLoginControllerResetBefore
actionAdminLoginControllerResetAfter
Hooks kept for backward compatibility:
actionAdminLoginControllerSetMedia
so you can add some custom assets on the login pagedisplayAdminLogin
so you can add custom HTML on the login pageNew hooks
actionBackOfficeLoginForm
to modify the login form (the form builder is passed via the form_builder
hook parameter)actionEmployeeRequestPasswordResetForm
to modify the request password form (the form builder is passed via the form_builder
hook parameter)The login logic, however, is not handled by PrestaShop’s internal code so there is no handler that interprets the submitted data (and that’s why there are so few new hooks). Instead we rely on Symfony form login authentication system, so it’s Symfony’s internal system that interprets the data submitted, validates it and authenticate the employee to the back office.
If you need to integrate your code with the authentication process you should now rely on the Authentication events described in their documentation, you can also inspire yourself from our internal subscriber.
Related PRs:
A significant amount of code in the core relies on the legacy Context
class, which stores certain data as a singleton. However, this legacy implementation has many drawbacks, so refactoring was initiated in PrestaShop 9.0 to replace its usage.
You can find more details about the reason behind this and the implementation in the related ADR about Context refactoring.
You should now favor using the modern split context services:
PrestaShop\PrestaShop\Core\Context\ApiClientContext
PrestaShop\PrestaShop\Core\Context\CountryContext
PrestaShop\PrestaShop\Core\Context\CurrencyContext
PrestaShop\PrestaShop\Core\Context\EmployeeContext
PrestaShop\PrestaShop\Core\Context\LanguageContext
PrestaShop\PrestaShop\Core\Context\LegacyControllerContext
PrestaShop\PrestaShop\Core\Context\ShopContext
We still have some work to do to replace all the usages of the legacy context with the modern ones, but the new code will stop using the legacy ones. We recommend that developers rely on the new ones from now on.
The migration of back office pages to Symfony imply several breaking changes, we won’t detail all of them for each page but here is a summary:
Here is the list of migrated pages in v9:
Edition of features on the new product page, use a new optimized data structure in the form. You may need to adapt your module if you rely on the old structure.
trans
methodFollowing PrestaShop/PrestaShop#30415, the function trans()
does not escape anymore strings. In PrestaShop 8, you could pass parameters like htmspecialcharacters
or addslashes
to trans()
to perform additional escape, but it’s been removed. It also affects the l
function in Smarty.
Since PrestaShop/PrestaShop#31900, the trans
method always keep the behavior it had with the (now removed) _raw
parameter, meaning the content is not modified anymore. You need to use htmlspecialchars
on your parameters provided on input and on the returned string if you need to modify it.
Presenter
classesThis changes the data passed to the Smarty templates:
Page | Details |
---|---|
Category | New hook available, actionPresentCategory |
Supplier | Different template structure and new hooks |
Manufacturer | Different template structure and new hooks |
Store | New hook available actionPresentStore |
The list below is one of the most important changes in PrestaShop 9.0, it includes changes in the behaviour of the core, rules that have been removed or changed, and some code that has been removed. It is very important to check this list to understand the changes in the core as it may impact your solutions.
Each change links to the pull request that introduced it, so you can find more details about the modification.
Change | Details |
---|---|
Customization quantity feature has been removed | The customization quantity is now the one from the cart_product row. In the pull request you can find more details about the changes, also related to themes. |
Refactor AdminModulesControllers and remove obsolete features | n/a |
Invalid characters are being saved as Social titles | n/a |
AbstractCategoryType constructor changed | n/a |
HTTPs check in BackOffice is now based on Symfony Request::isSecure instead of legacy Tools::usingSecureMode | n/a |
PrestaShopAutoload has been removed in favor of prestashop/autoload | If you rely on classes/PrestaShopAutoload.php class, you need to adapt your solutions |
Form Types/Extensions have been migrated to be autowired and rely on FQCN service names: | If you decorated or override some services modified in the pull request, you may need to adapt your solutions. |
- https://github.com/PrestaShop/PrestaShop/pull/31138 | |
- https://github.com/PrestaShop/PrestaShop/pull/31193 | |
- https://github.com/PrestaShop/PrestaShop/pull/31391 | |
Enable/disable module on mobile feature was removed | Module configuration to prevent display on mobile devices is no longer possible. This feature has been removed due to issues with cache and low reliability. You can still achieve the same result by implementing this mechanism on a module level. |
Standardize filterManufacturerContent hook | Parameters inside filterManufacturerContent hook have changed. |
Legacy images format no longer supported | n/a |
PrestaShopBundle\Kernel\ModuleRepository was removed along with its Factory | n/a |
Product::getDefaultCategory always returns an int | n/a |
Remove high DPI images feature | HighDPI images feature wasn’t used in PrestaShop 8 and has been removed in PrestaShop 9. You can still achieve the same results by creating more image types and using sourceset |
Remove Category menu thumbnail feature | n/a |
Symfony Parameter prestashop.addons.categories was removed | n/a |
UpdateHookStatusCommand parameter is no the new expected status | n/a |
Replaced SwiftMailer by Symfony Mailer | n/a |
Replaced TactitianBundle by Symfony Messenger | n/a |
Removed Advanced Stock Management remains | Advanced stock management functionality has been partially removed and deprecated for a whole lifetime of 1.7, but not completely removed. This PR removes all the logic from the code along with everything related - supply orders, warehouses, old stock management etc. |
Remove shop activity during install | n/a |
Remove Multi Address delivery | This change may have an impact on third-party solutions,especially ones that modify checkout process in PrestaShop. Address IDs from the cart_product and customization tables are no longer relevant and you should not rely on them. |
Remove non responsive component in BO | n/a |
Lazy load Product feature in FO | By default, Product::getProductsProperties will no longer contain features data. |
Removed cover_image_id from Product lazy array | Removes cover_image_id property that was used to override id_image. You can still alter the cover by using standard presenter hook. It also adds a new functionality that allows to choose behavior of cover image for products with combinations. |
Removed attribute_price from Product::getProductProperties | n/a |
Doctrine dependencies updated | This change brings a lot of backward incompatible changes. All details are available inside a pull request. It is really important to understand those changes to make your solutions compatible. |
FrontController::addJqueryUi no only adds the requested component | This performance optimization may result in a different loading of jQuery UI components. |
Removed code from old product page | The old product page has been removed in version 9, this brings a lot of backward incompatible changes, especially if you used some of the components of the old product page. |
ObjectModel fields definition can contain translatable wording | This change introduced a new approach to translating error messages related to ObjectModel fields. |
Image feature flag has been removed | n/a |
Forbid sensitive files in modules directory | This pull request has been merged following discussion about securing PrestaShop modules better. It will require the adaptation of your modules and switching from calling .php files directly to using ModuleFrontController . You can check all the details inside the discussion and pull request, as well as related pull requests from native modules which you can use as an example implementation. |
Remove legacy tab system | n/a |
Upgrade jquery to latest version, drop polyfills | n/a |
EmployeeId has strict type, OrderStatusForViewing has new constructor parameters | TBD |
RedirectTargetProvider and ProductRedirectTarget moved into generic namespace | n/a |
Changed return types of all ean13 properties in CQRS | ean13 has been replaced with gtin which may require adjustments to third-party solutions |
ModuleManagerBuilder and ThemeManager internal properties no longer public | n/a |
Admin API, lots of experimental code renamed | n/a |
Empty value no longer allowed for redirect_type in product tables | n/a |
PHPStan Doctrine extension introduced that impact all existing Entities | n/a |
IBM Plex Sans
and Material Symbols
is part of the rebranding effort. These fonts are included in both the default
theme and also the new-theme
through the UI Kit.postcss.config.js
: The postcss.config.js
file, which used the deprecated postcss-cssnext
package, has been removed. This package is no longer needed.admin-dev/themes/default/scss/modules/_colors.scss
and the directory admin-dev/themes/default/scss/modules/colors/
have been removed. These are no longer used in the back office. Update your stylesheets to avoid relying on these outdated resources.By the end of 2023, PrestaShop introduced a new brand identity to reinforce its strengths and enhance credibility and trust. The PrestaShop back office was one of the final components to undergo rebranding, with the release of PrestaShop 9 providing an ideal opportunity for this update.
The PrestaShop back office consists of two themes. Both themes have been modified to achieve a complete rebranding:
_root.scss
file from the PrestaShop UI Kit to expose CSS variables from the UI Kit