Passed
Pull Request — master (#1939)
by
unknown
02:54
created

Util   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 348
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 29
eloc 102
dl 0
loc 348
c 3
b 0
f 0
ccs 48
cts 48
cp 1
rs 10

15 Methods

Rating   Name   Duplication   Size   Complexity  
A isValidSeedFileName() 0 3 1
A isUniqueMigrationClassName() 0 5 1
A loadPhpFile() 0 21 3
A globAll() 0 9 2
A mapFileNameToClassName() 0 12 3
A isValidPhinxClassName() 0 3 1
A getVersionFromFileName() 0 6 1
A mapClassNameToFileName() 0 9 1
A getCurrentTimestamp() 0 5 1
A relativePath() 0 18 4
A getExistingMigrationClassNames() 0 19 4
A isValidMigrationFileName() 0 5 2
A parseDsn() 0 43 2
A glob() 0 3 2
A getFiles() 0 12 1
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 MIGRATION_FILE_NAME_NO_NAME_PATTERN = '/^[0-9]{14}\.php$/';
30
31
    /**
32
     * @var string
33
     */
34
    protected const SEED_FILE_NAME_PATTERN = '/^([a-z][a-z\d]*)\.php$/i';
35
36
    /**
37
     * @var string
38
     */
39
    protected const CLASS_NAME_PATTERN = '/^(?:[A-Z][a-z\d]*)+$/';
40
41
    /**
42
     * Gets the current timestamp string, in UTC.
43
     *
44
     * @return string
45
     */
46
    public static function getCurrentTimestamp()
47
    {
48
        $dt = new DateTime('now', new DateTimeZone('UTC'));
49
50
        return $dt->format(static::DATE_FORMAT);
51
    }
52
53 15
    /**
54
     * Gets an array of all the existing migration class names.
55 15
     *
56 15
     * @param string $path Path
57
     *
58
     * @return string[]
59
     */
60
    public static function getExistingMigrationClassNames($path)
61
    {
62
        $classNames = [];
63
64 15
        if (!is_dir($path)) {
65
            return $classNames;
66 15
        }
67
68 15
        // filter the files to only get the ones that match our naming scheme
69 1
        $phpFiles = static::getFiles($path);
70
71
        foreach ($phpFiles as $filePath) {
72
            $fileName = basename($filePath);
73 14
            if (preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName)) {
74
                $classNames[] = static::mapFileNameToClassName($fileName);
75 14
            }
76 3
        }
77 3
78 3
        return $classNames;
79 14
    }
80
81 14
    /**
82
     * Get the version from the beginning of a file name.
83
     *
84
     * @param string $fileName File Name
85
     *
86
     * @return string
87
     */
88
    public static function getVersionFromFileName($fileName)
89
    {
90 395
        $matches = [];
91
        preg_match('/^[0-9]+/', basename($fileName), $matches);
92 395
93 395
        return $matches[0];
94 395
    }
95
96
    /**
97
     * Turn migration names like 'CreateUserTable' into file names like
98
     * '12345678901234_create_user_table.php' or 'LimitResourceNamesTo30Chars' into
99
     * '12345678901234_limit_resource_names_to_30_chars.php'.
100
     *
101
     * @param string $className Class Name
102
     *
103
     * @return string
104
     */
105 14
    public static function mapClassNameToFileName($className)
106
    {
107 14
        $snake = function ($matches) {
108 14
            return '_' . strtolower($matches[0]);
109 14
        };
110 14
        $fileName = preg_replace_callback('/\d+|[A-Z]/', $snake, $className);
111
        $fileName = static::getCurrentTimestamp() . "$fileName.php";
112
113
        return $fileName;
114
    }
115
116
    /**
117
     * Turn file names like '12345678901234_create_user_table.php' into class
118
     * names like 'CreateUserTable'.
119
     *
120 391
     * @param string $fileName File Name
121
     *
122 391
     * @return string
123 391
     */
124 391
    public static function mapFileNameToClassName(string $fileName): string
125 391
    {
126
        $matches = [];
127 391
        if (preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName, $matches)) {
128
            $fileName = $matches[1];
129
        } elseif (preg_match(static::MIGRATION_FILE_NAME_NO_NAME_PATTERN, $fileName)) {
130
            return "V" . substr($fileName, 0, strlen($fileName) - 4);
131
        }
132
133
        $className = str_replace('_', '', ucwords($fileName, '_'));
134
135
        return $className;
136
    }
137
138
    /**
139
     * Check if a migration class name is unique regardless of the
140
     * timestamp.
141
     *
142
     * This method takes a class name and a path to a migrations directory.
143
     *
144
     * Migration class names must be in PascalCase format but consecutive
145 13
     * capitals are allowed.
146
     * e.g: AddIndexToPostsTable or CustomHTMLTitle.
147 13
     *
148 13
     * @param string $className Class Name
149
     * @param string $path Path
150
     *
151
     * @return bool
152
     */
153
    public static function isUniqueMigrationClassName($className, $path)
154
    {
155
        $existingClassNames = static::getExistingMigrationClassNames($path);
156
157
        return !in_array($className, $existingClassNames, true);
158
    }
159
160
    /**
161
     * Check if a migration/seed class name is valid.
162 16
     *
163
     * Migration & Seed class names must be in CamelCase format.
164 16
     * e.g: CreateUserTable, AddIndexToPostsTable or UserSeeder.
165
     *
166
     * Single words are not allowed on their own.
167
     *
168
     * @param string $className Class Name
169
     *
170
     * @return bool
171
     */
172
    public static function isValidPhinxClassName($className)
