diff --git a/.gitattributes b/.gitattributes index e4c272f..c481848 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,12 +5,11 @@ test-resources export-ignore .github export-ignore .gitattributes export-ignore .gitignore export-ignore -.phpstorm.meta.php export-ignore CHANGELOG.md export-ignore docker-compose.yml export-ignore Dockerfile export-ignore indocker export-ignore -phpcs.xml export-ignore +mago.toml export-ignore phpstan.neon export-ignore phpunit.xml.dist export-ignore shlink-installer.gif export-ignore diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php deleted file mode 100644 index 9e34cb7..0000000 --- a/.phpstorm.meta.php +++ /dev/null @@ -1,23 +0,0 @@ - [ - '' == '@', - ], - ServiceLocatorInterface::build('') => [ - '' == '@', - ], -]; diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d01417..14921bb 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). +## [10.1.0] - 2026-06-11 +### Added +* *Nothing* + +### Changed +* Migrate form PHP_CodeSniffer to Mago for code linting and formatting. + +### Deprecated +* *Nothing* + +### Removed +* *Nothing* + +### Fixed +* *Nothing* + + ## [10.0.0] - 2026-02-08 ### Added * Add support for Symfony 8.0 diff --git a/composer.json b/composer.json index b93d016..2997f0a 100644 --- a/composer.json +++ b/composer.json @@ -23,12 +23,12 @@ "webimpress/safe-writer": "^2.2" }, "require-dev": { + "carthage-software/mago": "^1.28", "devster/ubench": "^2.1", "phpstan/phpstan": "^2.1", "phpstan/phpstan-phpunit": "^2.0", "phpunit/phpunit": "^13.0", - "roave/security-advisories": "dev-master", - "shlinkio/php-coding-standard": "~2.5.0", + "shlinkio/shlink-mago-config": "^1.0", "symfony/var-dumper": "^8.0" }, "autoload": { @@ -47,8 +47,12 @@ "@stan", "@test:ci" ], - "cs": "phpcs", - "cs:fix": "phpcbf", + "lint": "mago lint", + "lint:fix": "mago lint --fix", + "format": "mago format", + "format:check": "mago format --check", + "cs": ["@lint", "@format:check"], + "cs:fix": ["@lint:fix", "@format"], "stan": "phpstan analyse", "test": "phpunit --order-by=random --testdox --testdox-summary", "test:ci": [ diff --git a/config/config.php b/config/config.php index 1a048c9..90eabee 100644 --- a/config/config.php +++ b/config/config.php @@ -13,7 +13,6 @@ use Symfony\Component\Process\PhpExecutableFinder; return [ - 'dependencies' => [ 'factories' => [ Console\Application::class => Factory\ApplicationFactory::class, @@ -47,32 +46,32 @@ 'DATABASE' => [ 'Database > Driver' => Config\Option\Database\DatabaseDriverConfigOption::class, 'Database > Name' => Config\Option\Database\DatabaseNameConfigOption::class, - 'Database > Host (or unix socket for PostgreSQL)' - => Config\Option\Database\DatabaseHostConfigOption::class, + 'Database > Host (or unix socket for PostgreSQL)' => + Config\Option\Database\DatabaseHostConfigOption::class, 'Database > Port' => Config\Option\Database\DatabasePortConfigOption::class, 'Database > User' => Config\Option\Database\DatabaseUserConfigOption::class, 'Database > Password' => Config\Option\Database\DatabasePasswordConfigOption::class, - 'Database > Unix socket (Mysql/MariaDB)' - => Config\Option\Database\DatabaseUnixSocketConfigOption::class, + 'Database > Unix socket (Mysql/MariaDB)' => + Config\Option\Database\DatabaseUnixSocketConfigOption::class, 'Database > Use encryption' => Config\Option\Database\DatabaseUseEncryptionConfigOption::class, ], 'URL SHORTENER' => [ 'URL shortener > Short domain' => Config\Option\UrlShortener\ShortDomainHostConfigOption::class, 'URL shortener > Schema' => Config\Option\UrlShortener\ShortDomainSchemaConfigOption::class, 'URL shortener > Short codes length' => Config\Option\UrlShortener\ShortCodeLengthOption::class, - 'URL shortener > Auto resolve titles' - => Config\Option\UrlShortener\AutoResolveTitlesConfigOption::class, + 'URL shortener > Auto resolve titles' => + Config\Option\UrlShortener\AutoResolveTitlesConfigOption::class, 'URL shortener > Extra path mode' => Config\Option\UrlShortener\ExtraPathModeConfigOption::class, - 'URL shortener > Multi-segment slugs' - => Config\Option\UrlShortener\EnableMultiSegmentSlugsConfigOption::class, + 'URL shortener > Multi-segment slugs' => + Config\Option\UrlShortener\EnableMultiSegmentSlugsConfigOption::class, 'URL shortener > Trailing slashes' => Config\Option\UrlShortener\EnableTrailingSlashConfigOption::class, 'URL shortener > Mode' => Config\Option\UrlShortener\ShortUrlModeConfigOption::class, 'GeoLite2 license key' => Config\Option\UrlShortener\GeoLiteLicenseKeyConfigOption::class, - 'Redirects > Status code (301/302/307/308)' - => Config\Option\UrlShortener\RedirectStatusCodeConfigOption::class, + 'Redirects > Status code (301/302/307/308)' => + Config\Option\UrlShortener\RedirectStatusCodeConfigOption::class, 'Redirects > Caching life time' => Config\Option\UrlShortener\RedirectCacheLifeTimeConfigOption::class, - 'Redirects > Caching visibility' - => Config\Option\UrlShortener\RedirectCacheVisibilityConfigOption::class, + 'Redirects > Caching visibility' => + Config\Option\UrlShortener\RedirectCacheVisibilityConfigOption::class, ], 'TRACKING' => [ 'Tracking > Orphan visits tracking' => Config\Option\Tracking\OrphanVisitsTrackingConfigOption::class, @@ -82,8 +81,8 @@ 'Tracking > Disable IP address tracking' => Config\Option\Tracking\DisableIpTrackingConfigOption::class, 'Tracking > IP Anonymization' => Config\Option\Tracking\IpAnonymizationConfigOption::class, 'Tracking > Disable user agent tracking' => Config\Option\Tracking\DisableUaTrackingConfigOption::class, - 'Tracking > Disable referrer tracking' - => Config\Option\Tracking\DisableReferrerTrackingConfigOption::class, + 'Tracking > Disable referrer tracking' => + Config\Option\Tracking\DisableReferrerTrackingConfigOption::class, ], 'REDIRECTS' => [ 'Redirects > Base URL' => Config\Option\Redirect\BaseUrlRedirectConfigOption::class, @@ -300,5 +299,4 @@ ], ], ], - ]; diff --git a/config/container.php b/config/container.php index 79ecbce..b278802 100644 --- a/config/container.php +++ b/config/container.php @@ -24,7 +24,7 @@ $shlinkConfigLoader = static function () { $appConfigPath = __DIR__ . '/../../../../config/config.php'; - if (! file_exists($appConfigPath)) { + if (!file_exists($appConfigPath)) { return []; } @@ -35,12 +35,12 @@ return $appConfig; }; -$config = (new ConfigAggregator([ +$config = new ConfigAggregator([ Config\ConfigProvider::class, - new PhpFileProvider(__DIR__ . '/config.php'), // Installer config - $shlinkConfigLoader, // Overwritten config coming from Shlink + new PhpFileProvider(__DIR__ . '/config.php'), // Installer config + $shlinkConfigLoader, // Overwritten config coming from Shlink new PhpFileProvider(__DIR__ . '/config.local.php'), // Local config -]))->getMergedConfig(); +])->getMergedConfig(); $container = new ServiceManager($config['dependencies']); $container->setService('config', $config); diff --git a/mago.toml b/mago.toml new file mode 100644 index 0000000..c55cb50 --- /dev/null +++ b/mago.toml @@ -0,0 +1,19 @@ +#:schema https://mago.carthage.software/1.27.1/schema.json +# Welcome to Mago! +# For full documentation, see https://mago.carthage.software/tools/overview +version = "1" +php-version = "8.4.0" +extends = "vendor/shlinkio/shlink-mago-config/mago.toml" + +[source] +workspace = "." +paths = ["config", "src", "test"] +excludes = ["vendor"] + +[source.glob] +literal-separator = true + +[linter.rules] +no-literal-password = { exclude = ["test"] } +no-empty-catch-clause = { exclude = ["test"] } +no-empty-loop = { exclude = ["test"] } diff --git a/phpcs.xml b/phpcs.xml deleted file mode 100644 index d08b686..0000000 --- a/phpcs.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - Coding standard - - - - - - - - - - bin - config - src - test - - - config/params/* - diff --git a/src/Command/InitCommand.php b/src/Command/InitCommand.php index 9cabc75..a0fc35f 100644 --- a/src/Command/InitCommand.php +++ b/src/Command/InitCommand.php @@ -18,7 +18,7 @@ #[AsCommand( name: InitCommand::NAME, description: 'Initializes external dependencies required for Shlink to properly work, like DB, cache warmup, ' - . 'initial GeoLite DB download, etc', + . 'initial GeoLite DB download, etc', )] class InitCommand extends Command { @@ -33,16 +33,25 @@ public function __invoke(SymfonyStyle $io, InputInterface $input, #[MapInput] In { $commands = [...$inputData->resolveCommands()]; - return array_reduce($commands, function (bool $carry, array $commandInfo) use ($input, $io): bool { - /** @var array{InstallationCommand, string|null} $commandInfo */ - [$command, $arg] = $commandInfo; - - return $this->commandsRunner->execPhpCommand( - name: $command->value, - io: $io, - interactive: $input->isInteractive(), - args: $arg !== null ? [$arg] : [], - ) && $carry; - }, initial: true) ? self::SUCCESS : self::FAILURE; + return array_reduce( + $commands, + function (bool $carry, array $commandInfo) use ($input, $io): bool { + /** @var array{InstallationCommand, string|null} $commandInfo */ + [$command, $arg] = $commandInfo; + + return ( + $this->commandsRunner->execPhpCommand( + name: $command->value, + io: $io, + interactive: $input->isInteractive(), + args: $arg !== null ? [$arg] : [], + ) + && $carry + ); + }, + initial: true, + ) + ? self::SUCCESS + : self::FAILURE; } } diff --git a/src/Command/Model/InitCommandInput.php b/src/Command/Model/InitCommandInput.php index 1da335f..4d40f1b 100644 --- a/src/Command/Model/InitCommandInput.php +++ b/src/Command/Model/InitCommandInput.php @@ -42,7 +42,7 @@ final class InitCommandInput */ public function resolveCommands(): iterable { - if (! $this->skipInitializeDb) { + if (!$this->skipInitializeDb) { yield [InstallationCommand::DB_CREATE_SCHEMA, null]; } @@ -53,7 +53,7 @@ public function resolveCommands(): iterable yield [InstallationCommand::ORM_CLEAR_CACHE, null]; } - if (! $this->skipDownloadGeolite) { + if (!$this->skipDownloadGeolite) { yield [InstallationCommand::GEOLITE_DOWNLOAD_DB, null]; } diff --git a/src/Command/Model/InitOption.php b/src/Command/Model/InitOption.php index 1e51c14..b59b0ea 100644 --- a/src/Command/Model/InitOption.php +++ b/src/Command/Model/InitOption.php @@ -1,5 +1,7 @@ groups = array_filter( iterator_to_array($this->flattenGroupsWithTitle($groups)), - static fn (string $configOption) => $enabledOptions === null || ArrayUtils::contains( + static fn (string $configOption) => $enabledOptions === null + || ArrayUtils::contains( $configOption, $enabledOptions, ), @@ -56,7 +57,7 @@ private function flattenGroupsWithTitle(iterable $groups): Generator foreach ($groups as $key => $value) { if (is_iterable($value)) { yield from $this->flattenGroupsWithTitle($value); - } elseif (! is_numeric($key)) { + } elseif (!is_numeric($key)) { yield $key => $value; } } @@ -64,7 +65,7 @@ private function flattenGroupsWithTitle(iterable $groups): Generator public function __invoke(SymfonyStyle $io): int { - if (! $this->filesystem->exists($this->generatedConfigPath)) { + if (!$this->filesystem->exists($this->generatedConfigPath)) { throw InvalidShlinkPathException::forCurrentPath(); } diff --git a/src/Config/ConfigGenerator.php b/src/Config/ConfigGenerator.php index d18a966..8669e69 100644 --- a/src/Config/ConfigGenerator.php +++ b/src/Config/ConfigGenerator.php @@ -21,8 +21,7 @@ public function __construct( private readonly ConfigOptionsManagerInterface $configOptionsManager, private readonly array $configOptionsGroups, private readonly array|null $enabledOptions, - ) { - } + ) {} public function generateConfigInteractively(StyleInterface $io, array $previousConfig): array { @@ -33,12 +32,13 @@ 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 || ArrayUtils::contains( + $optionIsEnabled = $this->enabledOptions === null + || ArrayUtils::contains( $configOption, $this->enabledOptions, ); $shouldAsk = $optionIsEnabled && $plugin->shouldBeAsked($answers); - if (! $shouldAsk) { + if (!$shouldAsk) { if ($plugin instanceof ConfigOptionMigratorInterface && isset($answers[$plugin->getEnvVar()])) { $answers[$plugin->getEnvVar()] = $plugin->tryToMigrateValue($answers[$plugin->getEnvVar()]); } @@ -47,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 (! ArrayUtils::contains($title, $alreadyRenderedTitles)) { + if (!ArrayUtils::contains($title, $alreadyRenderedTitles)) { $alreadyRenderedTitles[] = $title; $io->title($title); } @@ -73,17 +73,23 @@ private function resolveAndSortOptions(): array // 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, + 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, - fn (string $option) => $this->enabledOptions === null || ArrayUtils::contains( - $option, - $this->enabledOptions, + fn (string $option) => ( + $this->enabledOptions === null + || ArrayUtils::contains( + $option, + $this->enabledOptions, + ) ), ); diff --git a/src/Config/ConfigOptionsManager.php b/src/Config/ConfigOptionsManager.php index 91082fd..6b0ab4f 100644 --- a/src/Config/ConfigOptionsManager.php +++ b/src/Config/ConfigOptionsManager.php @@ -4,20 +4,19 @@ namespace Shlinkio\Shlink\Installer\Config; -use Laminas\ServiceManager\AbstractPluginManager; +use Laminas\ServiceManager\AbstractSingleInstancePluginManager; use Laminas\ServiceManager\Exception\InvalidServiceException; use function get_debug_type; use function sprintf; /** - * @extends AbstractPluginManager - * @todo Extend from AbstractSingleInstancePluginManager once servicemanager 3 is no longer supported + * @extends AbstractSingleInstancePluginManager */ -class ConfigOptionsManager extends AbstractPluginManager implements ConfigOptionsManagerInterface +class ConfigOptionsManager extends AbstractSingleInstancePluginManager implements ConfigOptionsManagerInterface { /** @var class-string */ - protected $instanceOf = Option\ConfigOptionInterface::class; // phpcs:ignore + protected string $instanceOf = Option\ConfigOptionInterface::class; // phpcs:ignore public function validate(mixed $instance): void { diff --git a/src/Config/ConfigOptionsManagerInterface.php b/src/Config/ConfigOptionsManagerInterface.php index ff48201..58b9c59 100644 --- a/src/Config/ConfigOptionsManagerInterface.php +++ b/src/Config/ConfigOptionsManagerInterface.php @@ -6,6 +6,4 @@ use Psr\Container\ContainerInterface; -interface ConfigOptionsManagerInterface extends ContainerInterface -{ -} +interface ConfigOptionsManagerInterface extends ContainerInterface {} diff --git a/src/Config/Option/BaseConfigOption.php b/src/Config/Option/BaseConfigOption.php index 4e91f92..4ca0af0 100644 --- a/src/Config/Option/BaseConfigOption.php +++ b/src/Config/Option/BaseConfigOption.php @@ -10,6 +10,6 @@ abstract class BaseConfigOption implements ConfigOptionInterface { public function shouldBeAsked(array $currentOptions): bool { - return ! array_key_exists($this->getEnvVar(), $currentOptions); + return !array_key_exists($this->getEnvVar(), $currentOptions); } } diff --git a/src/Config/Option/BasePathConfigOption.php b/src/Config/Option/BasePathConfigOption.php index 0efe9e2..76e9836 100644 --- a/src/Config/Option/BasePathConfigOption.php +++ b/src/Config/Option/BasePathConfigOption.php @@ -15,9 +15,11 @@ public function getEnvVar(): string public function ask(StyleInterface $io, array $currentOptions): string { - return $io->ask( - 'What is the path from which shlink is going to be served? (It must include a leading bar, like "/shlink". ' - . 'Leave empty if you plan to serve shlink from the root of the domain)', - ) ?? ''; + return ( + $io->ask( + 'What is the path from which shlink is going to be served? (It must include a leading bar, like "/shlink". ' + . 'Leave empty if you plan to serve shlink from the root of the domain)', + ) ?? '' + ); } } diff --git a/src/Config/Option/Cors/CorsAllowOriginConfigOption.php b/src/Config/Option/Cors/CorsAllowOriginConfigOption.php index f3dc5cc..2d352d8 100644 --- a/src/Config/Option/Cors/CorsAllowOriginConfigOption.php +++ b/src/Config/Option/Cors/CorsAllowOriginConfigOption.php @@ -22,14 +22,16 @@ public function ask(StyleInterface $io, array $currentOptions): string '*' => 'All hosts are implicitly allowed (Access-Control-Allow-Origin is set to "*")', '' => 'All hosts are explicitly allowed (Access-Control-Allow-Origin is set to the value in request\'s ' - . 'Origin header)', + . 'Origin header)', 'allowlist' => 'Provide a list of hosts that are allowed', ], '*', ); - return $answer !== 'allowlist' ? $answer : $io->ask( - 'Provide a comma-separated list of origins that should be allowed to perform CORS requests to Shlink', - ); + return $answer !== 'allowlist' + ? $answer + : $io->ask( + 'Provide a comma-separated list of origins that should be allowed to perform CORS requests to Shlink', + ); } } diff --git a/src/Config/Option/Database/AbstractDriverDependentConfigOption.php b/src/Config/Option/Database/AbstractDriverDependentConfigOption.php index 1b80b56..2877453 100644 --- a/src/Config/Option/Database/AbstractDriverDependentConfigOption.php +++ b/src/Config/Option/Database/AbstractDriverDependentConfigOption.php @@ -7,8 +7,7 @@ use Shlinkio\Shlink\Installer\Config\Option\BaseConfigOption; use Shlinkio\Shlink\Installer\Config\Option\DependentConfigOptionInterface; -abstract class AbstractDriverDependentConfigOption extends BaseConfigOption implements - DependentConfigOptionInterface +abstract class AbstractDriverDependentConfigOption extends BaseConfigOption implements DependentConfigOptionInterface { public function getDependentOption(): string { diff --git a/src/Config/Option/Matomo/AbstractMatomoEnabledConfigOption.php b/src/Config/Option/Matomo/AbstractMatomoEnabledConfigOption.php index 3ada3b0..d55d13e 100644 --- a/src/Config/Option/Matomo/AbstractMatomoEnabledConfigOption.php +++ b/src/Config/Option/Matomo/AbstractMatomoEnabledConfigOption.php @@ -1,5 +1,7 @@ ask( 'RabbitMQ port', $useSsl ? '5671' : '5672', - fn (mixed $value) => ConfigOptionsValidator::validateNumberBetween($value, 1, 65535), + static fn (mixed $value) => ConfigOptionsValidator::validateNumberBetween($value, 1, 65_535), ); } diff --git a/src/Config/Option/RealTimeUpdates/RealTimeUpdatesTopicsConfigOption.php b/src/Config/Option/RealTimeUpdates/RealTimeUpdatesTopicsConfigOption.php index d9f1c4f..5263c6b 100644 --- a/src/Config/Option/RealTimeUpdates/RealTimeUpdatesTopicsConfigOption.php +++ b/src/Config/Option/RealTimeUpdates/RealTimeUpdatesTopicsConfigOption.php @@ -1,5 +1,7 @@ confirm('Do you still want to disable anonymization?', false); + return !$io->confirm('Do you still want to disable anonymization?', false); } public function getDependentOption(): string diff --git a/src/Config/Option/TrustedProxiesConfigOption.php b/src/Config/Option/TrustedProxiesConfigOption.php index 26bd138..f90c457 100644 --- a/src/Config/Option/TrustedProxiesConfigOption.php +++ b/src/Config/Option/TrustedProxiesConfigOption.php @@ -16,7 +16,7 @@ public function getEnvVar(): string public function ask(StyleInterface $io, array $currentOptions): string|int|null { - if (! $io->confirm('Do you have more than one proxy in front of this Shlink instance?', default: false)) { + if (!$io->confirm('Do you have more than one proxy in front of this Shlink instance?', default: false)) { return null; } diff --git a/src/Config/Option/UrlShortener/AutoResolveTitlesConfigOption.php b/src/Config/Option/UrlShortener/AutoResolveTitlesConfigOption.php index c8c9553..0e99fb3 100644 --- a/src/Config/Option/UrlShortener/AutoResolveTitlesConfigOption.php +++ b/src/Config/Option/UrlShortener/AutoResolveTitlesConfigOption.php @@ -18,7 +18,7 @@ public function ask(StyleInterface $io, array $currentOptions): bool { return $io->confirm( 'Do you want Shlink to resolve the short URL title based on the long URL\'s title tag (if any)? ' - . 'Otherwise, it will be kept empty unless explicitly provided.', + . 'Otherwise, it will be kept empty unless explicitly provided.', ); } } diff --git a/src/Config/Option/UrlShortener/EnableMultiSegmentSlugsConfigOption.php b/src/Config/Option/UrlShortener/EnableMultiSegmentSlugsConfigOption.php index 55501ec..a8e06da 100644 --- a/src/Config/Option/UrlShortener/EnableMultiSegmentSlugsConfigOption.php +++ b/src/Config/Option/UrlShortener/EnableMultiSegmentSlugsConfigOption.php @@ -25,9 +25,10 @@ public function ask(StyleInterface $io, array $currentOptions): bool [ self::YES => 'Custom slugs will support multiple segments (https://example.com/foo/bar/baz). Orphan visits will ' - . 'only have either "base_url" or "invalid_short_url" type.', - self::NO => 'Slugs and short codes will support only one segment (https://example.com/foo). Orphan ' - . 'visits will have one of "base_url", "invalid_short_url" or "regular_404" type.', + . 'only have either "base_url" or "invalid_short_url" type.', + self::NO => + 'Slugs and short codes will support only one segment (https://example.com/foo). Orphan ' + . 'visits will have one of "base_url", "invalid_short_url" or "regular_404" type.', ], self::NO, ) === self::YES; diff --git a/src/Config/Option/UrlShortener/ExtraPathModeConfigOption.php b/src/Config/Option/UrlShortener/ExtraPathModeConfigOption.php index 6037c8d..85f7e89 100644 --- a/src/Config/Option/UrlShortener/ExtraPathModeConfigOption.php +++ b/src/Config/Option/UrlShortener/ExtraPathModeConfigOption.php @@ -24,20 +24,20 @@ public function ask(StyleInterface $io, array $currentOptions): string { return $io->choice( question: << {longUrl}/[...extraPath] - * https://s.test/abc123 -> https://www.example.com - * https://s.test/abc123/shlinkio -> https://www.example.com/shlinkio + append: + * {shortDomain}/{shortCode}/[...extraPath] -> {longUrl}/[...extraPath] + * https://s.test/abc123 -> https://www.example.com + * https://s.test/abc123/shlinkio -> https://www.example.com/shlinkio - ignore: - * {shortDomain}/{shortCode}/[...extraPath] -> {longUrl} - * https://s.test/abc123 -> https://www.example.com - * https://s.test/abc123/shlinkio -> https://www.example.com + ignore: + * {shortDomain}/{shortCode}/[...extraPath] -> {longUrl} + * https://s.test/abc123 -> https://www.example.com + * https://s.test/abc123/shlinkio -> https://www.example.com - QUESTION, + QUESTION, choices: self::MODES, default: 'default', ); diff --git a/src/Config/Option/UrlShortener/RedirectStatusCodeConfigOption.php b/src/Config/Option/UrlShortener/RedirectStatusCodeConfigOption.php index 3657ee8..bec499e 100644 --- a/src/Config/Option/UrlShortener/RedirectStatusCodeConfigOption.php +++ b/src/Config/Option/UrlShortener/RedirectStatusCodeConfigOption.php @@ -14,8 +14,9 @@ class RedirectStatusCodeConfigOption extends BaseConfigOption public const string ENV_VAR = 'REDIRECT_STATUS_CODE'; private const array REDIRECT_STATUSES = [ 302 => 'All visits will always be tracked. Not that good for SEO. Only GET requests will be redirected.', - 301 => 'Best option for SEO. Redirect will be cached for a short period of time, making some visits not to be ' - . 'tracked. Only GET requests will be redirected.', + 301 => + 'Best option for SEO. Redirect will be cached for a short period of time, making some visits not to be ' + . 'tracked. Only GET requests will be redirected.', 307 => 'Same as 302, but Shlink will also redirect on non-GET requests.', 308 => 'Same as 301, but Shlink will also redirect on non-GET requests.', ]; diff --git a/src/Config/Option/UrlShortener/ShortCodeLengthOption.php b/src/Config/Option/UrlShortener/ShortCodeLengthOption.php index 8d44f0c..78ca770 100644 --- a/src/Config/Option/UrlShortener/ShortCodeLengthOption.php +++ b/src/Config/Option/UrlShortener/ShortCodeLengthOption.php @@ -21,7 +21,7 @@ public function ask(StyleInterface $io, array $currentOptions): int 'What is the default length you want generated short codes to have? (You will still be able to override ' . 'this on every created short URL)', '5', - fn ($value) => ConfigOptionsValidator::validateNumberGreaterThan($value, 4), + static fn ($value) => ConfigOptionsValidator::validateNumberGreaterThan($value, 4), ); } } diff --git a/src/Config/Option/UrlShortener/ShortUrlModeConfigOption.php b/src/Config/Option/UrlShortener/ShortUrlModeConfigOption.php index f606f75..3b42899 100644 --- a/src/Config/Option/UrlShortener/ShortUrlModeConfigOption.php +++ b/src/Config/Option/UrlShortener/ShortUrlModeConfigOption.php @@ -12,10 +12,12 @@ class ShortUrlModeConfigOption extends BaseConfigOption { private const MODES = [ - 'strict' => 'Short codes and custom slugs will be matched in a case-sensitive way ("foo" !== "FOO"). ' - . 'Generated short codes will include lowercase letters, uppercase letters and numbers.', - 'loose' => 'Short codes and custom slugs will be matched in a case-insensitive way ("foo" === "FOO"). ' - . 'Generated short codes will include only lowercase letters and numbers.', + 'strict' => + 'Short codes and custom slugs will be matched in a case-sensitive way ("foo" !== "FOO"). ' + . 'Generated short codes will include lowercase letters, uppercase letters and numbers.', + 'loose' => + 'Short codes and custom slugs will be matched in a case-insensitive way ("foo" === "FOO"). ' + . 'Generated short codes will include only lowercase letters and numbers.', ]; public function getEnvVar(): string diff --git a/src/Config/Util/ConfigOptionsValidator.php b/src/Config/Util/ConfigOptionsValidator.php index 260dbc3..8ceb743 100644 --- a/src/Config/Util/ConfigOptionsValidator.php +++ b/src/Config/Util/ConfigOptionsValidator.php @@ -23,7 +23,7 @@ public static function validateUrl(string|null $value): string|null . '\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*)/i'; $valueIsValid = $value === null || (bool) preg_match($httpUrlRegexp, $value); - if (! $valueIsValid) { + if (!$valueIsValid) { throw new InvalidConfigOptionException( sprintf('Provided value "%s" is not a valid URL', $value), ); @@ -45,7 +45,7 @@ public static function validatePositiveNumber(mixed $value): int public static function validateNumberGreaterThan(mixed $value, int $min): int { $intValue = (int) $value; - if (! is_numeric($value) || $intValue < $min) { + if (!is_numeric($value) || $intValue < $min) { throw new InvalidConfigOptionException( sprintf('Provided value "%s" is invalid. Expected a number greater or equal than %s', $value, $min), ); @@ -57,7 +57,7 @@ public static function validateNumberGreaterThan(mixed $value, int $min): int public static function validateNumberBetween(mixed $value, int $min, int $max): int { $intValue = (int) $value; - if (! is_numeric($value) || $intValue < $min || $intValue > $max) { + if (!is_numeric($value) || $intValue < $min || $intValue > $max) { throw new InvalidConfigOptionException( sprintf('Provided value "%s" is invalid. Expected a number between %s and %s', $value, $min, $max), ); @@ -77,7 +77,7 @@ public static function validateHexColor(string $color): string ); } - if (! ctype_xdigit($onlyDigitsColor)) { + if (!ctype_xdigit($onlyDigitsColor)) { throw new InvalidConfigOptionException( 'Provided value must be the hexadecimal number representation of a color', ); diff --git a/src/Config/Util/RuntimeType.php b/src/Config/Util/RuntimeType.php index 3802ba1..f4824b5 100644 --- a/src/Config/Util/RuntimeType.php +++ b/src/Config/Util/RuntimeType.php @@ -1,5 +1,7 @@ processHelper->run($io, new Process($command, timeout: $commandConfig['timeout'] ?? 60)); $isSuccess = $process->isSuccessful(); - $isWarning = ! $isSuccess && ! $failOnError; + $isWarning = !$isSuccess && !$failOnError; $isVerbose = $io->isVerbose(); $verbosityIndicator = $interactive ? 'Run with -vvv' : 'Set SHELL_VERBOSITY=3'; @@ -68,7 +68,7 @@ public function execPhpCommand(string $name, SymfonyStyle $io, bool $interactive } elseif ($isWarning) { $io->write(' Warning!'); $io->writeln($isVerbose ? '' : sprintf(' %s to see error.', $verbosityIndicator)); - } elseif (! $isVerbose) { + } elseif (!$isVerbose) { $io->error( sprintf('%s. %s to see specific error info.', $errorMessage, $verbosityIndicator), ); diff --git a/src/Service/InstallationRunner.php b/src/Service/InstallationRunner.php index db28b92..883f222 100644 --- a/src/Service/InstallationRunner.php +++ b/src/Service/InstallationRunner.php @@ -19,8 +19,7 @@ public function __construct( private ConfigWriterInterface $configWriter, private ShlinkAssetsHandlerInterface $assetsHandler, private ConfigGeneratorInterface $configGenerator, - ) { - } + ) {} /** @inheritDoc */ public function runInstallation(SymfonyStyle $io, Command|null $initCommand): int diff --git a/src/Service/ShlinkAssetsHandler.php b/src/Service/ShlinkAssetsHandler.php index 5162563..7c7499f 100644 --- a/src/Service/ShlinkAssetsHandler.php +++ b/src/Service/ShlinkAssetsHandler.php @@ -20,9 +20,7 @@ class ShlinkAssetsHandler implements ShlinkAssetsHandlerInterface private const string GEO_LITE_DB_PATH = 'data/GeoLite2-City.mmdb'; private const string ROAD_RUNNER_BINARY_PATH = 'bin/rr'; - public function __construct(private Filesystem $filesystem) - { - } + public function __construct(private Filesystem $filesystem) {} /** * @throws IOException @@ -36,7 +34,7 @@ public function dropCachedConfigIfAny(StyleInterface $io): void private function dropCachedConfigFile(string $file, StyleInterface $io): void { - if (! $this->filesystem->exists($file)) { + if (!$this->filesystem->exists($file)) { return; } @@ -56,7 +54,7 @@ public function resolvePreviousConfig(StyleInterface $io): ImportedConfig 'Do you want to import configuration from previous installation? (You will still be asked for any new ' . 'config option that did not exist in previous shlink versions)', ); - if (! $importConfig) { + if (!$importConfig) { return ImportedConfig::notImported(); } @@ -71,12 +69,14 @@ public function resolvePreviousConfig(StyleInterface $io): ImportedConfig $configFile = sprintf('%s/%s', $installationPath, self::GENERATED_CONFIG_PATH); $configExists = $this->filesystem->exists($configFile); } while ( - ! $configExists && - $io->confirm('Provided path does not seem to be a valid shlink root path. Do you want to try another path?') + !$configExists + && $io->confirm( + 'Provided path does not seem to be a valid shlink root path. Do you want to try another path?', + ) ); // If after some retries the user has chosen not to test another path, return - if (! $configExists) { + if (!$configExists) { return ImportedConfig::notImported(); } @@ -91,7 +91,7 @@ public function importShlinkAssetsFromPath(StyleInterface $io, string $path): vo private function importSqliteIfNeeded(StyleInterface $io, string $fileToImport): void { - if (! $this->filesystem->exists($fileToImport)) { + if (!$this->filesystem->exists($fileToImport)) { return; } @@ -105,7 +105,7 @@ private function importSqliteIfNeeded(StyleInterface $io, string $fileToImport): private function importGeoLiteDbIfNeeded(StyleInterface $io, string $fileToImport): void { - if (! $this->filesystem->exists($fileToImport)) { + if (!$this->filesystem->exists($fileToImport)) { return; } diff --git a/src/Util/ConfigWriter.php b/src/Util/ConfigWriter.php index 25ba8a1..f674c7e 100644 --- a/src/Util/ConfigWriter.php +++ b/src/Util/ConfigWriter.php @@ -12,14 +12,13 @@ class ConfigWriter implements ConfigWriterInterface { private const CONFIG_TEMPLATE = <<