diff --git a/CHANGELOG.md b/CHANGELOG.md index 476d202..e437676 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org). +## [Unreleased] +### Added +* *Nothing* + +### Changed +* Removed dependency on functional-php + +### Deprecated +* *Nothing* + +### Removed +* *Nothing* + +### Fixed +* *Nothing* + + ## [8.6.0] - 2023-11-25 ### Added * Add `CacheNamespaceConfigOption` to customize the cache namespace. diff --git a/composer.json b/composer.json index 674ec68..dd0b5ac 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,6 @@ "laminas/laminas-config-aggregator": "^1.14", "laminas/laminas-servicemanager": "^3.22", "laminas/laminas-stdlib": "^3.18", - "lstrojny/functional-php": "^1.17", "shlinkio/shlink-config": "^2.4", "symfony/console": "^6.3", "symfony/filesystem": "^6.3", diff --git a/src/Command/InitCommand.php b/src/Command/InitCommand.php index 8bf3d0b..55b2f9c 100644 --- a/src/Command/InitCommand.php +++ b/src/Command/InitCommand.php @@ -14,7 +14,7 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -use function Functional\every; +use function array_reduce; class InitCommand extends Command { @@ -56,10 +56,10 @@ protected function execute(InputInterface $input, OutputInterface $output): ?int generateApiKey: $this->initialApiKey->get($input), downloadGeoLiteDb: ! $this->skipDownloadGeoLiteDb->get($input), ); - $commands = InstallationCommand::resolveCommandsForConfig($config); + $commands = [...InstallationCommand::resolveCommandsForConfig($config)]; $io = new SymfonyStyle($input, $output); - return every($commands, function (array $commandInfo) use ($input, $io): bool { + return array_reduce($commands, function (bool $carry, array $commandInfo) use ($input, $io): bool { /** @var array{InstallationCommand, string | null} $commandInfo */ [$command, $arg] = $commandInfo; @@ -68,7 +68,7 @@ protected function execute(InputInterface $input, OutputInterface $output): ?int io: $io, interactive: $input->isInteractive(), args: $arg !== null ? [$arg] : [], - ); - }) ? 0 : -1; + ) && $carry; + }, initial: true) ? 0 : -1; } } diff --git a/src/Command/SetOptionCommand.php b/src/Command/SetOptionCommand.php index 2af7a3e..901ee09 100644 --- a/src/Command/SetOptionCommand.php +++ b/src/Command/SetOptionCommand.php @@ -11,6 +11,7 @@ use Shlinkio\Shlink\Installer\Exception\InvalidShlinkPathException; use Shlinkio\Shlink\Installer\Service\ShlinkAssetsHandler; use Shlinkio\Shlink\Installer\Service\ShlinkAssetsHandlerInterface; +use Shlinkio\Shlink\Installer\Util\ArrayUtils; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -19,7 +20,6 @@ use function array_filter; use function array_keys; -use function Functional\contains; use function getcwd; use function is_iterable; use function is_numeric; @@ -43,7 +43,10 @@ public function __construct( parent::__construct(); $this->groups = array_filter( iterator_to_array($this->flattenGroupsWithTitle($groups)), - static fn (string $configOption) => $enabledOptions === null || contains($enabledOptions, $configOption), + static fn (string $configOption) => $enabledOptions === null || ArrayUtils::contains( + $configOption, + $enabledOptions, + ), ); $this->generatedConfigPath = getcwd() . '/' . ShlinkAssetsHandler::GENERATED_CONFIG_PATH; } diff --git a/src/Config/ConfigGenerator.php b/src/Config/ConfigGenerator.php index 87e1169..cd0c2e8 100644 --- a/src/Config/ConfigGenerator.php +++ b/src/Config/ConfigGenerator.php @@ -7,15 +7,13 @@ use Shlinkio\Shlink\Installer\Config\Option\ConfigOptionInterface; use Shlinkio\Shlink\Installer\Config\Option\ConfigOptionMigratorInterface; use Shlinkio\Shlink\Installer\Config\Option\DependentConfigOptionInterface; +use Shlinkio\Shlink\Installer\Util\ArrayUtils; use Symfony\Component\Console\Style\StyleInterface; use function array_combine; -use function Functional\compose; -use function Functional\contains; -use function Functional\map; -use function Functional\select; -use function Functional\sort; -use function get_class; +use function array_filter; +use function array_map; +use function usort; class ConfigGenerator implements ConfigGeneratorInterface { @@ -35,7 +33,10 @@ public function generateConfigInteractively(StyleInterface $io, array $previousC // FIXME Improve code quality on these nested loops foreach ($pluginsGroups as $title => $configOptions) { foreach ($configOptions as $configOption => $plugin) { - $optionIsEnabled = $this->enabledOptions === null || contains($this->enabledOptions, $configOption); + $optionIsEnabled = $this->enabledOptions === null || ArrayUtils::contains( + $configOption, + $this->enabledOptions, + ); $shouldAsk = $optionIsEnabled && $plugin->shouldBeAsked($answers); if (! $shouldAsk) { if ($plugin instanceof ConfigOptionMigratorInterface && isset($answers[$plugin->getEnvVar()])) { @@ -46,7 +47,7 @@ public function generateConfigInteractively(StyleInterface $io, array $previousC } // Render every title only once, and only as soon as we find a plugin that should be asked - if (! contains($alreadyRenderedTitles, $title)) { + if (! ArrayUtils::contains($title, $alreadyRenderedTitles)) { $alreadyRenderedTitles[] = $title; $io->title($title); } @@ -63,24 +64,32 @@ public function generateConfigInteractively(StyleInterface $io, array $previousC */ private function resolveAndSortOptions(): array { - // Sort plugins based on which other plugins they depend on - $dependentPluginSorter = static fn (ConfigOptionInterface $a, ConfigOptionInterface $b): int => - $a instanceof DependentConfigOptionInterface && $a->getDependentOption() === get_class($b) ? 1 : 0; - $sortAndResolvePlugins = fn (array $configOptions) => array_combine( + $resolveAndSortPlugins = function (array $configOptions) { + $plugins = array_map( + fn (string $configOption) => $this->configOptionsManager->get($configOption), + $configOptions, + ); + + // Sort plugins based on which other plugins they depend on + usort( + $plugins, + static fn (ConfigOptionInterface $a, ConfigOptionInterface $b): int => + $a instanceof DependentConfigOptionInterface && $a->getDependentOption() === $b::class ? 1 : 0, + ); + + return array_combine($configOptions, $plugins); + }; + $filterDisabledOptions = fn (array $configOptions) => array_filter( $configOptions, - sort( - map( - $configOptions, - fn (string $configOption) => $this->configOptionsManager->get($configOption), - ), - $dependentPluginSorter, + fn (string $option) => $this->enabledOptions === null || ArrayUtils::contains( + $option, + $this->enabledOptions, ), ); - $filterDisabledOptions = fn (array $configOptions) => select( - $configOptions, - fn (string $option) => $this->enabledOptions === null || contains($this->enabledOptions, $option), - ); - return map($this->configOptionsGroups, compose($filterDisabledOptions, $sortAndResolvePlugins)); + return array_map( + static fn (array $configOptions) => $resolveAndSortPlugins($filterDisabledOptions($configOptions)), + $this->configOptionsGroups, + ); } } diff --git a/src/Config/Option/UrlShortener/RedirectCacheLifeTimeConfigOption.php b/src/Config/Option/UrlShortener/RedirectCacheLifeTimeConfigOption.php index 4137326..a34f389 100644 --- a/src/Config/Option/UrlShortener/RedirectCacheLifeTimeConfigOption.php +++ b/src/Config/Option/UrlShortener/RedirectCacheLifeTimeConfigOption.php @@ -7,10 +7,9 @@ use Shlinkio\Shlink\Installer\Config\Option\BaseConfigOption; use Shlinkio\Shlink\Installer\Config\Option\DependentConfigOptionInterface; use Shlinkio\Shlink\Installer\Config\Util\ConfigOptionsValidatorsTrait; +use Shlinkio\Shlink\Installer\Util\ArrayUtils; use Symfony\Component\Console\Style\StyleInterface; -use function Functional\contains; - class RedirectCacheLifeTimeConfigOption extends BaseConfigOption implements DependentConfigOptionInterface { use ConfigOptionsValidatorsTrait; @@ -28,7 +27,7 @@ public function shouldBeAsked(array $currentOptions): bool private function isPermanentRedirectStatus(int $redirectStatus): bool { - return contains([301, 308], $redirectStatus); + return ArrayUtils::contains($redirectStatus, [301, 308]); } public function ask(StyleInterface $io, array $currentOptions): int diff --git a/src/Config/Util/ConfigOptionsValidatorsTrait.php b/src/Config/Util/ConfigOptionsValidatorsTrait.php index d65572f..940ed61 100644 --- a/src/Config/Util/ConfigOptionsValidatorsTrait.php +++ b/src/Config/Util/ConfigOptionsValidatorsTrait.php @@ -7,7 +7,7 @@ use Shlinkio\Shlink\Installer\Exception\InvalidConfigOptionException; use Shlinkio\Shlink\Installer\Util\Utils; -use function Functional\map; +use function array_map; use function is_numeric; use function preg_match; use function sprintf; @@ -37,7 +37,7 @@ public function splitAndValidateMultipleUrls(?string $urls): array } $splitUrls = Utils::commaSeparatedToList($urls); - return map($splitUrls, [$this, 'validateUrl']); + return array_map($this->validateUrl(...), $splitUrls); } public function validateOptionalPositiveNumber(mixed $value): ?int diff --git a/src/Util/ArrayUtils.php b/src/Util/ArrayUtils.php new file mode 100644 index 0000000..b5f0c6e --- /dev/null +++ b/src/Util/ArrayUtils.php @@ -0,0 +1,29 @@ + [...$carry, ...$value], + initial: [], + ); + } +} diff --git a/src/Util/Utils.php b/src/Util/Utils.php index f2d925a..4f24049 100644 --- a/src/Util/Utils.php +++ b/src/Util/Utils.php @@ -9,10 +9,10 @@ use Shlinkio\Shlink\Installer\Config\Util\DatabaseDriver; use function array_filter; +use function array_map; +use function array_reduce; use function ctype_upper; use function explode; -use function Functional\every; -use function Functional\map; use function implode; use function is_array; use function is_bool; @@ -25,24 +25,27 @@ class Utils { public static function commaSeparatedToList(string $list): array { - return map(explode(',', $list), static fn (string $value) => trim($value)); + return array_map(static fn (string $value) => trim($value), explode(',', $list)); } public static function normalizeAndKeepEnvVarKeys(array $array): array { $dbEnvVar = DatabaseDriverConfigOption::ENV_VAR; + $filteredEnvVars = array_filter( + $array, + static fn (string $key) => + // Filter out env vars which are not fully in uppercase. + // Numbers are also valid, as some env vars (like `DEFAULT_REGULAR_404_REDIRECT`) contain them. + array_reduce( + explode('_', $key), + static fn (bool $carry, string $part) => $carry && (ctype_upper($part) || is_numeric($part)), + initial: true, + ), + ARRAY_FILTER_USE_KEY, + ); - return map( - array_filter( - $array, - static fn (string $key) => - // Filter out env vars which are not fully in uppercase. - // Numbers are also valid, as some env vars (like `DEFAULT_REGULAR_404_REDIRECT`) contain them. - every(explode('_', $key), static fn (string $part) => ctype_upper($part) || is_numeric($part)), - ARRAY_FILTER_USE_KEY, - ), - // This maps old values that have been imported, to the new expected values - static fn (mixed $value, string $envVar) => match (true) { + foreach ($filteredEnvVars as $envVar => $value) { + $filteredEnvVars[$envVar] = match (true) { is_array($value) => implode(',', $value), $envVar === ShortDomainSchemaConfigOption::ENV_VAR && ! is_bool($value) => $value === 'https', $envVar === $dbEnvVar && $value === 'pdo_pgsql' => DatabaseDriver::POSTGRES->value, @@ -50,7 +53,9 @@ public static function normalizeAndKeepEnvVarKeys(array $array): array $envVar === $dbEnvVar && $value === 'pdo_sqlsrv' => DatabaseDriver::MSSQL->value, $envVar === $dbEnvVar && $value === 'pdo_mysql' => DatabaseDriver::MYSQL->value, default => $value, - }, - ); + }; + } + + return $filteredEnvVars; } } diff --git a/test/Config/ConfigGeneratorTest.php b/test/Config/ConfigGeneratorTest.php index f39ac5c..d03dfc9 100644 --- a/test/Config/ConfigGeneratorTest.php +++ b/test/Config/ConfigGeneratorTest.php @@ -13,10 +13,10 @@ use Shlinkio\Shlink\Installer\Config\Option\ConfigOptionInterface; use Shlinkio\Shlink\Installer\Config\Option\ConfigOptionMigratorInterface; use Shlinkio\Shlink\Installer\Config\Option\DependentConfigOptionInterface; +use Shlinkio\Shlink\Installer\Util\ArrayUtils; use Symfony\Component\Console\Style\StyleInterface; use function count; -use function Functional\flatten; use function get_class; class ConfigGeneratorTest extends TestCase @@ -38,7 +38,7 @@ public function configuresExpectedPlugins( ?array $enabledOptions, int $expectedPrintTitleCalls, ): void { - $totalPlugins = count(flatten($configOptionsGroups)); + $totalPlugins = count(ArrayUtils::flatten($configOptionsGroups)); $expectedQuestions = $enabledOptions === null ? $totalPlugins : count($enabledOptions); $this->plugin->expects($this->exactly($expectedQuestions))->method('shouldBeAsked')->willReturn(true); diff --git a/test/Factory/ApplicationFactoryTest.php b/test/Factory/ApplicationFactoryTest.php index dc9e6f4..54e8d5c 100644 --- a/test/Factory/ApplicationFactoryTest.php +++ b/test/Factory/ApplicationFactoryTest.php @@ -8,11 +8,11 @@ use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use Shlinkio\Shlink\Installer\Factory\ApplicationFactory; +use Shlinkio\Shlink\Installer\Util\ArrayUtils; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputDefinition; use function array_filter; -use function Functional\contains; use const ARRAY_FILTER_USE_KEY; @@ -55,7 +55,7 @@ public function serviceIsCreated(): void $commands = array_filter( $app->all(), // Remove standard symfony commands - static fn (string $key) => ! contains(['list', 'help', 'completion', '_complete'], $key), + static fn (string $key) => ! ArrayUtils::contains($key, ['list', 'help', 'completion', '_complete']), ARRAY_FILTER_USE_KEY, ); diff --git a/test/Service/InstallationCommandsRunnerTest.php b/test/Service/InstallationCommandsRunnerTest.php index 6eacebd..e66395d 100644 --- a/test/Service/InstallationCommandsRunnerTest.php +++ b/test/Service/InstallationCommandsRunnerTest.php @@ -17,7 +17,7 @@ use Symfony\Component\Process\Process; use function array_combine; -use function Functional\map; +use function array_map; use function implode; use function sprintf; use function str_contains; @@ -46,14 +46,17 @@ public function setUp(): void private function buildCommands(): array { $names = ['foo', 'bar', 'null_command', 'multiple spaces ']; - return array_combine($names, map($names, fn (string $name) => [ - 'command' => $name === 'null_command' ? null : sprintf('%s something', $name), - 'initMessage' => sprintf('%s_init', $name), - 'errorMessage' => sprintf('%s_error', $name), - 'failOnError' => $name === 'foo', - 'printOutput' => false, - 'timeout' => $name === 'foo' ? 1000 : null, - ])); + return array_combine( + $names, + array_map(fn (string $name) => [ + 'command' => $name === 'null_command' ? null : sprintf('%s something', $name), + 'initMessage' => sprintf('%s_init', $name), + 'errorMessage' => sprintf('%s_error', $name), + 'failOnError' => $name === 'foo', + 'printOutput' => false, + 'timeout' => $name === 'foo' ? 1000 : null, + ], $names), + ); } #[Test] diff --git a/test/Service/ShlinkAssetsHandlerTest.php b/test/Service/ShlinkAssetsHandlerTest.php index 52f2fb4..70f8138 100644 --- a/test/Service/ShlinkAssetsHandlerTest.php +++ b/test/Service/ShlinkAssetsHandlerTest.php @@ -15,7 +15,7 @@ use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Filesystem; -use function Functional\map; +use function array_map; use function str_starts_with; class ShlinkAssetsHandlerTest extends TestCase @@ -113,9 +113,9 @@ public function assetsAreProperlyImportedIfTheyExist(bool $assetsExist, Invocati { $path = '/foo/bar'; $assets = ['database.sqlite', 'GeoLite2-City.mmdb']; - $this->filesystem->expects($this->exactly(2))->method('exists')->willReturnMap(map( - $assets, + $this->filesystem->expects($this->exactly(2))->method('exists')->willReturnMap(array_map( fn (string $asset) => [$path . '/data/' . $asset, $assetsExist], + $assets, )); $this->filesystem->expects($expectedCopies)->method('copy')->withAnyParameters();