From 056988382ca2cca475bf90ec67b568a2da889bd9 Mon Sep 17 00:00:00 2001 From: Jonathan LELIEVRE Date: Wed, 16 Oct 2024 16:40:27 +0200 Subject: [PATCH] Refacto the Symfony command to update hooks on autoupgrade module, find old and new hooks based on the fixtures files and then export the two SQL queries in the upgrade file --- ...ppendHooksListForSqlUpgradeFileCommand.php | 262 +++++++++--------- .../config/services/adapter/services.yml | 8 +- .../Resources/config/services/core/hook.yml | 30 +- .../config/services_dev/bundle/command.yml | 12 +- 4 files changed, 165 insertions(+), 147 deletions(-) diff --git a/src/PrestaShopBundle/Command/AppendHooksListForSqlUpgradeFileCommand.php b/src/PrestaShopBundle/Command/AppendHooksListForSqlUpgradeFileCommand.php index 058257dc62324..e111342679ffd 100644 --- a/src/PrestaShopBundle/Command/AppendHooksListForSqlUpgradeFileCommand.php +++ b/src/PrestaShopBundle/Command/AppendHooksListForSqlUpgradeFileCommand.php @@ -27,12 +27,11 @@ namespace PrestaShopBundle\Command; use Employee; -use PrestaShop\PrestaShop\Adapter\Hook\HookInformationProvider; use PrestaShop\PrestaShop\Adapter\LegacyContext; -use PrestaShop\PrestaShop\Core\Hook\Generator\HookDescriptionGenerator; use PrestaShop\PrestaShop\Core\Hook\HookDescription; -use PrestaShop\PrestaShop\Core\Hook\Provider\GridDefinitionHookByServiceIdsProvider; -use PrestaShop\PrestaShop\Core\Hook\Provider\IdentifiableObjectHookByFormTypeProvider; +use PrestaShop\PrestaShop\Core\Version; +use RuntimeException; +use SimpleXMLElement; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -40,78 +39,30 @@ use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Exception\FileNotFoundException; use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Contracts\HttpClient\HttpClientInterface; /** * Appends sql upgrade file with the sql which can be used to create new hooks. + * + * The command compares the current hook.xml fixture file with the previous one (you need to specify + * the previous version to define the base to compare to). + * + * Thanks to the comparison we get new and obsolete hooks, then two SQL queries are generated and + * appended in the autoupgrade file (you must specify its local path), the upgrade file matching the + * current version will be appended with these two SQL queries. + * + * No check of previous request in the file is done you must check manually that there are no duplicates. */ class AppendHooksListForSqlUpgradeFileCommand extends Command { - /** - * @var string - */ - private $env; - - /** - * @var LegacyContext - */ - private $legacyContext; - - /** - * @var GridDefinitionHookByServiceIdsProvider - */ - private $gridDefinitionHookByServiceIdsProvider; - - /** - * @var IdentifiableObjectHookByFormTypeProvider - */ - private $identifiableObjectHookByFormTypeProvider; - - /** - * @var HookInformationProvider - */ - private $hookInformationProvider; - - /** - * @var HookDescriptionGenerator - */ - private $hookDescriptionGenerator; - - /** - * @var array - */ - private $serviceIds; - - /** - * @var array - */ - private $optionFormHookNames; - - /** - * @var array - */ - private $formTypes; - public function __construct( - string $env, - LegacyContext $legacyContext, - GridDefinitionHookByServiceIdsProvider $gridDefinitionHookByServiceIdsProvider, - IdentifiableObjectHookByFormTypeProvider $identifiableObjectHookByFormTypeProvider, - HookInformationProvider $hookInformationProvider, - HookDescriptionGenerator $hookDescriptionGenerator, - array $serviceIds, - array $optionFormHookNames, - array $formTypes + private string $env, + private LegacyContext $legacyContext, + private HttpClientInterface $httpClient, + private string $projectDir, ) { parent::__construct(); - $this->env = $env; - $this->legacyContext = $legacyContext; - $this->gridDefinitionHookByServiceIdsProvider = $gridDefinitionHookByServiceIdsProvider; - $this->identifiableObjectHookByFormTypeProvider = $identifiableObjectHookByFormTypeProvider; - $this->hookInformationProvider = $hookInformationProvider; - $this->hookDescriptionGenerator = $hookDescriptionGenerator; - $this->serviceIds = $serviceIds; - $this->optionFormHookNames = $optionFormHookNames; - $this->formTypes = $formTypes; } /** @@ -125,9 +76,9 @@ protected function configure() 'Adds sql to sql upgrade file which contains hook insert operation' ) ->addArgument( - 'ps-version', + 'previous-ps-version', InputArgument::REQUIRED, - 'The prestashop version for which sql upgrade file will be searched' + 'The previous prestashop version based on which we know the previous existing hooks' ) ->addArgument( 'autoupgrade-path', @@ -149,21 +100,22 @@ protected function execute(InputInterface $input, OutputInterface $output) return 1; } - $hookNames = $this->getHookNames(); - $hookNames = $this->getWithoutRegisteredHooks($hookNames); + $currentHooks = $this->getCurrentHooks(); + $previousHooks = $this->getPreviousHooks($input->getArgument('previous-ps-version')); - if (empty($hookNames)) { - $io->note('No hooks found.'); + $newHooks = array_diff($currentHooks, $previousHooks); + $removedHooks = array_diff($previousHooks, $currentHooks); + + if (empty($newHooks) && empty($removedHooks)) { + $io->note('No hooks modification found.'); return 0; } - $hookDescriptions = $this->getHookDescriptions($hookNames); - - $prestashopVersion = $input->getArgument('ps-version'); + // Get SQL upgrade file from module for the current version try { $sqlUpgradeFile = $this->getSqlUpgradeFileByPrestaShopVersion( - $prestashopVersion, + Version::VERSION, $input->getArgument('autoupgrade-path') ); } catch (FileNotFoundException $exception) { @@ -175,17 +127,33 @@ protected function execute(InputInterface $input, OutputInterface $output) return 1; } - $sqlInsertStatement = $this->getSqlInsertStatement($hookDescriptions, $prestashopVersion); - - $this->appendSqlToFile($sqlUpgradeFile, $sqlInsertStatement); + // First add new hooks to SQL file + if (!empty($newHooks)) { + $hookDescriptions = $this->extractHookDescriptions($newHooks); + + $sqlInsertStatement = $this->getSqlInsertStatement($hookDescriptions, Version::VERSION); + $this->appendSqlToFile($sqlUpgradeFile, $sqlInsertStatement); + $io->success( + sprintf( + 'All new %s hooks have been listed to file %s', + count($newHooks), + $sqlUpgradeFile + ) + ); + } - $io->success( - sprintf( - 'All %s hooks have been listed to file %s', - count($hookNames), - $sqlUpgradeFile - ) - ); + // Now delete removed hooks + if (!empty($removedHooks)) { + $sqlDeleteStatement = $this->getSqlDeleteStatement($removedHooks, Version::VERSION); + $this->appendSqlToFile($sqlUpgradeFile, $sqlDeleteStatement); + $io->success( + sprintf( + 'All obsolete %s hooks have been removed in file %s', + count($removedHooks), + $sqlUpgradeFile + ) + ); + } return 0; } @@ -203,24 +171,6 @@ private function initContext() } } - /** - * Gets all hooks names which need to be appended. - * - * @return string[] - */ - private function getHookNames() - { - $gridDefinitionHookNames = $this->gridDefinitionHookByServiceIdsProvider->getHookNames($this->serviceIds); - - $identifiableObjectHookNames = $this->identifiableObjectHookByFormTypeProvider->getHookNames($this->formTypes); - - return array_merge( - $identifiableObjectHookNames, - $this->optionFormHookNames, - $gridDefinitionHookNames - ); - } - /** * Gets sql upgrade file by PrestaShop version. * @@ -247,7 +197,7 @@ private function getSqlUpgradeFileByPrestaShopVersion($version, $autoUpgradeModu * * @return string */ - private function getSqlInsertStatement(array $hookDescriptions, string $prestashopVersion) + private function getSqlInsertStatement(array $hookDescriptions, string $prestashopVersion): string { $valuesToInsert = []; foreach ($hookDescriptions as $hookDescription) { @@ -266,11 +216,28 @@ private function getSqlInsertStatement(array $hookDescriptions, string $prestash $insertSQL = PHP_EOL . "/* Auto generated hooks added for version $prestashopVersion */" . PHP_EOL; $insertSQL .= 'INSERT IGNORE INTO `PREFIX_hook` (`id_hook`, `name`, `title`, `description`, `position`) VALUES' . PHP_EOL; $insertSQL .= implode(',' . PHP_EOL, $valuesToInsert); - $insertSQL .= PHP_EOL . ';'; + $insertSQL .= PHP_EOL . ';' . PHP_EOL; return $insertSQL; } + private function getSqlDeleteStatement(array $removedHooks, string $prestashopVersion): string + { + if (empty($removedHooks)) { + return ''; + } + + $deleteSQL = PHP_EOL . "/* Auto generated hooks removed for version $prestashopVersion */" . PHP_EOL; + $deleteSQL .= 'DELETE FROM `PREFIX_hook` WHERE `name` IN (' . PHP_EOL; + $deleteSQL .= implode(',' . PHP_EOL, array_map(fn (string $hookName) => " '$hookName'", $removedHooks)); + $deleteSQL .= PHP_EOL . ');' . PHP_EOL; + $deleteSQL .= '/* Clean hook registrations related to removed hooks */' . PHP_EOL; + $deleteSQL .= 'DELETE FROM `PREFIX_hook_module` WHERE `id_hook` NOT IN (SELECT id_hook FROM `PREFIX_hook`);' . PHP_EOL; + $deleteSQL .= 'DELETE FROM `PREFIX_hook_module_exceptions` WHERE `id_hook` NOT IN (SELECT id_hook FROM `PREFIX_hook`);' . PHP_EOL; + + return $deleteSQL; + } + /** * Appends new content to the given file. * @@ -284,37 +251,70 @@ private function appendSqlToFile($pathToFile, $content) $fileSystem->appendToFile($pathToFile, $content); } - /** - * Filters out already registered hooks. - * - * @param array $hookNames - * - * @return array - */ - private function getWithoutRegisteredHooks(array $hookNames) + private function getCurrentHooks(): array { - $registeredHooks = $this->hookInformationProvider->getHooks(); - $registeredHookNames = array_column($registeredHooks, 'name'); + $currentHookXml = file_get_contents($this->projectDir . '/install-dev/data/xml/hook.xml'); - return array_diff($hookNames, $registeredHookNames); + return $this->extractHookNamesFromXML($currentHookXml); } - /** - * Gets hook descriptions - * - * @param array $hookNames - * - * @return HookDescription[] - */ - private function getHookDescriptions(array $hookNames) + private function getPreviousHooks(string $previousVersion): array + { + $previousHookFile = sprintf('https://raw.githubusercontent.com/PrestaShop/PrestaShop/refs/tags/%s/install-dev/data/xml/hook.xml', $previousVersion); + $response = $this->httpClient->request('GET', $previousHookFile); + if ($response->getStatusCode() !== Response::HTTP_OK) { + throw new RuntimeException('Could not get previous hook information ' . $previousHookFile); + } + + return $this->extractHookNamesFromXML($response->getContent()); + } + + private function extractHookNamesFromXML(string $xmlContent): array { - $descriptions = []; - foreach ($hookNames as $hookName) { - $hookDescription = $this->hookDescriptionGenerator->generate($hookName); + $xmlFileContent = new SimpleXMLElement($xmlContent); + + if (!isset($xmlFileContent->entities, $xmlFileContent->entities->hook)) { + throw new RuntimeException('Invalid hook fixtures files could not find hooks node'); + } + + $hookNames = []; + foreach ($xmlFileContent->entities->hook as $hook) { + if (!isset($hook->name)) { + continue; + } - $descriptions[] = $hookDescription; + $hookNames[] = $hook->name->__toString(); + } + + return $hookNames; + } + + private function extractHookDescriptions(array $extractedHooks): array + { + $currentHookXml = file_get_contents($this->projectDir . '/install-dev/data/xml/hook.xml'); + $xmlFileContent = new SimpleXMLElement($currentHookXml); + + if (!isset($xmlFileContent->entities, $xmlFileContent->entities->hook)) { + throw new RuntimeException('Invalid hook fixtures files could not find hooks node'); + } + + $hookNames = []; + foreach ($xmlFileContent->entities->hook as $hook) { + if (!isset($hook->name)) { + continue; + } + $hookName = $hook->name->__toString(); + if (!in_array($hookName, $extractedHooks)) { + continue; + } + + $hookNames[] = new HookDescription( + $hook->name->__toString(), + $hook->title->__toString(), + $hook->description->__toString() + ); } - return $descriptions; + return $hookNames; } } diff --git a/src/PrestaShopBundle/Resources/config/services/adapter/services.yml b/src/PrestaShopBundle/Resources/config/services/adapter/services.yml index f55126b46f0bc..0e8879e9a83b1 100644 --- a/src/PrestaShopBundle/Resources/config/services/adapter/services.yml +++ b/src/PrestaShopBundle/Resources/config/services/adapter/services.yml @@ -52,8 +52,14 @@ services: prestashop.adapter.legacy.logger: class: PrestaShop\PrestaShop\Adapter\LegacyLogger + PrestaShop\PrestaShop\Adapter\Hook\HookInformationProvider: + public: false + prestashop.adapter.legacy.hook: - class: PrestaShop\PrestaShop\Adapter\Hook\HookInformationProvider + alias: PrestaShop\PrestaShop\Adapter\Hook\HookInformationProvider + deprecated: + package: PrestaShop\PrestaShop + version: 9.0 # Legacy Hooks registrator prestashop.adapter.legacy.hook.subscriber: diff --git a/src/PrestaShopBundle/Resources/config/services/core/hook.yml b/src/PrestaShopBundle/Resources/config/services/core/hook.yml index 20e19e44bdd0e..648971870a82d 100644 --- a/src/PrestaShopBundle/Resources/config/services/core/hook.yml +++ b/src/PrestaShopBundle/Resources/config/services/core/hook.yml @@ -11,22 +11,40 @@ services: prestashop.core.hook.dispatcher: alias: 'PrestaShop\PrestaShop\Core\Hook\HookDispatcher' - prestashop.core.hook.provider.grid_definition_hook_by_service_ids_provider: - class: PrestaShop\PrestaShop\Core\Hook\Provider\GridDefinitionHookByServiceIdsProvider + PrestaShop\PrestaShop\Core\Hook\Provider\GridDefinitionHookByServiceIdsProvider: + public: false arguments: - '@service_container' - prestashop.core.hook.provider.identifiable_object_hook_by_form_type_provider: - class: PrestaShop\PrestaShop\Core\Hook\Provider\IdentifiableObjectHookByFormTypeProvider + prestashop.core.hook.provider.grid_definition_hook_by_service_ids_provider: + alias: PrestaShop\PrestaShop\Core\Hook\Provider\GridDefinitionHookByServiceIdsProvider + deprecated: + package: PrestaShop\PrestaShop + version: 9.0 + + PrestaShop\PrestaShop\Core\Hook\Provider\IdentifiableObjectHookByFormTypeProvider: + public: false arguments: - '@form.factory' - prestashop.core.hook.generator.hook_description_generator: - class: 'PrestaShop\PrestaShop\Core\Hook\Generator\HookDescriptionGenerator' + prestashop.core.hook.provider.identifiable_object_hook_by_form_type_provider: + alias: PrestaShop\PrestaShop\Core\Hook\Provider\IdentifiableObjectHookByFormTypeProvider + deprecated: + package: PrestaShop\PrestaShop + version: 9.0 + + PrestaShop\PrestaShop\Core\Hook\Generator\HookDescriptionGenerator: + public: false autowire: true arguments: $hookDescriptions: '%hook_descriptions%' + prestashop.core.hook.generator.hook_description_generator: + alias: 'PrestaShop\PrestaShop\Core\Hook\Generator\HookDescriptionGenerator' + deprecated: + package: PrestaShop\PrestaShop + version: 9.0 + parameters: hook_descriptions: 'grid_definition_modifier': diff --git a/src/PrestaShopBundle/Resources/config/services_dev/bundle/command.yml b/src/PrestaShopBundle/Resources/config/services_dev/bundle/command.yml index b1c2ef616500d..eb09791d4bb41 100644 --- a/src/PrestaShopBundle/Resources/config/services_dev/bundle/command.yml +++ b/src/PrestaShopBundle/Resources/config/services_dev/bundle/command.yml @@ -17,16 +17,10 @@ services: - 'console.command' PrestaShopBundle\Command\AppendHooksListForSqlUpgradeFileCommand: + autowire: true arguments: - - '%kernel.environment%' - - '@prestashop.adapter.legacy.context' - - '@prestashop.core.hook.provider.grid_definition_hook_by_service_ids_provider' - - '@prestashop.core.hook.provider.identifiable_object_hook_by_form_type_provider' - - '@prestashop.adapter.legacy.hook' - - '@prestashop.core.hook.generator.hook_description_generator' - - '%prestashop.core.grid.definition.service_ids%' - - '%prestashop.hook.option_form_hook_names%' - - '%prestashop.core.form.identifiable_object.form_types%' + $env: '%kernel.environment%' + $projectDir: '%kernel.project_dir%' tags: - 'console.command'