depage-db
v1.4.0
Toggle main menu visibility
Loading...
Searching...
No Matches
Schema.php
Go to the documentation of this file.
1
<?php
2
11
12
namespace
Depage\Db;
13
14
class
Schema
15
{
16
public
const
TABLENAME_TAG
=
'@tablename'
;
17
public
const
CONNECTION_TAG
=
'@connection'
;
18
public
const
VERSION_TAG
=
'@version'
;
19
protected
$replaceFunction
= [];
20
protected
$updateData
= [];
21
protected
$dryRun
;
22
protected
$pdo
=
null
;
23
24
public
function
__construct
(
$pdo
)
25
{
26
$this->pdo =
$pdo
;
27
}
28
29
public
function
loadGlob
($path)
30
{
31
$fileNames = glob($path);
32
if
(empty($fileNames)) {
33
trigger_error(
'No file found matching "'
. $path .
'".'
, E_USER_WARNING);
34
}
35
sort($fileNames);
36
37
foreach
($fileNames as $fileName) {
38
$this->
loadFile
($fileName);
39
}
40
41
return
$this;
42
}
43
public
function
loadFile
($fileName)
44
{
45
if
(!is_readable($fileName)) {
46
throw
new
Exceptions\SchemaException
(
'File "'
. $fileName .
'" doesn\'t exist or isn\'t readable.'
);
47
}
48
49
$parser =
new
SqlParser
();
50
$header =
true
;
51
$versions = [];
52
$dictionary = [];
53
$tableName;
54
55
foreach
(file($fileName) as $key => $line) {
56
$number = $key + 1;
57
$split = $parser->split($line);
58
$tag = $this->
extractTag
($split);
59
60
if
($tag[self::VERSION_TAG]) {
61
$versions[$tag[self::VERSION_TAG]] = $number;
62
}
63
64
if
($header) {
65
if
($tag[self::TABLENAME_TAG]) {
66
if
(isset($tableName)) {
67
throw
new
Exceptions\SchemaException
(
'More than one tablename tags in "'
. $fileName .
'".'
);
68
}
else
{
69
$tableName = $tag[self::TABLENAME_TAG];
70
$dictionary[$tableName] = $this->
replace
($tableName);
71
}
72
}
73
74
if
($tag[self::CONNECTION_TAG]) {
75
$dictionary[$tag[self::CONNECTION_TAG]] = $this->
replace
($tag[self::CONNECTION_TAG]);
76
}
77
78
if
(!$parser->isEndOfStatement()) {
79
$header =
false
;
80
if
(!isset($tableName)) {
81
throw
new
Exceptions\SchemaException
(
'Tablename tag missing in "'
. $fileName .
'".'
);
82
}
83
if
(empty($versions)) {
84
throw
new
Exceptions\SchemaException
(
'There is code without version tags in "'
. $fileName .
'" at line '
. $number .
'.'
);
85
}
86
}
87
}
88
89
$this->
checkDictionary
($dictionary);
90
$replaced = $this->
replaceIdentifiers
($dictionary, $split);
91
$statements = $parser->tidy($replaced);
92
93
if
($statements) {
94
$statementBlock[$number] = $statements;
95
}
96
}
97
98
if
(!$parser->isEndOfStatement()) {
99
throw
new
Exceptions\SchemaException
(
'Incomplete statement at the end of "'
. $fileName .
'".'
);
100
}
101
if
(empty($versions)) {
102
throw
new
Exceptions\SchemaException
(
'No version tags found in "'
. $fileName .
'".'
);
103
}
104
if
(empty($tableName)) {
105
throw
new
Exceptions\SchemaException
(
'No tablename tag found in "'
. $fileName .
'".'
);
106
}
107
108
$this->updateData[] = [
109
'tableName'
=> $this->
replace
($tableName),
110
'statementBlock'
=> $statementBlock,
111
'versions'
=> $versions,
112
];
113
114
return
$this;
115
}
116
public
function
dryRun
()
117
{
118
$this->
dryRun
=
true
;
119
$this->history = [];
120
$this->
run
();
121
return
$this->history;
122
}
123
public
function
update
()
124
{
125
$this->
dryRun
=
false
;
126
$this->
run
();
127
}
128
protected
function
run
()
129
{
130
foreach
($this->updateData as $dataSet) {
131
extract($dataSet);
132
$keys = array_keys($versions);
133
134
if
($this->
tableExists
($tableName)) {
135
$currentVersion = $this->
currentTableVersion
($tableName);
136
$search = array_search($currentVersion, $keys);
137
138
if
($search == count($keys) - 1) {
139
$startKey =
false
;
140
} elseif ($search ===
false
) {
141
$startKey =
false
;
142
trigger_error(
'Current table version ('
. $currentVersion .
') not in schema file.'
, E_USER_WARNING);
143
}
else
{
144
$startKey = $keys[$search + 1];
145
}
146
}
else
{
147
$startKey = $keys[0];
148
}
149
150
if
($startKey !==
false
) {
151
$startLine = $versions[$startKey];
152
153
foreach
($statementBlock as $lineNumber => $statements) {
154
if
($lineNumber >= $startLine) {
155
$this->
execute
($lineNumber, $statements);
156
}
157
}
158
159
$lastVersion = $keys[count($keys) - 1];
160
$this->
updateTableVersion
($tableName, $lastVersion);
161
}
162
}
163
164
$this->updateData = [];
165
}
166
protected
function
execute
($number, $statements)
167
{
168
foreach
($statements as $statement) {
169
if
($this->
dryRun
) {
170
$this->history[] = $statement;
171
}
else
{
172
try
{
173
$this->pdo->exec($statement);
174
}
catch
(\PDOException $e) {
175
if
(class_exists(
'\ReflectionClass'
,
false
)) {
176
$PDOExceptionReflection = new \ReflectionClass(
'PDOException'
);
177
$line = $PDOExceptionReflection->getProperty(
'line'
);
178
$message = $PDOExceptionReflection->getProperty(
'message'
);
179
180
$line->setAccessible(
true
);
181
$line->setValue($e, $number);
182
$line->setAccessible(
false
);
183
$message->setAccessible(
true
);
184
$message->setValue($e, preg_replace(
'/ at line [0-9]+$/'
,
' at line '
. $number, $message->getValue($e)));
185
$message->setAccessible(
false
);
186
}
187
throw
$e;
188
}
189
}
190
}
191
}
192
193
protected
function
tableExists
($tableName)
194
{
195
$exists =
false
;
196
197
try
{
198
$this->pdo->query(
'SELECT 1 FROM '
. $tableName);
199
$exists =
true
;
200
}
catch
(\PDOException $e) {
201
// only catch "table doesn't exist" exception
202
if
(!preg_match(
"/SQLSTATE\\[42S02\\]/"
, $e->getMessage())) {
203
throw
$e;
204
}
205
}
206
207
return
$exists;
208
}
209
protected
function
currentTableVersion
($tableName)
210
{
211
try
{
212
$query =
'SELECT TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = "'
. $tableName .
'" AND TABLE_SCHEMA=database() LIMIT 1'
;
213
$statement = $this->pdo->query($query);
214
$statement->execute();
215
$row = $statement->fetch();
216
217
if
($row[
'TABLE_COMMENT'
] ==
''
) {
218
throw
new
Exceptions\SchemaException
(
'Missing version identifier in table "'
. $tableName .
'".'
);
219
}
220
221
$version = $row[
'TABLE_COMMENT'
];
222
}
catch
(\PDOException $e) {
223
$query =
'SHOW CREATE TABLE '
. $tableName;
224
$statement = $this->pdo->query($query);
225
$statement->execute();
226
$row = $statement->fetch();
227
228
if
(!preg_match(
'/COMMENT=\'(.*)\'/'
, $row[1], $matches)) {
229
throw
new
Exceptions\SchemaException
(
'Missing version identifier in table "'
. $tableName .
'".'
);
230
}
231
232
$version = array_pop($matches);
233
}
234
235
return
$version;
236
}
237
protected
function
updateTableVersion
($tableName, $version)
238
{
239
$statement =
'ALTER TABLE '
. $tableName .
' COMMENT \''
. $version .
'\''
;
240
$this->
execute
(
null
, [$statement]);
241
}
242
243
protected
function
extractTag
($split = [])
244
{
245
$tags = [
246
self::VERSION_TAG,
247
self::TABLENAME_TAG,
248
self::CONNECTION_TAG,
249
];
250
251
$comments = array_filter($split,
function
($v) {
252
return
$v[
'type'
] ==
'comment'
;
253
});
254
$matchedTags = [];
255
256
$values = array_values($comments);
257
$values = array_shift($values);
258
$comment = $values[
'string'
] ??
""
;
259
260
foreach
($tags as $tag) {
261
if
(
262
count($comments) == 1
263
&& preg_match(
'/'
. $tag .
'\s+(\S.*\S)\s*$/'
, $comment, $matches)
264
&& count($matches) == 2
265
) {
266
// @todo get rid of '*/' in preg_match
267
$matchedTags[$tag] = preg_replace(
'/\s*\*\/\s*$/'
,
''
, $matches[1]);
268
}
else
{
269
$matchedTags[$tag] =
false
;
270
}
271
}
272
273
return
$matchedTags;
274
}
275
protected
function
checkDictionary
($dictionary)
276
{
277
$tags = array_keys($dictionary);
278
while
($tags) {
279
$current = array_pop($tags);
280
281
foreach
($tags as $test) {
282
if
(
283
strpos($current, $test) !==
false
284
|| strpos($test, $current) !==
false
285
) {
286
throw
new
Exceptions\SchemaException
(
'Tags cannot be substrings of each other ("'
. $current .
'", "'
. $test .
'").'
);
287
}
288
}
289
}
290
}
291
public
function
setReplace
(
$replaceFunction
)
292
{
293
$this->replaceFunction =
$replaceFunction
;
294
295
return
$this;
296
}
297
protected
function
replace
($tableName)
298
{
299
if
(is_callable($this->replaceFunction)) {
300
$tableName = call_user_func($this->replaceFunction, $tableName);
301
}
302
303
return
$tableName;
304
}
305
protected
function
replaceIdentifiers
($dictionary, $split = [])
306
{
307
$replaced = array_map(
308
function
($v) use ($dictionary) {
309
if
($v[
'type'
] ==
'code'
) {
310
$element = [
311
'type'
=>
'code'
,
312
'string'
=> str_replace(array_keys($dictionary), $dictionary, $v[
'string'
]),
313
];
314
}
else
{
315
$element = $v;
316
}
317
318
return
$element;
319
},
320
$split,
321
);
322
323
return
$replaced;
324
}
325
}
326
327
/* vim:set ft=php sw=4 sts=4 fdm=marker et : */
Depage\Db\Exceptions\SchemaException
Definition
SchemaException.php:5
Depage\Db\Schema
Definition
Schema.php:15
Depage\Db\Schema\loadGlob
loadGlob($path)
Definition
Schema.php:29
Depage\Db\Schema\extractTag
extractTag($split=[])
Definition
Schema.php:243
Depage\Db\Schema\checkDictionary
checkDictionary($dictionary)
Definition
Schema.php:275
Depage\Db\Schema\replace
replace($tableName)
Definition
Schema.php:297
Depage\Db\Schema\currentTableVersion
currentTableVersion($tableName)
Definition
Schema.php:209
Depage\Db\Schema\setReplace
setReplace($replaceFunction)
Definition
Schema.php:291
Depage\Db\Schema\$pdo
$pdo
Definition
Schema.php:22
Depage\Db\Schema\$dryRun
$dryRun
Definition
Schema.php:21
Depage\Db\Schema\tableExists
tableExists($tableName)
Definition
Schema.php:193
Depage\Db\Schema\execute
execute($number, $statements)
Definition
Schema.php:166
Depage\Db\Schema\CONNECTION_TAG
const CONNECTION_TAG
Definition
Schema.php:17
Depage\Db\Schema\replaceIdentifiers
replaceIdentifiers($dictionary, $split=[])
Definition
Schema.php:305
Depage\Db\Schema\update
update()
Definition
Schema.php:123
Depage\Db\Schema\VERSION_TAG
const VERSION_TAG
Definition
Schema.php:18
Depage\Db\Schema\loadFile
loadFile($fileName)
Definition
Schema.php:43
Depage\Db\Schema\TABLENAME_TAG
const TABLENAME_TAG
Definition
Schema.php:16
Depage\Db\Schema\updateTableVersion
updateTableVersion($tableName, $version)
Definition
Schema.php:237
Depage\Db\Schema\dryRun
dryRun()
Definition
Schema.php:116
Depage\Db\Schema\$replaceFunction
$replaceFunction
Definition
Schema.php:19
Depage\Db\Schema\$updateData
$updateData
Definition
Schema.php:20
Depage\Db\Schema\__construct
__construct($pdo)
Definition
Schema.php:24
Depage\Db\Schema\run
run()
Definition
Schema.php:128
Depage\Db\SqlParser
Definition
SqlParser.php:14
Schema.php
Generated on
for depage-db by
doxygen
1.18.0
For an overview of all docs, go to
docs.depage.net