Completed
Push — master ( 06cc99...5a9ebb )
by mark
21s queued 12s
created

Util::parseDsn()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 44

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 44
ccs 0
cts 0
cp 0
rs 9.216
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 6
1
<?php
2
3
/**
4
 * MIT License
5
 * For full license information, please view the LICENSE file that was distributed with this source code.
6
 */
7
8
namespace Phinx\Util;
9
10
use DateTime;
11
use DateTimeZone;
12
use Exception;
13
14
class Util
15
{
16
    /**
17
     * @var string
18
     */
19
    public const DATE_FORMAT = 'YmdHis';
20
21
    /**
22
     * @var string
23
     */
24
    protected const MIGRATION_FILE_NAME_PATTERN = '/^\d+_([a-z][a-z\d]*(?:_[a-z\d]+)*)\.php$/i';
25
26
    /**
27
     * @var string
28
     */
29
    protected const SEED_FILE_NAME_PATTERN = '/^([a-z][a-z\d]*)\.php$/i';
30
31
    /**
32
     * @var string
33
     */
34
    protected const CLASS_NAME_PATTERN = '/^(?:[A-Z][a-z\d]*)+$/';
35
36
    /**
37
     * Gets the current timestamp string, in UTC.
38
     *
39
     * @return string
40
     */
41
    public static function getCurrentTimestamp()
42
    {
43
        $dt = new DateTime('now', new DateTimeZone('UTC'));
44
45
        return $dt->format(static::DATE_FORMAT);
46
    }
47
48
    /**
49
     * Gets an array of all the existing migration class names.
50
     *
51
     * @param string $path Path
52
     *
53 15
     * @return string[]
54
     */
55 15
    public static function getExistingMigrationClassNames($path)
56 15
    {
57
        $classNames = [];
58
59
        if (!is_dir($path)) {
60
            return $classNames;
61
        }
62
63
        // filter the files to only get the ones that match our naming scheme
64 15
        $phpFiles = static::getFiles($path);
65
66 15
        foreach ($phpFiles as $filePath) {
67
            $fileName = basename($filePath);
68 15
            if (preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName)) {
69 1
                $classNames[] = static::mapFileNameToClassName($fileName);
70
            }
71
        }
72
73 14
        return $classNames;
74
    }
75 14
76 3
    /**
77 3
     * Get the version from the beginning of a file name.
78 3
     *
79 14
     * @param string $fileName File Name
80
     *
81 14
     * @return string
82
     */
83
    public static function getVersionFromFileName($fileName)
84
    {
85
        $matches = [];
86
        preg_match('/^[0-9]+/', basename($fileName), $matches);
87
88
        return $matches[0];
89
    }
90 395
91
    /**
92 395
     * Turn migration names like 'CreateUserTable' into file names like
93 395
     * '12345678901234_create_user_table.php' or 'LimitResourceNamesTo30Chars' into
94 395
     * '12345678901234_limit_resource_names_to_30_chars.php'.
95
     *
96
     * @param string $className Class Name
97
     *
98
     * @return string
99
     */
100
    public static function mapClassNameToFileName($className)
101
    {
102
        $snake = function ($matches) {
103
            return '_' . strtolower($matches[0]);
104
        };
105 14
        $fileName = preg_replace_callback('/\d+|[A-Z]/', $snake, $className);
106
        $fileName = static::getCurrentTimestamp() . "$fileName.php";
107 14
108 14
        return $fileName;
109 14
    }
110 14
111
    /**
112
     * Turn file names like '12345678901234_create_user_table.php' into class
113
     * names like 'CreateUserTable'.
114
     *
115
     * @param string $fileName File Name
116
     *
117
     * @return string
118
     */
119
    public static function mapFileNameToClassName($fileName)
120 391
    {
121
        $matches = [];
122 391
        if (preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName, $matches)) {
123 391
            $fileName = $matches[1];
124 391
        }
125 391
126
        $className = str_replace('_', '', ucwords($fileName, '_'));
127 391
128
        return $className;
129
    }
130
131
    /**
132
     * Check if a migration class name is unique regardless of the
133
     * timestamp.
134
     *
135
     * This method takes a class name and a path to a migrations directory.
136
     *
137
     * Migration class names must be in PascalCase format but consecutive
138
     * capitals are allowed.
139
     * e.g: AddIndexToPostsTable or CustomHTMLTitle.
140
     *
141
     * @param string $className Class Name
142
     * @param string $path Path
143
     *
144
     * @return bool
145 13
     */
146
    public static function isUniqueMigrationClassName($className, $path)
147 13
    {
148 13
        $existingClassNames = static::getExistingMigrationClassNames($path);
149
150
        return !in_array($className, $existingClassNames, true);
151
    }
