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 | use Symfony\Component\Console\Input\InputInterface; |
||||||
14 | use Symfony\Component\Console\Output\OutputInterface; |
||||||
15 | |||||||
16 | class Util |
||||||
17 | { |
||||||
18 | /** |
||||||
19 | * @var string |
||||||
20 | */ |
||||||
21 | public const DATE_FORMAT = 'YmdHis'; |
||||||
22 | |||||||
23 | /** |
||||||
24 | * @var string |
||||||
25 | */ |
||||||
26 | protected const MIGRATION_FILE_NAME_PATTERN = '/^\d+_([a-z][a-z\d]*(?:_[a-z\d]+)*)\.php$/i'; |
||||||
27 | |||||||
28 | /** |
||||||
29 | * @var string |
||||||
30 | */ |
||||||
31 | protected const MIGRATION_FILE_NAME_NO_NAME_PATTERN = '/^[0-9]{14}\.php$/'; |
||||||
32 | |||||||
33 | /** |
||||||
34 | * @var string |
||||||
35 | */ |
||||||
36 | protected const SEED_FILE_NAME_PATTERN = '/^([a-z][a-z\d]*)\.php$/i'; |
||||||
37 | |||||||
38 | /** |
||||||
39 | * @var string |
||||||
40 | */ |
||||||
41 | protected const CLASS_NAME_PATTERN = '/^(?:[A-Z][a-z\d]*)+$/'; |
||||||
42 | |||||||
43 | /** |
||||||
44 | * Gets the current timestamp string, in UTC. |
||||||
45 | * |
||||||
46 | * @return string |
||||||
47 | */ |
||||||
48 | public static function getCurrentTimestamp() |
||||||
49 | { |
||||||
50 | $dt = new DateTime('now', new DateTimeZone('UTC')); |
||||||
51 | |||||||
52 | return $dt->format(static::DATE_FORMAT); |
||||||
53 | 15 | } |
|||||
54 | |||||||
55 | 15 | /** |
|||||
56 | 15 | * Gets an array of all the existing migration class names. |
|||||
57 | * |
||||||
58 | * @param string $path Path |
||||||
59 | * @return string[] |
||||||
60 | */ |
||||||
61 | public static function getExistingMigrationClassNames($path) |
||||||
62 | { |
||||||
63 | $classNames = []; |
||||||
64 | 15 | ||||||
65 | if (!is_dir($path)) { |
||||||
66 | 15 | return $classNames; |
|||||
67 | } |
||||||
68 | 15 | ||||||
69 | 1 | // filter the files to only get the ones that match our naming scheme |
|||||
70 | $phpFiles = static::getFiles($path); |
||||||
71 | |||||||
72 | foreach ($phpFiles as $filePath) { |
||||||
73 | 14 | $fileName = basename($filePath); |
|||||
74 | if (preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName)) { |
||||||
75 | 14 | $classNames[] = static::mapFileNameToClassName($fileName); |
|||||
76 | 3 | } |
|||||
77 | 3 | } |
|||||
78 | 3 | ||||||
79 | 14 | return $classNames; |
|||||
80 | } |
||||||
81 | 14 | ||||||
82 | /** |
||||||
83 | * Get the version from the beginning of a file name. |
||||||
84 | * |
||||||
85 | * @param string $fileName File Name |
||||||
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 | * @return string |
||||||
103 | */ |
||||||
104 | public static function mapClassNameToFileName($className) |
||||||
105 | 14 | { |
|||||
106 | $snake = function ($matches) { |
||||||
107 | 14 | return '_' . strtolower($matches[0]); |
|||||
108 | 14 | }; |
|||||
109 | 14 | $fileName = preg_replace_callback('/\d+|[A-Z]/', $snake, $className); |
|||||
110 | 14 | $fileName = static::getCurrentTimestamp() . "$fileName.php"; |
|||||
111 | |||||||
112 | return $fileName; |
||||||
113 | } |
||||||
114 | |||||||
115 | /** |
||||||
116 | * Turn file names like '12345678901234_create_user_table.php' into class |
||||||
117 | * names like 'CreateUserTable'. |
||||||
118 | * |
||||||
119 | * @param string $fileName File Name |
||||||
120 | 391 | * @return string |
|||||
121 | */ |
||||||
122 | 391 | public static function mapFileNameToClassName(string $fileName): string |
|||||
123 | 391 | { |
|||||
124 | 391 | $matches = []; |
|||||
125 | 391 | if (preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName, $matches)) { |
|||||
126 | $fileName = $matches[1]; |
||||||
127 | 391 | } elseif (preg_match(static::MIGRATION_FILE_NAME_NO_NAME_PATTERN, $fileName)) { |
|||||
128 | return 'V' . substr($fileName, 0, strlen($fileName) - 4); |
||||||
129 | } |
||||||
130 | |||||||
131 | $className = str_replace('_', '', ucwords($fileName, '_')); |
||||||
132 | |||||||
133 | return $className; |
||||||
134 | } |
||||||
135 | |||||||
136 | /** |
||||||
137 | * Check if a migration class name is unique regardless of the |
||||||
138 | * timestamp. |
||||||
139 | * |
||||||
140 | * This method takes a class name and a path to a migrations directory. |
||||||
141 | * |
||||||
142 | * Migration class names must be in PascalCase format but consecutive |
||||||
143 | * capitals are allowed. |
||||||
144 | * e.g: AddIndexToPostsTable or CustomHTMLTitle. |
||||||
145 | 13 | * |
|||||
146 | * @param string $className Class Name |
||||||
147 | 13 | * @param string $path Path |
|||||
148 | 13 | * @return bool |
|||||
149 | */ |
||||||
150 | public static function isUniqueMigrationClassName($className, $path) |
||||||
151 | { |
||||||
152 | $existingClassNames = static::getExistingMigrationClassNames($path); |
||||||
153 | |||||||
154 | return !in_array($className, $existingClassNames, true); |
||||||
155 | } |
||||||
156 | |||||||
157 | /** |
||||||
158 | * Check if a migration/seed class name is valid. |
||||||
159 | * |
||||||
160 | * Migration & Seed class names must be in CamelCase format. |
||||||
161 | * e.g: CreateUserTable, AddIndexToPostsTable or UserSeeder. |
||||||
162 | 16 | * |
|||||
163 | * Single words are not allowed on their own. |
||||||
164 | 16 | * |
|||||
165 | * @param string $className Class Name |
||||||
166 | * @return bool |
||||||
167 | */ |
||||||
168 | public static function isValidPhinxClassName($className) |
||||||
169 | { |
||||||
170 | return (bool)preg_match(static::CLASS_NAME_PATTERN, $className); |
||||||
171 | } |
||||||
172 | |||||||
173 | 387 | /** |
|||||
174 | * Check if a migration file name is valid. |
||||||
175 | 387 | * |
|||||
176 | 387 | * @param string $fileName File Name |
|||||
177 | * @return bool |
||||||
178 | */ |
||||||
179 | public static function isValidMigrationFileName(string $fileName): bool |
||||||
180 | { |
||||||
181 | return (bool)preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName) |
||||||
182 | || (bool)preg_match(static::MIGRATION_FILE_NAME_NO_NAME_PATTERN, $fileName); |
||||||
183 | } |
||||||
184 | |||||||
185 | 11 | /** |
|||||
186 | * Check if a seed file name is valid. |
||||||
187 | 11 | * |
|||||
188 | 11 | * @param string $fileName File Name |
|||||
189 | * @return bool |
||||||
190 | */ |
||||||
191 | public static function isValidSeedFileName($fileName) |
||||||
192 | { |
||||||
193 | return (bool)preg_match(static::SEED_FILE_NAME_PATTERN, $fileName); |
||||||
194 | } |
||||||
195 | |||||||
196 | /** |
||||||
197 | 33 | * Expands a set of paths with curly braces (if supported by the OS). |
|||||
198 | * |
||||||
199 | 33 | * @param string[] $paths Paths |
|||||
200 | * @return string[] |
||||||
201 | 33 | */ |
|||||
202 | 33 | public static function globAll(array $paths) |
|||||
203 | 33 | { |
|||||
204 | $result = []; |
||||||
205 | 33 | ||||||
206 | foreach ($paths as $path) { |
||||||
207 | $result = array_merge($result, static::glob($path)); |
||||||
208 | } |
||||||
209 | |||||||
210 | return $result; |
||||||
211 | } |
||||||
212 | |||||||
213 | /** |
||||||
214 | 431 | * Expands a path with curly braces (if supported by the OS). |
|||||
215 | * |
||||||
216 | 431 | * @param string $path Path |
|||||
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 | * @param \Symfony\Component\Console\Input\InputInterface|null $input Input |
||||||
229 | * @param \Symfony\Component\Console\Output\OutputInterface|null $output Output |
||||||
230 | * @param \Phinx\Console\Command\AbstractCommand|mixed|null $context Context |
||||||
231 | * @throws \Exception |
||||||
232 | * @return string |
||||||
233 | */ |
||||||
234 | public static function loadPhpFile($filename, ?InputInterface $input = null, ?OutputInterface $output = null, $context = null) |
||||||
0 ignored issues
–
show
The parameter
$output 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
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() The parameter
$context 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
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||||
235 | { |
||||||
236 | $filePath = realpath($filename); |
||||||
237 | if (!file_exists($filePath)) { |
||||||
238 | throw new Exception(sprintf("File does not exist: %s \n", $filename)); |
||||||
239 | } |
||||||
240 | |||||||
241 | /** |
||||||
242 | * I lifed this from phpunits FileLoader class |
||||||
243 | * |
||||||
244 | * @see https://github.com/sebastianbergmann/phpunit/pull/2751 |
||||||
245 | */ |
||||||
246 | $isReadable = @fopen($filePath, 'r') !== false; |
||||||
247 | |||||||
248 | if (!$isReadable) { |
||||||
249 | throw new Exception(sprintf("Cannot open file %s \n", $filename)); |
||||||
250 | } |
||||||
251 | |||||||
252 | // prevent this to be propagated to the included file |
||||||
253 | unset($isReadable); |
||||||
254 | |||||||
255 | include_once $filePath; |
||||||
256 | |||||||
257 | return $filePath; |
||||||
258 | } |
||||||
259 | |||||||
260 | /** |
||||||
261 | * Given an array of paths, return all unique PHP files that are in them |
||||||
262 | * |
||||||
263 | * @param string|string[] $paths Path or array of paths to get .php files. |
||||||
264 | * @return string[] |
||||||
265 | */ |
||||||
266 | public static function getFiles($paths) |
||||||
267 | { |
||||||
268 | $files = static::globAll(array_map(function ($path) { |
||||||
269 | return $path . DIRECTORY_SEPARATOR . '*.php'; |
||||||
270 | }, (array)$paths)); |
||||||
271 | // glob() can return the same file multiple times |
||||||
272 | // This will cause the migration to fail with a |
||||||
273 | // false assumption of duplicate migrations |
||||||
274 | // http://php.net/manual/en/function.glob.php#110340 |
||||||
275 | $files = array_unique($files); |
||||||
276 | |||||||
277 | return $files; |
||||||
278 | } |
||||||
279 | |||||||
280 | /** |
||||||
281 | * Attempt to remove the current working directory from a path for output. |
||||||
282 | * |
||||||
283 | * @param string $path Path to remove cwd prefix from |
||||||
284 | * @return string |
||||||
285 | */ |
||||||
286 | public static function relativePath($path) |
||||||
287 | { |
||||||
288 | $realpath = realpath($path); |
||||||
289 | if ($realpath !== false) { |
||||||
290 | $path = $realpath; |
||||||
291 | } |
||||||
292 | |||||||
293 | $cwd = getcwd(); |
||||||
294 | if ($cwd !== false) { |
||||||
295 | $cwd .= DIRECTORY_SEPARATOR; |
||||||
296 | $cwdLen = strlen($cwd); |
||||||
297 | |||||||
298 | if (substr($path, 0, $cwdLen) === $cwd) { |
||||||
299 | $path = substr($path, $cwdLen); |
||||||
300 | } |
||||||
301 | } |
||||||
302 | |||||||
303 | return $path; |
||||||
304 | } |
||||||
305 | |||||||
306 | /** |
||||||
307 | * Parses DSN string into db config array. |
||||||
308 | * |
||||||
309 | * @param string $dsn DSN string |
||||||
310 | * @return array |
||||||
311 | */ |
||||||
312 | public static function parseDsn(string $dsn): array |
||||||
313 | { |
||||||
314 | $pattern = <<<'REGEXP' |
||||||
315 | { |
||||||
316 | ^ |
||||||
317 | (?: |
||||||
318 | (?P<adapter>[\w\\\\]+):// |
||||||
319 | ) |
||||||
320 | (?: |
||||||
321 | (?P<user>.*?) |
||||||
322 | (?: |
||||||
323 | :(?P<pass>.*?) |
||||||
324 | )? |
||||||
325 | @ |
||||||
326 | )? |
||||||
327 | (?: |
||||||
328 | (?P<host>[^?#/:@]+) |
||||||
329 | (?: |
||||||
330 | :(?P<port>\d+) |
||||||
331 | )? |
||||||
332 | )? |
||||||
333 | (?: |
||||||
334 | /(?P<name>[^?#]*) |
||||||
335 | )? |
||||||
336 | (?: |
||||||
337 | \?(?P<query>[^#]*) |
||||||
338 | )? |
||||||
339 | $ |
||||||
340 | }x |
||||||
341 | REGEXP; |
||||||
342 | |||||||
343 | if (!preg_match($pattern, $dsn, $parsed)) { |
||||||
344 | return []; |
||||||
345 | } |
||||||
346 | |||||||
347 | // filter out everything except the matched groups |
||||||
348 | $config = array_intersect_key($parsed, array_flip(['adapter', 'user', 'pass', 'host', 'port', 'name'])); |
||||||
349 | $config = array_filter($config); |
||||||
350 | |||||||
351 | parse_str($parsed['query'] ?? '', $query); |
||||||
352 | $config = array_merge($query, $config); |
||||||
353 | |||||||
354 | return $config; |
||||||
355 | } |
||||||
356 | } |
||||||
357 |
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.