-
Notifications
You must be signed in to change notification settings - Fork 3.4k
/
SchemaLoader.php
196 lines (181 loc) · 6.44 KB
/
SchemaLoader.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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @since 4.3.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\TestSuite\Fixture;
use Cake\Core\Exception\CakeException;
use Cake\Database\Connection;
use Cake\Database\Schema\TableSchema;
use Cake\Datasource\ConnectionManager;
use Cake\TestSuite\ConnectionHelper;
use InvalidArgumentException;
/**
* Create test database schema from one or more SQL dump files.
*
* This class can be useful to create test database schema when
* your schema is managed by tools external to your CakePHP
* application.
*
* It is not well suited for applications/plugins that need to
* support multiple database platforms. You should use migrations
* for that instead.
*/
class SchemaLoader
{
/**
* @var \Cake\TestSuite\ConnectionHelper
*/
protected ConnectionHelper $helper;
/**
* Constructor.
*/
public function __construct()
{
$this->helper = new ConnectionHelper();
}
/**
* Load and apply schema sql file, or an array of files.
*
* @param array<string>|string $paths Schema files to load
* @param string $connectionName Connection name
* @param bool $dropTables Drop all tables prior to loading schema files
* @param bool $truncateTables Truncate all tables after loading schema files
* @return void
*/
public function loadSqlFiles(
array|string $paths,
string $connectionName = 'test',
bool $dropTables = true,
bool $truncateTables = false
): void {
$files = (array)$paths;
// Don't create schema if we are in a phpunit separate process test method.
if (isset($GLOBALS['__PHPUNIT_BOOTSTRAP'])) {
return;
}
if ($dropTables) {
$this->helper->dropTables($connectionName);
}
/** @var \Cake\Database\Connection $connection */
$connection = ConnectionManager::get($connectionName);
foreach ($files as $file) {
if (!file_exists($file)) {
throw new InvalidArgumentException(sprintf('Unable to load SQL file `%s`.', $file));
}
$sql = file_get_contents($file);
if ($sql === false) {
throw new CakeException(sprintf('Cannot read file content of `%s`', $file));
}
// Use the underlying PDO connection so we can avoid prepared statements
// which don't support multiple queries in postgres.
$driver = $connection->getDriver();
$driver->exec($sql);
}
if ($truncateTables) {
$this->helper->truncateTables($connectionName);
}
}
/**
* Load and apply CakePHP schema file.
*
* This method will process the array returned by `$file` and treat
* the contents as a list of table schema.
*
* An example table is:
*
* ```
* return [
* 'articles' => [
* 'columns' => [
* 'id' => [
* 'type' => 'integer',
* ],
* 'author_id' => [
* 'type' => 'integer',
* 'null' => true,
* ],
* 'title' => [
* 'type' => 'string',
* 'null' => true,
* ],
* 'body' => 'text',
* 'published' => [
* 'type' => 'string',
* 'length' => 1,
* 'default' => 'N',
* ],
* ],
* 'constraints' => [
* 'primary' => [
* 'type' => 'primary',
* 'columns' => [
* 'id',
* ],
* ],
* ],
* ],
* ];
* ```
*
* This schema format can be useful for plugins that want to include
* tables to test against but don't need to include production
* ready schema via migrations. Applications should favour using migrations
* or SQL dump files over this format for ease of maintenance.
*
* A more complete example can be found in `tests/schema.php`.
*
* @param string $file Schema file
* @param string $connectionName Connection name
* @throws \InvalidArgumentException For missing table name(s).
* @return void
*/
public function loadInternalFile(string $file, string $connectionName = 'test'): void
{
// Don't reload schema when we are in a separate process state.
if (isset($GLOBALS['__PHPUNIT_BOOTSTRAP'])) {
return;
}
$this->helper->dropTables($connectionName);
$tables = include $file;
/**
* @var \Cake\Database\Connection $connection
*/
$connection = ConnectionManager::get($connectionName);
$connection->disableConstraints(function (Connection $connection) use ($tables): void {
foreach ($tables as $tableName => $table) {
$name = $table['table'] ?? $tableName;
if (!is_string($name)) {
throw new InvalidArgumentException(
sprintf('`%s` is not a valid table name. Either use a string key for the table definition'
. '(`\'articles\' => [...]`) or define the `table` key in the table definition.', $name)
);
}
$schema = new TableSchema($name, $table['columns']);
if (isset($table['indexes'])) {
foreach ($table['indexes'] as $key => $index) {
$schema->addIndex($key, $index);
}
}
if (isset($table['constraints'])) {
foreach ($table['constraints'] as $key => $index) {
$schema->addConstraint($key, $index);
}
}
// Generate SQL for each table.
foreach ($schema->createSql($connection) as $sql) {
$connection->execute($sql);
}
}
});
}
}