-
-
Notifications
You must be signed in to change notification settings - Fork 189
/
Copy pathDatabaseCommandController.php
150 lines (136 loc) · 6.33 KB
/
DatabaseCommandController.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<?php
namespace Neos\Flow\Command;
/*
* This file is part of the Neos.Flow package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ConnectionException;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\DriverManager;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Cli\CommandController;
use Neos\Flow\Mvc\Exception\StopActionException;
/**
* Command controller for tasks related to database handling
*
* @Flow\Scope("singleton")
*/
class DatabaseCommandController extends CommandController
{
/**
* @Flow\InjectConfiguration(path="persistence")
* @var array
*/
protected $persistenceSettings = [];
/**
* @var Connection
*/
protected $connection;
/**
* Create a Doctrine DBAL Connection with the configured settings.
*
* @return void
* @throws DBALException
*/
protected function initializeConnection()
{
$this->connection = DriverManager::getConnection($this->persistenceSettings['backendOptions']);
}
/**
* Convert the database schema to use the given character set and collation (defaults to utf8mb4 and utf8mb4_unicode_ci).
*
* This command can be used to convert the database configured in the Flow settings to the utf8mb4 character
* set and the utf8mb4_unicode_ci collation (by default, a custom collation can be given). It will only
* work when using the pdo_mysql driver.
*
* <b>Make a backup</b> before using it, to be on the safe side. If you want to inspect the statements used
* for conversion, you can use the $output parameter to write them into a file. This file can be used to do
* the conversion manually.
*
* For background information on this, see:
*
* - http://stackoverflow.com/questions/766809/
* - http://dev.mysql.com/doc/refman/5.5/en/alter-table.html
* - https://medium.com/@adamhooper/in-mysql-never-use-utf8-use-utf8mb4-11761243e434
* - https://mathiasbynens.be/notes/mysql-utf8mb4
* - https://florian.ec/articles/mysql-doctrine-utf8/
*
* The main purpose of this is to fix setups that were created with Flow before version 5.0. In those cases,
* the tables will have a collation that does not match the default collation of later Flow versions, potentially
* leading to problems when creating foreign key constraints (among others, potentially).
*
* If you have special needs regarding the charset and collation, you <i>can</i> override the defaults with
* different ones.
*
* Note: This command <b>is not a general purpose conversion tool</b>. It will specifically not fix cases
* of actual utf8 stored in latin1 columns. For this a conversion to BLOB followed by a conversion to the
* proper type, charset and collation is needed instead.
*
* @param string $characterSet Character set, defaults to utf8mb4
* @param string $collation Collation to use, defaults to utf8mb4_unicode_ci
* @param string $output A file to write SQL to, instead of executing it
* @param boolean $verbose If set, the statements will be shown as they are executed
* @throws ConnectionException
* @throws DBALException
* @throws StopActionException
*/
public function setCharsetCommand(string $characterSet = 'utf8mb4', string $collation = 'utf8mb4_unicode_ci', string $output = null, bool $verbose = false)
{
if (!in_array($this->persistenceSettings['backendOptions']['driver'], ['pdo_mysql', 'mysqli'])) {
$this->outputLine('Database charset/collation fixing is only supported on MySQL.');
$this->quit(1);
}
if ($this->persistenceSettings['backendOptions']['host'] === null) {
$this->outputLine('Database charset/collation fixing has been SKIPPED, the host backend option is not set in the configuration.');
$this->quit(1);
}
$this->initializeConnection();
$this->convertToCharacterSetAndCollation($characterSet, $collation, $output, $verbose);
if ($output === null) {
$this->outputLine('Database charset/collation was converted.');
} else {
$this->outputLine('Wrote SQL for converting database charset/collation to file "' . $output . '".');
}
}
/**
* Convert the tables in the current database to use given character set and collation.
*
* @param string $characterSet Character set to convert to
* @param string $collation Collation to set, must be compatible with the character set
* @param string $outputPathAndFilename
* @param boolean $verbose
* @throws ConnectionException
* @throws DBALException
*/
protected function convertToCharacterSetAndCollation(string $characterSet, string $collation, string $outputPathAndFilename = null, bool $verbose = false)
{
$statements = ['SET foreign_key_checks = 0'];
$statements[] = 'ALTER DATABASE ' . $this->connection->quoteIdentifier($this->persistenceSettings['backendOptions']['dbname']) . ' CHARACTER SET ' . $characterSet . ' COLLATE ' . $collation;
$tableNames = $this->connection->getSchemaManager()->listTableNames();
foreach ($tableNames as $tableName) {
$statements[] = 'ALTER TABLE ' . $this->connection->quoteIdentifier($tableName) . ' DEFAULT CHARACTER SET ' . $characterSet . ' COLLATE ' . $collation;
$statements[] = 'ALTER TABLE ' . $this->connection->quoteIdentifier($tableName) . ' CONVERT TO CHARACTER SET ' . $characterSet . ' COLLATE ' . $collation;
}
$statements[] = 'SET foreign_key_checks = 1';
if ($outputPathAndFilename === null) {
try {
foreach ($statements as $statement) {
if ($verbose) {
$this->outputLine($statement);
}
$this->connection->exec($statement);
}
} catch (\Exception $exception) {
$this->outputLine('<error>[ERROR] %s</error>', [$exception->getMessage()]);
}
} else {
file_put_contents($outputPathAndFilename, implode(';' . PHP_EOL, $statements) . ';');
}
}
}