152
153
    /**
154
     * Check if a migration/seed class name is valid.
155
     *
156
     * Migration & Seed class names must be in CamelCase format.
157
     * e.g: CreateUserTable, AddIndexToPostsTable or UserSeeder.
158
     *
159
     * Single words are not allowed on their own.
160
     *
161
     * @param string $className Class Name
162 16
     *
163
     * @return bool
164 16
     */
165
    public static function isValidPhinxClassName($className)
166
    {
167
        return (bool)preg_match(static::CLASS_NAME_PATTERN, $className);
168
    }
169
170
    /**
171
     * Check if a migration file name is valid.
172
     *
173 387
     * @param string $fileName File Name
174
     *
175 387
     * @return bool
176 387
     */
177
    public static function isValidMigrationFileName($fileName)
178
    {
179
        return (bool)preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName);
180
    }
181
182
    /**
183
     * Check if a seed file name is valid.
184
     *
185 11
     * @param string $fileName File Name
186
     *
187 11
     * @return bool
188 11
     */
189
    public static function isValidSeedFileName($fileName)
190
    {
191
        return (bool)preg_match(static::SEED_FILE_NAME_PATTERN, $fileName);
192
    }
193
194
    /**
195
     * Expands a set of paths with curly braces (if supported by the OS).
196
     *
197 33
     * @param string[] $paths Paths
198
     *
199 33
     * @return string[]
200
     */
201 33
    public static function globAll(array $paths)
202 33
    {
203 33
        $result = [];
204
205 33
        foreach ($paths as $path) {
206
            $result = array_merge($result, static::glob($path));
207
        }
208
209
        return $result;
210
    }
211
212
    /**
213
     * Expands a path with curly braces (if supported by the OS).
214 431
     *
215
     * @param string $path Path
216 431
     *
217
     * @return string[]
218
     */
219
    public static function glob($path)
220
    {
221
        return glob($path, defined('GLOB_BRACE') ? GLOB_BRACE : 0);
222
    }
223
224
    /**
225
     * Takes the path to a php file and attempts to include it if readable
226
     *
227
     * @param string $filename Filename
228
     *
229
     * @throws \Exception
230
     *
231
     * @return string
232
     */
233
    public static function loadPhpFile($filename)
234
    {
235
        $filePath = realpath($filename);
236
        if (!file_exists($filePath)) {
237
            throw new Exception(sprintf("File does not exist: %s \n", $filename));
238
        }
239
240
        /**
241
         * I lifed this from phpunits FileLoader class
242
         *
243
         * @see https://github.com/sebastianbergmann/phpunit/pull/2751
244
         */
245
        $isReadable = @fopen($filePath, 'r') !== false;
246
247
        if (!$isReadable) {
248
            throw new Exception(sprintf("Cannot open file %s \n", $filename));
249
        }
250
251
        include_once $filePath;
252
253
        return $filePath;
254
    }
255
256
    /**
257
     * Given an array of paths, return all unique PHP files that are in them
258
     *
259
     * @param string|string[] $paths Path or array of paths to get .php files.
260
     *
261
     * @return string[]
262
     */
263
    public static function getFiles($paths)
264
    {
265
        $files = static::globAll(array_map(function ($path) {
266
            return $path . DIRECTORY_SEPARATOR . "*.php";
267
        }, (array)$paths));
268
        // glob() can return the same file multiple times
269
        // This will cause the migration to fail with a
270
        // false assumption of duplicate migrations
271
        // http://php.net/manual/en/function.glob.php#110340
272
        $files = array_unique($files);
273
274
        return $files;
275
    }
276
277
    /**
278
     * Parses DSN string into db config array.
279
     *
280
     * @param string $dsn DSN string
281
     * @return array
282
     */
283
    public static function parseDsn(string $dsn): array
284
    {
285
        $pattern = <<<'REGEXP'
286
{
287
    ^
288
    (?:
289
        (?P<adapter>[\w\\\\]+)://
290
    )
291
    (?:
292
        (?P<user>.*?)
293
        (?:
294
            :(?P<pass>.*?)
295
        )?
296
        @
297
    )?
298
    (?:
299
        (?P<host>[^?#/:@]+)
300
        (?:
301
            :(?P<port>\d+)
302
        )?
303
    )?
304
    (?:
305
        /(?P<name>[^?#]*)
306
    )?
307
    (?:
308
        \?(?P<query>[^#]*)
309
    )?
310
    $
311
}x
312
REGEXP;
313
314
        if (!preg_match($pattern, $dsn, $parsed)) {
315
            return [];
316
        }
317
318
        // filter out everything except the matched groups
319
        $config = array_intersect_key($parsed, array_flip(['adapter', 'user', 'pass', 'host', 'port', 'name']));
320
        $config = array_filter($config);
321
322
        parse_str($parsed['query'] ?? '', $query);
323
        $config = array_merge($query, $config);
324
325
        return $config;
326
    }
327
}
328