diff --git a/.gitattributes b/.gitattributes index c49537b..e4c272f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,7 +7,6 @@ test-resources export-ignore .gitignore export-ignore .phpstorm.meta.php export-ignore CHANGELOG.md export-ignore -docker-compose.override.yml.dist export-ignore docker-compose.yml export-ignore Dockerfile export-ignore indocker export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7aa46b0..9a8ed85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,3 +10,5 @@ on: jobs: ci: uses: shlinkio/github-actions/.github/workflows/php-lib-ci.yml@main + with: + coverage-driver: 'xdebug' diff --git a/.gitignore b/.gitignore index 58ed40a..8ee36dd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ build composer.lock vendor/ -docker-compose.override.yml config/*.local.php data bin/rr +test-resources/config/params/generated-in-test.php diff --git a/CHANGELOG.md b/CHANGELOG.md index c8d22ea..dc16b59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,25 @@ 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). +## [9.3.0] - 2024-11-24 +### Added +* *Nothing* + +### Changed +* Switch to xdebug for code coverage reports, as pcov is not marking functions as covered +* Update shlinkio coding standard to v2.4 +* Update to PHPStan 2.0 + +### Deprecated +* *Nothing* + +### Removed +* Remove dependency on `laminas/laminas-config`. + +### Fixed +* *Nothing* + + ## [9.2.0] - 2024-08-11 ### Added * Add `ROBOTS_ALLOW_ALL_SHORT_URLS` config option. diff --git a/Dockerfile b/Dockerfile index 5dcd3a3..11300e4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,7 @@ FROM composer:2 -RUN apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} && \ - pecl install pcov && \ - docker-php-ext-enable pcov && \ +RUN apk add --update linux-headers && \ + apk add --no-cache --virtual .phpize-deps ${PHPIZE_DEPS} && \ + pecl install xdebug && \ + docker-php-ext-enable xdebug && \ apk del .phpize-deps diff --git a/composer.json b/composer.json index 62b003d..318c8c5 100644 --- a/composer.json +++ b/composer.json @@ -13,22 +13,22 @@ ], "require": { "php": "^8.2", - "laminas/laminas-config": "^3.9", "laminas/laminas-config-aggregator": "^1.15", "laminas/laminas-servicemanager": "^4.2 || ^3.22", "laminas/laminas-stdlib": "^3.19", "shlinkio/shlink-config": "^3.1", "symfony/console": "^7.1", "symfony/filesystem": "^7.1", - "symfony/process": "^7.1" + "symfony/process": "^7.1", + "webimpress/safe-writer": "^2.2" }, "require-dev": { "devster/ubench": "^2.1", - "phpstan/phpstan": "^1.11", - "phpstan/phpstan-phpunit": "^1.4", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", "phpunit/phpunit": "^11.3", "roave/security-advisories": "dev-master", - "shlinkio/php-coding-standard": "~2.3.0", + "shlinkio/php-coding-standard": "~2.4.0", "symfony/var-dumper": "^7.1" }, "autoload": { @@ -51,8 +51,14 @@ "cs:fix": "phpcbf", "stan": "phpstan analyse", "test": "phpunit --order-by=random --testdox --testdox-summary", - "test:ci": "@test --coverage-clover=build/clover.xml", - "test:pretty": "@test --coverage-html=build/coverage-html" + "test:ci": [ + "@putenv XDEBUG_MODE=coverage", + "@test --coverage-clover=build/clover.xml" + ], + "test:pretty": [ + "@putenv XDEBUG_MODE=coverage", + "@test --coverage-html=build/coverage-html" + ] }, "scripts-descriptions": { "ci": "Alias for \"cs\", \"stan\" and \"test:ci\"", diff --git a/config/config.php b/config/config.php index d3771e2..cb9c951 100644 --- a/config/config.php +++ b/config/config.php @@ -4,9 +4,9 @@ namespace Shlinkio\Shlink\Installer; -use Laminas\Config\Writer\PhpArray as PhpArrayConfigWriter; use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory; use Laminas\ServiceManager\Factory\InvokableFactory; +use Shlinkio\Shlink\Installer\Util\ConfigWriter; use Shlinkio\Shlink\Installer\Util\InstallationCommand; use Symfony\Component\Console; use Symfony\Component\Filesystem\Filesystem; @@ -19,7 +19,7 @@ Console\Application::class => Factory\ApplicationFactory::class, Filesystem::class => InvokableFactory::class, PhpExecutableFinder::class => InvokableFactory::class, - PhpArrayConfigWriter::class => InvokableFactory::class, + ConfigWriter::class => InvokableFactory::class, Console\Helper\ProcessHelper::class => Factory\ProcessHelperFactory::class, Service\InstallationCommandsRunner::class => ConfigAbstractFactory::class, @@ -209,17 +209,17 @@ ], Command\InstallCommand::class => [ - PhpArrayConfigWriter::class, + ConfigWriter::class, Service\ShlinkAssetsHandler::class, Config\ConfigGenerator::class, ], Command\UpdateCommand::class => [ - PhpArrayConfigWriter::class, + ConfigWriter::class, Service\ShlinkAssetsHandler::class, Config\ConfigGenerator::class, ], Command\SetOptionCommand::class => [ - PhpArrayConfigWriter::class, + ConfigWriter::class, Service\ShlinkAssetsHandler::class, Config\ConfigOptionsManager::class, Filesystem::class, diff --git a/docker-compose.override.yml.dist b/docker-compose.override.yml.dist deleted file mode 100644 index f823f01..0000000 --- a/docker-compose.override.yml.dist +++ /dev/null @@ -1,6 +0,0 @@ -services: - shlink_installer_php: - user: 1000:1000 - volumes: - - /etc/passwd:/etc/passwd:ro - - /etc/group:/etc/group:ro diff --git a/docker-compose.yml b/docker-compose.yml index b0fcb7d..950b9e9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,7 @@ services: shlink_installer_php: container_name: shlink_installer_php + user: 1000:1000 build: context: . volumes: diff --git a/src/Command/AbstractInstallCommand.php b/src/Command/AbstractInstallCommand.php index 02aa003..6d3c753 100644 --- a/src/Command/AbstractInstallCommand.php +++ b/src/Command/AbstractInstallCommand.php @@ -4,12 +4,12 @@ namespace Shlinkio\Shlink\Installer\Command; -use Laminas\Config\Writer\WriterInterface; use Shlinkio\Shlink\Installer\Command\Model\InitOption; use Shlinkio\Shlink\Installer\Config\ConfigGeneratorInterface; use Shlinkio\Shlink\Installer\Model\ImportedConfig; use Shlinkio\Shlink\Installer\Service\ShlinkAssetsHandler; use Shlinkio\Shlink\Installer\Service\ShlinkAssetsHandlerInterface; +use Shlinkio\Shlink\Installer\Util\ConfigWriterInterface; use Shlinkio\Shlink\Installer\Util\Utils; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\ArrayInput; @@ -20,7 +20,7 @@ abstract class AbstractInstallCommand extends Command { public function __construct( - private readonly WriterInterface $configWriter, + private readonly ConfigWriterInterface $configWriter, private readonly ShlinkAssetsHandlerInterface $assetsHandler, private readonly ConfigGeneratorInterface $configGenerator, ) { @@ -47,7 +47,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $normalizedConfig = Utils::normalizeAndKeepEnvVarKeys($config); // Generate config params files - $this->configWriter->toFile(ShlinkAssetsHandler::GENERATED_CONFIG_PATH, $normalizedConfig, false); + $this->configWriter->toFile(ShlinkAssetsHandler::GENERATED_CONFIG_PATH, $normalizedConfig); $io->text('Custom configuration properly generated!'); $io->newLine(); diff --git a/src/Command/SetOptionCommand.php b/src/Command/SetOptionCommand.php index 23dd59c..acdc88c 100644 --- a/src/Command/SetOptionCommand.php +++ b/src/Command/SetOptionCommand.php @@ -5,13 +5,13 @@ namespace Shlinkio\Shlink\Installer\Command; use Generator; -use Laminas\Config\Writer\WriterInterface; use Shlinkio\Shlink\Installer\Config\ConfigOptionsManagerInterface; use Shlinkio\Shlink\Installer\Config\Option\ConfigOptionInterface; 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 Shlinkio\Shlink\Installer\Util\ConfigWriterInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -33,12 +33,12 @@ class SetOptionCommand extends Command private string $generatedConfigPath; public function __construct( - private WriterInterface $configWriter, + private ConfigWriterInterface $configWriter, private ShlinkAssetsHandlerInterface $assetsHandler, private ConfigOptionsManagerInterface $optionsManager, private Filesystem $filesystem, array $groups, - ?array $enabledOptions, + array|null $enabledOptions, ) { parent::__construct(); $this->groups = array_filter( @@ -85,7 +85,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $plugin = $this->optionsManager->get($this->groups[$optionTitle]); $answers = include $this->generatedConfigPath; $answers[$plugin->getEnvVar()] = $plugin->ask($io, $answers); - $this->configWriter->toFile($this->generatedConfigPath, $answers, false); + $this->configWriter->toFile($this->generatedConfigPath, $answers); $this->assetsHandler->dropCachedConfigIfAny($io); $io->success('Configuration properly updated'); diff --git a/src/Config/ConfigGenerator.php b/src/Config/ConfigGenerator.php index cd0c2e8..d18a966 100644 --- a/src/Config/ConfigGenerator.php +++ b/src/Config/ConfigGenerator.php @@ -20,7 +20,7 @@ class ConfigGenerator implements ConfigGeneratorInterface public function __construct( private readonly ConfigOptionsManagerInterface $configOptionsManager, private readonly array $configOptionsGroups, - private readonly ?array $enabledOptions, + private readonly array|null $enabledOptions, ) { } diff --git a/src/Config/Option/Database/DatabaseUnixSocketConfigOption.php b/src/Config/Option/Database/DatabaseUnixSocketConfigOption.php index 59126cd..85ee68a 100644 --- a/src/Config/Option/Database/DatabaseUnixSocketConfigOption.php +++ b/src/Config/Option/Database/DatabaseUnixSocketConfigOption.php @@ -14,7 +14,7 @@ public function getEnvVar(): string return 'DB_UNIX_SOCKET'; } - public function ask(StyleInterface $io, array $currentOptions): ?string + public function ask(StyleInterface $io, array $currentOptions): string|null { return $io->ask('Unix socket (leave empty to not use a socket)'); } diff --git a/src/Config/Option/Mercure/MercureInternalUrlConfigOption.php b/src/Config/Option/Mercure/MercureInternalUrlConfigOption.php index 7274325..740cebf 100644 --- a/src/Config/Option/Mercure/MercureInternalUrlConfigOption.php +++ b/src/Config/Option/Mercure/MercureInternalUrlConfigOption.php @@ -13,7 +13,7 @@ public function getEnvVar(): string return 'MERCURE_INTERNAL_HUB_URL'; } - public function ask(StyleInterface $io, array $currentOptions): ?string + public function ask(StyleInterface $io, array $currentOptions): string|null { return $io->ask('Internal URL of the mercure hub server (leave empty to use the public one)'); } diff --git a/src/Config/Option/QrCode/DefaultBgColorConfigOption.php b/src/Config/Option/QrCode/DefaultBgColorConfigOption.php index a1b1a86..b0a63db 100644 --- a/src/Config/Option/QrCode/DefaultBgColorConfigOption.php +++ b/src/Config/Option/QrCode/DefaultBgColorConfigOption.php @@ -15,7 +15,7 @@ public function getEnvVar(): string return 'DEFAULT_QR_CODE_BG_COLOR'; } - public function ask(StyleInterface $io, array $currentOptions): ?string + public function ask(StyleInterface $io, array $currentOptions): string|null { return $io->ask( 'What\'s the default background color for generated QR codes', diff --git a/src/Config/Option/QrCode/DefaultColorConfigOption.php b/src/Config/Option/QrCode/DefaultColorConfigOption.php index 43b9f7c..dda4d2e 100644 --- a/src/Config/Option/QrCode/DefaultColorConfigOption.php +++ b/src/Config/Option/QrCode/DefaultColorConfigOption.php @@ -15,7 +15,7 @@ public function getEnvVar(): string return 'DEFAULT_QR_CODE_COLOR'; } - public function ask(StyleInterface $io, array $currentOptions): ?string + public function ask(StyleInterface $io, array $currentOptions): string|null { return $io->ask( 'What\'s the default foreground color for generated QR codes', diff --git a/src/Config/Option/QrCode/DefaultLogoUrlConfigOption.php b/src/Config/Option/QrCode/DefaultLogoUrlConfigOption.php index 02e5249..52cc8a2 100644 --- a/src/Config/Option/QrCode/DefaultLogoUrlConfigOption.php +++ b/src/Config/Option/QrCode/DefaultLogoUrlConfigOption.php @@ -15,7 +15,7 @@ public function getEnvVar(): string return 'DEFAULT_QR_CODE_LOGO_URL'; } - public function ask(StyleInterface $io, array $currentOptions): ?string + public function ask(StyleInterface $io, array $currentOptions): string|null { return $io->ask( 'Provide a URL for a logo to be placed inside the QR code (leave empty to use no logo)', diff --git a/src/Config/Option/Redirect/BaseUrlRedirectConfigOption.php b/src/Config/Option/Redirect/BaseUrlRedirectConfigOption.php index b879ed7..f539db2 100644 --- a/src/Config/Option/Redirect/BaseUrlRedirectConfigOption.php +++ b/src/Config/Option/Redirect/BaseUrlRedirectConfigOption.php @@ -15,7 +15,7 @@ public function getEnvVar(): string return 'DEFAULT_BASE_URL_REDIRECT'; } - public function ask(StyleInterface $io, array $currentOptions): ?string + public function ask(StyleInterface $io, array $currentOptions): string|null { return $io->ask( 'Custom URL to redirect to when a user hits Shlink\'s base URL (If no value is provided, the ' diff --git a/src/Config/Option/Redirect/InvalidShortUrlRedirectConfigOption.php b/src/Config/Option/Redirect/InvalidShortUrlRedirectConfigOption.php index 2e98d14..f57d097 100644 --- a/src/Config/Option/Redirect/InvalidShortUrlRedirectConfigOption.php +++ b/src/Config/Option/Redirect/InvalidShortUrlRedirectConfigOption.php @@ -15,7 +15,7 @@ public function getEnvVar(): string return 'DEFAULT_INVALID_SHORT_URL_REDIRECT'; } - public function ask(StyleInterface $io, array $currentOptions): ?string + public function ask(StyleInterface $io, array $currentOptions): string|null { return $io->ask( 'Custom URL to redirect to when a user hits an invalid short URL (If no value is provided, the ' diff --git a/src/Config/Option/Redirect/Regular404RedirectConfigOption.php b/src/Config/Option/Redirect/Regular404RedirectConfigOption.php index 0098a81..16e68d8 100644 --- a/src/Config/Option/Redirect/Regular404RedirectConfigOption.php +++ b/src/Config/Option/Redirect/Regular404RedirectConfigOption.php @@ -15,7 +15,7 @@ public function getEnvVar(): string return 'DEFAULT_REGULAR_404_REDIRECT'; } - public function ask(StyleInterface $io, array $currentOptions): ?string + public function ask(StyleInterface $io, array $currentOptions): string|null { return $io->ask( 'Custom URL to redirect to when a user hits a not found URL other than an invalid short URL ' diff --git a/src/Config/Option/Redis/RedisSentinelServiceConfigOption.php b/src/Config/Option/Redis/RedisSentinelServiceConfigOption.php index 9839595..2f0e5a7 100644 --- a/src/Config/Option/Redis/RedisSentinelServiceConfigOption.php +++ b/src/Config/Option/Redis/RedisSentinelServiceConfigOption.php @@ -21,7 +21,7 @@ public function shouldBeAsked(array $currentOptions): bool return $isRedisEnabled !== null && parent::shouldBeAsked($currentOptions); } - public function ask(StyleInterface $io, array $currentOptions): ?string + public function ask(StyleInterface $io, array $currentOptions): string|null { return $io->ask('Provide the name of the sentinel service (leave empty if not using redis sentinel)'); } diff --git a/src/Config/Option/Redis/RedisServersConfigOption.php b/src/Config/Option/Redis/RedisServersConfigOption.php index d1b49a7..bce5ec2 100644 --- a/src/Config/Option/Redis/RedisServersConfigOption.php +++ b/src/Config/Option/Redis/RedisServersConfigOption.php @@ -16,7 +16,7 @@ public function getEnvVar(): string return self::ENV_VAR; } - public function ask(StyleInterface $io, array $currentOptions): ?string + public function ask(StyleInterface $io, array $currentOptions): string|null { $useRedis = $io->confirm( 'Do you want to use a redis instance, redis cluster or redis sentinels as a shared cache for Shlink? ' diff --git a/src/Config/Option/Robots/RobotsUserAgentsConfigOption.php b/src/Config/Option/Robots/RobotsUserAgentsConfigOption.php index 566e23f..334599f 100644 --- a/src/Config/Option/Robots/RobotsUserAgentsConfigOption.php +++ b/src/Config/Option/Robots/RobotsUserAgentsConfigOption.php @@ -14,7 +14,7 @@ public function getEnvVar(): string return 'ROBOTS_USER_AGENTS'; } - public function ask(StyleInterface $io, array $currentOptions): ?string + public function ask(StyleInterface $io, array $currentOptions): string|null { return $io->ask( 'Provide a comma-separated list of user agents for your robots.txt file. Defaults to all user agents (*)', diff --git a/src/Config/Option/TimezoneConfigOption.php b/src/Config/Option/TimezoneConfigOption.php index 2308ef9..2f67db0 100644 --- a/src/Config/Option/TimezoneConfigOption.php +++ b/src/Config/Option/TimezoneConfigOption.php @@ -13,7 +13,7 @@ public function getEnvVar(): string return 'TIMEZONE'; } - public function ask(StyleInterface $io, array $currentOptions): ?string + public function ask(StyleInterface $io, array $currentOptions): string|null { return $io->ask( 'Set the timezone in which your Shlink instance is running (leave empty to use the one set in PHP config)', diff --git a/src/Config/Option/Tracking/DisableTrackParamConfigOption.php b/src/Config/Option/Tracking/DisableTrackParamConfigOption.php index bbd2365..79d4602 100644 --- a/src/Config/Option/Tracking/DisableTrackParamConfigOption.php +++ b/src/Config/Option/Tracking/DisableTrackParamConfigOption.php @@ -13,7 +13,7 @@ public function getEnvVar(): string return 'DISABLE_TRACK_PARAM'; } - public function ask(StyleInterface $io, array $currentOptions): ?string + public function ask(StyleInterface $io, array $currentOptions): string|null { return $io->ask( 'Provide a parameter name that you will be able to use to disable tracking on specific request to ' diff --git a/src/Config/Option/Tracking/DisableTrackingFromConfigOption.php b/src/Config/Option/Tracking/DisableTrackingFromConfigOption.php index 65fe271..b3e172a 100644 --- a/src/Config/Option/Tracking/DisableTrackingFromConfigOption.php +++ b/src/Config/Option/Tracking/DisableTrackingFromConfigOption.php @@ -13,7 +13,7 @@ public function getEnvVar(): string return 'DISABLE_TRACKING_FROM'; } - public function ask(StyleInterface $io, array $currentOptions): ?string + public function ask(StyleInterface $io, array $currentOptions): string|null { return $io->ask( 'Provide a comma-separated list of IP addresses, CIDR blocks or wildcard addresses (1.2.*.*) from ' diff --git a/src/Config/Option/UrlShortener/GeoLiteLicenseKeyConfigOption.php b/src/Config/Option/UrlShortener/GeoLiteLicenseKeyConfigOption.php index 3035006..5910d13 100644 --- a/src/Config/Option/UrlShortener/GeoLiteLicenseKeyConfigOption.php +++ b/src/Config/Option/UrlShortener/GeoLiteLicenseKeyConfigOption.php @@ -14,7 +14,7 @@ public function getEnvVar(): string return 'GEOLITE_LICENSE_KEY'; } - public function ask(StyleInterface $io, array $currentOptions): ?string + public function ask(StyleInterface $io, array $currentOptions): string|null { return $io->ask( 'Provide a GeoLite2 license key. Leave empty to disable geolocation. ' diff --git a/src/Config/Option/Visit/VisitsThresholdConfigOption.php b/src/Config/Option/Visit/VisitsThresholdConfigOption.php index ca699f1..bc02190 100644 --- a/src/Config/Option/Visit/VisitsThresholdConfigOption.php +++ b/src/Config/Option/Visit/VisitsThresholdConfigOption.php @@ -15,7 +15,7 @@ public function getEnvVar(): string return 'DELETE_SHORT_URL_THRESHOLD'; } - public function ask(StyleInterface $io, array $currentOptions): ?int + public function ask(StyleInterface $io, array $currentOptions): int|null { $result = $io->ask( 'What is the amount of visits from which the system will not allow short URLs to be deleted? Leave empty ' diff --git a/src/Config/Util/ConfigOptionsValidator.php b/src/Config/Util/ConfigOptionsValidator.php index ebf3026..7306164 100644 --- a/src/Config/Util/ConfigOptionsValidator.php +++ b/src/Config/Util/ConfigOptionsValidator.php @@ -15,7 +15,7 @@ class ConfigOptionsValidator { - public static function validateUrl(?string $value): ?string + public static function validateUrl(string|null $value): string|null { $httpUrlRegexp = '/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}' @@ -31,7 +31,7 @@ public static function validateUrl(?string $value): ?string return $value; } - public static function validateOptionalPositiveNumber(mixed $value): ?int + public static function validateOptionalPositiveNumber(mixed $value): int|null { return $value === null ? $value : self::validatePositiveNumber($value); } diff --git a/src/Config/Util/DatabaseDriver.php b/src/Config/Util/DatabaseDriver.php index 1c96e22..3297cc4 100644 --- a/src/Config/Util/DatabaseDriver.php +++ b/src/Config/Util/DatabaseDriver.php @@ -11,7 +11,7 @@ enum DatabaseDriver: string case MSSQL = 'mssql'; case SQLITE = 'sqlite'; - public function defaultPort(): ?string + public function defaultPort(): string|null { return match ($this) { self::MYSQL => '3306', diff --git a/src/Factory/ApplicationFactory.php b/src/Factory/ApplicationFactory.php index 0a8a84f..f11dece 100644 --- a/src/Factory/ApplicationFactory.php +++ b/src/Factory/ApplicationFactory.php @@ -11,7 +11,7 @@ class ApplicationFactory { - public function __invoke(ContainerInterface $container, string $requestedName, ?array $options = null): Application + public function __invoke(ContainerInterface $container, string $requestedName): Application { $commandMap = $container->get('config')['installer']['commands'] ?? []; $app = new Application( diff --git a/src/Util/AskUtilsTrait.php b/src/Util/AskUtilsTrait.php index 9078777..4cf6fd1 100644 --- a/src/Util/AskUtilsTrait.php +++ b/src/Util/AskUtilsTrait.php @@ -9,7 +9,7 @@ trait AskUtilsTrait { - private function askRequired(StyleInterface $io, string $optionName, ?string $question = null): string + private function askRequired(StyleInterface $io, string $optionName, string|null $question = null): string { return $io->ask($question ?? $optionName, null, static function ($value) use ($optionName) { if (empty($value)) { diff --git a/src/Util/ConfigWriter.php b/src/Util/ConfigWriter.php new file mode 100644 index 0000000..25ba8a1 --- /dev/null +++ b/src/Util/ConfigWriter.php @@ -0,0 +1,29 @@ +assetsHandler = $this->createMock(ShlinkAssetsHandlerInterface::class); $this->assetsHandler->expects($this->once())->method('dropCachedConfigIfAny'); - $this->configWriter = $this->createMock(WriterInterface::class); + $this->configWriter = $this->createMock(ConfigWriterInterface::class); $configGenerator = $this->createMock(ConfigGeneratorInterface::class); $configGenerator->method('generateConfigInteractively')->willReturn([]); @@ -66,11 +66,7 @@ public function commandIsExecutedAsExpected(): void )->willReturn(0); $this->assetsHandler->expects($this->never())->method('resolvePreviousConfig'); $this->assetsHandler->expects($this->never())->method('importShlinkAssetsFromPath'); - $this->configWriter->expects($this->once())->method('toFile')->with( - $this->anything(), - $this->isType('array'), - false, - ); + $this->configWriter->expects($this->once())->method('toFile')->with($this->anything(), $this->isType('array')); $this->commandTester->setInputs(['no']); $this->commandTester->execute([]); diff --git a/test/Command/SetOptionCommandTest.php b/test/Command/SetOptionCommandTest.php index 71a72b8..0ef290c 100644 --- a/test/Command/SetOptionCommandTest.php +++ b/test/Command/SetOptionCommandTest.php @@ -4,7 +4,6 @@ namespace ShlinkioTest\Shlink\Installer\Command; -use Laminas\Config\Writer\WriterInterface; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -13,6 +12,7 @@ use Shlinkio\Shlink\Installer\Config\Option\ConfigOptionInterface; use Shlinkio\Shlink\Installer\Exception\InvalidShlinkPathException; use Shlinkio\Shlink\Installer\Service\ShlinkAssetsHandlerInterface; +use Shlinkio\Shlink\Installer\Util\ConfigWriterInterface; use Symfony\Component\Console\Application; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Tester\CommandTester; @@ -24,7 +24,7 @@ class SetOptionCommandTest extends TestCase { private CommandTester $commandTester; - private MockObject & WriterInterface $configWriter; + private MockObject & ConfigWriterInterface $configWriter; private MockObject & ShlinkAssetsHandlerInterface $assetsHandler; private MockObject & ConfigOptionsManagerInterface $optionsManager; private MockObject & Filesystem $filesystem; @@ -35,7 +35,7 @@ public function setUp(): void $this->initialCwd = getcwd() ?: ''; chdir(__DIR__ . '/../../test-resources'); - $this->configWriter = $this->createMock(WriterInterface::class); + $this->configWriter = $this->createMock(ConfigWriterInterface::class); $this->assetsHandler = $this->createMock(ShlinkAssetsHandlerInterface::class); $this->optionsManager = $this->createMock(ConfigOptionsManagerInterface::class); $this->filesystem = $this->createMock(Filesystem::class); diff --git a/test/Command/UpdateCommandTest.php b/test/Command/UpdateCommandTest.php index a9e36bd..e137486 100644 --- a/test/Command/UpdateCommandTest.php +++ b/test/Command/UpdateCommandTest.php @@ -4,7 +4,6 @@ namespace ShlinkioTest\Shlink\Installer\Command; -use Laminas\Config\Writer\WriterInterface; use PHPUnit\Framework\Assert; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; @@ -15,6 +14,7 @@ use Shlinkio\Shlink\Installer\Config\ConfigGeneratorInterface; use Shlinkio\Shlink\Installer\Model\ImportedConfig; use Shlinkio\Shlink\Installer\Service\ShlinkAssetsHandlerInterface; +use Shlinkio\Shlink\Installer\Util\ConfigWriterInterface; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\ArrayInput; @@ -23,7 +23,7 @@ class UpdateCommandTest extends TestCase { private CommandTester $commandTester; - private MockObject & WriterInterface $configWriter; + private MockObject & ConfigWriterInterface $configWriter; private MockObject & ShlinkAssetsHandlerInterface $assetsHandler; private MockObject & Command $initCommand; @@ -32,7 +32,7 @@ public function setUp(): void $this->assetsHandler = $this->createMock(ShlinkAssetsHandlerInterface::class); $this->assetsHandler->expects($this->once())->method('dropCachedConfigIfAny'); - $this->configWriter = $this->createMock(WriterInterface::class); + $this->configWriter = $this->createMock(ConfigWriterInterface::class); $generator = $this->createMock(ConfigGeneratorInterface::class); $generator->method('generateConfigInteractively')->willReturn([]); @@ -67,11 +67,7 @@ public function commandIsExecutedAsExpected(bool $rrBinExists, string $postUpdat ImportedConfig::notImported(), ); $this->assetsHandler->expects($this->once())->method('importShlinkAssetsFromPath'); - $this->configWriter->expects($this->once())->method('toFile')->with( - $this->anything(), - $this->isType('array'), - false, - ); + $this->configWriter->expects($this->once())->method('toFile')->with($this->anything(), $this->isType('array')); $this->commandTester->setInputs(['no']); $this->commandTester->execute([]); diff --git a/test/Config/ConfigGeneratorTest.php b/test/Config/ConfigGeneratorTest.php index d03dfc9..ee29289 100644 --- a/test/Config/ConfigGeneratorTest.php +++ b/test/Config/ConfigGeneratorTest.php @@ -35,7 +35,7 @@ public function setUp(): void #[Test, DataProvider('provideConfigOptions')] public function configuresExpectedPlugins( array $configOptionsGroups, - ?array $enabledOptions, + array|null $enabledOptions, int $expectedPrintTitleCalls, ): void { $totalPlugins = count(ArrayUtils::flatten($configOptionsGroups)); diff --git a/test/Config/Option/BasePathConfigOptionTest.php b/test/Config/Option/BasePathConfigOptionTest.php index 93c0b6d..820a594 100644 --- a/test/Config/Option/BasePathConfigOptionTest.php +++ b/test/Config/Option/BasePathConfigOptionTest.php @@ -26,7 +26,7 @@ public function returnsExpectedEnvVar(): void } #[Test, DataProvider('provideValidAnswers')] - public function expectedQuestionIsAsked(?string $answer, string $expectedAnswer): void + public function expectedQuestionIsAsked(string|null $answer, string $expectedAnswer): void { $io = $this->createMock(StyleInterface::class); $io->expects($this->once())->method('ask')->with( diff --git a/test/Config/Option/Redis/RedisSentinelServiceConfigOptionTest.php b/test/Config/Option/Redis/RedisSentinelServiceConfigOptionTest.php index 4773461..9465463 100644 --- a/test/Config/Option/Redis/RedisSentinelServiceConfigOptionTest.php +++ b/test/Config/Option/Redis/RedisSentinelServiceConfigOptionTest.php @@ -27,7 +27,7 @@ public function returnsExpectedEnvVar(): void } #[Test, DataProvider('provideValidAnswers')] - public function expectedQuestionIsAsked(?string $answer): void + public function expectedQuestionIsAsked(string|null $answer): void { $io = $this->createMock(StyleInterface::class); $io->expects($this->once())->method('ask')->with( diff --git a/test/Config/Option/Redis/RedisServersConfigOptionTest.php b/test/Config/Option/Redis/RedisServersConfigOptionTest.php index 4b5205a..319167e 100644 --- a/test/Config/Option/Redis/RedisServersConfigOptionTest.php +++ b/test/Config/Option/Redis/RedisServersConfigOptionTest.php @@ -44,7 +44,7 @@ public function serversAreNotRequestedWhenNoRedisConfigIsProvided(): void } #[Test, DataProvider('provideAnswers')] - public function serversAreRequestedWhenRedisConfigIsProvided(?string $serversAnswer): void + public function serversAreRequestedWhenRedisConfigIsProvided(string|null $serversAnswer): void { $this->io->expects($this->once())->method('confirm')->with( 'Do you want to use a redis instance, redis cluster or redis sentinels as a shared cache for Shlink? ' diff --git a/test/Config/Option/TimezoneConfigOptionTest.php b/test/Config/Option/TimezoneConfigOptionTest.php index 0c8d5dd..4c18a08 100644 --- a/test/Config/Option/TimezoneConfigOptionTest.php +++ b/test/Config/Option/TimezoneConfigOptionTest.php @@ -26,7 +26,7 @@ public function returnsExpectedEnvVar(): void } #[Test, DataProvider('provideValidAnswers')] - public function expectedQuestionIsAsked(?string $answer, string $expectedAnswer): void + public function expectedQuestionIsAsked(string|null $answer, string $expectedAnswer): void { $io = $this->createMock(StyleInterface::class); $io->expects($this->once())->method('ask')->with( diff --git a/test/Config/Option/Tracking/DisableTrackingFromConfigOptionTest.php b/test/Config/Option/Tracking/DisableTrackingFromConfigOptionTest.php index 52ecd5d..edab494 100644 --- a/test/Config/Option/Tracking/DisableTrackingFromConfigOptionTest.php +++ b/test/Config/Option/Tracking/DisableTrackingFromConfigOptionTest.php @@ -26,7 +26,7 @@ public function returnsExpectedEnvVar(): void } #[Test, DataProvider('provideAnswers')] - public function expectedQuestionIsAsked(?string $answer): void + public function expectedQuestionIsAsked(string|null $answer): void { $io = $this->createMock(StyleInterface::class); $io->expects($this->once())->method('ask')->with( diff --git a/test/Config/Option/UrlShortener/GeoLiteLicenseKeyConfigOptionTest.php b/test/Config/Option/UrlShortener/GeoLiteLicenseKeyConfigOptionTest.php index 87c9e9d..27cd293 100644 --- a/test/Config/Option/UrlShortener/GeoLiteLicenseKeyConfigOptionTest.php +++ b/test/Config/Option/UrlShortener/GeoLiteLicenseKeyConfigOptionTest.php @@ -26,7 +26,7 @@ public function returnsExpectedEnvVar(): void } #[Test, DataProvider('provideAnswers')] - public function expectedQuestionIsAsked(?string $answer, ?string $expectedResult): void + public function expectedQuestionIsAsked(string|null $answer, string|null $expectedResult): void { $io = $this->createMock(StyleInterface::class); $io->expects($this->once())->method('ask')->with( diff --git a/test/Config/Option/Visit/VisitsThresholdConfigOptionTest.php b/test/Config/Option/Visit/VisitsThresholdConfigOptionTest.php index e766987..89a7c0f 100644 --- a/test/Config/Option/Visit/VisitsThresholdConfigOptionTest.php +++ b/test/Config/Option/Visit/VisitsThresholdConfigOptionTest.php @@ -26,7 +26,7 @@ public function returnsExpectedEnvVar(): void } #[Test, DataProvider('provideValidAnswers')] - public function expectedQuestionIsAsked(string|int|null $answer, ?int $expectedAnswer): void + public function expectedQuestionIsAsked(string|int|null $answer, int|null $expectedAnswer): void { $io = $this->createMock(StyleInterface::class); $io->expects($this->once())->method('ask')->with( diff --git a/test/Config/Util/ConfigOptionsValidatorTest.php b/test/Config/Util/ConfigOptionsValidatorTest.php index b3d8dd5..a8b60be 100644 --- a/test/Config/Util/ConfigOptionsValidatorTest.php +++ b/test/Config/Util/ConfigOptionsValidatorTest.php @@ -56,8 +56,10 @@ public static function providePositiveNumbers(): iterable } #[Test, DataProvider('provideOptionalPositiveNumbers')] - public function validateOptionalPositiveNumberCastsToIntWhenProvidedValueIsValid(mixed $value, ?int $expected): void - { + public function validateOptionalPositiveNumberCastsToIntWhenProvidedValueIsValid( + mixed $value, + int|null $expected, + ): void { self::assertEquals($expected, ConfigOptionsValidator::validateOptionalPositiveNumber($value)); } diff --git a/test/Util/ConfigWriterTest.php b/test/Util/ConfigWriterTest.php new file mode 100644 index 0000000..043084a --- /dev/null +++ b/test/Util/ConfigWriterTest.php @@ -0,0 +1,48 @@ +configWriter = new ConfigWriter(); + } + + public static function tearDownAfterClass(): void + { + unlink(self::FILENAME); + } + + #[Test] + #[TestWith([[ + 'foo' => 'foo', + 'bar' => 'bar', + 'baz' => 'baz', + ]])] + #[TestWith([[ + 'foo' => null, + 'bar' => 123, + 'baz' => true, + ]])] + public function configIsExportedAndWrittenToFile(array $config): void + { + $this->configWriter->toFile(self::FILENAME, $config); + + self::assertFileExists(self::FILENAME); + self::assertEquals($config, require self::FILENAME); + } +}