/
githubmirror
/
magento2
Обзор
Документация
Войти
/
githubmirror
/
magento2
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
2.4-develop
lib/internal/Magento/Framework/Mview/TriggerCleaner.php
223 строки
6 KB
Vincent ENJALBERT
#33386 Fix triggers management issue during setup:upgrade
03 апр 2026, 11:28
03 апр 2026, 11:28
a51ef48
Код
Авторство
О чём код?
<?php /** * Copyright 2020 Adobe * All Rights Reserved. */ declare(strict_types=1); namespace Magento\Framework\Mview; use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Ddl\Trigger; use Magento\Framework\Mview\View\CollectionFactory; use Magento\Framework\Mview\View\StateInterface; use Magento\Framework\Mview\View\Subscription; /** * Class for removing old triggers that were created by mview */ class TriggerCleaner { /** * @var CollectionFactory */ private $viewCollectionFactory; /** * @var ResourceConnection */ private $resource; /** * @var ViewFactory */ private $viewFactory; /** * @var array */ private $processedTriggers = []; /** * @var array */ private $DbTriggers = []; /** * @param CollectionFactory $viewCollectionFactory * @param ResourceConnection $resource * @param ViewFactory $viewFactory */ public function __construct( CollectionFactory $viewCollectionFactory, ResourceConnection $resource, ViewFactory $viewFactory ) { $this->viewCollectionFactory = $viewCollectionFactory; $this->resource = $resource; $this->viewFactory = $viewFactory; } /** * Remove the outdated trigger from the system * * @return bool * @throws \Exception */ public function removeTriggers(): bool { $this->getDbTriggers(); // Get list of views that are enabled $viewCollection = $this->viewCollectionFactory->create(); $viewList = $viewCollection->getViewsByStateMode(StateInterface::MODE_ENABLED); // Check triggers declaration for the enabled views and update them if any changes foreach ($viewList as $view) { $subscriptions = $view->getSubscriptions(); foreach ($subscriptions as $subscriptionConfig) { /* @var $subscription Subscription */ $subscription = $view->initSubscriptionInstance($subscriptionConfig); $viewTriggers = $subscription->create(false)->getTriggers(); $this->processViewTriggers($viewTriggers, $subscription); } } // Remove any remaining triggers from db that are not linked to a view $remainingTriggers = array_diff_key($this->DbTriggers, $this->processedTriggers); foreach ($remainingTriggers as $trigger) { $view = $this->createViewByTableName($trigger['EVENT_OBJECT_TABLE']); if ($view->getActionClass()) { $view->unsubscribe(); $view->getState()->delete(); } } return true; } /** * Process and update View Triggers if changes were made * * @param array $viewTriggers * @param Subscription $subscription * @return void */ private function processViewTriggers(array $viewTriggers, Subscription $subscription): void { foreach ($viewTriggers as $viewTrigger) { if ($this->shouldUpdateTrigger($viewTrigger)) { $subscription->saveTrigger($viewTrigger); } $this->processedTriggers[$viewTrigger->getName()] = true; } } /** * Determine whether an existing DB trigger needs to be recreated. * * @param Trigger $viewTrigger * @return bool */ private function shouldUpdateTrigger(Trigger $viewTrigger): bool { if (!array_key_exists($viewTrigger->getName(), $this->DbTriggers)) { return true; } $expectedStatement = $this->normalizeTriggerStatement( implode(PHP_EOL, $this->getStatementsFromViewTrigger($viewTrigger)) ); $dbStatement = $this->normalizeTriggerStatement( $this->DbTriggers[$viewTrigger->getName()]['ACTION_STATEMENT'] ); return $expectedStatement !== $dbStatement; } /** * Retrieve list of all triggers from DB * * @return void */ private function getDbTriggers(): void { $connection = $this->resource->getConnection(); $dbName = $this->resource->getSchemaName(ResourceConnection::DEFAULT_CONNECTION); $sql = $connection->select() ->from( ['information_schema.TRIGGERS'], ['TRIGGER_NAME', 'ACTION_STATEMENT', 'EVENT_OBJECT_TABLE'] ) ->where('TRIGGER_SCHEMA = ?', $dbName); $this->DbTriggers = $connection->fetchAssoc($sql); } /** * Create view by db table name * * Create a view that has the table name so that unsubscribe can be used to * remove triggers with the correct naming structure from the db * * @param string $tableName * @return ViewInterface */ private function createViewByTableName(string $tableName): ViewInterface { $subscription[$tableName] = [ 'name' => $tableName, 'column' => '', 'subscription_model' => null ]; $data['data'] = [ 'subscriptions' => $subscription, ]; $view = $this->viewFactory->create($data); $view->setId('old_view'); $view->getState()->setMode(StateInterface::MODE_ENABLED); return $view; } /** * Get trigger statements for further analyze * * @param Trigger $trigger * @return string[] */ private function getStatementsFromViewTrigger(Trigger $trigger): array { $statements = $trigger->getStatements(); //Check for staged entity attribute subscription $statement = array_shift($statements); if (str_contains($statement, 'SET')) { $splitStatements = explode(PHP_EOL, $statement); $statements += $splitStatements; } else { array_unshift($statements, $statement); } return $statements; } /** * Normalize trigger statements so equivalent SQL can be compared reliably. * * @param string $statement * @return string */ private function normalizeTriggerStatement(string $statement): string { $statement = trim($statement); if (preg_match('/^BEGIN\s+(.*)\s+END;?$/is', $statement, $matches)) { $statement = $matches[1]; } $statement = preg_replace('/\s*;\s*/', ';', $statement); return trim((string)preg_replace('/\s+/', ' ', $statement)); } }