Skip to content
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

Merged
merged 2 commits into from Nov 13, 2023

Conversation

M0rgan01
Copy link
Contributor

@M0rgan01 M0rgan01 commented Oct 26, 2023

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 -

@prestonBot prestonBot added develop Branch Refactoring Type: Refactoring labels Oct 26, 2023
@M0rgan01 M0rgan01 force-pushed the improvement/33191 branch 4 times, most recently from b6248a9 to 495d193 Compare October 27, 2023 14:45
@M0rgan01 M0rgan01 force-pushed the improvement/33191 branch 6 times, most recently from eefdbdf to debad42 Compare November 6, 2023 13:55
@M0rgan01 M0rgan01 added this to the 9.0.0 milestone Nov 6, 2023
@M0rgan01 M0rgan01 marked this pull request as ready for review November 6, 2023 15:32
@M0rgan01 M0rgan01 requested a review from a team as a code owner November 6, 2023 15:32
src/Adapter/Context/ControllerContextBuilder.php Outdated Show resolved Hide resolved
src/Adapter/Context/ControllerContextBuilder.php Outdated Show resolved Hide resolved
src/Adapter/Context/ControllerContextBuilder.php Outdated Show resolved Hide resolved
src/Adapter/Context/Controller.php Outdated Show resolved Hide resolved
@M0rgan01 M0rgan01 force-pushed the improvement/33191 branch 4 times, most recently from e2b84ff to fa5406c Compare November 7, 2023 14:37
use Module;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Traversable;

Copy link
Contributor

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 😄

@M0rgan01 M0rgan01 force-pushed the improvement/33191 branch 9 times, most recently from ab861ef to df55257 Compare November 8, 2023 15:02
@@ -1,5 +1,16 @@
parameters:
multishop.settings.share_orders: !php/const Shop::SHARE_ORDER
controllers.locked.to.all.shop.context:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
controllers.locked.to.all.shop.context:
prestashop.all_shops_controllers:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Contributor

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

phpstan-baseline.neon Show resolved Hide resolved
*
* @return $this
*/
public function setController(?LegacyController $legacyController): self
Copy link
Contributor

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

Copy link
Contributor

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;
Copy link
Contributor

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())) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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_
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$coreDir: !php/const _PS_CORE_DIR_
$projectDir: '%kernel.project_dir%'


trait ApiPlatformTrait
{
protected function isApiRequest(Request $request): bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
protected function isApiRequest(Request $request): bool
protected function isApiPlatformRequest(Request $request): bool

Copy link
Contributor Author

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
Copy link
Contributor

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

Copy link
Contributor

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

@@ -33,6 +33,7 @@
define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__, 2) . '/vendor/autoload.php');
}

define('_COOKIE_KEY_', Tools::passwdGen(56));
Copy link
Contributor

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? 🤔

Copy link
Contributor Author

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

@M0rgan01 M0rgan01 force-pushed the improvement/33191 branch 8 times, most recently from 15e339b to 5872232 Compare November 10, 2023 09:52
*
* @return $this
*/
public function setController(?LegacyControllerContext $legacyController): self
Copy link
Contributor

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)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public function setController(?LegacyControllerContext $legacyController): self
public function setController(?LegacyControllerContext|Controller $legacyController): self

Copy link
Contributor

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/...)

Comment on lines 96 to 98
public array $_conf;

public array $_error;
Copy link
Contributor

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)

Copy link
Contributor Author

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

Copy link
Contributor

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

src/Core/Context/LegacyControllerContext.php Show resolved Hide resolved
}
}

private function getConf(): array
Copy link
Contributor

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
Copy link
Contributor

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 ?

Copy link
Contributor Author

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'));
Copy link
Contributor

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$this->assertEquals('AdminProductsController', $context->controller->controller_name);
$this->assertEquals('AdminOrdersController', $context->controller->controller_name);

{
use MockConfigurationTrait;

public function testBuild(): void
Copy link
Contributor

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

Copy link
Contributor

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)

@M0rgan01 M0rgan01 force-pushed the improvement/33191 branch 4 times, most recently from bc4f2ee to 07f7289 Compare November 10, 2023 15:36
Copy link
Contributor

@jolelievre jolelievre left a 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;
Copy link
Contributor

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,
Copy link
Contributor

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
7,
ShopConstraint::ALL_SHOPS | ShopConstraint::SHOP | ShopConstraint::SHOP_GROUP,

Copy link
Contributor

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 😄

Copy link
Contributor

@matks matks left a 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,
Copy link
Contributor

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 😄

@ps-jarvis ps-jarvis added the Waiting for QA Status: action required, waiting for test feedback label Nov 13, 2023
@M0rgan01 M0rgan01 merged commit 2cb5615 into PrestaShop:develop Nov 13, 2023
18 checks passed
@M0rgan01 M0rgan01 removed the Waiting for QA Status: action required, waiting for test feedback label Nov 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
develop Branch Refactoring Type: Refactoring
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Handle backward compatibility for smarty and context controller
6 participants