Skip to content

Commit

Permalink
Refacto the Symfony command to update hooks on autoupgrade module, fi…
Browse files Browse the repository at this point in the history
…nd old and new hooks based on the fixtures files and then export the two SQL queries in the upgrade file
  • Loading branch information
jolelievre committed Oct 20, 2024
1 parent 98d54fd commit 0569883
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 147 deletions.
262 changes: 131 additions & 131 deletions src/PrestaShopBundle/Command/AppendHooksListForSqlUpgradeFileCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,91 +27,42 @@
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;
use Symfony\Component\Console\Output\OutputInterface;
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;
}

/**
Expand All @@ -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',
Expand All @@ -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) {
Expand All @@ -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;
}
Expand All @@ -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.
*
Expand All @@ -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) {
Expand All @@ -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.
*
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading

0 comments on commit 0569883

Please sign in to comment.