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
Context refactorisation for controller #34390
Conversation
M0rgan01
commented
Oct 26, 2023
•
edited
edited
Questions | Answers |
---|---|
Branch? | develop |
Description? | - |
Type? | refacto |
Category? | BO |
BC breaks? | no |
Deprecations? | no |
How to test? | - |
UI Tests | Legacy: https://github.com/M0rgan01/ga.tests.ui.pr/actions/runs/6827242059 / Symfony: https://github.com/M0rgan01/ga.tests.ui.pr/actions/runs/6827243906 |
Fixed issue or discussion? | Fixes #33191 |
Related PRs | - |
Sponsor company | - |
b6248a9
to
495d193
Compare
eefdbdf
to
debad42
Compare
e2b84ff
to
fa5406c
Compare
use Module; | ||
use Symfony\Component\DependencyInjection\ContainerInterface; | ||
use Traversable; | ||
|
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.
You can use php strict typing 😄
ab861ef
to
df55257
Compare
df55257
to
2f41140
Compare
@@ -1,5 +1,16 @@ | |||
parameters: | |||
multishop.settings.share_orders: !php/const Shop::SHARE_ORDER | |||
controllers.locked.to.all.shop.context: |
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.
controllers.locked.to.all.shop.context: | |
prestashop.all_shops_controllers: |
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.
outdated
classes/Context.php
Outdated
@@ -28,6 +28,7 @@ | |||
use PrestaShop\PrestaShop\Adapter\ContainerFinder; | |||
use PrestaShop\PrestaShop\Adapter\Module\Repository\ModuleRepository; | |||
use PrestaShop\PrestaShop\Adapter\SymfonyContainer; | |||
use PrestaShop\PrestaShop\Core\Context\LegacyController as ControllerContext; |
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'm not sure the alias is needed, we could keep LegacyController
which describes more the object IMHO
src/Adapter/ContextStateManager.php
Outdated
* | ||
* @return $this | ||
*/ | ||
public function setController(?LegacyController $legacyController): self |
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.
Nothing to add about the method itself, however this MUST be tested At least in unit tests of the ContextStateManager here:
class ContextStateManagerTest extends ContextStateTestCase |
You can inspire yourself from what I did in my PR for language context https://github.com/PrestaShop/PrestaShop/pull/34500/files#diff-b20eac977acbd6001bb22e5a07171efb9b80020d3abefa5c66cf34f0ed6e3a30R179
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 didn't have time to look into the integration tests yet so I don't remember how hard they are to adapt But this component is too sensitive to remain untested
class LegacyController | ||
{ | ||
// Dependency container | ||
protected ContainerInterface $container; |
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.
With the Symfony convention, that we try to follow, the protected method/fields are supposed to be declared after the public ones, so it'd be nice to move this one a bit lower
@@ -42,7 +45,7 @@ public function __construct( | |||
|
|||
public function onKernelRequest(RequestEvent $event): void | |||
{ | |||
if (!$event->isMainRequest()) { | |||
if (!$event->isMainRequest() && $this->isApiRequest($event->getRequest())) { |
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.
if (!$event->isMainRequest() && $this->isApiRequest($event->getRequest())) { | |
if (!$event->isMainRequest() || $this->isApiRequest($event->getRequest())) { |
Isn't it an OR condition here? 🤔 Same comment for all the other listeners since it's the same code
bind: | ||
$controllersLockedToAllShopContext: '%prestashop.controllers_all_shop_context%' | ||
$container: "@service_container" | ||
$coreDir: !php/const _PS_CORE_DIR_ |
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.
$coreDir: !php/const _PS_CORE_DIR_ | |
$projectDir: '%kernel.project_dir%' |
|
||
trait ApiPlatformTrait | ||
{ | ||
protected function isApiRequest(Request $request): bool |
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.
protected function isApiRequest(Request $request): bool | |
protected function isApiPlatformRequest(Request $request): bool |
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 don't think it's a good thing to name a method by the name of a framework. If we change tomorrow we will have to change all the calls, in addition to changing the body of the method
Same thing for file name
{ | ||
use MockConfigurationTrait; | ||
|
||
public function testLegacyBuild(): void |
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.
Another argument about my comment above, about making the controller name settable via a setter method It would make it easier to test this via a data provider You can loop through different values of the controller name that will impact the deduced values like className
, shopLinkType
, multishop_context
, overrideFolder
and so on Thus you will be able to test and validate much more things from the builder internal behaviour
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.
Note that even if we don't go for the setter strategy for the controller name, the tests can still be dynamized and enhanced since you can also mock the request with different controller name values
tests/Unit/bootstrap.php
Outdated
@@ -33,6 +33,7 @@ | |||
define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__, 2) . '/vendor/autoload.php'); | |||
} | |||
|
|||
define('_COOKIE_KEY_', Tools::passwdGen(56)); |
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.
Hmm, why is this suddenly needed? 🤔
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.
We call Tools::getAdminToken in the builder, it needs the constant
15e339b
to
5872232
Compare
src/Adapter/ContextStateManager.php
Outdated
* | ||
* @return $this | ||
*/ | ||
public function setController(?LegacyControllerContext $legacyController): self |
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.
This shouldn't be limited to the legacy controller only, but all the potential classes allowed in Context::controller Including the real legacy controllers (both admin and front)
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.
public function setController(?LegacyControllerContext $legacyController): self | |
public function setController(?LegacyControllerContext|Controller $legacyController): self |
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.
Using the legacy Controller
parent class will allow using all the different types (front/admin/modulefront/moduleadmin/...)
public array $_conf; | ||
|
||
public array $_error; |
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 looked a bit more into detail the usages of these two fields, seems like they are mostly used internally as well by admin controllers like they would use $this->_conf[45]
to add a message in smarty or in a json response
But as we removed some other internal properties I'm not sure these ones are actually relevant, so we can maybe remove them as well (at least for now until someone complains about it)
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.
The strategy 'we keep as much of the public field as possible because we don't know which ones are used' from the start is no longer true x) But it is better in the sense that it cleans the fields that should have been protected
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.
Yep I changed my mind a little bit But I didn't realize there were so many public properties that are actually purely related to internal behaviour of the admin controller and that should have been protected probably
} | ||
} | ||
|
||
private function getConf(): array |
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.
As commented above, maybe this could be removed 🤔
* Utility Trait, enabling the detection of whether a request is an API Platform request. | ||
* This allows us to condition listeners and thus control the creation of contexts. | ||
*/ | ||
trait ApiPlatformTrait |
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 though you didn't like the ApiPlatform
part because it's too related to the framework
Didn't we think about ExternalApiTrait::isExternalApiRequest
?
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.
Yes I prefer ExternalApi.
$contextStateManager = new ContextStateManager($this->legacyContext); | ||
$this->assertNull($contextStateManager->getContextFieldsStack()); | ||
|
||
$contextStateManager->setController($this->createLegacyControllerContextMock('AdminProductsController')); |
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.
You should use a third different value as this one is the same as the initial one
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.
$contextStateManager->setController($this->createLegacyControllerContextMock('AdminProductsController')); | |
$contextStateManager->setController($this->createLegacyControllerContextMock('AdminOrdersController')); |
$this->assertNull($contextStateManager->getContextFieldsStack()); | ||
|
||
$contextStateManager->setController($this->createLegacyControllerContextMock('AdminProductsController')); | ||
$this->assertEquals('AdminProductsController', $context->controller->controller_name); |
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.
$this->assertEquals('AdminProductsController', $context->controller->controller_name); | |
$this->assertEquals('AdminOrdersController', $context->controller->controller_name); |
{ | ||
use MockConfigurationTrait; | ||
|
||
public function testBuild(): void |
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.
Are you familiar with the @dataProvider
in PHPUnit tests? This is more adapted when you mean to test different values with the same test logic You can dynamize the parameters of the test method and pass it multiple settings (from the initialization parameters to the expected values)
It's easier to add multiple use cases then
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.
You can find an example here
public function testBuild(int $languageId, string $expectedName, string $expectedSymbol, string $expectedPattern): void |
This would allow you testing all the special use cases (especially for className and multishop context) efficiently and it reduces the size of code needed while keeping the readability easier (at least in my opinion, but maybe it's because I'm used to it)
bc4f2ee
to
07f7289
Compare
07f7289
to
b41e76d
Compare
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.
Thanks @M0rgan01
just a few suggestions but none of them are blocking. Nice work!
*/ | ||
class LegacyControllerContext | ||
{ | ||
public ?string $className; |
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.
Not necessarily to be done in this PR, but I think most of those fields are only read and never written, or at least they should never be written Since we can now use the readonly
property we should think about which fields could be forced as read only
This would enforce even more the contract that the controller is supposed to follow and reduce the potential bugs due to external modules and code modifying some internal fields
But again not blocking for this PR
yield 'AdminAccessController' => [ | ||
'AdminAccessController', | ||
'Profile', | ||
7, |
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.
It would be easier to understand if you used the combination of the const here for future developers, but it's a detail not worth blocking the PR
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.
7, | |
ShopConstraint::ALL_SHOPS | ShopConstraint::SHOP | ShopConstraint::SHOP_GROUP, |
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.
Constant or comment, but yeah 7 is not explicit 😄
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.
Nothing to add to @jolelievre review
yield 'AdminAccessController' => [ | ||
'AdminAccessController', | ||
'Profile', | ||
7, |
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.
Constant or comment, but yeah 7 is not explicit 😄