-
Notifications
You must be signed in to change notification settings - Fork 529
Module AdminControllers for back office documentation has to be adjus… #2058
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 9.x
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -9,75 +9,179 @@ Using modern pages, you will have access to the PrestaShop debug toolbar, the se | |||||
|
|
||||||
| ## How to declare a new Controller | ||||||
|
|
||||||
| Somewhere in your module declare a new class that will act as a Controller: | ||||||
| {{% notice warning %}} | ||||||
| **PrestaShop 9.0+**: The `FrameworkBundleAdminController` is **deprecated** and will be removed in PrestaShop 10.0. Use `PrestaShopAdminController` instead for new controllers. | ||||||
| {{% /notice %}} | ||||||
|
|
||||||
| In your module, create a new controller class in the `src/Controller/` directory: | ||||||
|
|
||||||
| ```php | ||||||
| <?php | ||||||
| // modules/your-module/src/Controller/DemoController.php | ||||||
|
|
||||||
| namespace MyModule\Controller; | ||||||
|
|
||||||
| use Doctrine\Common\Cache\CacheProvider; | ||||||
| use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController; | ||||||
| use PrestaShopBundle\Controller\Admin\PrestaShopAdminController; | ||||||
| use Symfony\Component\DependencyInjection\Attribute\Autowire; | ||||||
| use Symfony\Component\HttpFoundation\Request; | ||||||
| use Symfony\Component\HttpFoundation\Response; | ||||||
|
|
||||||
| class DemoController extends FrameworkBundleAdminController | ||||||
| class DemoController extends PrestaShopAdminController | ||||||
| { | ||||||
| private $cache; | ||||||
|
|
||||||
| // you can use symfony DI to inject services | ||||||
| public function __construct(CacheProvider $cache) | ||||||
| /** | ||||||
| * Inject services via constructor for services used across multiple actions | ||||||
| */ | ||||||
| public function __construct( | ||||||
| private readonly MyCustomService $myCustomService, | ||||||
| ) { | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * You can also inject services directly in action methods | ||||||
| */ | ||||||
| public function demoAction(AnotherService $anotherService): Response | ||||||
| { | ||||||
| $this->cache = $cache; | ||||||
| // Use injected services | ||||||
| $data = $this->myCustomService->getData(); | ||||||
| $moreData = $anotherService->getMoreData(); | ||||||
|
|
||||||
| return $this->render('@Modules/your-module/templates/admin/demo.html.twig', [ | ||||||
| 'data' => $data, | ||||||
| 'moreData' => $moreData, | ||||||
| ]); | ||||||
| } | ||||||
|
|
||||||
| public function demoAction() | ||||||
| /** | ||||||
| * You can also use the #[Autowire] attribute to inject services by their ID | ||||||
| */ | ||||||
| public function advancedAction( | ||||||
| Request $request, | ||||||
| ShopRepository $shopRepository, | ||||||
| #[Autowire(service: 'my_module.custom_service')] $customService, | ||||||
| ): Response | ||||||
| { | ||||||
| return $this->render('@Modules/your-module/templates/admin/demo.html.twig'); | ||||||
| // Use the injected service | ||||||
| $customService->execute(); | ||||||
|
|
||||||
| return $this->render('@Modules/your-module/templates/admin/advanced.html.twig'); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Use helper methods provided by PrestaShopAdminController | ||||||
| */ | ||||||
| public function anotherAction(): Response | ||||||
| { | ||||||
| // PrestaShopAdminController provides helper methods for common services | ||||||
| $config = $this->getConfiguration()->get('my_config'); | ||||||
|
|
||||||
| return $this->render('@Modules/your-module/templates/admin/another.html.twig',[ | ||||||
| 'my_config' => $config, | ||||||
| ]); | ||||||
| } | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| The example above demonstrates a complete controller implementation with: | ||||||
| - **Constructor injection** for services used across multiple actions (`MyCustomService`) | ||||||
| - **Method parameter injection** for services used in specific actions (`AnotherService`) | ||||||
| - **Autowire attribute** for injecting services by their ID (`#[Autowire(service: 'my_module.custom_service')]`) | ||||||
| - **Helper methods** provided by `PrestaShopAdminController` for accessing common PrestaShop services | ||||||
|
|
||||||
| You have access to Twig as rendering engine and everything from the Symfony framework ecosystem. | ||||||
| Note that you must return a `Response` object, but this can be a `JsonResponse` if you plan to make a single page application (or "SPA"). | ||||||
|
|
||||||
| {{% notice note %}} | ||||||
| This controller works exactly the same as the Core Back Office ones. | ||||||
| {{% /notice %}} | ||||||
|
|
||||||
| {{% notice info %}} | ||||||
| **Accessing PrestaShop services:** | ||||||
|
|
||||||
| The `PrestaShopAdminController` provides helper methods to access common PrestaShop services: | ||||||
| - `$this->getConfiguration()` - Configuration service | ||||||
| - `$this->getTranslator()` or `$this->trans()` - Translation service | ||||||
| - `$this->getRouter()` - Router service | ||||||
| - `$this->getEmployeeContext()` - Employee context | ||||||
| - `$this->getShopContext()` - Shop context | ||||||
| - `$this->getLanguageContext()` - Language context | ||||||
| - `$this->getCurrencyContext()` - Currency context | ||||||
| - `$this->getCountryContext()` - Country context | ||||||
| - `$this->dispatchCommand()` - Execute CQRS commands | ||||||
| - `$this->dispatchQuery()` - Execute CQRS queries | ||||||
| - `$this->dispatchHookWithParameters()` - Dispatch hooks | ||||||
| - `$this->presentGrid()` - Present grid data | ||||||
|
|
||||||
| See the complete list in the [PrestaShopAdminController source code](https://github.com/PrestaShop/PrestaShop/blob/9.0.0/src/PrestaShopBundle/Controller/Admin/PrestaShopAdminController.php). | ||||||
| {{% /notice %}} | ||||||
|
|
||||||
| {{% notice warning %}} | ||||||
| **Doctrine repositories must be injected:** | ||||||
|
|
||||||
| Doctrine ORM is not directly accessible via `$this->getDoctrine()` or similar methods. All Doctrine repositories and entity managers must be injected as dependencies in your controller's constructor or action methods. | ||||||
|
|
||||||
| Example: | ||||||
| ```php | ||||||
| use Doctrine\ORM\EntityManagerInterface; | ||||||
| use App\Repository\ProductRepository; | ||||||
|
|
||||||
| public function __construct( | ||||||
| private readonly EntityManagerInterface $entityManager, | ||||||
| private readonly ProductRepository $productRepository, | ||||||
| ) { | ||||||
| } | ||||||
| ``` | ||||||
| {{% /notice %}} | ||||||
|
|
||||||
| ## Service Configuration | ||||||
|
|
||||||
| In PrestaShop 9.0, controllers must be defined as services. You have two main approaches to configure your controller service: | ||||||
|
|
||||||
| ### Option 1: Explicit service configuration with tags | ||||||
|
|
||||||
| If you want Symfony Dependency Injection to inject services into your controller, you need to use specific YAML service declaration: | ||||||
| ```yaml | ||||||
| # modules/your-module/config/services.yml | ||||||
| services: | ||||||
| # The name of the service must match the full namespace class | ||||||
| MyModule\Controller\DemoController: | ||||||
| class: MyModule\Controller\DemoController | ||||||
| arguments: | ||||||
| - '@doctrine.cache.provider' | ||||||
| autowire: true | ||||||
| autoconfigure: true | ||||||
| tags: | ||||||
| - { name: controller.service_arguments } | ||||||
| ``` | ||||||
|
|
||||||
| You can also retrieve services with the container available in symfony controllers -> | ||||||
| ```php | ||||||
| <?php | ||||||
| // modules/your-module/src/Controller/DemoController.php | ||||||
| ### Option 2: Using service defaults | ||||||
|
|
||||||
| namespace MyModule\Controller; | ||||||
| ```yaml | ||||||
| # modules/your-module/config/services.yml | ||||||
| services: | ||||||
| _defaults: | ||||||
| public: false | ||||||
| autowire: true | ||||||
| autoconfigure: true | ||||||
|
|
||||||
| MyModule\Controller\DemoController: ~ | ||||||
| ``` | ||||||
|
|
||||||
| use Doctrine\Common\Cache\CacheProvider; | ||||||
| use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController; | ||||||
| {{% notice warning %}} | ||||||
| **Service configuration is mandatory:** | ||||||
|
|
||||||
| class DemoController extends FrameworkBundleAdminController | ||||||
| { | ||||||
| public function demoAction() | ||||||
| { | ||||||
| // you can also retrieve services directly from the container | ||||||
| $cache = $this->container->get('doctrine.cache'); | ||||||
|
|
||||||
| return $this->render('@Modules/your-module/templates/admin/demo.html.twig'); | ||||||
| } | ||||||
| } | ||||||
| ``` | ||||||
| One of the two service configuration options above is **essential and required** for your controller to work properly. Without this configuration: | ||||||
| - **The controller will not function** and will throw errors | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I'm not sure which one is more correct in english, maybe I'm biased with my french point of view 😅 |
||||||
| - Helper methods from `PrestaShopAdminController` will not be accessible | ||||||
| - Services like Twig, Form, Router, and other Symfony components will not be available | ||||||
|
|
||||||
| You have access to the Container, to Twig as rendering engine, the Doctrine ORM, everything from Symfony framework ecosystem. | ||||||
| Note that you must return a `Response` object, but this can be a `JsonResponse` if you plan to make a single page application (or "SPA"). | ||||||
| **Important:** The service name (e.g., `MyModule\Controller\DemoController`) **must exactly match** the fully qualified class name (FQCN) of your controller. If the service name doesn't match the class name, Symfony and PrestaShop will not be able to identify and instantiate the controller, resulting in errors. | ||||||
|
|
||||||
| {{% notice note %}} | ||||||
| This controller works exactly the same as the Core Back Office ones. | ||||||
| {{% /notice %}} | ||||||
| **Understanding the configuration:** | ||||||
| - `autowire: true` - Automatically injects services in constructors and method parameters | ||||||
| - `autoconfigure: true` - Automatically configures the controller as a service and enables all controller features | ||||||
| - `controller.service_arguments` tag - Required if your controller doesn't extend `AbstractController` to enable method parameter injection | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's true! But should we mention this? And by that I mean should we encourage people having controller that do not extend Or should we simply document: Use this class |
||||||
| {{% /notice %}} | ||||||
|
|
||||||
| ## Autoloading Configuration | ||||||
|
|
||||||
| You must enable the autoloading for this Controller. For example using a `composer.json` file for your module. | ||||||
|
|
||||||
| #### Example using PSR-4 namespacing | ||||||
| ### Example using PSR-4 namespacing | ||||||
|
|
||||||
| 1. Use namespace for your Controller file | ||||||
|
|
||||||
|
|
@@ -87,7 +191,7 @@ You must enable the autoloading for this Controller. For example using a `compos | |||||
|
|
||||||
| namespace MyModule\Controller; | ||||||
|
|
||||||
| use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController; | ||||||
| use PrestaShopBundle\Controller\Admin\PrestaShopAdminController; | ||||||
| ``` | ||||||
|
|
||||||
| 2. Configure composer to autoload this namespace | ||||||
|
|
@@ -98,7 +202,7 @@ You must enable the autoloading for this Controller. For example using a `compos | |||||
| "description": "...", | ||||||
| "autoload": { | ||||||
| "psr-4": { | ||||||
| "MyModule\\Controller\\": "src/Controller/" | ||||||
| "MyModule\\": "src/" | ||||||
| } | ||||||
| }, | ||||||
| "config": { | ||||||
|
|
@@ -182,6 +286,48 @@ In order to generate the valid URI of a controller you created from inside the m | |||||
| ``` | ||||||
|
|
||||||
|
|
||||||
| ## Key Changes in PrestaShop 9.0 (Symfony 6.4) | ||||||
|
|
||||||
| ### Controllers as Services | ||||||
|
|
||||||
| In Symfony 6.4, controllers must be defined as services. The container passed to controllers is no longer the "global container" but a dedicated, optimized container based on the services injected into it. | ||||||
|
|
||||||
| **Important implications:** | ||||||
|
|
||||||
| - The `$this->get('service_name')` method is no longer available in modern controllers | ||||||
| - `FrameworkBundleAdminController` maintains backward compatibility but is **deprecated** and will be removed in PrestaShop 10.0 | ||||||
| - New controllers should extend `PrestaShopAdminController` which follows Symfony best practices | ||||||
|
|
||||||
| ### Dependency Injection Methods | ||||||
|
|
||||||
| You have three main ways to inject services: | ||||||
|
|
||||||
| 1. **Constructor injection** - For services used across multiple actions | ||||||
| 2. **Method injection** - For services used in a single action (more optimized) | ||||||
| 3. **Autowire attribute** - Use `#[Autowire(service: 'service_id')]` to inject services by their ID | ||||||
|
|
||||||
| ### PrestaShopAdminController Helper Methods | ||||||
|
|
||||||
| The new base controller provides convenient helper methods for commonly used services: | ||||||
|
|
||||||
| - `$this->getConfiguration()` - Access configuration service | ||||||
| - `$this->getTranslator()` - Access translator service | ||||||
| - `$this->getRouter()` - Access router service | ||||||
| - `$this->getFlashBag()` - Access flash messages | ||||||
|
Comment on lines
+313
to
+316
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this a doublon with the paragraph above that lists even more thelper methods? |
||||||
|
|
||||||
| See the [full list of helper methods](https://github.com/PrestaShop/PrestaShop/blob/9.0.0/src/PrestaShopBundle/Controller/Admin/PrestaShopAdminController.php) in the class. | ||||||
|
|
||||||
| {{% notice info %}} | ||||||
| **Learn more about Symfony controllers:** | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah that's what I mentioned in another comment, perfect! |
||||||
| - [Controllers as services](https://symfony.com/doc/6.4/controller/service.html) | ||||||
| - [Service Subscribers & Locators](https://symfony.com/doc/6.4/service_container/service_subscribers_locators.html) | ||||||
| - [Service container](https://symfony.com/doc/6.4/service_container.html) | ||||||
| {{% /notice %}} | ||||||
|
|
||||||
| ## Secure your controller | ||||||
|
|
||||||
| It is safer to define permissions required to use your controller, this can be configured using the `#[AdminSecurity]` attribute and some configuration in your routing file. You can read this documentation if you need more details about [Controller Security]({{< ref "/9/development/architecture/migration-guide/controller-routing.md#security" >}}). | ||||||
|
|
||||||
| {{% notice note %}} | ||||||
| **PrestaShop 9.0+**: The `@AdminSecurity` annotation has been replaced with the `#[AdminSecurity]` attribute following PHP 8 standards. | ||||||
| {{% /notice %}} | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these ones are doublons, you can either use the tag alon Or the autoconfigure alone but both are doing almost the same thing
So I would favor relying on
autoconfigure: trueand removing the tag part to keep the definition as simple as possibleThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, regarding the autowiring, maybe we should add an example when you want to inject a service manually But we're entering more in Symfony many possibilities for DI, so instead of redocumenting Symfony we should probably add a notice about the pre-requisite knowledge from the framework itself
The same links that were added in the 9.0 changes pages:
and maybe https://symfony.com/doc/6.4/controller.html for a more generic view on what a Symfony controller can do