Passed
Push — master ( 700b0f...aafb0a )
by Björn
18:25 queued 10s
created

DbStructUpdater::getDiffSql()   C

Complexity

Conditions 14
Paths 12

Size

Total Lines 54

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 210

Importance

Changes 0
Metric Value
dl 0
loc 54
ccs 0
cts 32
cp 0
rs 6.2666
c 0
b 0
f 0
cc 14
nc 12
nop 1
crap 210

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace Application\Model;
3
/**
4
 * @TODO: maybe character set utf8 collate utf8_unicode_ci == collate utf8_unicode_ci
5
 * @TODO: make it work even without ';' delimiters or at least warn about that
6
 * @TODO: better parse error reporting
7
 * @TODO: accept empty datetime value and 0000-00-00 00:00:00 are equal, similar with date and time, also enum('0','1') [default 0], what's with floats?(float(10,2) NOT NULL default '0.00'); text,mediumtext,etc;
8
 * @TODO: option to add database name with dot before the table names
9
 * @TODO: add option "order does matter"
10
 * @DONE: skippen commas inside definitions like this one: FULLTEXT KEY `name` (`name`,`comment`,`placeName`) - inside brackets
11
 * @DONE: breaks table definition on commas and brackets, not newlines
12
 * @DONE: handles `database`.`table` in CREATE TABLE string (but does not add database to result sql for a while - and if it
13
 *        should? as same tables struct in 2 DBs compared is also a case)
14
 * @DONE: handles double (and more) spaces in CREATE TABLE string
15
 * @DONE: add filter option (fields: MODIFY, ADD, DROP, tables: CREATE, DROP)
16
 * @DONE: make it work also with comments
17
 * @DONE: move all options to $this->config
18
 */
