Completed
Pull Request — master (#1828)
by
unknown
01:37
created

Util::relativePath()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
ccs 0
cts 0
cp 0
cc 3
nc 3
nop 1
crap 12
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);
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
     *
292
     * @return string
293
     */
294
    public static function relativePath($path)
295
    {
296
        $cwd = getcwd();
297
        if ($cwd) {
298
            $cwd .= DIRECTORY_SEPARATOR;
299
            $cwdLen = strlen($cwd);
300
301
            if (substr($path, 0, $cwdLen) === $cwd) {
302
                $path = substr($path, $cwdLen);
303
            }
304
        }
305
306
        return $path;
307
    }
308
309
    /**
310
     * Parses DSN string into db config array.
311
     *
312
     * @param string $dsn DSN string
313
     * @return array
314
     */
315
    public static function parseDsn(string $dsn): array
316
    {
317
        $pattern = <<<'REGEXP'
318
{
319
    ^
320
    (?:
321
        (?P<adapter>[\w\\\\]+)://
322
    )
323
    (?:
324
        (?P<user>.*?)
325
        (?:
326
            :(?P<pass>.*?)
327
        )?
328
        @
329
    )?
330
    (?:
331
        (?P<host>[^?#/:@]+)
332
        (?:
333
            :(?P<port>\d+)
334
        )?
335
    )?
336
    (?:
337
        /(?P<name>[^?#]*)
338
    )?
339
    (?:
340
        \?(?P<query>[^#]*)
341
    )?
342
    $
343
}x
344
REGEXP;
345
346
        if (!preg_match($pattern, $dsn, $parsed)) {
347
            return [];
348
        }
349
350
        // filter out everything except the matched groups
351
        $config = array_intersect_key($parsed, array_flip(['adapter', 'user', 'pass', 'host', 'port', 'name']));
352
        $config = array_filter($config);
353
354
        parse_str($parsed['query'] ?? '', $query);
355
        $config = array_merge($query, $config);
356
357
        return $config;
358
    }
359
}
360