Passed
Push — master ( 229462...d74785 )
by Andreas
02:20 queued 14s
created

SchemaDumper::pregMatch()   B

Complexity

Conditions 7
Paths 21

Size

Total Lines 38
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 9.9357

Importance

Changes 0
Metric Value
cc 7
eloc 29
c 0
b 0
f 0
nc 21
nop 5
dl 0
loc 38
ccs 14
cts 23
cp 0.6087
crap 9.9357
rs 8.5226
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Migrations;
6
7
use Doctrine\DBAL\Platforms\AbstractPlatform;
8
use Doctrine\DBAL\Schema\AbstractSchemaManager;
9
use Doctrine\DBAL\Schema\Table;
10
use Doctrine\Migrations\Exception\NoTablesFound;
11
use Doctrine\Migrations\Generator\Generator;
12
use Doctrine\Migrations\Generator\SqlGenerator;
13
use InvalidArgumentException;
14
use const PREG_BACKTRACK_LIMIT_ERROR;
15
use const PREG_BAD_UTF8_ERROR;
16
use const PREG_BAD_UTF8_OFFSET_ERROR;
17
use const PREG_INTERNAL_ERROR;
18
use const PREG_RECURSION_LIMIT_ERROR;
19
use function array_merge;
20
use function count;
21
use function implode;
22
use function preg_last_error;
23
use function preg_match;
24
use function restore_error_handler;
25
use function set_error_handler;
26
use function sprintf;
27
28
/**
29
 * The SchemaDumper class is responsible for dumping the current state of your database schema to a migration. This
30
 * is to be used in conjunction with the Rollup class.
31
 *
32
 * @internal
33
 *
34
 * @see Doctrine\Migrations\Rollup
35
 */
36
class SchemaDumper
37
{
38
    /** @var AbstractPlatform */
39
    private $platform;
40
41
    /** @var AbstractSchemaManager */
42
    private $schemaManager;
43
44
    /** @var Generator */
45
    private $migrationGenerator;
46
47
    /** @var SqlGenerator */
48
    private $migrationSqlGenerator;
49
50
    /** @var string[] */
51
    private $excludedTablesRegexes;
52
53
    /**
54
     * @param string[] $excludedTablesRegexes
55
     */
56 5
    public function __construct(
57
        AbstractPlatform $platform,
58
        AbstractSchemaManager $schemaManager,
59
        Generator $migrationGenerator,
60
        SqlGenerator $migrationSqlGenerator,
61
        array $excludedTablesRegexes = []
62
    ) {
63 5
        $this->platform              = $platform;
64 5
        $this->schemaManager         = $schemaManager;
65 5
        $this->migrationGenerator    = $migrationGenerator;
66 5
        $this->migrationSqlGenerator = $migrationSqlGenerator;
67 5
        $this->excludedTablesRegexes = $excludedTablesRegexes;
68 5
    }
69
70
    /**
71
     * @param string[] $excludedTablesRegexes
72
     *
73
     * @throws NoTablesFound
74
     */
75 5
    public function dump(
76
        string $fqcn,
77
        array $excludedTablesRegexes = [],
78
        bool $formatted = false,
79
        int $lineLength = 120
80
    ) : string {
81 5
        $schema = $this->schemaManager->createSchema();
82
83 5
        $up   = [];
84 5
        $down = [];
85
86 5
        foreach ($schema->getTables() as $table) {
87 4
            if ($this->shouldSkipTable($table, $excludedTablesRegexes)) {
88 2
                continue;
89
            }
90
91 1
            $upSql = $this->platform->getCreateTableSQL($table);
92
93 1
            $upCode = $this->migrationSqlGenerator->generate(
94 1
                $upSql,
95 1
                $formatted,
96 1
                $lineLength
97
            );
98
99 1
            if ($upCode !== '') {
100 1
                $up[] = $upCode;
101
            }
102
103 1
            $downSql = [$this->platform->getDropTableSQL($table)];
104
105 1
            $downCode = $this->migrationSqlGenerator->generate(
106 1
                $downSql,
107 1
                $formatted,
108 1
                $lineLength
109
            );
110
111 1
            if ($downCode === '') {
112
                continue;
113
            }
114
115 1
            $down[] = $downCode;
116
        }
117
118 4
        if (count($up) === 0) {
119 3
            throw NoTablesFound::new();
120
        }
121
122 1
        $up   = implode("\n", $up);
123 1
        $down = implode("\n", $down);
124
125 1
        return $this->migrationGenerator->generateMigration(
126 1
            $fqcn,
127 1
            $up,
128 1
            $down
129
        );
130
    }
131
132
    /**
133
     * @param string[] $excludedTablesRegexes
134
     */
135 4
    private function shouldSkipTable(Table $table, array $excludedTablesRegexes) : bool
136
    {
137 4
        foreach (array_merge($excludedTablesRegexes, $this->excludedTablesRegexes) as $regex) {
138 4
            if (self::pregMatch($regex, $table->getName()) !== 0) {
139 2
                return true;
140
            }
141
        }
142
143 1
        return false;
144
    }
145
146
    /**
147
     * A local wrapper for "preg_match" which will throw a InvalidArgumentException if there
148
     * is an internal error in the PCRE engine.
149
     * Copied from https://github.com/symfony/symfony/blob/62216ea67762b18982ca3db73c391b0748a49d49/src/Symfony/Component/Yaml/Parser.php#L1072-L1090
150
     *
151
     * @internal
152
     *
153
     * @param mixed[] $matches
154
     */
155 4
    private static function pregMatch(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0) : int
156
    {
157
        try {
158 4
            $errorMessages = [];
159
            set_error_handler(static function (int $severity, string $message, string $file, int $line, array $params) use (&$errorMessages) : bool {
0 ignored issues
show
Unused Code introduced by
The parameter $params is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

159
            set_error_handler(static function (int $severity, string $message, string $file, int $line, /** @scrutinizer ignore-unused */ array $params) use (&$errorMessages) : bool {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $file is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

159
            set_error_handler(static function (int $severity, string $message, /** @scrutinizer ignore-unused */ string $file, int $line, array $params) use (&$errorMessages) : bool {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $line is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

159
            set_error_handler(static function (int $severity, string $message, string $file, /** @scrutinizer ignore-unused */ int $line, array $params) use (&$errorMessages) : bool {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
160 1
                $errorMessages[] = $message;
161
162 1
                return true;
163 4
            });
164 4
            $ret = preg_match($pattern, $subject, $matches, $flags, $offset);
165 4
        } finally {
166 4
            restore_error_handler();
167
        }
168
169 4
        if ($ret === false) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ret does not seem to be defined for all execution paths leading up to this point.
Loading history...
170 1
            switch (preg_last_error()) {
171
                case PREG_INTERNAL_ERROR:
172 1
                    $error = sprintf('Internal PCRE error, please check your Regex. Reported errors: %s.', implode(', ', $errorMessages));
173 1
                    break;
174
                case PREG_BACKTRACK_LIMIT_ERROR:
175
                    $error = 'pcre.backtrack_limit reached.';
176
                    break;
177
                case PREG_RECURSION_LIMIT_ERROR:
178
                    $error = 'pcre.recursion_limit reached.';
179
                    break;
180
                case PREG_BAD_UTF8_ERROR:
181
                    $error = 'Malformed UTF-8 data.';
182
                    break;
183
                case PREG_BAD_UTF8_OFFSET_ERROR:
184
                    $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.';
185
                    break;
186
                default:
187
                    $error = 'Error.';
188
            }
189 1
            throw new InvalidArgumentException($error);
190
        }
191
192 3
        return $ret;
193
    }
194
}
195