19
/**
20
* The class provides ability to compare 2 database structure dumps and compile a set of sql statements to update
21
* one database to make it structure identical to another.
22
*
23
* The input for the script could be taken from the phpMyAdmin structure dump, or provided by some custom code
24
* that uses 'SHOW CREATE TABLE' query to get database structure table by table.
25
* The output is either array of sql statements suitable for executions right from php or a string where the
26
* statements are placed each at new line and delimited with ';' - suitable for execution from phpMyAdmin SQL
27
* page.
28
* The resulting sql may contain queries that aim to:
29
* Create missing table (CREATE TABLE query)
30
* Delete table which should not longer exist (DROP TABLE query)
31
* Update, drop or add table field or index definition (ALTER TABLE query)
32
*
33
* Some features:
34
* - AUTO_INCREMENT value is ommited during the comparison and in resulting CREATE TABLE sql
35
* - fields with definitions like "(var)char (255) NOT NULL default ''" and "(var)char (255) NOT NULL" are treated
36
*   as equal, the same for (big|tiny)int NOT NULL default 0;
37
* - IF NOT EXISTS is automatically added to the resulting sql CREATE TABLE statement
38
* - fields updating queries always come before key modification ones for each table
39
* Not implemented:
40
* - The class even does not try to insert or re-order fields in the same order as in the original table.
41
*   Does order matter?
42
* IMPORTANT!!! Class will not handle a case when the field was renamed. It will generate 2 queries - one to drop
43
* the column with the old name and one to create column with the new name, so if there is a data in the dropped
44
* column, it will be lost.
45
* Usage example:
46
*   $updater = new dbStructUpdater();
47
*   $res = $updater->getUpdates($struct1, $struct2);
48
*   -----
49
*   $res == array (
50
*       [0]=>"ALTER TABLE `b` MODIFY `name` varchar(255) NOT NULL",
51
*       ...
52
*   )
53
 *
54
* @author Kirill Gerasimenko <[email protected]>
55
*/
56
class DbStructUpdater
57
{
58
    var $sourceStruct = '';//structure dump of the reference database
59
    var $destStruct = '';//structure dump of database to update
60
    var $config = array();//updater configuration
61
62
    /**
63
    * Constructor
64
     *
65
    * @access public
66
    */
67
    public function __construct()
68
    {
69
        $this->init();
70
    }
71
72
    /**
73
    * Constructor
74
     *
75
    * @access public
76
    */
77
    public function dbStructUpdater()
78
    {
79
        $this->init();
80
    }
81
82 1
    function init()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
83
    {
84
        //table operations: create, drop; field operations: add, remove, modify
85 1
        $this->config['updateTypes'] = 'create, drop, add, remove, modify';
86
        //ignores default part in cases like (var)char NOT NULL default '' upon the    comparison
87 1
        $this->config['varcharDefaultIgnore'] = true;
88
        //the same for int NOT NULL default 0
89 1
        $this->config['intDefaultIgnore'] = true;
90
        //ignores table autoincrement field value, also remove AUTO_INCREMENT value from the create query if exists
91 1
        $this->config['ignoreIncrement'] = true;
92
        //add 'IF NOT EXIST' to each CREATE TABLE query
93 1
        $this->config['forceIfNotExists'] = true;
94
        //remove 'IF NOT EXIST' if already exists CREATE TABLE dump
95 1
        $this->config['ingoreIfNotExists'] = false;
96 1
    }
97
98
    /**
99
    * merges current updater config with the given one
100
     *
101
    * @param assoc_array $config new configuration values
102
    */
103
    public function setConfig($config=array())
104
    {
105
        if (is_array($config)) {
106
            $this->config = array_merge($this->config, $config);
107
        }
108
    }
109
110
    /**
111
    * Returns array of update SQL with default options, $source, $dest - database structures
112
     *
113
    * @access public
114
    * @param  string $source   structure dump of database to update
115
    * @param  string $dest     structure dump of the reference database
116
    * @param  bool   $asString if true - result will be a string, otherwise - array
117
    * @return array|string update sql statements - in array or string (separated with ';')
118
    */
119
    public function getUpdates($source, $dest, $asString=false)
120
    {
121
        $result = $asString?'':array();
122
        $compRes = $this->compare($source, $dest);
123
        if (empty($compRes)) {
124
            return $result;
125
        }
126
        $compRes = $this->filterDiffs($compRes);
127
        if (empty($compRes)) {
128
            return $result;
129
        }
130
        $result = $this->getDiffSql($compRes);
131
        if ($asString) {
132
            $result = implode(";\r\n", $result);
133
        }
134
        return $result;
135
    }
136
137
    /**
138
    * Filters comparison result and lefts only sync actions allowed by 'updateTypes' option
139
    */
140
    public function filterDiffs($compRes)
141
    {
142
        $result = array();
143
        if (is_array($this->config['updateTypes'])) {
144
            $updateActions = $this->config['updateTypes'];
145
        }
146
        else
147
        {
148
            $updateActions = array_map('trim', explode(',', $this->config['updateTypes']));
149
        }
150
        $allowedActions = array('create', 'drop', 'add', 'remove', 'modify');
151
        $updateActions = array_intersect($updateActions, $allowedActions);
152
        foreach($compRes as $table=>$info)
153
        {
154
            if ($info['sourceOrphan']) {
155
                if (in_array('create', $updateActions)) {
156
                    $result[$table] = $info;
157
                }
158
            }
159
            elseif ($info['destOrphan']) {
160
                if (in_array('drop', $updateActions)) {
161
                    $result[$table] = $info;
162
                }
163
            }
164
            elseif($info['differs']) {
165
                $resultInfo = $info;
166
                unset($resultInfo['differs']);
167
                foreach ($info['differs'] as $diff)
168
                {
169
                    if (empty($diff['dest']) && in_array('add', $updateActions)) {
170
                        $resultInfo['differs'][] = $diff;
171
                    }
172
                    elseif (empty($diff['source']) && in_array('remove', $updateActions)) {
173
                        $resultInfo['differs'][] = $diff;
174
                    }
175
                    elseif(in_array('modify', $updateActions)) {
176
                        $resultInfo['differs'][] = $diff;
177
                    }
178
                }
179
                if (!empty($resultInfo['differs'])) {
180
                    $result[$table] = $resultInfo;
181
                }
182
            }
183
        }
184
        return $result;
185
    }
186
    
187
    /**
188
    * Gets structured general info about the databases diff :
189
    * array(sourceOrphans=>array(...), destOrphans=>array(...), different=>array(...))
190
    */
191
    public function getDiffInfo($compRes)
192
    {        
193
        if (!is_array($compRes)) {
194
            return false;
195
        }
196
        $result = array('sourceOrphans'=>array(), 'destOrphans'=>array(), 'different'=>array());
197
        foreach($compRes as $table=>$info)
198
        {
199
            if ($info['sourceOrphan']) {
200
                $result['sourceOrphans'][] = $table;
201
            }
202
            elseif ($info['destOrphan']) {
203
                $result['destOrphans'][] = $table;
204
            }
205
            else
206
            {
207
                $result['different'][] = $table;
208
            }
209
        }
210
        return $result;
211
    }
212
213
    /**
214
    * Makes comparison of the given database structures, support some options
215
     *
216
    * @access private
217
    * @param  string $source and $dest are strings - database tables structures
218
    * @return array
219
    * - table (array)
220
    *        - destOrphan (boolean)
221
    *        - sourceOrphan (boolean)
222
    *        - differs (array) OR (boolean) false if no diffs
223
    *            - [0](array)
224
    *                - source (string) structure definition line in the out-of-date table
225
    *                - dest (string) structure definition line in the reference table
226
    *            - [1](array) ...
227
    */
228
    private function compare($source, $dest)
229
    {
230
        $this->sourceStruct = $source;
231
        $this->destStruct = $dest;
232
233
        $result = array();
234
        $destTabNames = $this->getTableList($this->destStruct);
235
        $sourceTabNames = $this->getTableList($this->sourceStruct);
236
237
        $common = array_intersect($destTabNames, $sourceTabNames);
238
        $destOrphans = array_diff($destTabNames, $common);
239
        $sourceOrphans = array_diff($sourceTabNames, $common);
240
        $all = array_unique(array_merge($destTabNames, $sourceTabNames));
241
        sort($all);
242
        foreach ($all as $tab)
243
        {
244
            $info = array('destOrphan'=>false, 'sourceOrphan'=>false, 'differs'=>false);
245
            if(in_array($tab, $destOrphans)) {
246
                $info['destOrphan'] = true;
247
            }
248
            elseif (in_array($tab, $sourceOrphans)) {
249
                $info['sourceOrphan'] = true;
250
            }
251
            else
252
            {                
253
                $destSql = $this->getTabSql($this->destStruct, $tab, true);
254
                $sourceSql = $this->getTabSql($this->sourceStruct, $tab, true);
255
                $diffs = $this->compareSql($sourceSql, $destSql);                
0 ignored issues
show
Documentation introduced by
$sourceSql is of type string, but the function expects a object<Application\Model\sring>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$destSql is of type string, but the function expects a object<Application\Model\sring>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
256
                if ($diffs===false) {
257
                    trigger_error('[WARNING] error parsing definition of table "'.$tab.'" - skipped');
258
                    continue;
259
                }
260
                elseif (!empty($diffs))//not empty array
261
                {
262
                    $info['differs'] = $diffs;                    
263
                }                
264
                else { continue;//empty array
265
                }         
266
            }
267
            $result[$tab] = $info;
268
        }
269
        return $result;
270
    }
271
272
    /**
273
    * Retrieves list of table names from the database structure dump
274
     *
275
    * @access private
276
    * @param  string $struct database structure listing
277
    */
278
    private function getTableList($struct)
279
    {
280
        $result = array();
281
        if (preg_match_all('/CREATE(?:\s*TEMPORARY)?\s*TABLE\s*(?:IF NOT EXISTS\s*)?(?:`?(\w+)`?\.)?`?(\w+)`?/i', $struct, $m)) {
282
            foreach($m[2] as $match)//m[1] is a database name if any
283
            {
284
                $result[] = $match;
285
            }
286
        }
287
        return $result;
288
    }
289
290
    /**
291
    * Retrieves table structure definition from the database structure dump
292
     *
293
    * @access private
294
    * @param  string $struct         database structure listing
295
    * @param  string $tab            table name
296
    * @param  bool   $removeDatabase - either to remove database name in "CREATE TABLE database.tab"-like declarations
297
    * @return string table structure definition
298
    */
299
    private function getTabSql($struct, $tab, $removeDatabase=true)
300
    {
301
        $result = '';
302
        /* create table should be single line in this case*/
303
        //1 - part before database, 2-database name, 3 - part after database
304
        if (preg_match('/(CREATE(?:\s*TEMPORARY)?\s*TABLE\s*(?:IF NOT EXISTS\s*)?)(?:`?(\w+)`?\.)?(`?('.$tab.')`?(\W|$))/i', $struct, $m, PREG_OFFSET_CAPTURE)) {
305
            $tableDef = $m[0][0];
306
            $start = $m[0][1];
307
            $database = $m[2][0];
308
            $offset = $start+strlen($m[0][0]);
309
            $end = $this->getDelimPos($struct, $offset);
310
            if ($end === false) {
311
                $result = substr($struct, $start);
312
            }
313
            else
314
            {
315
                $result = substr($struct, $start, $end-$start);//already without ';'
316
            }
317
        }
318
        $result = trim($result);
319
        if ($database && $removeDatabase) {
0 ignored issues
show
Bug introduced by
The variable $database does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
320
            $result = str_replace($tableDef, $m[1][0].$m[3][0], $result);
0 ignored issues
show
Bug introduced by
The variable $tableDef does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
321
        }        
322
        return $result;
323
    }
324
    
325
    /**
326
    * Splits table sql into indexed array
327
    */
328
    public function splitTabSql($sql)
329
    {
330
        $result = array();
331
        //find opening bracket, get the prefix along with it
332
        $openBracketPos = $this->getDelimPos($sql, 0, '(');
333
        if ($openBracketPos===false) {
334
            trigger_error('[WARNING] can not find opening bracket in table definition');
335
            return false;
336
        }
337
        $prefix = substr($sql, 0, $openBracketPos+1);//prefix can not be empty, so do not check it, just trim
338
        $result[] = trim($prefix);
339
        $body = substr($sql, strlen($prefix));//fields, indexes and part after closing bracket
340
        //split by commas, get part by part
341
        while(($commaPos = $this->getDelimPos($body, 0, ',', true))!==false)
342
        {
343
            $part = trim(substr($body, 0, $commaPos+1));//read another part and shorten $body
344
            if ($part) {
345
                $result[] = $part;
346
            }
347
            $body = substr($body, $commaPos+1);
348
        }
349
        //here we have last field (or index) definition + part after closing bracket (ENGINE, ect)
350
        $closeBracketPos = $this->getDelimRpos($body, 0, ')');
351
        if ($closeBracketPos===false) {
352
            trigger_error('[WARNING] can not find closing bracket in table definition');
353
            return false;
354
        }
355
        //get last field / index definition before closing bracket
356
        $part = substr($body, 0, $closeBracketPos);
357
        $result[] = trim($part);
358
        //get the suffix part along with the closing bracket
359
        $suffix = substr($body, $closeBracketPos);
360
        $suffix = trim($suffix);
361
        if ($suffix) {
362
            $result[] = $suffix;
363
        }
364
        return $result;
365
    }
366
367
    /**
368
    * returns array of fields or keys definitions that differs in the given tables structure
369
     *
370
    * @access private
371
    * @param  sring $sourceSql table structure
372
    * @param  sring $destSql   right table structure supports some $options
373
    * supports some $options
374
    * @return array
375
    *     - [0]
376
    *         - source (string) out-of-date table field definition
377
    *         - dest (string) reference table field definition
378
    *     - [1]...
379
    */
380
    private function compareSql($sourceSql, $destSql)//$sourceSql, $destSql
381
    {
382
        $result = array();        
383
        //split with comma delimiter, not line breaks
384
        $sourceParts =  $this->splitTabSql($sourceSql);
385
        if ($sourceParts===false)//error parsing sql
386
        {
387
            trigger_error('[WARNING] error parsing source sql');
388
            return false;
389
        }
390
        $destParts = $this->splitTabSql($destSql);
391
        if ($destParts===false) {
392
            trigger_error('[WARNING] error parsing destination sql');
393
            return false;
394
        }
395
        $sourcePartsIndexed = array();
396
        $destPartsIndexed = array();
397
        foreach($sourceParts as $line)
398
        {            
399
            $lineInfo = $this->processLine($line);
400
            if (!$lineInfo) { continue; 
401
            }
402
            $sourcePartsIndexed[$lineInfo['key']] = $lineInfo['line'];
403
        }
404
        foreach($destParts as $line)
405
        {            
406
            $lineInfo = $this->processLine($line);
407
            if (!$lineInfo) { continue; 
408
            }
409
            $destPartsIndexed[$lineInfo['key']] = $lineInfo['line'];
410
        }
411
        $sourceKeys = array_keys($sourcePartsIndexed);
412
        $destKeys = array_keys($destPartsIndexed);
413
        $all = array_unique(array_merge($sourceKeys, $destKeys));
414
        sort($all);//fields first, then indexes - because fields are prefixed with '!'
415
416
        foreach ($all as $key)
417
        {
418
            $info = array('source'=>'', 'dest'=>'');
419
            $inSource= in_array($key, $sourceKeys);
420
            $inDest= in_array($key, $destKeys);
421
            $sourceOrphan = $inSource && !$inDest;
422
            $destOrphan = $inDest && !$inSource;
423
            $different =  $inSource && $inDest && $destPartsIndexed[$key]!=$sourcePartsIndexed[$key];
424
            if ($sourceOrphan) {
425
                $info['source'] = $sourcePartsIndexed[$key];
426
            }
427
            elseif ($destOrphan) {
428
                $info['dest'] = $destPartsIndexed[$key];
429
            }
430
            elseif ($different) {
431
                $info['source'] = $sourcePartsIndexed[$key];
432
                $info['dest'] = $destPartsIndexed[$key];
433
            }
434
            else { continue; 
435
            }
436
            $result[] = $info;
437
        }
438
        return $result;
439
    }
440
441
    /**
442
    * Transforms table structure defnition line into key=>value pair where the key is a string that uniquely
443
    * defines field or key desribed
444
     *
445
    * @access private
446
    * @param  string $line field definition string
447
    * @return array array with single key=>value pair as described in the description
448
    * implements some options
449
    */
450
    private function processLine($line)
451
    {
452
        $options = $this->config;
453
        $result = array('key'=>'', 'line'=>'');
454
        $line = rtrim(trim($line), ',');
455
        if (preg_match('/^(CREATE TABLE)|(\) ENGINE=)/i', $line))//first or last table definition line
456
        {
457
            return false;
458
        }
459
        if (preg_match('/^(PRIMARY KEY)|(((UNIQUE )|(FULLTEXT ))?KEY `?\w+`?)/i', $line, $m))//key definition
460
        {
461
            $key = $m[0];
462
        }
463
        elseif (preg_match('/^`?\w+`?/i', $line, $m))//field definition
464
        {
465
            $key = '!'.$m[0];//to make sure fields will be synchronised before the keys
466
        }
467
        else
468
        {
469
            return false;//line has no valuable info (empty or comment)
470
        }
471
        //$key = str_replace('`', '', $key);
472
        if (!empty($options['varcharDefaultIgnore'])) {
473
            $line = preg_replace("/(var)?char\(([0-9]+)\) NOT NULL default ''/i", '$1char($2) NOT NULL', $line);
474
        }
475
        if (!empty($options['intDefaultIgnore'])) {
476
            $line = preg_replace("/((?:big)|(?:tiny))?int\(([0-9]+)\) NOT NULL default '0'/i", '$1int($2) NOT NULL', $line);
477
        }
478
        if (!empty($options['ignoreIncrement'])) {
479
            $line = preg_replace("/ AUTO_INCREMENT=[0-9]+/i", '', $line);
480
        }
481
        $result['key'] = $key;
482
        $result['line']= $line;
483
        return $result;
484
    }
485
486
    /**
487
    * Takes an output of compare() method to generate the set of sql needed to update source table to make it
488
    * look as a destination one
489
     *
490
    * @access private
491
    * @param  array $diff compare() method output
492
    * @return array list of sql statements
493
    * supports query generation options
494
    */
495
    private function getDiffSql($diff)//maybe add option to ommit or force 'IF NOT EXISTS', skip autoincrement
496
    {
497
        $options = $this->config;
498
        $sqls = array();
499
        if (!is_array($diff) || empty($diff)) {
500
            return $sqls;
501
        }
502
        foreach($diff as $tab=>$info)
503
        {
504
            if ($info['sourceOrphan'])//delete it
505
            {
506
                $sqls[] = 'DROP TABLE `'.$tab.'`';
507
            }
508
            elseif ($info['destOrphan'])//create destination table in source
509
            {
510
                $database = '';
511
                $destSql = $this->getTabSql($this->destStruct, $tab, $database);
0 ignored issues
show
Documentation introduced by
$database is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
512
                if (!empty($options['ignoreIncrement'])) {
513
                    $destSql = preg_replace("/\s*AUTO_INCREMENT=[0-9]+/i", '', $destSql);
514
                }
515
                if (!empty($options['ingoreIfNotExists'])) {
516
                    $destSql = preg_replace("/IF NOT EXISTS\s*/i", '', $destSql);
517
                }
518
                if (!empty($options['forceIfNotExists'])) {
519
                    $destSql = preg_replace('/(CREATE(?:\s*TEMPORARY)?\s*TABLE\s*)(?:IF NOT EXISTS\s*)?(`?\w+`?)/i', '$1IF NOT EXISTS $2', $destSql);
520
                }
521
                $sqls[] = $destSql;
522
            }
523
            else
524
            {
525
                foreach($info['differs'] as $finfo)
526
                {
527
                    $inDest = !empty($finfo['dest']);
528
                    $inSource = !empty($finfo['source']);
529
                    if ($inSource && !$inDest) {
530
                        $sql = $finfo['source'];
531
                        $action = 'drop';
532
                    }
533
                    elseif ($inDest && !$inSource) {
534
                        $sql = $finfo['dest'];
535
                        $action = 'add';
536
                    }
537
                    else
538
                    {
539
                        $sql = $finfo['dest'];
540
                        $action = 'modify';
541
                    }
542
                    $sql = $this->getActionSql($action, $tab, $sql);
543
                    $sqls[] = $sql;
544
                }
545
            }
546
        }
547
        return $sqls;
548
    }
549
550
    /**
551
    * Compiles update sql
552
     *
553
    * @access private
554
    * @param  string $action - 'drop', 'add' or 'modify'
555
    * @param  string $tab    table name
556
    * @param  string $sql    definition of the element to change
557
    * @return string update sql
558
    */
559
    private function getActionSql($action, $tab, $sql)
560
    {
561
        $result = 'ALTER TABLE `'.$tab.'` ';
562
        $action = strtolower($action);
563
        $keyField = '`?\w`?(?:\(\d+\))?';//matches `name`(10)
564
        $keyFieldList = '(?:'.$keyField.'(?:,\s?)?)+';//matches `name`(10),`desc`(255)
565
        if (preg_match('/((?:PRIMARY )|(?:UNIQUE )|(?:FULLTEXT ))?KEY `?(\w+)?`?\s(\('.$keyFieldList.'\))/i', $sql, $m)) {   //key and index operations
566
            $type = strtolower(trim($m[1]));
567
            $name = trim($m[2]);
568
            $fields = trim($m[3]);
569
            switch($action)
570
            {
571
            case 'drop':
572
                if ($type=='primary') {
573
                    $result.= 'DROP PRIMARY KEY';
574
                }
575
                else
576
                {
577
                    $result.= 'DROP INDEX `'.$name.'`';
578
                }
579
                break;
580
            case 'add':
581
                if ($type=='primary') {
582
                    $result.= 'ADD PRIMARY KEY '.$fields;
583
                }
584
                elseif ($type=='') {
585
                    $result.= 'ADD INDEX `'.$name.'` '.$fields;
586
                }
587 View Code Duplication
                else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
588
                {
589
                    $result .='ADD '.strtoupper($type).' `'.$name.'` '.$fields;//fulltext or unique
590
                }
591
                break;
592
            case 'modify':
593
                if ($type=='primary') {
594
                    $result.='DROP PRIMARY KEY, ADD PRIMARY KEY '.$fields;
595
                }
596 View Code Duplication
                elseif ($type=='') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
597
                    $result.='DROP INDEX `'.$name.'`, ADD INDEX `'.$name.'` '.$fields;
598
                }
599 View Code Duplication
                else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
600
                {
601
                    $result.='DROP INDEX `'.$name.'`, ADD '.strtoupper($type).' `'.$name.'` '.$fields;//fulltext or unique
602
                }
603
                break;
604
605
            }
606
        }
607
        else //fields operations
608
        {
609
            $sql = rtrim(trim($sql), ',');
610
            $result.= strtoupper($action);
611
            if ($action=='drop') {
612
                $spacePos = strpos($sql, ' ');
613
                $result.= ' '.substr($sql, 0, $spacePos);
614
            }
615
            else
616
            {
617
                $result.= ' '.$sql;
618
            }
619
        }
620
        return $result;
621
    }