173 387
    {
174
        return (bool)preg_match(static::CLASS_NAME_PATTERN, $className);
175 387
    }
176 387
177
    /**
178
     * Check if a migration file name is valid.
179
     *
180
     * @param string $fileName File Name
181
     *
182
     * @return bool
183
     */
184
    public static function isValidMigrationFileName(string $fileName): bool
185 11
    {
186
        return (
187 11
            (bool)preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName)
188 11
            || (bool)preg_match(static::MIGRATION_FILE_NAME_NO_NAME_PATTERN, $fileName)
189
        );
190
    }
191
192
    /**
193
     * Check if a seed file name is valid.
194
     *
195
     * @param string $fileName File Name
196
     *
197 33
     * @return bool
198
     */
199 33
    public static function isValidSeedFileName($fileName)
200
    {
201 33
        return (bool)preg_match(static::SEED_FILE_NAME_PATTERN, $fileName);
202 33
    }
203 33
204
    /**
205 33
     * Expands a set of paths with curly braces (if supported by the OS).
206
     *
207
     * @param string[] $paths Paths
208
     *
209
     * @return string[]
210
     */
211
    public static function globAll(array $paths)
212
    {
213
        $result = [];
214 431
215
        foreach ($paths as $path) {
216 431
            $result = array_merge($result, static::glob($path));
217
        }
218
219
        return $result;
220
    }
221
222
    /**
223
     * Expands a path with curly braces (if supported by the OS).
224
     *
225
     * @param string $path Path
226
     *
227
     * @return string[]
228
     */
229
    public static function glob($path)
230
    {
231
        return glob($path, defined('GLOB_BRACE') ? GLOB_BRACE : 0);
0 ignored issues
show
Bug Best Practice introduced by
The expression return glob($path, defin...nx\Util\GLOB_BRACE : 0) could also return false which is incompatible with the documented return type string[]. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
232
    }
233
234
    /**
235
     * Takes the path to a php file and attempts to include it if readable
236
     *
237
     * @param string $filename Filename
238
     *
239
     * @throws \Exception
240
     *
241
     * @return string
242
     */
243
    public static function loadPhpFile($filename)
244
    {
245
        $filePath = realpath($filename);
246
        if (!file_exists($filePath)) {
247
            throw new Exception(sprintf("File does not exist: %s \n", $filename));
248
        }
249
250
        /**
251
         * I lifed this from phpunits FileLoader class
252
         *
253
         * @see https://github.com/sebastianbergmann/phpunit/pull/2751
254
         */
255
        $isReadable = @fopen($filePath, 'r') !== false;
256
257
        if (!$isReadable) {
258
            throw new Exception(sprintf("Cannot open file %s \n", $filename));
259
        }
260
261
        include_once $filePath;
262
263
        return $filePath;
264
    }
265
266
    /**
267
     * Given an array of paths, return all unique PHP files that are in them
268
     *
269
     * @param string|string[] $paths Path or array of paths to get .php files.
270
     *
271
     * @return string[]
272
     */
273
    public static function getFiles($paths)
274
    {
275
        $files = static::globAll(array_map(function ($path) {
276
            return $path . DIRECTORY_SEPARATOR . "*.php";
277
        }, (array)$paths));
278
        // glob() can return the same file multiple times
279
        // This will cause the migration to fail with a
280
        // false assumption of duplicate migrations
281
        // http://php.net/manual/en/function.glob.php#110340
282
        $files = array_unique($files);
283
284
        return $files;
285
    }
286
287
    /**
288
     * Attempt to remove the current working directory from a path for output.
289
     *
290
     * @param string $path Path to remove cwd prefix from
291
     * @return string
292
     */
293
    public static function relativePath($path)
294
    {
295
        $realpath = realpath($path);
296
        if ($realpath !== false) {
297
            $path = $realpath;
298
        }
299
300
        $cwd = getcwd();
301
        if ($cwd !== false) {
302
            $cwd .= DIRECTORY_SEPARATOR;
303
            $cwdLen = strlen($cwd);
304
305
            if (substr($path, 0, $cwdLen) === $cwd) {
306
                $path = substr($path, $cwdLen);
307
            }
308
        }
309
310
        return $path;
311
    }
312
313
    /**
314
     * Parses DSN string into db config array.
315
     *
316
     * @param string $dsn DSN string
317
     * @return array
318
     */
319
    public static function parseDsn(string $dsn): array
320
    {
321
        $pattern = <<<'REGEXP'
322
{
323
    ^
324
    (?:
325
        (?P<adapter>[\w\\\\]+)://
326
    )
327
    (?:
328
        (?P<user>.*?)
329
        (?:
330
            :(?P<pass>.*?)
331
        )?
332
        @
333
    )?
334
    (?:
335
        (?P<host>[^?#/:@]+)
336
        (?:
337
            :(?P<port>\d+)
338
        )?
339
    )?
340
    (?:
341
        /(?P<name>[^?#]*)
342
    )?
343
    (?:
344
        \?(?P<query>[^#]*)
345
    )?
346
    $
347
}x
348
REGEXP;
349
350
        if (!preg_match($pattern, $dsn, $parsed)) {
351
            return [];
352
        }
353
354
        // filter out everything except the matched groups
355
        $config = array_intersect_key($parsed, array_flip(['adapter', 'user', 'pass', 'host', 'port', 'name']));
356
        $config = array_filter($config);
357
358
        parse_str($parsed['query'] ?? '', $query);
359
        $config = array_merge($query, $config);
360
361
        return $config;
362
    }
363
}
364