format(static::DATE_FORMAT); } /** * Gets an array of all the existing migration class names. * * @param string $path Path * @return string[] */ public static function getExistingMigrationClassNames(string $path): array { $classNames = []; if (!is_dir($path)) { return $classNames; } // filter the files to only get the ones that match our naming scheme $phpFiles = static::getFiles($path); foreach ($phpFiles as $filePath) { $fileName = basename($filePath); if (preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName)) { $classNames[] = static::mapFileNameToClassName($fileName); } } return $classNames; } /** * Get the version from the beginning of a file name. * * @param string $fileName File Name * @return int */ public static function getVersionFromFileName(string $fileName): int { $matches = []; preg_match('/^[0-9]+/', basename($fileName), $matches); $value = (int)($matches[0] ?? null); if (!$value) { throw new RuntimeException(sprintf('Cannot get a valid version from filename `%s`', $fileName)); } return $value; } /** * Turn migration names like 'CreateUserTable' into file names like * '12345678901234_create_user_table.php' or 'LimitResourceNamesTo30Chars' into * '12345678901234_limit_resource_names_to_30_chars.php'. * * @param string $className Class Name * @return string */ public static function mapClassNameToFileName(string $className): string { $snake = function ($matches) { return '_' . strtolower($matches[0]); }; $fileName = preg_replace_callback('/\d+|[A-Z]/', $snake, $className); $fileName = static::getCurrentTimestamp() . "$fileName.php"; return $fileName; } /** * Turn file names like '12345678901234_create_user_table.php' into class * names like 'CreateUserTable'. * * @param string $fileName File Name * @return string */ public static function mapFileNameToClassName(string $fileName): string { $matches = []; if (preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName, $matches)) { $fileName = $matches[1]; } elseif (preg_match(static::MIGRATION_FILE_NAME_NO_NAME_PATTERN, $fileName)) { return 'V' . substr($fileName, 0, strlen($fileName) - 4); } $className = str_replace('_', '', ucwords($fileName, '_')); return $className; } /** * Check if a migration class name is unique regardless of the * timestamp. * * This method takes a class name and a path to a migrations directory. * * Migration class names must be in PascalCase format but consecutive * capitals are allowed. * e.g: AddIndexToPostsTable or CustomHTMLTitle. * * @param string $className Class Name * @param string $path Path * @return bool */ public static function isUniqueMigrationClassName(string $className, string $path): bool { $existingClassNames = static::getExistingMigrationClassNames($path); return !in_array($className, $existingClassNames, true); } /** * Check if a migration/seed class name is valid. * * Migration & Seed class names must be in CamelCase format. * e.g: CreateUserTable, AddIndexToPostsTable or UserSeeder. * * Single words are not allowed on their own. * * @param string $className Class Name * @return bool */ public static function isValidPhinxClassName(string $className): bool { return (bool)preg_match(static::CLASS_NAME_PATTERN, $className); } /** * Check if a migration file name is valid. * * @param string $fileName File Name * @return bool */ public static function isValidMigrationFileName(string $fileName): bool { return (bool)preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName) || (bool)preg_match(static::MIGRATION_FILE_NAME_NO_NAME_PATTERN, $fileName); } /** * Check if a seed file name is valid. * * @param string $fileName File Name * @return bool */ public static function isValidSeedFileName(string $fileName): bool { return (bool)preg_match(static::SEED_FILE_NAME_PATTERN, $fileName); } /** * Expands a set of paths with curly braces (if supported by the OS). * * @param string[] $paths Paths * @return string[] */ public static function globAll(array $paths): array { $result = []; foreach ($paths as $path) { $result = array_merge($result, static::glob($path)); } return $result; } /** * Expands a path with curly braces (if supported by the OS). * * @param string $path Path * @return string[] */ public static function glob(string $path): array { return glob($path, defined('GLOB_BRACE') ? GLOB_BRACE : 0); } /** * Takes the path to a php file and attempts to include it if readable * * @param string $filename Filename * @param \think\console\Input|null $input Input * @param \think\console\Output|null $output Output * @param \Phinx\Console\Command\AbstractCommand|mixed|null $context Context * @throws \Exception * @return string */ public static function loadPhpFile(string $filename, ?InputInterface $input = null, ?OutputInterface $output = null, $context = null): string { $filePath = realpath($filename); if (!file_exists($filePath)) { throw new Exception(sprintf("File does not exist: %s \n", $filename)); } /** * I lifed this from phpunits FileLoader class * * @see https://github.com/sebastianbergmann/phpunit/pull/2751 */ $isReadable = @fopen($filePath, 'r') !== false; if (!$isReadable) { throw new Exception(sprintf("Cannot open file %s \n", $filename)); } // prevent this to be propagated to the included file unset($isReadable); include_once $filePath; return $filePath; } /** * Given an array of paths, return all unique PHP files that are in them * * @param string|string[] $paths Path or array of paths to get .php files. * @return string[] */ public static function getFiles($paths): array { $files = static::globAll(array_map(function ($path) { return $path . DIRECTORY_SEPARATOR . '*.php'; }, (array)$paths)); // glob() can return the same file multiple times // This will cause the migration to fail with a // false assumption of duplicate migrations // https://php.net/manual/en/function.glob.php#110340 $files = array_unique($files); return $files; } /** * Attempt to remove the current working directory from a path for output. * * @param string $path Path to remove cwd prefix from * @return string */ public static function relativePath(string $path): string { $realpath = realpath($path); if ($realpath !== false) { $path = $realpath; } $cwd = getcwd(); if ($cwd !== false) { $cwd .= DIRECTORY_SEPARATOR; $cwdLen = strlen($cwd); if (substr($path, 0, $cwdLen) === $cwd) { $path = substr($path, $cwdLen); } } return $path; } /** * Parses DSN string into db config array. * * @param string $dsn DSN string * @return array */ public static function parseDsn(string $dsn): array { $pattern = <<<'REGEXP' { ^ (?: (?P[\w\\\\]+):// ) (?: (?P.*?) (?: :(?P.*?) )? @ )? (?: (?P[^?#/:@]+) (?: :(?P\d+) )? )? (?: /(?P[^?#]*) )? (?: \?(?P[^#]*) )? $ }x REGEXP; if (!preg_match($pattern, $dsn, $parsed)) { return []; } // filter out everything except the matched groups $config = array_intersect_key($parsed, array_flip(['adapter', 'user', 'pass', 'host', 'port', 'name'])); $config = array_filter($config); parse_str($parsed['query'] ?? '', $query); $config = array_merge($query, $config); return $config; } }