622
623
    /**
624
    * Searches for the position of the next delimiter which is not inside string literal like 'this ; ' or
625
    * like "this ; ".
626
    *
627
    * Handles escaped \" and \'. Also handles sql comments.
628
    * Actualy it is regex-based Finit State Machine (FSN)
629
    *
630
    * $skipInBrackets - if true, delimiter will be skipped if located inside (brackets) which are not string literals or comment parts
631
    */
632
    public function getDelimPos($string, $offset=0, $delim=';', $skipInBrackets=false)
633
    {
634
        $stack = array();
635
        $rbs = '\\\\';    //reg - escaped backslash
636
        $regPrefix = "(?<!$rbs)(?:$rbs{2})*";
637
        $reg = $regPrefix.'("|\')|(/\\*)|(\\*/)|(-- )|(\r\n|\r|\n)|';
638
        if ($skipInBrackets) {
639
            $reg.='(\(|\))|';
640
        }
641
        else 
642
        {
643
            $reg.='()';
644
        }
645
        $reg .= '('.preg_quote($delim).')';
646
        while (preg_match('%'.$reg.'%', $string, $m, PREG_OFFSET_CAPTURE, $offset))
647
        {
648
            $offset = $m[0][1]+strlen($m[0][0]);
649 View Code Duplication
            if (end($stack)=='/*') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
650
                if (!empty($m[3][0])) {
651
                    array_pop($stack);
652
                }
653
                continue;//here we could also simplify regexp
654
            }
655 View Code Duplication
            if (end($stack)=='-- ') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
656
                if (!empty($m[5][0])) {
657
                    array_pop($stack);
658
                }
659
                continue;//here we could also simplify regexp
660
            }
