123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- namespace Magento\Framework\Setup\Declaration\Schema\Diff;
- use Magento\Framework\Component\ComponentRegistrar;
- use Magento\Framework\Setup\Declaration\Schema\Dto\Constraint;
- use Magento\Framework\Setup\Declaration\Schema\Dto\Constraints\Reference;
- use Magento\Framework\Setup\Declaration\Schema\Dto\ElementInterface;
- use Magento\Framework\Setup\Declaration\Schema\Dto\Index;
- use Magento\Framework\Setup\Declaration\Schema\Dto\Table;
- use Magento\Framework\Setup\Declaration\Schema\Dto\TableElementInterface;
- use Magento\Framework\Setup\Declaration\Schema\ElementHistory;
- use Magento\Framework\Setup\Declaration\Schema\ElementHistoryFactory;
- use Magento\Framework\Setup\Declaration\Schema\Operations\DropReference;
- /**
- * Holds information about all changes between 2 schemas: db and declaration XML.
- * Holds 2 items:
- * - new (Should be changed to)
- * - old ()
- * @api
- * @since 102.0.0
- */
- class Diff implements DiffInterface
- {
- /**
- * Whitelist file name.
- */
- const GENERATED_WHITELIST_FILE_NAME = 'db_schema_whitelist.json';
- /**
- * @var array
- */
- private $changes;
- /**
- * This changes created only for debug reasons.
- *
- * @var array
- * @since 102.0.0
- */
- public $debugChanges;
- /**
- * @var array
- */
- private $whiteListTables = [];
- /**
- * @var ComponentRegistrar
- */
- private $componentRegistrar;
- /**
- * @var ElementHistoryFactory
- */
- private $elementHistoryFactory;
- /**
- * This indexes is needed to ensure that sort order in which table operations
- * will be executed is correct.
- *
- * @var array
- */
- private $tableIndexes;
- /**
- * List of operations that are destructive from the point of declarative setup
- * and can make system unstable, for example DropTable.
- *
- * @var string[]
- */
- private $destructiveOperations;
- /**
- * Constructor.
- *
- * @param ComponentRegistrar $componentRegistrar
- * @param ElementHistoryFactory $elementHistoryFactory
- * @param array $tableIndexes
- * @param array $destructiveOperations
- */
- public function __construct(
- ComponentRegistrar $componentRegistrar,
- ElementHistoryFactory $elementHistoryFactory,
- array $tableIndexes,
- array $destructiveOperations
- ) {
- $this->componentRegistrar = $componentRegistrar;
- $this->elementHistoryFactory = $elementHistoryFactory;
- $this->tableIndexes = $tableIndexes;
- $this->destructiveOperations = $destructiveOperations;
- }
- /**
- * We return all sorted changes.
- *
- * All changes are sorted because there are dependencies between tables, like foreign keys.
- *
- * @inheritdoc
- * @since 102.0.0
- */
- public function getAll()
- {
- if ($this->changes) {
- ksort($this->changes);
- }
- return $this->changes;
- }
- /**
- * Retrieve all changes for specific table.
- *
- * @param string $table
- * @param string $operation
- * @return ElementHistory[]
- * @since 102.0.0
- */
- public function getChange($table, $operation)
- {
- $tableIndex = $this->tableIndexes[$table];
- return $this->changes[$tableIndex][$operation] ?? [];
- }
- /**
- * Retrieve array of whitelisted tables.
- * Whitelist tables should have JSON format and should be added through
- * CLI command: should be done in next story.
- *
- * @return array
- */
- private function getWhiteListTables()
- {
- if (!$this->whiteListTables) {
- foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $path) {
- $whiteListPath = $path . DIRECTORY_SEPARATOR . 'etc' .
- DIRECTORY_SEPARATOR . 'db_schema_whitelist.json';
- if (file_exists($whiteListPath)) {
- $this->whiteListTables = array_replace_recursive(
- $this->whiteListTables,
- json_decode(file_get_contents($whiteListPath), true)
- );
- }
- }
- }
- return $this->whiteListTables;
- }
- /**
- * Check whether element can be registered.
- *
- * For example, if element is not in db_schema_whitelist.json it cant
- * be registered due to backward incompatibility
- * Extensibility point: if you want to add some dynamic rules of applying or ignoring any schema elements
- * you can do this by pluginizing this method
- *
- * @param ElementInterface | Table $object
- * @param string $operation
- * @return bool
- * @since 102.0.0
- */
- public function canBeRegistered(ElementInterface $object, $operation): bool
- {
- if (!isset($this->destructiveOperations[$operation])) {
- return true;
- }
- $checkResult = false;
- $whiteList = $this->getWhiteListTables();
- if ($object instanceof TableElementInterface) {
- $tableNameWithoutPrefix = $object->getTable()->getNameWithoutPrefix();
- $type = $object->getElementType();
- if ($this->isElementHaveAutoGeneratedName($object)) {
- $checkResult =
- isset($whiteList[$tableNameWithoutPrefix][$type][$object->getNameWithoutPrefix()]);
- } else {
- $checkResult = isset($whiteList[$tableNameWithoutPrefix][$type][$object->getName()]);
- }
- } elseif ($object instanceof Table) {
- $checkResult = isset($whiteList[$object->getNameWithoutPrefix()]);
- }
- return $checkResult;
- }
- /**
- * Check if the element has an auto-generated name.
- *
- * @param ElementInterface $element
- * @return bool
- */
- private function isElementHaveAutoGeneratedName(ElementInterface $element): bool
- {
- return in_array($element->getElementType(), [Index::TYPE, Constraint::TYPE], true);
- }
- /**
- * Register DTO object.
- *
- * @param TableElementInterface $dtoObject
- * @inheritdoc
- * @since 102.0.0
- */
- public function register(
- ElementInterface $dtoObject,
- $operation,
- ElementInterface $oldDtoObject = null
- ) {
- if (!$this->canBeRegistered($dtoObject, $operation)) {
- return $this;
- }
- $historyData = ['new' => $dtoObject, 'old' => $oldDtoObject];
- $history = $this->elementHistoryFactory->create($historyData);
- //dtoObjects can have 4 types: column, constraint, index, table
- $this->changes[$this->findTableIndex($dtoObject, $operation)][$operation][] = $history;
- $this->debugChanges[$operation][] = $history;
- return $this;
- }
- /**
- * As tables can references to each other, we need to take into account
- * that they should goes in specific structure: parent table -> child table
- * Also we should take into account, that first of all in any case we need to remove all foreign keys
- * from tables and only then modify that tables
- *
- * @param ElementInterface $element
- * @param string $operation
- * @return int
- */
- private function findTableIndex(ElementInterface $element, string $operation) : int
- {
- $elementName = $element instanceof TableElementInterface ?
- $element->getTable()->getName() : $element->getName();
- //We use not real tables but table indexes in order to be sure that order of table is correct
- $tableIndex = $this->tableIndexes[$elementName] ?? INF;
- return $operation === DropReference::OPERATION_NAME ? 0 : (int) $tableIndex;
- }
- }
|