From 3ecdac201a2527f473328abd517f849ef8c0756c Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 29 Jan 2024 00:13:19 -0500 Subject: [PATCH 1/7] Take notes on what will need to change when swapping backends. --- src/Command/MigrationsCommand.php | 4 ++++ src/ConfigurationTrait.php | 2 ++ src/Migrations.php | 3 +++ src/MigrationsDispatcher.php | 3 +++ src/MigrationsPlugin.php | 1 + 5 files changed, 13 insertions(+) diff --git a/src/Command/MigrationsCommand.php b/src/Command/MigrationsCommand.php index 56161e26..d9fb5626 100644 --- a/src/Command/MigrationsCommand.php +++ b/src/Command/MigrationsCommand.php @@ -51,6 +51,7 @@ public static function defaultName(): string if (parent::defaultName() === 'migrations') { return 'migrations'; } + // TODO this will need to be patched. $command = new MigrationsDispatcher::$phinxCommands[static::$commandName](); $name = $command->getName(); @@ -77,7 +78,10 @@ public function getOptionParser(): ConsoleOptionParser return parent::getOptionParser(); } $parser = parent::getOptionParser(); + // Use new methods $command = new MigrationsDispatcher::$phinxCommands[static::$commandName](); + + // Skip conversions for new commands. $parser->setDescription($command->getDescription()); $definition = $command->getDefinition(); foreach ($definition->getOptions() as $option) { diff --git a/src/ConfigurationTrait.php b/src/ConfigurationTrait.php index 2c55c56c..86341025 100644 --- a/src/ConfigurationTrait.php +++ b/src/ConfigurationTrait.php @@ -120,6 +120,8 @@ public function getConfig(bool $forceRefresh = false): ConfigInterface $connectionConfig = (array)ConnectionManager::getConfig($connection); + // TODO(mark) Replace this with cakephp connection + // instead of array parameter passing $adapterName = $this->getAdapterName($connectionConfig['driver']); $dsnOptions = $this->extractDsnOptions($adapterName, $connectionConfig); diff --git a/src/Migrations.php b/src/Migrations.php index 8c387d40..3e977e89 100644 --- a/src/Migrations.php +++ b/src/Migrations.php @@ -238,6 +238,7 @@ public function markMigrated(int|string|null $version = null, array $options = [ $input = $this->getInput('MarkMigrated', ['version' => $version], $options); $this->setInput($input); + // This will need to vary based on the config option. $migrationPaths = $this->getConfig()->getMigrationPaths(); $config = $this->getConfig(true); $params = [ @@ -290,6 +291,7 @@ public function seed(array $options = []): bool */ protected function run(string $method, array $params, InputInterface $input): mixed { + // This will need to vary based on the backend configuration if ($this->configuration instanceof Config) { $migrationPaths = $this->getConfig()->getMigrationPaths(); $migrationPath = array_pop($migrationPaths); @@ -309,6 +311,7 @@ protected function run(string $method, array $params, InputInterface $input): mi $manager = $this->getManager($newConfig); $manager->setInput($input); + // Why is this being done? Is this something we can eliminate in the new code path? if ($pdo !== null) { /** @var \Phinx\Db\Adapter\PdoAdapter|\Migrations\CakeAdapter $adapter */ /** @psalm-suppress PossiblyNullReference */ diff --git a/src/MigrationsDispatcher.php b/src/MigrationsDispatcher.php index d3a315f3..8e0dbf57 100644 --- a/src/MigrationsDispatcher.php +++ b/src/MigrationsDispatcher.php @@ -23,6 +23,8 @@ class MigrationsDispatcher extends Application { /** + * TODO convert this to a method so that config can be used. + * * @var array * @psalm-var array|class-string<\Migrations\Command\Phinx\BaseCommand>> */ @@ -46,6 +48,7 @@ class MigrationsDispatcher extends Application public function __construct(string $version) { parent::__construct('Migrations plugin, based on Phinx by Rob Morgan.', $version); + // Update this to use the methods foreach (static::$phinxCommands as $value) { $this->add(new $value()); } diff --git a/src/MigrationsPlugin.php b/src/MigrationsPlugin.php index f7782111..8896475e 100644 --- a/src/MigrationsPlugin.php +++ b/src/MigrationsPlugin.php @@ -73,6 +73,7 @@ public function console(CommandCollection $commands): CommandCollection return $commands->addMany($found); } $found = []; + // Convert to a method and use config to toggle command names. foreach ($this->migrationCommandsList as $class) { $name = $class::defaultName(); // If the short name has been used, use the full name. From 6a18ccfd9b17939e9f15ead714f4841f8db417c2 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 29 Jan 2024 00:24:28 -0500 Subject: [PATCH 2/7] Import Config and ConfigInterface I've merged the NamespaceAwareTrait and Interface in as that seemed like the correct simplification to make based on how the classes were composed together before. --- src/Config/Config.php | 593 +++++++++++++++++++++++++++++++++ src/Config/ConfigInterface.php | 185 ++++++++++ 2 files changed, 778 insertions(+) create mode 100644 src/Config/Config.php create mode 100644 src/Config/ConfigInterface.php diff --git a/src/Config/Config.php b/src/Config/Config.php new file mode 100644 index 00000000..e6b0f639 --- /dev/null +++ b/src/Config/Config.php @@ -0,0 +1,593 @@ +configFilePath = $configFilePath; + $this->values = $this->replaceTokens($configArray); + + if (isset($this->values['feature_flags'])) { + FeatureFlags::setFlagsFromConfig($this->values['feature_flags']); + } + } + + /** + * Create a new instance of the config class using a Yaml file path. + * + * @param string $configFilePath Path to the Yaml File + * @throws \RuntimeException + * @return \Phinx\Config\ConfigInterface + */ + public static function fromYaml(string $configFilePath): ConfigInterface + { + if (!class_exists('Symfony\\Component\\Yaml\\Yaml', true)) { + // @codeCoverageIgnoreStart + throw new RuntimeException('Missing yaml parser, symfony/yaml package is not installed.'); + // @codeCoverageIgnoreEnd + } + + $configFile = file_get_contents($configFilePath); + $configArray = Yaml::parse($configFile); + + if (!is_array($configArray)) { + throw new RuntimeException(sprintf( + 'File \'%s\' must be valid YAML', + $configFilePath + )); + } + + return new static($configArray, $configFilePath); + } + + /** + * Create a new instance of the config class using a JSON file path. + * + * @param string $configFilePath Path to the JSON File + * @throws \RuntimeException + * @return \Phinx\Config\ConfigInterface + */ + public static function fromJson(string $configFilePath): ConfigInterface + { + if (!function_exists('json_decode')) { + // @codeCoverageIgnoreStart + throw new RuntimeException('Need to install JSON PHP extension to use JSON config'); + // @codeCoverageIgnoreEnd + } + + $configArray = json_decode(file_get_contents($configFilePath), true); + if (!is_array($configArray)) { + throw new RuntimeException(sprintf( + 'File \'%s\' must be valid JSON', + $configFilePath + )); + } + + return new static($configArray, $configFilePath); + } + + /** + * Create a new instance of the config class using a PHP file path. + * + * @param string $configFilePath Path to the PHP File + * @throws \RuntimeException + * @return \Phinx\Config\ConfigInterface + */ + public static function fromPhp(string $configFilePath): ConfigInterface + { + ob_start(); + /** @noinspection PhpIncludeInspection */ + $configArray = include $configFilePath; + + // Hide console output + ob_end_clean(); + + if (!is_array($configArray)) { + throw new RuntimeException(sprintf( + 'PHP file \'%s\' must return an array', + $configFilePath + )); + } + + return new static($configArray, $configFilePath); + } + + /** + * @inheritDoc + */ + public function getEnvironments(): ?array + { + if (isset($this->values['environments'])) { + $environments = []; + foreach ($this->values['environments'] as $key => $value) { + if (is_array($value)) { + $environments[$key] = $value; + } + } + + return $environments; + } + + return null; + } + + /** + * @inheritDoc + */ + public function getEnvironment(string $name): ?array + { + $environments = $this->getEnvironments(); + + if (isset($environments[$name])) { + if ( + isset($this->values['environments']['default_migration_table']) + && !isset($environments[$name]['migration_table']) + ) { + $environments[$name]['migration_table'] = + $this->values['environments']['default_migration_table']; + } + + if ( + isset($environments[$name]['adapter']) + && $environments[$name]['adapter'] === 'sqlite' + && !empty($environments[$name]['memory']) + ) { + $environments[$name]['name'] = SQLiteAdapter::MEMORY; + } + + return $this->parseAgnosticDsn($environments[$name]); + } + + return null; + } + + /** + * @inheritDoc + */ + public function hasEnvironment(string $name): bool + { + return $this->getEnvironment($name) !== null; + } + + /** + * @inheritDoc + */ + public function getDefaultEnvironment(): string + { + // The $PHINX_ENVIRONMENT variable overrides all other default settings + $env = getenv('PHINX_ENVIRONMENT'); + if (!empty($env)) { + if ($this->hasEnvironment($env)) { + return $env; + } + + throw new RuntimeException(sprintf( + 'The environment configuration (read from $PHINX_ENVIRONMENT) for \'%s\' is missing', + $env + )); + } + + // deprecated: to be removed 0.13 + if (isset($this->values['environments']['default_database'])) { + trigger_error('default_database in the config has been deprecated since 0.12, use default_environment instead.', E_USER_DEPRECATED); + $this->values['environments']['default_environment'] = $this->values['environments']['default_database']; + } + + // if the user has configured a default environment then use it, + // providing it actually exists! + if (isset($this->values['environments']['default_environment'])) { + if ($this->hasEnvironment($this->values['environments']['default_environment'])) { + return $this->values['environments']['default_environment']; + } + + throw new RuntimeException(sprintf( + 'The environment configuration for \'%s\' is missing', + $this->values['environments']['default_environment'] + )); + } + + // else default to the first available one + if (is_array($this->getEnvironments()) && count($this->getEnvironments()) > 0) { + $names = array_keys($this->getEnvironments()); + + return $names[0]; + } + + throw new RuntimeException('Could not find a default environment'); + } + + /** + * @inheritDoc + */ + public function getAlias($alias): ?string + { + return !empty($this->values['aliases'][$alias]) ? $this->values['aliases'][$alias] : null; + } + + /** + * @inheritDoc + */ + public function getAliases(): array + { + return !empty($this->values['aliases']) ? $this->values['aliases'] : []; + } + + /** + * @inheritDoc + */ + public function getConfigFilePath(): ?string + { + return $this->configFilePath; + } + + /** + * @inheritDoc + * @throws \UnexpectedValueException + */ + public function getMigrationPaths(): array + { + if (!isset($this->values['paths']['migrations'])) { + throw new UnexpectedValueException('Migrations path missing from config file'); + } + + if (is_string($this->values['paths']['migrations'])) { + $this->values['paths']['migrations'] = [$this->values['paths']['migrations']]; + } + + return $this->values['paths']['migrations']; + } + + /** + * @inheritDoc + * @throws \UnexpectedValueException + */ + public function getSeedPaths(): array + { + if (!isset($this->values['paths']['seeds'])) { + throw new UnexpectedValueException('Seeds path missing from config file'); + } + + if (is_string($this->values['paths']['seeds'])) { + $this->values['paths']['seeds'] = [$this->values['paths']['seeds']]; + } + + return $this->values['paths']['seeds']; + } + + /** + * @inheritdoc + */ + public function getMigrationBaseClassName(bool $dropNamespace = true): string + { + $className = !isset($this->values['migration_base_class']) ? 'Phinx\Migration\AbstractMigration' : $this->values['migration_base_class']; + + return $dropNamespace ? (substr(strrchr($className, '\\'), 1) ?: $className) : $className; + } + + /** + * @inheritdoc + */ + public function getSeedBaseClassName(bool $dropNamespace = true): string + { + $className = !isset($this->values['seed_base_class']) ? 'Phinx\Seed\AbstractSeed' : $this->values['seed_base_class']; + + return $dropNamespace ? substr(strrchr($className, '\\'), 1) : $className; + } + + /** + * @inheritdoc + */ + public function getTemplateFile(): string|false + { + if (!isset($this->values['templates']['file'])) { + return false; + } + + return $this->values['templates']['file']; + } + + /** + * @inheritdoc + */ + public function getTemplateClass(): string|false + { + if (!isset($this->values['templates']['class'])) { + return false; + } + + return $this->values['templates']['class']; + } + + /** + * @inheritdoc + */ + public function getTemplateStyle(): string + { + if (!isset($this->values['templates']['style'])) { + return self::TEMPLATE_STYLE_CHANGE; + } + + return $this->values['templates']['style'] === self::TEMPLATE_STYLE_UP_DOWN ? self::TEMPLATE_STYLE_UP_DOWN : self::TEMPLATE_STYLE_CHANGE; + } + + /** + * @inheritdoc + */ + public function getDataDomain(): array + { + if (!isset($this->values['data_domain'])) { + return []; + } + + return $this->values['data_domain']; + } + + /** + * @inheritDoc + */ + public function getContainer(): ?ContainerInterface + { + if (!isset($this->values['container'])) { + return null; + } + + return $this->values['container']; + } + + /** + * @inheritdoc + */ + public function getVersionOrder(): string + { + if (!isset($this->values['version_order'])) { + return self::VERSION_ORDER_CREATION_TIME; + } + + return $this->values['version_order']; + } + + /** + * @inheritdoc + */ + public function isVersionOrderCreationTime(): bool + { + $versionOrder = $this->getVersionOrder(); + + return $versionOrder == self::VERSION_ORDER_CREATION_TIME; + } + + /** + * @inheritdoc + */ + public function getBootstrapFile(): string|false + { + if (!isset($this->values['paths']['bootstrap'])) { + return false; + } + + return $this->values['paths']['bootstrap']; + } + + /** + * Replace tokens in the specified array. + * + * @param array $arr Array to replace + * @return array + */ + protected function replaceTokens(array $arr): array + { + // Get environment variables + // Depending on configuration of server / OS and variables_order directive, + // environment variables either end up in $_SERVER (most likely) or $_ENV, + // so we search through both + $tokens = []; + foreach (array_merge($_ENV, $_SERVER) as $varname => $varvalue) { + if (strpos($varname, 'PHINX_') === 0) { + $tokens['%%' . $varname . '%%'] = $varvalue; + } + } + + // Phinx defined tokens (override env tokens) + $tokens['%%PHINX_CONFIG_PATH%%'] = $this->getConfigFilePath(); + $tokens['%%PHINX_CONFIG_DIR%%'] = $this->getConfigFilePath() !== null ? dirname($this->getConfigFilePath()) : ''; + + // Recurse the array and replace tokens + return $this->recurseArrayForTokens($arr, $tokens); + } + + /** + * Recurse an array for the specified tokens and replace them. + * + * @param array $arr Array to recurse + * @param string[] $tokens Array of tokens to search for + * @return array + */ + protected function recurseArrayForTokens(array $arr, array $tokens): array + { + $out = []; + foreach ($arr as $name => $value) { + if (is_array($value)) { + $out[$name] = $this->recurseArrayForTokens($value, $tokens); + continue; + } + if (is_string($value)) { + foreach ($tokens as $token => $tval) { + $value = str_replace($token, $tval ?? '', $value); + } + $out[$name] = $value; + continue; + } + $out[$name] = $value; + } + + return $out; + } + + /** + * Parse a database-agnostic DSN into individual options. + * + * @param array $options Options + * @return array + */ + protected function parseAgnosticDsn(array $options): array + { + $parsed = Util::parseDsn($options['dsn'] ?? ''); + if ($parsed) { + unset($options['dsn']); + } + + $options += $parsed; + + return $options; + } + + /** + * {@inheritDoc} + * + * @param mixed $id ID + * @param mixed $value Value + * @return void + */ + public function offsetSet($id, $value): void + { + $this->values[$id] = $value; + } + + /** + * {@inheritDoc} + * + * @param mixed $id ID + * @throws \InvalidArgumentException + * @return mixed + */ + #[ReturnTypeWillChange] + public function offsetGet($id) + { + if (!array_key_exists($id, $this->values)) { + throw new InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id)); + } + + return $this->values[$id] instanceof Closure ? $this->values[$id]($this) : $this->values[$id]; + } + + /** + * {@inheritDoc} + * + * @param mixed $id ID + * @return bool + */ + public function offsetExists($id): bool + { + return isset($this->values[$id]); + } + + /** + * {@inheritDoc} + * + * @param mixed $id ID + * @return void + */ + public function offsetUnset($id): void + { + unset($this->values[$id]); + } + + /** + * @inheritdoc + */ + public function getSeedTemplateFile(): ?string + { + return $this->values['templates']['seedFile'] ?? null; + } + + /** + * Search $needle in $haystack and return key associate with him. + * + * @param string $needle Needle + * @param string[] $haystack Haystack + * @return string|null + */ + protected function searchNamespace(string $needle, array $haystack): ?string + { + $needle = realpath($needle); + $haystack = array_map('realpath', $haystack); + + $key = array_search($needle, $haystack, true); + + return is_string($key) ? trim($key, '\\') : null; + } + + /** + * Get Migration Namespace associated with path. + * + * @param string $path Path + * @return string|null + */ + public function getMigrationNamespaceByPath(string $path): ?string + { + $paths = $this->getMigrationPaths(); + + return $this->searchNamespace($path, $paths); + } + + /** + * Get Seed Namespace associated with path. + * + * @param string $path Path + * @return string|null + */ + public function getSeedNamespaceByPath(string $path): ?string + { + $paths = $this->getSeedPaths(); + + return $this->searchNamespace($path, $paths); + } +} diff --git a/src/Config/ConfigInterface.php b/src/Config/ConfigInterface.php new file mode 100644 index 00000000..912259c0 --- /dev/null +++ b/src/Config/ConfigInterface.php @@ -0,0 +1,185 @@ +null if no environments exist. + * + * @return array|null + */ + public function getEnvironments(): ?array; + + /** + * Returns the configuration for a given environment. + * + * This method returns null if the specified environment + * doesn't exist. + * + * @param string $name Environment Name + * @return array|null + */ + public function getEnvironment(string $name): ?array; + + /** + * Does the specified environment exist in the configuration file? + * + * @param string $name Environment Name + * @return bool + */ + public function hasEnvironment(string $name): bool; + + /** + * Gets the default environment name. + * + * @throws \RuntimeException + * @return string + */ + public function getDefaultEnvironment(): string; + + /** + * Get the aliased value from a supplied alias. + * + * @param string $alias Alias + * @return string|null + */ + public function getAlias(string $alias): ?string; + + /** + * Get all the aliased values. + * + * @return string[] + */ + public function getAliases(): array; + + /** + * Gets the config file path. + * + * @return string|null + */ + public function getConfigFilePath(): ?string; + + /** + * Gets the paths to search for migration files. + * + * @return string[] + */ + public function getMigrationPaths(): array; + + /** + * Gets the paths to search for seed files. + * + * @return string[] + */ + public function getSeedPaths(): array; + + /** + * Get the template file name. + * + * @return string|false + */ + public function getTemplateFile(): string|false; + + /** + * Get the template class name. + * + * @return string|false + */ + public function getTemplateClass(): string|false; + + /** + * Get the template style to use, either change or up_down. + * + * @return string + */ + public function getTemplateStyle(): string; + + /** + * Get the user-provided container for instantiating seeds + * + * @return \Psr\Container\ContainerInterface|null + */ + public function getContainer(): ?ContainerInterface; + + /** + * Get the data domain array. + * + * @return array + */ + public function getDataDomain(): array; + + /** + * Get the version order. + * + * @return string + */ + public function getVersionOrder(): string; + + /** + * Is version order creation time? + * + * @return bool + */ + public function isVersionOrderCreationTime(): bool; + + /** + * Get the bootstrap file path + * + * @return string|false + */ + public function getBootstrapFile(): string|false; + + /** + * Gets the base class name for migrations. + * + * @param bool $dropNamespace Return the base migration class name without the namespace. + * @return string + */ + public function getMigrationBaseClassName(bool $dropNamespace = true): string; + + /** + * Gets the base class name for seeders. + * + * @param bool $dropNamespace Return the base seeder class name without the namespace. + * @return string + */ + public function getSeedBaseClassName(bool $dropNamespace = true): string; + + /** + * Get the seeder template file name or null if not set. + * + * @return string|null + */ + public function getSeedTemplateFile(): ?string; + + /** + * Get Migration Namespace associated with path. + * + * @param string $path Path + * @return string|null + */ + public function getMigrationNamespaceByPath(string $path): ?string; + + /** + * Get Seed Namespace associated with path. + * + * @param string $path Path + * @return string|null + */ + public function getSeedNamespaceByPath(string $path): ?string; +} From 12ddd50b7a49815d77c1be154feefcf6e27d51b8 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 29 Jan 2024 00:51:30 -0500 Subject: [PATCH 3/7] Note which methods will not be supported Deprecate methods that we won't have in the future and remove them from the interface. --- src/Config/Config.php | 16 +++++++- src/Config/ConfigInterface.php | 70 ---------------------------------- 2 files changed, 15 insertions(+), 71 deletions(-) diff --git a/src/Config/Config.php b/src/Config/Config.php index e6b0f639..eeb5ad3e 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -10,6 +10,7 @@ use Closure; use InvalidArgumentException; +use Phinx\Config\ConfigInterface as PhinxConfigInterface; use Phinx\Db\Adapter\SQLiteAdapter; use Phinx\Util\Util; use Psr\Container\ContainerInterface; @@ -21,7 +22,7 @@ /** * Phinx configuration class. */ -class Config implements ConfigInterface +class Config implements ConfigInterface, PhinxConfigInterface { /** * The value that identifies a version order by creation time. @@ -66,6 +67,7 @@ public function __construct(array $configArray, ?string $configFilePath = null) * @param string $configFilePath Path to the Yaml File * @throws \RuntimeException * @return \Phinx\Config\ConfigInterface + * @deprecated 4.2 To be removed in 5.x */ public static function fromYaml(string $configFilePath): ConfigInterface { @@ -94,6 +96,7 @@ public static function fromYaml(string $configFilePath): ConfigInterface * @param string $configFilePath Path to the JSON File * @throws \RuntimeException * @return \Phinx\Config\ConfigInterface + * @deprecated 4.2 To be removed in 5.x */ public static function fromJson(string $configFilePath): ConfigInterface { @@ -120,6 +123,7 @@ public static function fromJson(string $configFilePath): ConfigInterface * @param string $configFilePath Path to the PHP File * @throws \RuntimeException * @return \Phinx\Config\ConfigInterface + * @deprecated 4.2 To be removed in 5.x */ public static function fromPhp(string $configFilePath): ConfigInterface { @@ -142,6 +146,7 @@ public static function fromPhp(string $configFilePath): ConfigInterface /** * @inheritDoc + * @deprecated 4.2 To be removed in 5.x */ public function getEnvironments(): ?array { @@ -191,6 +196,7 @@ public function getEnvironment(string $name): ?array /** * @inheritDoc + * @deprecated 4.2 To be removed in 5.x */ public function hasEnvironment(string $name): bool { @@ -199,6 +205,7 @@ public function hasEnvironment(string $name): bool /** * @inheritDoc + * @deprecated 4.2 To be removed in 5.x */ public function getDefaultEnvironment(): string { @@ -246,6 +253,7 @@ public function getDefaultEnvironment(): string /** * @inheritDoc + * @deprecated 4.2 To be removed in 5.x */ public function getAlias($alias): ?string { @@ -254,6 +262,7 @@ public function getAlias($alias): ?string /** * @inheritDoc + * @deprecated 4.2 To be removed in 5.x */ public function getAliases(): array { @@ -360,6 +369,7 @@ public function getTemplateStyle(): string /** * @inheritdoc + * @deprecated 4.2 To be removed in 5.x */ public function getDataDomain(): array { @@ -406,6 +416,7 @@ public function isVersionOrderCreationTime(): bool /** * @inheritdoc + * @deprecated 4.2 To be removed in 5.x */ public function getBootstrapFile(): string|false { @@ -554,6 +565,7 @@ public function getSeedTemplateFile(): ?string * @param string $needle Needle * @param string[] $haystack Haystack * @return string|null + * @deprecated 4.2 To be removed in 5.x */ protected function searchNamespace(string $needle, array $haystack): ?string { @@ -570,6 +582,7 @@ protected function searchNamespace(string $needle, array $haystack): ?string * * @param string $path Path * @return string|null + * @deprecated 4.2 To be removed in 5.x */ public function getMigrationNamespaceByPath(string $path): ?string { @@ -583,6 +596,7 @@ public function getMigrationNamespaceByPath(string $path): ?string * * @param string $path Path * @return string|null + * @deprecated 4.2 To be removed in 5.x */ public function getSeedNamespaceByPath(string $path): ?string { diff --git a/src/Config/ConfigInterface.php b/src/Config/ConfigInterface.php index 912259c0..2ddef52d 100644 --- a/src/Config/ConfigInterface.php +++ b/src/Config/ConfigInterface.php @@ -16,15 +16,6 @@ */ interface ConfigInterface extends ArrayAccess { - /** - * Returns the configuration for each environment. - * - * This method returns null if no environments exist. - * - * @return array|null - */ - public function getEnvironments(): ?array; - /** * Returns the configuration for a given environment. * @@ -36,37 +27,6 @@ public function getEnvironments(): ?array; */ public function getEnvironment(string $name): ?array; - /** - * Does the specified environment exist in the configuration file? - * - * @param string $name Environment Name - * @return bool - */ - public function hasEnvironment(string $name): bool; - - /** - * Gets the default environment name. - * - * @throws \RuntimeException - * @return string - */ - public function getDefaultEnvironment(): string; - - /** - * Get the aliased value from a supplied alias. - * - * @param string $alias Alias - * @return string|null - */ - public function getAlias(string $alias): ?string; - - /** - * Get all the aliased values. - * - * @return string[] - */ - public function getAliases(): array; - /** * Gets the config file path. * @@ -116,13 +76,6 @@ public function getTemplateStyle(): string; */ public function getContainer(): ?ContainerInterface; - /** - * Get the data domain array. - * - * @return array - */ - public function getDataDomain(): array; - /** * Get the version order. * @@ -137,13 +90,6 @@ public function getVersionOrder(): string; */ public function isVersionOrderCreationTime(): bool; - /** - * Get the bootstrap file path - * - * @return string|false - */ - public function getBootstrapFile(): string|false; - /** * Gets the base class name for migrations. * @@ -166,20 +112,4 @@ public function getSeedBaseClassName(bool $dropNamespace = true): string; * @return string|null */ public function getSeedTemplateFile(): ?string; - - /** - * Get Migration Namespace associated with path. - * - * @param string $path Path - * @return string|null - */ - public function getMigrationNamespaceByPath(string $path): ?string; - - /** - * Get Seed Namespace associated with path. - * - * @param string $path Path - * @return string|null - */ - public function getSeedNamespaceByPath(string $path): ?string; } From a5b433dc9d5dc0b973e734e497c79153668cd347 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 1 Feb 2024 00:41:46 -0500 Subject: [PATCH 4/7] Add Config object and relevant tests. --- src/Config/Config.php | 8 +- tests/TestCase/Config/AbstractConfigTest.php | 110 +++++ .../Config/ConfigMigrationPathsTest.php | 45 ++ tests/TestCase/Config/ConfigSeedPathsTest.php | 45 ++ .../Config/ConfigSeedTemplatePathsTest.php | 61 +++ tests/TestCase/Config/ConfigTest.php | 410 ++++++++++++++++++ 6 files changed, 672 insertions(+), 7 deletions(-) create mode 100644 tests/TestCase/Config/AbstractConfigTest.php create mode 100644 tests/TestCase/Config/ConfigMigrationPathsTest.php create mode 100644 tests/TestCase/Config/ConfigSeedPathsTest.php create mode 100644 tests/TestCase/Config/ConfigSeedTemplatePathsTest.php create mode 100644 tests/TestCase/Config/ConfigTest.php diff --git a/src/Config/Config.php b/src/Config/Config.php index eeb5ad3e..e6bf7a54 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -222,12 +222,6 @@ public function getDefaultEnvironment(): string )); } - // deprecated: to be removed 0.13 - if (isset($this->values['environments']['default_database'])) { - trigger_error('default_database in the config has been deprecated since 0.12, use default_environment instead.', E_USER_DEPRECATED); - $this->values['environments']['default_environment'] = $this->values['environments']['default_database']; - } - // if the user has configured a default environment then use it, // providing it actually exists! if (isset($this->values['environments']['default_environment'])) { @@ -318,7 +312,7 @@ public function getMigrationBaseClassName(bool $dropNamespace = true): string { $className = !isset($this->values['migration_base_class']) ? 'Phinx\Migration\AbstractMigration' : $this->values['migration_base_class']; - return $dropNamespace ? (substr(strrchr($className, '\\'), 1) ?: $className) : $className; + return $dropNamespace ? (substr((string)strrchr($className, '\\'), 1) ?: $className) : $className; } /** diff --git a/tests/TestCase/Config/AbstractConfigTest.php b/tests/TestCase/Config/AbstractConfigTest.php new file mode 100644 index 00000000..bc5824d0 --- /dev/null +++ b/tests/TestCase/Config/AbstractConfigTest.php @@ -0,0 +1,110 @@ + [ + 'paths' => [ + 'migrations' => '%%PHINX_CONFIG_PATH%%/testmigrations2', + 'seeds' => '%%PHINX_CONFIG_PATH%%/db/seeds', + ], + ], + 'paths' => [ + 'migrations' => $this->getMigrationPaths(), + 'seeds' => $this->getSeedPaths(), + ], + 'templates' => [ + 'file' => '%%PHINX_CONFIG_PATH%%/tpl/testtemplate.txt', + 'class' => '%%PHINX_CONFIG_PATH%%/tpl/testtemplate.php', + ], + 'environments' => [ + 'default_migration_table' => 'phinxlog', + 'default_environment' => 'testing', + 'testing' => [ + 'adapter' => 'sqllite', + 'wrapper' => 'testwrapper', + 'path' => '%%PHINX_CONFIG_PATH%%/testdb/test.db', + ], + 'production' => [ + 'adapter' => 'mysql', + ], + ], + 'data_domain' => [ + 'phone_number' => [ + 'type' => 'string', + 'null' => true, + 'length' => 15, + ], + ], + ]; + } + + public function getMigrationsConfigArray(): array + { + return [ + 'paths' => [ + 'migrations' => $this->getMigrationPaths(), + 'seeds' => $this->getSeedPaths(), + ], + 'environment' => [ + 'migration_table' => 'phinxlog', + 'connection' => ConnectionManager::get('test'), + ], + ]; + } + + /** + * Generate dummy migration paths + * + * @return string[] + */ + protected function getMigrationPaths() + { + if ($this->migrationPath === null) { + $this->migrationPath = uniqid('phinx', true); + } + + return [$this->migrationPath]; + } + + /** + * Generate dummy seed paths + * + * @return string[] + */ + protected function getSeedPaths() + { + if ($this->seedPath === null) { + $this->seedPath = uniqid('phinx', true); + } + + return [$this->seedPath]; + } +} diff --git a/tests/TestCase/Config/ConfigMigrationPathsTest.php b/tests/TestCase/Config/ConfigMigrationPathsTest.php new file mode 100644 index 00000000..073676f2 --- /dev/null +++ b/tests/TestCase/Config/ConfigMigrationPathsTest.php @@ -0,0 +1,45 @@ +expectException(UnexpectedValueException::class); + + $config->getMigrationPaths(); + } + + /** + * Normal behavior + */ + public function testGetMigrationPaths() + { + $config = new Config($this->getConfigArray()); + $this->assertEquals($this->getMigrationPaths(), $config->getMigrationPaths()); + } + + public function testGetMigrationPathConvertsStringToArray() + { + $values = [ + 'paths' => [ + 'migrations' => '/test', + ], + ]; + + $config = new Config($values); + $paths = $config->getMigrationPaths(); + + $this->assertIsArray($paths); + $this->assertCount(1, $paths); + } +} diff --git a/tests/TestCase/Config/ConfigSeedPathsTest.php b/tests/TestCase/Config/ConfigSeedPathsTest.php new file mode 100644 index 00000000..06685472 --- /dev/null +++ b/tests/TestCase/Config/ConfigSeedPathsTest.php @@ -0,0 +1,45 @@ +expectException(UnexpectedValueException::class); + + $config->getSeedPaths(); + } + + /** + * Normal behavior + */ + public function testGetSeedPaths() + { + $config = new Config($this->getConfigArray()); + $this->assertEquals($this->getSeedPaths(), $config->getSeedPaths()); + } + + public function testGetSeedPathConvertsStringToArray() + { + $values = [ + 'paths' => [ + 'seeds' => '/test', + ], + ]; + + $config = new Config($values); + $paths = $config->getSeedPaths(); + + $this->assertIsArray($paths); + $this->assertCount(1, $paths); + } +} diff --git a/tests/TestCase/Config/ConfigSeedTemplatePathsTest.php b/tests/TestCase/Config/ConfigSeedTemplatePathsTest.php new file mode 100644 index 00000000..4f55dbd1 --- /dev/null +++ b/tests/TestCase/Config/ConfigSeedTemplatePathsTest.php @@ -0,0 +1,61 @@ + [ + 'seeds' => '/test', + ], + 'templates' => [ + 'seedFile' => 'seedFilePath', + ], + ]; + + $config = new Config($values); + + $actualValue = $config->getSeedTemplateFile(); + $this->assertEquals('seedFilePath', $actualValue); + } + + public function testTemplateIsSetButNoPath() + { + // Here is used another key just to keep the node 'template' not empty + $values = [ + 'paths' => [ + 'seeds' => '/test', + ], + 'templates' => [ + 'file' => 'migration_template_file', + ], + ]; + + $config = new Config($values); + + $actualValue = $config->getSeedTemplateFile(); + $this->assertNull($actualValue); + } + + public function testNoCustomSeedTemplate() + { + $values = [ + 'paths' => [ + 'seeds' => '/test', + ], + ]; + $config = new Config($values); + + $actualValue = $config->getSeedTemplateFile(); + $this->assertNull($actualValue); + + $config->getSeedPaths(); + } +} diff --git a/tests/TestCase/Config/ConfigTest.php b/tests/TestCase/Config/ConfigTest.php new file mode 100644 index 00000000..884802e7 --- /dev/null +++ b/tests/TestCase/Config/ConfigTest.php @@ -0,0 +1,410 @@ +getConfigArray()); + $this->assertCount(2, $config->getEnvironments()); + $this->assertArrayHasKey('testing', $config->getEnvironments()); + $this->assertArrayHasKey('production', $config->getEnvironments()); + } + + /** + * @covers \Phinx\Config\Config::hasEnvironment + */ + public function testHasEnvironmentDoesntHave() + { + $config = new Config([]); + $this->assertFalse($config->hasEnvironment('dummy')); + } + + /** + * @covers \Phinx\Config\Config::hasEnvironment + */ + public function testHasEnvironmentHasOne() + { + $config = new Config($this->getConfigArray()); + $this->assertTrue($config->hasEnvironment('testing')); + } + + /** + * @covers \Phinx\Config\Config::getEnvironments + */ + public function testGetEnvironmentsNotSet() + { + $config = new Config([]); + $this->assertNull($config->getEnvironments()); + } + + /** + * @covers \Phinx\Config\Config::getEnvironment + */ + public function testGetEnvironmentMethod() + { + $config = new Config($this->getConfigArray()); + $db = $config->getEnvironment('testing'); + $this->assertEquals('sqllite', $db['adapter']); + } + + /** + * @covers \Phinx\Config\Config::getEnvironment + */ + public function testHasEnvironmentMethod() + { + $configArray = $this->getConfigArray(); + $config = new Config($configArray); + $this->assertTrue($config->hasEnvironment('testing')); + $this->assertFalse($config->hasEnvironment('fakeenvironment')); + } + + /** + * @covers \Phinx\Config\Config::getDataDomain + */ + public function testGetDataDomainMethod() + { + $config = new Config($this->getConfigArray()); + $this->assertIsArray($config->getDataDomain()); + } + + /** + * @covers \Phinx\Config\Config::getDataDomain + */ + public function testReturnsEmptyArrayWithEmptyDataDomain() + { + $config = new Config([]); + $this->assertIsArray($config->getDataDomain()); + $this->assertCount(0, $config->getDataDomain()); + } + + /** + * @covers \Phinx\Config\Config::getDefaultEnvironment + */ + public function testGetDefaultEnvironmentUsingDatabaseKey() + { + $configArray = $this->getConfigArray(); + $configArray['environments']['default_environment'] = 'production'; + $config = new Config($configArray); + $this->assertEquals('production', $config->getDefaultEnvironment()); + } + + public function testEnvironmentHasMigrationTable() + { + $configArray = $this->getConfigArray(); + $configArray['environments']['production']['migration_table'] = 'test_table'; + $config = new Config($configArray); + + $this->assertSame('phinxlog', $config->getEnvironment('testing')['migration_table']); + $this->assertSame('test_table', $config->getEnvironment('production')['migration_table']); + } + + /** + * @covers \Phinx\Config\Config::offsetGet + * @covers \Phinx\Config\Config::offsetSet + * @covers \Phinx\Config\Config::offsetExists + * @covers \Phinx\Config\Config::offsetUnset + */ + public function testArrayAccessMethods() + { + $config = new Config([]); + $config['foo'] = 'bar'; + $this->assertEquals('bar', $config['foo']); + $this->assertArrayHasKey('foo', $config); + unset($config['foo']); + $this->assertArrayNotHasKey('foo', $config); + } + + /** + * @covers \Phinx\Config\Config::offsetGet + */ + public function testUndefinedArrayAccess() + { + $config = new Config([]); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Identifier "foo" is not defined.'); + + $config['foo']; + } + + /** + * @covers \Phinx\Config\Config::getMigrationBaseClassName + */ + public function testGetMigrationBaseClassNameGetsDefaultBaseClass() + { + $config = new Config([]); + $this->assertEquals('AbstractMigration', $config->getMigrationBaseClassName()); + } + + /** + * @covers \Phinx\Config\Config::getMigrationBaseClassName + */ + public function testGetMigrationBaseClassNameGetsDefaultBaseClassWithNamespace() + { + $config = new Config([]); + $this->assertEquals('Phinx\Migration\AbstractMigration', $config->getMigrationBaseClassName(false)); + } + + /** + * @covers \Phinx\Config\Config::getMigrationBaseClassName + */ + public function testGetMigrationBaseClassNameGetsAlternativeBaseClass() + { + $config = new Config(['migration_base_class' => 'Phinx\Migration\AlternativeAbstractMigration']); + $this->assertEquals('AlternativeAbstractMigration', $config->getMigrationBaseClassName()); + } + + /** + * @covers \Phinx\Config\Config::getMigrationBaseClassName + */ + public function testGetMigrationBaseClassNameGetsAlternativeBaseClassWithNamespace() + { + $config = new Config(['migration_base_class' => 'Phinx\Migration\AlternativeAbstractMigration']); + $this->assertEquals('Phinx\Migration\AlternativeAbstractMigration', $config->getMigrationBaseClassName(false)); + } + + /** + * @covers \Phinx\Config\Config::getTemplateFile + * @covers \Phinx\Config\Config::getTemplateClass + */ + public function testGetTemplateValuesFalseOnEmpty() + { + $config = new Config([]); + $this->assertFalse($config->getTemplateFile()); + $this->assertFalse($config->getTemplateClass()); + } + + public function testGetAliasNoAliasesEntry() + { + $config = new Config([]); + $this->assertNull($config->getAlias('Short')); + } + + public function testGetAliasEmptyAliasesEntry() + { + $config = new Config(['aliases' => []]); + $this->assertNull($config->getAlias('Short')); + } + + public function testGetAliasInvalidAliasRequest() + { + $config = new Config(['aliases' => ['Medium' => 'Some\Long\Classname']]); + $this->assertNull($config->getAlias('Short')); + } + + public function testGetAliasValidAliasRequest() + { + $config = new Config(['aliases' => ['Short' => 'Some\Long\Classname']]); + $this->assertEquals('Some\Long\Classname', $config->getAlias('Short')); + } + + public function testGetSeedPath() + { + $config = new Config(['paths' => ['seeds' => 'db/seeds']]); + $this->assertEquals(['db/seeds'], $config->getSeedPaths()); + + $config = new Config(['paths' => ['seeds' => ['db/seeds1', 'db/seeds2']]]); + $this->assertEquals(['db/seeds1', 'db/seeds2'], $config->getSeedPaths()); + } + + /** + * @covers \Phinx\Config\Config::getSeedPaths + */ + public function testGetSeedPathThrowsException() + { + $config = new Config([]); + + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Seeds path missing from config file'); + + $config->getSeedPaths(); + } + + /** + * Checks if base class is returned correctly when specified without + * a namespace. + * + * @covers \Phinx\Config\Config::getMigrationBaseClassName + */ + public function testGetMigrationBaseClassNameNoNamespace() + { + $config = new Config(['migration_base_class' => 'BaseMigration']); + $this->assertEquals('BaseMigration', $config->getMigrationBaseClassName()); + } + + /** + * Checks if base class is returned correctly when specified without + * a namespace. + * + * @covers \Phinx\Config\Config::getMigrationBaseClassName + */ + public function testGetMigrationBaseClassNameNoNamespaceNoDrop() + { + $config = new Config(['migration_base_class' => 'BaseMigration']); + $this->assertEquals('BaseMigration', $config->getMigrationBaseClassName(false)); + } + + /** + * @covers \Phinx\Config\Config::getVersionOrder + */ + public function testGetVersionOrder() + { + $config = new Config([]); + $config['version_order'] = Config::VERSION_ORDER_EXECUTION_TIME; + $this->assertEquals(Config::VERSION_ORDER_EXECUTION_TIME, $config->getVersionOrder()); + } + + /** + * @covers \Phinx\Config\Config::isVersionOrderCreationTime + * @dataProvider isVersionOrderCreationTimeDataProvider + */ + public function testIsVersionOrderCreationTime($versionOrder, $expected) + { + // get config stub + $configStub = $this->getMockBuilder(Config::class) + ->onlyMethods(['getVersionOrder']) + ->setConstructorArgs([[]]) + ->getMock(); + + $configStub->expects($this->once()) + ->method('getVersionOrder') + ->will($this->returnValue($versionOrder)); + + $this->assertEquals($expected, $configStub->isVersionOrderCreationTime()); + } + + /** + * @covers \Phinx\Config\Config::isVersionOrderCreationTime + */ + public static function isVersionOrderCreationTimeDataProvider() + { + return [ + 'With Creation Time Version Order' => + [ + Config::VERSION_ORDER_CREATION_TIME, true, + ], + 'With Execution Time Version Order' => + [ + Config::VERSION_ORDER_EXECUTION_TIME, false, + ], + ]; + } + + public function testConfigReplacesEnvironmentTokens() + { + $_SERVER['PHINX_TEST_CONFIG_ADAPTER'] = 'sqlite'; + $_SERVER['PHINX_TEST_CONFIG_SUFFIX'] = 'sqlite3'; + $_ENV['PHINX_TEST_CONFIG_NAME'] = 'phinx_testing'; + $_ENV['PHINX_TEST_CONFIG_SUFFIX'] = 'foo'; + + try { + $config = new Config([ + 'environments' => [ + 'production' => [ + 'adapter' => '%%PHINX_TEST_CONFIG_ADAPTER%%', + 'name' => '%%PHINX_TEST_CONFIG_NAME%%', + 'suffix' => '%%PHINX_TEST_CONFIG_SUFFIX%%', + ], + ], + ]); + + $this->assertSame( + ['adapter' => 'sqlite', 'name' => 'phinx_testing', 'suffix' => 'sqlite3'], + $config->getEnvironment('production') + ); + } finally { + unset($_SERVER['PHINX_TEST_CONFIG_ADAPTER']); + unset($_SERVER['PHINX_TEST_CONFIG_SUFFIX']); + unset($_ENV['PHINX_TEST_CONFIG_NAME']); + unset($_ENV['PHINX_TEST_CONFIG_SUFFIX']); + } + } + + public function testSqliteMemorySetsName() + { + $config = new Config([ + 'environments' => [ + 'production' => [ + 'adapter' => 'sqlite', + 'memory' => true, + ], + ], + ]); + $this->assertSame( + ['adapter' => 'sqlite', 'memory' => true, 'name' => ':memory:'], + $config->getEnvironment('production') + ); + } + + public function testSqliteMemoryOverridesName() + { + $config = new Config([ + 'environments' => [ + 'production' => [ + 'adapter' => 'sqlite', + 'memory' => true, + 'name' => 'blah', + ], + ], + ]); + $this->assertSame( + ['adapter' => 'sqlite', 'memory' => true, 'name' => ':memory:'], + $config->getEnvironment('production') + ); + } + + public function testSqliteNonBooleanMemory() + { + $config = new Config([ + 'environments' => [ + 'production' => [ + 'adapter' => 'sqlite', + 'memory' => 'yes', + ], + ], + ]); + $this->assertSame( + ['adapter' => 'sqlite', 'memory' => 'yes', 'name' => ':memory:'], + $config->getEnvironment('production') + ); + } + + public function testDefaultTemplateStyle(): void + { + $config = new Config([]); + $this->assertSame('change', $config->getTemplateStyle()); + } + + public static function templateStyleDataProvider(): array + { + return [ + ['change', 'change'], + ['up_down', 'up_down'], + ['foo', 'change'], + ]; + } + + /** + * @dataProvider templateStyleDataProvider + */ + public function testTemplateStyle(string $style, string $expected): void + { + $config = new Config(['templates' => ['style' => $style]]); + $this->assertSame($expected, $config->getTemplateStyle()); + } +} From f84bf985062c4cb9bdb51dfd39f82bd02c3665ad Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 2 Feb 2024 00:44:03 -0500 Subject: [PATCH 5/7] Get more tests passing. --- src/Config/Config.php | 21 ++++++++------- src/ConfigurationTrait.php | 1 + src/Migration/Manager.php | 33 +++++++++++++++--------- tests/TestCase/Migration/ManagerTest.php | 2 +- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/Config/Config.php b/src/Config/Config.php index e6bf7a54..165a4159 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -66,7 +66,7 @@ public function __construct(array $configArray, ?string $configFilePath = null) * * @param string $configFilePath Path to the Yaml File * @throws \RuntimeException - * @return \Phinx\Config\ConfigInterface + * @return \Migrations\Config\ConfigInterface * @deprecated 4.2 To be removed in 5.x */ public static function fromYaml(string $configFilePath): ConfigInterface @@ -95,7 +95,7 @@ public static function fromYaml(string $configFilePath): ConfigInterface * * @param string $configFilePath Path to the JSON File * @throws \RuntimeException - * @return \Phinx\Config\ConfigInterface + * @return \Migrations\Config\ConfigInterface * @deprecated 4.2 To be removed in 5.x */ public static function fromJson(string $configFilePath): ConfigInterface @@ -106,7 +106,7 @@ public static function fromJson(string $configFilePath): ConfigInterface // @codeCoverageIgnoreEnd } - $configArray = json_decode(file_get_contents($configFilePath), true); + $configArray = json_decode((string)file_get_contents($configFilePath), true); if (!is_array($configArray)) { throw new RuntimeException(sprintf( 'File \'%s\' must be valid JSON', @@ -122,7 +122,7 @@ public static function fromJson(string $configFilePath): ConfigInterface * * @param string $configFilePath Path to the PHP File * @throws \RuntimeException - * @return \Phinx\Config\ConfigInterface + * @return \Migrations\Config\ConfigInterface * @deprecated 4.2 To be removed in 5.x */ public static function fromPhp(string $configFilePath): ConfigInterface @@ -231,13 +231,14 @@ public function getDefaultEnvironment(): string throw new RuntimeException(sprintf( 'The environment configuration for \'%s\' is missing', - $this->values['environments']['default_environment'] + (string)$this->values['environments']['default_environment'] )); } // else default to the first available one - if (is_array($this->getEnvironments()) && count($this->getEnvironments()) > 0) { - $names = array_keys($this->getEnvironments()); + $environments = $this->getEnvironments(); + if (is_array($environments) && count($environments) > 0) { + $names = array_keys($environments); return $names[0]; } @@ -433,6 +434,8 @@ protected function replaceTokens(array $arr): array // Depending on configuration of server / OS and variables_order directive, // environment variables either end up in $_SERVER (most likely) or $_ENV, // so we search through both + + /** @var array $tokens */ $tokens = []; foreach (array_merge($_ENV, $_SERVER) as $varname => $varvalue) { if (strpos($varname, 'PHINX_') === 0) { @@ -442,7 +445,7 @@ protected function replaceTokens(array $arr): array // Phinx defined tokens (override env tokens) $tokens['%%PHINX_CONFIG_PATH%%'] = $this->getConfigFilePath(); - $tokens['%%PHINX_CONFIG_DIR%%'] = $this->getConfigFilePath() !== null ? dirname($this->getConfigFilePath()) : ''; + $tokens['%%PHINX_CONFIG_DIR%%'] = $this->getConfigFilePath() !== null ? dirname((string)$this->getConfigFilePath()) : ''; // Recurse the array and replace tokens return $this->recurseArrayForTokens($arr, $tokens); @@ -452,7 +455,7 @@ protected function replaceTokens(array $arr): array * Recurse an array for the specified tokens and replace them. * * @param array $arr Array to recurse - * @param string[] $tokens Array of tokens to search for + * @param string|null[] $tokens Array of tokens to search for * @return array */ protected function recurseArrayForTokens(array $arr, array $tokens): array diff --git a/src/ConfigurationTrait.php b/src/ConfigurationTrait.php index 86341025..f982caf4 100644 --- a/src/ConfigurationTrait.php +++ b/src/ConfigurationTrait.php @@ -114,6 +114,7 @@ public function getConfig(bool $forceRefresh = false): ConfigInterface mkdir($seedsPath, 0777, true); } + // TODO this should use Migrations\Config $phinxTable = $this->getPhinxTable($plugin); $connection = $this->getConnectionName($this->input()); diff --git a/src/Migration/Manager.php b/src/Migration/Manager.php index cb1fea20..6673e7e3 100644 --- a/src/Migration/Manager.php +++ b/src/Migration/Manager.php @@ -11,8 +11,8 @@ use DateTime; use Exception; use InvalidArgumentException; -use Phinx\Config\ConfigInterface; -use Phinx\Config\NamespaceAwareInterface; +use Migrations\Config\Config; +use Migrations\Config\ConfigInterface; use Phinx\Migration\AbstractMigration; use Phinx\Migration\MigrationInterface; use Phinx\Seed\AbstractSeed; @@ -30,7 +30,7 @@ class Manager public const BREAKPOINT_UNSET = 3; /** - * @var \Phinx\Config\ConfigInterface + * @var \Migrations\Config\ConfigInterface */ protected ConfigInterface $config; @@ -70,7 +70,7 @@ class Manager private int $verbosityLevel = OutputInterface::OUTPUT_NORMAL | OutputInterface::VERBOSITY_NORMAL; /** - * @param \Phinx\Config\ConfigInterface $config Configuration Object + * @param \Migrations\Config\ConfigInterface $config Configuration Object * @param \Symfony\Component\Console\Input\InputInterface $input Console Input * @param \Symfony\Component\Console\Output\OutputInterface $output Console Output */ @@ -740,8 +740,9 @@ public function getEnvironment(string $name): Environment return $this->environments[$name]; } + $config = $this->getConfig(); // check the environment exists - if (!$this->getConfig()->hasEnvironment($name)) { + if ($config instanceof Config && !$config->hasEnvironment($name)) { throw new InvalidArgumentException(sprintf( 'The environment "%s" does not exist', $name @@ -749,9 +750,11 @@ public function getEnvironment(string $name): Environment } // create an environment instance and cache it - $envOptions = $this->getConfig()->getEnvironment($name); - $envOptions['version_order'] = $this->getConfig()->getVersionOrder(); - $envOptions['data_domain'] = $this->getConfig()->getDataDomain(); + $envOptions = $config->getEnvironment($name); + $envOptions['version_order'] = $config->getVersionOrder(); + if ($config instanceof Config) { + $envOptions['data_domain'] = $config->getDataDomain(); + } $environment = new Environment($name, $envOptions); $this->environments[$name] = $environment; @@ -876,7 +879,10 @@ function ($phpFile) { } $config = $this->getConfig(); - $namespace = $config instanceof NamespaceAwareInterface ? $config->getMigrationNamespaceByPath(dirname($filePath)) : null; + $namespace = null; + if ($config instanceof Config) { + $namespace = $config->getMigrationNamespaceByPath(dirname($filePath)); + } // convert the filename to a class name $class = ($namespace === null ? '' : $namespace . '\\') . Util::mapFileNameToClassName(basename($filePath)); @@ -1025,7 +1031,10 @@ public function getSeeds(string $environment): array foreach ($phpFiles as $filePath) { if (Util::isValidSeedFileName(basename($filePath))) { $config = $this->getConfig(); - $namespace = $config instanceof NamespaceAwareInterface ? $config->getSeedNamespaceByPath(dirname($filePath)) : null; + $namespace = null; + if ($config instanceof Config) { + $namespace = $config->getSeedNamespaceByPath(dirname($filePath)); + } // convert the filename to a class name $class = ($namespace === null ? '' : $namespace . '\\') . pathinfo($filePath, PATHINFO_FILENAME); @@ -1101,7 +1110,7 @@ protected function getSeedFiles(): array /** * Sets the config. * - * @param \Phinx\Config\ConfigInterface $config Configuration Object + * @param \Migrations\Config\ConfigInterface $config Configuration Object * @return $this */ public function setConfig(ConfigInterface $config) @@ -1114,7 +1123,7 @@ public function setConfig(ConfigInterface $config) /** * Gets the config. * - * @return \Phinx\Config\ConfigInterface + * @return \Migrations\Config\ConfigInterface */ public function getConfig(): ConfigInterface { diff --git a/tests/TestCase/Migration/ManagerTest.php b/tests/TestCase/Migration/ManagerTest.php index f1399078..0c60ac43 100644 --- a/tests/TestCase/Migration/ManagerTest.php +++ b/tests/TestCase/Migration/ManagerTest.php @@ -6,10 +6,10 @@ use Cake\Datasource\ConnectionManager; use DateTime; use InvalidArgumentException; +use Migrations\Config\Config; use Migrations\Db\Adapter\AdapterInterface; use Migrations\Migration\Environment; use Migrations\Migration\Manager; -use Phinx\Config\Config; use Phinx\Console\Command\AbstractCommand; use PHPUnit\Framework\TestCase; use RuntimeException; From f413a861775ded6e10fb0fe5eb624171c7713149 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 2 Feb 2024 01:05:32 -0500 Subject: [PATCH 6/7] Fix abstract test case warning --- .../{AbstractConfigTest.php => AbstractConfigTestCase.php} | 2 +- tests/TestCase/Config/ConfigMigrationPathsTest.php | 2 +- tests/TestCase/Config/ConfigSeedPathsTest.php | 2 +- tests/TestCase/Config/ConfigSeedTemplatePathsTest.php | 2 +- tests/TestCase/Config/ConfigTest.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename tests/TestCase/Config/{AbstractConfigTest.php => AbstractConfigTestCase.php} (98%) diff --git a/tests/TestCase/Config/AbstractConfigTest.php b/tests/TestCase/Config/AbstractConfigTestCase.php similarity index 98% rename from tests/TestCase/Config/AbstractConfigTest.php rename to tests/TestCase/Config/AbstractConfigTestCase.php index bc5824d0..ad096b9c 100644 --- a/tests/TestCase/Config/AbstractConfigTest.php +++ b/tests/TestCase/Config/AbstractConfigTestCase.php @@ -10,7 +10,7 @@ * * @coversNothing */ -abstract class AbstractConfigTest extends TestCase +abstract class AbstractConfigTestCase extends TestCase { /** * @var string diff --git a/tests/TestCase/Config/ConfigMigrationPathsTest.php b/tests/TestCase/Config/ConfigMigrationPathsTest.php index 073676f2..eeac98a5 100644 --- a/tests/TestCase/Config/ConfigMigrationPathsTest.php +++ b/tests/TestCase/Config/ConfigMigrationPathsTest.php @@ -8,7 +8,7 @@ /** * Class ConfigMigrationPathsTest */ -class ConfigMigrationPathsTest extends AbstractConfigTest +class ConfigMigrationPathsTest extends AbstractConfigTestCase { public function testGetMigrationPathsThrowsExceptionForNoPath() { diff --git a/tests/TestCase/Config/ConfigSeedPathsTest.php b/tests/TestCase/Config/ConfigSeedPathsTest.php index 06685472..f8bfb147 100644 --- a/tests/TestCase/Config/ConfigSeedPathsTest.php +++ b/tests/TestCase/Config/ConfigSeedPathsTest.php @@ -8,7 +8,7 @@ /** * Class ConfigSeedPathsTest */ -class ConfigSeedPathsTest extends AbstractConfigTest +class ConfigSeedPathsTest extends AbstractConfigTestCase { public function testGetSeedPathsThrowsExceptionForNoPath() { diff --git a/tests/TestCase/Config/ConfigSeedTemplatePathsTest.php b/tests/TestCase/Config/ConfigSeedTemplatePathsTest.php index 4f55dbd1..d259d05e 100644 --- a/tests/TestCase/Config/ConfigSeedTemplatePathsTest.php +++ b/tests/TestCase/Config/ConfigSeedTemplatePathsTest.php @@ -7,7 +7,7 @@ /** * Class ConfigSeedTemplatePathsTest */ -class ConfigSeedTemplatePathsTest extends AbstractConfigTest +class ConfigSeedTemplatePathsTest extends AbstractConfigTestCase { public function testTemplateAndPathAreSet() { diff --git a/tests/TestCase/Config/ConfigTest.php b/tests/TestCase/Config/ConfigTest.php index 884802e7..5f2b61a8 100644 --- a/tests/TestCase/Config/ConfigTest.php +++ b/tests/TestCase/Config/ConfigTest.php @@ -12,7 +12,7 @@ * @package Test\Phinx\Config * @group config */ -class ConfigTest extends AbstractConfigTest +class ConfigTest extends AbstractConfigTestCase { /** * @covers \Phinx\Config\Config::getEnvironments From 8f596820e30a9e513f1e53bcb2afe1f07bf5a102 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 3 Feb 2024 18:33:17 -0500 Subject: [PATCH 7/7] Fix psalm and phpstan errors. --- psalm-baseline.xml | 27 +++++++++++++++++++++++++++ src/Config/Config.php | 23 ++++++++++------------- src/Config/ConfigInterface.php | 2 ++ 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index b1e9de84..7f40f89a 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -10,6 +10,27 @@ setInput + + + getEnvironments + getEnvironments + hasEnvironment + hasEnvironment + searchNamespace + searchNamespace + + + array + array + array + array + + + + + ArrayAccess + + output)]]> @@ -82,6 +103,12 @@ array_merge($versions, array_keys($migrations)) + + getDataDomain + getMigrationNamespaceByPath + getSeedNamespaceByPath + hasEnvironment + container)]]> diff --git a/src/Config/Config.php b/src/Config/Config.php index 165a4159..ace778f0 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -55,10 +55,6 @@ public function __construct(array $configArray, ?string $configFilePath = null) { $this->configFilePath = $configFilePath; $this->values = $this->replaceTokens($configArray); - - if (isset($this->values['feature_flags'])) { - FeatureFlags::setFlagsFromConfig($this->values['feature_flags']); - } } /** @@ -87,7 +83,7 @@ public static function fromYaml(string $configFilePath): ConfigInterface )); } - return new static($configArray, $configFilePath); + return new Config($configArray, $configFilePath); } /** @@ -114,7 +110,7 @@ public static function fromJson(string $configFilePath): ConfigInterface )); } - return new static($configArray, $configFilePath); + return new Config($configArray, $configFilePath); } /** @@ -141,7 +137,7 @@ public static function fromPhp(string $configFilePath): ConfigInterface )); } - return new static($configArray, $configFilePath); + return new Config($configArray, $configFilePath); } /** @@ -323,7 +319,7 @@ public function getSeedBaseClassName(bool $dropNamespace = true): string { $className = !isset($this->values['seed_base_class']) ? 'Phinx\Seed\AbstractSeed' : $this->values['seed_base_class']; - return $dropNamespace ? substr(strrchr($className, '\\'), 1) : $className; + return $dropNamespace ? substr((string)strrchr($className, '\\'), 1) : $className; } /** @@ -425,8 +421,8 @@ public function getBootstrapFile(): string|false /** * Replace tokens in the specified array. * - * @param array $arr Array to replace - * @return array + * @param array $arr Array to replace + * @return array */ protected function replaceTokens(array $arr): array { @@ -435,7 +431,7 @@ protected function replaceTokens(array $arr): array // environment variables either end up in $_SERVER (most likely) or $_ENV, // so we search through both - /** @var array $tokens */ + /** @var array $tokens */ $tokens = []; foreach (array_merge($_ENV, $_SERVER) as $varname => $varvalue) { if (strpos($varname, 'PHINX_') === 0) { @@ -455,11 +451,12 @@ protected function replaceTokens(array $arr): array * Recurse an array for the specified tokens and replace them. * * @param array $arr Array to recurse - * @param string|null[] $tokens Array of tokens to search for - * @return array + * @param array $tokens Array of tokens to search for + * @return array */ protected function recurseArrayForTokens(array $arr, array $tokens): array { + /** @var array $out */ $out = []; foreach ($arr as $name => $value) { if (is_array($value)) { diff --git a/src/Config/ConfigInterface.php b/src/Config/ConfigInterface.php index 2ddef52d..c0fb1789 100644 --- a/src/Config/ConfigInterface.php +++ b/src/Config/ConfigInterface.php @@ -13,6 +13,8 @@ /** * Phinx configuration interface. + * + * @template-implemements ArrayAccess */ interface ConfigInterface extends ArrayAccess {