661
662
            if (!empty($m[7][0]))// ';' found
663
            {
664
                if (empty($stack)) {
665
                    return $m[7][1];
666
                }
667
                else
668
                {
669
                    //var_dump($stack, substr($string, $offset-strlen($m[0][0])));
670
                }
671
            }
672
            if (!empty($m[6][0]))// '(' or ')' found
673
            {
674
                if (empty($stack) && $m[6][0]=='(') {
675
                    array_push($stack, $m[6][0]);
676
                }
677
                elseif($m[6][0]==')' && end($stack)=='(') {
678
                    array_pop($stack);
679
                }
680
            }
681
            elseif (!empty($m[1][0]))// ' or " found
682
            {
683
                if (end($stack)==$m[1][0]) {
684
                    array_pop($stack);
685
                }
686
                else
687
                {
688
                    array_push($stack, $m[1][0]);
689
                }
690
            }
691 View Code Duplication
            elseif (!empty($m[2][0])) // opening comment / *
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
692
            {
693
                array_push($stack, $m[2][0]);
694
            }
695 View Code Duplication
            elseif (!empty($m[4][0])) // opening comment --
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
696
            {
697
                array_push($stack, $m[4][0]);
698
            }
699
        }
700
        return false;
701
    }
702
    
703
    /**
704
    * works the same as getDelimPos except returns position of the first occurence of the delimiter starting from
705
    * the end of the string
706
    */
707
    public function getDelimRpos($string, $offset=0, $delim=';', $skipInBrackets=false)
708
    {
709
        $pos = $this->getDelimPos($string, $offset, $delim, $skipInBrackets);
710
        if ($pos===false) {
711
            return false;
712
        }
713
        do
714
        {
715
            $newPos=$this->getDelimPos($string, $pos+1, $delim, $skipInBrackets);
716
            if ($newPos !== false) {
717
                $pos = $newPos;
718
            }
719
        }
720
        while($newPos!==false);
721
        return $pos;
722
    }
723
}
724