1<?php
2
3declare(strict_types=1);
4
5/*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18namespace TYPO3\CMS\Core\Database\Schema;
19
20use Doctrine\DBAL\Platforms\AbstractPlatform;
21use Doctrine\DBAL\Platforms\MySqlPlatform;
22use Doctrine\DBAL\Schema\Column;
23use Doctrine\DBAL\Schema\Table;
24use Doctrine\DBAL\Types\BlobType;
25use Doctrine\DBAL\Types\TextType;
26use TYPO3\CMS\Core\Utility\ArrayUtility;
27use TYPO3\CMS\Core\Utility\GeneralUtility;
28
29/**
30 * Compares two Schemas and returns an instance of SchemaDiff.
31 *
32 * @internal
33 */
34class Comparator extends \Doctrine\DBAL\Schema\Comparator
35{
36    /**
37     * @var AbstractPlatform|null
38     */
39    protected $databasePlatform;
40
41    /**
42     * Comparator constructor.
43     *
44     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
45     */
46    public function __construct(AbstractPlatform $platform = null)
47    {
48        $this->databasePlatform = $platform;
49    }
50
51    /**
52     * Returns the difference between the tables $fromTable and $toTable.
53     *
54     * If there are no differences this method returns the boolean false.
55     *
56     * @param \Doctrine\DBAL\Schema\Table $fromTable
57     * @param \Doctrine\DBAL\Schema\Table $toTable
58     * @return false|\Doctrine\DBAL\Schema\TableDiff|\TYPO3\CMS\Core\Database\Schema\TableDiff
59     * @throws \InvalidArgumentException
60     */
61    public function diffTable(Table $fromTable, Table $toTable)
62    {
63        $newTableOptions = array_merge($fromTable->getOptions(), $toTable->getOptions());
64        $optionDiff = ArrayUtility::arrayDiffAssocRecursive($newTableOptions, $fromTable->getOptions(), true);
65        $tableDifferences = parent::diffTable($fromTable, $toTable);
66
67        // No changed table options, return parent result
68        if (count($optionDiff) === 0) {
69            return $tableDifferences;
70        }
71
72        if ($tableDifferences === false) {
73            $tableDifferences = GeneralUtility::makeInstance(TableDiff::class, $fromTable->getName());
74            $tableDifferences->fromTable = $fromTable;
75        } else {
76            $renamedColumns = $tableDifferences->renamedColumns;
77            $renamedIndexes = $tableDifferences->renamedIndexes;
78            // Rebuild TableDiff with enhanced TYPO3 TableDiff class
79            $tableDifferences = GeneralUtility::makeInstance(
80                TableDiff::class,
81                $tableDifferences->name,
82                $tableDifferences->addedColumns,
83                $tableDifferences->changedColumns,
84                $tableDifferences->removedColumns,
85                $tableDifferences->addedIndexes,
86                $tableDifferences->changedIndexes,
87                $tableDifferences->removedIndexes,
88                $tableDifferences->fromTable
89            );
90            $tableDifferences->renamedColumns = $renamedColumns;
91            $tableDifferences->renamedIndexes = $renamedIndexes;
92        }
93
94        // Set the table options to be parsed in the AlterTable event.
95        $tableDifferences->setTableOptions($optionDiff);
96
97        return $tableDifferences;
98    }
99
100    /**
101     * Returns the difference between the columns $column1 and $column2
102     * by first checking the doctrine diffColumn. Extend the Doctrine
103     * method by taking into account MySQL TINY/MEDIUM/LONG type variants.
104     *
105     * @param \Doctrine\DBAL\Schema\Column $column1
106     * @param \Doctrine\DBAL\Schema\Column $column2
107     * @return array
108     */
109    public function diffColumn(Column $column1, Column $column2)
110    {
111        $changedProperties = parent::diffColumn($column1, $column2);
112
113        // Only MySQL has variable length versions of TEXT/BLOB
114        if (!$this->databasePlatform instanceof MySqlPlatform) {
115            return $changedProperties;
116        }
117
118        $properties1 = $column1->toArray();
119        $properties2 = $column2->toArray();
120
121        if ($properties1['type'] instanceof BlobType || $properties1['type'] instanceof TextType) {
122            // Doctrine does not provide a length for LONGTEXT/LONGBLOB columns
123            $length1 = $properties1['length'] ?: 2147483647;
124            $length2 = $properties2['length'] ?: 2147483647;
125
126            if ($length1 !== $length2) {
127                $changedProperties[] = 'length';
128            }
129        }
130
131        return array_unique($changedProperties);
132    }
133}
134