From a9094dc0f61efa3c909735cd57cac20fc7ff2626 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 27 Dec 2017 15:25:59 +0100 Subject: [PATCH 01/56] Updated dependency constraints --- composer.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index 8cbe3267..d91330f9 100644 --- a/composer.json +++ b/composer.json @@ -15,13 +15,13 @@ "php": "^7.0", "acelaya/ze-content-based-error-handler": "^2.0", "cocur/slugify": "^3.0", - "doctrine/annotations": "^1.4 <1.5", - "doctrine/cache": "^1.6 <1.7", - "doctrine/collections": "^1.4 <1.5", - "doctrine/common": "^2.7 <2.8", - "doctrine/dbal": "^2.5 <2.6", + "doctrine/annotations": "^1.4", + "doctrine/cache": "^1.6", + "doctrine/collections": "^1.4", + "doctrine/common": "^2.7", + "doctrine/dbal": "^2.5", "doctrine/migrations": "^1.4", - "doctrine/orm": "^2.5 <2.6", + "doctrine/orm": "^2.5", "endroid/qrcode": "^1.7", "firebase/php-jwt": "^4.0", "guzzlehttp/guzzle": "^6.2", @@ -29,7 +29,7 @@ "mikehaertl/phpwkhtmltopdf": "^2.2", "monolog/monolog": "^1.21", "roave/security-advisories": "dev-master", - "symfony/console": "^3.0", + "symfony/console": "^3.4", "symfony/filesystem": "^3.0", "symfony/process": "^3.0", "theorchard/monolog-cascade": "^0.4", @@ -41,7 +41,7 @@ "zendframework/zend-expressive-platesrenderer": "^1.3", "zendframework/zend-i18n": "^2.7", "zendframework/zend-paginator": "^2.6", - "zendframework/zend-servicemanager": "^3.0", + "zendframework/zend-servicemanager": "^3.2", "zendframework/zend-stdlib": "^3.0" }, "require-dev": { @@ -50,7 +50,7 @@ "phpunit/phpcov": "^4.0", "phpunit/phpunit": "^6.0", "slevomat/coding-standard": "^4.0", - "squizlabs/php_codesniffer": "^3.1", + "squizlabs/php_codesniffer": "^3.1 <3.2", "symfony/var-dumper": "^3.0", "vlucas/phpdotenv": "^2.2", "zendframework/zend-expressive-tooling": "^0.4" @@ -102,7 +102,7 @@ "process-timeout": 0, "sort-packages": true, "platform": { - "php": "7.0" + "php": "7.0.8" } } } From af0ff0f65bf0e7dc2680c297f35bf6119169fcc3 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 27 Dec 2017 15:37:26 +0100 Subject: [PATCH 02/56] Console commands are now lazy loaded --- bin/cli | 2 ++ config/autoload/app_options.global.php | 6 ++-- module/CLI/config/cli.config.php | 34 +++++++++++-------- .../CLI/src/Command/Api/DisableKeyCommand.php | 4 ++- .../src/Command/Api/GenerateKeyCommand.php | 4 ++- .../CLI/src/Command/Api/ListKeysCommand.php | 4 ++- .../Command/Config/GenerateCharsetCommand.php | 4 ++- .../Command/Config/GenerateSecretCommand.php | 4 ++- .../Shortcode/GeneratePreviewCommand.php | 4 ++- .../Shortcode/GenerateShortcodeCommand.php | 4 ++- .../Command/Shortcode/GetVisitsCommand.php | 4 ++- .../Shortcode/ListShortcodesCommand.php | 4 ++- .../Command/Shortcode/ResolveUrlCommand.php | 4 ++- .../CLI/src/Command/Tag/CreateTagCommand.php | 4 ++- .../CLI/src/Command/Tag/DeleteTagsCommand.php | 4 ++- .../CLI/src/Command/Tag/ListTagsCommand.php | 4 ++- .../CLI/src/Command/Tag/RenameTagCommand.php | 4 ++- .../Command/Visit/ProcessVisitsCommand.php | 3 +- module/CLI/src/Factory/ApplicationFactory.php | 22 ++++++------ 19 files changed, 78 insertions(+), 45 deletions(-) diff --git a/bin/cli b/bin/cli index 263df59e..ea8cb5c3 100755 --- a/bin/cli +++ b/bin/cli @@ -1,5 +1,7 @@ #!/usr/bin/env php [ 'name' => 'Shlink', - 'version' => '1.2.0', - 'secret_key' => Common\env('SECRET_KEY'), + 'version' => '1.7.0', + 'secret_key' => env('SECRET_KEY'), ], ]; diff --git a/module/CLI/config/cli.config.php b/module/CLI/config/cli.config.php index be1bda6b..c1f9043b 100644 --- a/module/CLI/config/cli.config.php +++ b/module/CLI/config/cli.config.php @@ -9,21 +9,25 @@ return [ 'cli' => [ 'locale' => Common\env('CLI_LOCALE', 'en'), 'commands' => [ - Command\Shortcode\GenerateShortcodeCommand::class, - Command\Shortcode\ResolveUrlCommand::class, - Command\Shortcode\ListShortcodesCommand::class, - Command\Shortcode\GetVisitsCommand::class, - Command\Shortcode\GeneratePreviewCommand::class, - Command\Visit\ProcessVisitsCommand::class, - Command\Config\GenerateCharsetCommand::class, - Command\Config\GenerateSecretCommand::class, - Command\Api\GenerateKeyCommand::class, - Command\Api\DisableKeyCommand::class, - Command\Api\ListKeysCommand::class, - Command\Tag\ListTagsCommand::class, - Command\Tag\CreateTagCommand::class, - Command\Tag\RenameTagCommand::class, - Command\Tag\DeleteTagsCommand::class, + Command\Shortcode\GenerateShortcodeCommand::NAME => Command\Shortcode\GenerateShortcodeCommand::class, + Command\Shortcode\ResolveUrlCommand::NAME => Command\Shortcode\ResolveUrlCommand::class, + Command\Shortcode\ListShortcodesCommand::NAME => Command\Shortcode\ListShortcodesCommand::class, + Command\Shortcode\GetVisitsCommand::NAME => Command\Shortcode\GetVisitsCommand::class, + Command\Shortcode\GeneratePreviewCommand::NAME => Command\Shortcode\GeneratePreviewCommand::class, + + Command\Visit\ProcessVisitsCommand::NAME => Command\Visit\ProcessVisitsCommand::class, + + Command\Config\GenerateCharsetCommand::NAME => Command\Config\GenerateCharsetCommand::class, + Command\Config\GenerateSecretCommand::NAME => Command\Config\GenerateSecretCommand::class, + + Command\Api\GenerateKeyCommand::NAME => Command\Api\GenerateKeyCommand::class, + Command\Api\DisableKeyCommand::NAME => Command\Api\DisableKeyCommand::class, + Command\Api\ListKeysCommand::NAME => Command\Api\ListKeysCommand::class, + + Command\Tag\ListTagsCommand::NAME => Command\Tag\ListTagsCommand::class, + Command\Tag\CreateTagCommand::NAME => Command\Tag\CreateTagCommand::class, + Command\Tag\RenameTagCommand::NAME => Command\Tag\RenameTagCommand::class, + Command\Tag\DeleteTagsCommand::NAME => Command\Tag\DeleteTagsCommand::class, ], ], diff --git a/module/CLI/src/Command/Api/DisableKeyCommand.php b/module/CLI/src/Command/Api/DisableKeyCommand.php index e4390203..824229dd 100644 --- a/module/CLI/src/Command/Api/DisableKeyCommand.php +++ b/module/CLI/src/Command/Api/DisableKeyCommand.php @@ -12,6 +12,8 @@ use Zend\I18n\Translator\TranslatorInterface; class DisableKeyCommand extends Command { + const NAME = 'api-key:disable'; + /** * @var ApiKeyServiceInterface */ @@ -30,7 +32,7 @@ class DisableKeyCommand extends Command public function configure() { - $this->setName('api-key:disable') + $this->setName(self::NAME) ->setDescription($this->translator->translate('Disables an API key.')) ->addArgument('apiKey', InputArgument::REQUIRED, $this->translator->translate('The API key to disable')); } diff --git a/module/CLI/src/Command/Api/GenerateKeyCommand.php b/module/CLI/src/Command/Api/GenerateKeyCommand.php index 27006ceb..debcb7a5 100644 --- a/module/CLI/src/Command/Api/GenerateKeyCommand.php +++ b/module/CLI/src/Command/Api/GenerateKeyCommand.php @@ -12,6 +12,8 @@ use Zend\I18n\Translator\TranslatorInterface; class GenerateKeyCommand extends Command { + const NAME = 'api-key:generate'; + /** * @var ApiKeyServiceInterface */ @@ -30,7 +32,7 @@ class GenerateKeyCommand extends Command public function configure() { - $this->setName('api-key:generate') + $this->setName(self::NAME) ->setDescription($this->translator->translate('Generates a new valid API key.')) ->addOption( 'expirationDate', diff --git a/module/CLI/src/Command/Api/ListKeysCommand.php b/module/CLI/src/Command/Api/ListKeysCommand.php index a32c9160..d672025f 100644 --- a/module/CLI/src/Command/Api/ListKeysCommand.php +++ b/module/CLI/src/Command/Api/ListKeysCommand.php @@ -14,6 +14,8 @@ use Zend\I18n\Translator\TranslatorInterface; class ListKeysCommand extends Command { + const NAME = 'api-key:list'; + /** * @var ApiKeyServiceInterface */ @@ -32,7 +34,7 @@ class ListKeysCommand extends Command public function configure() { - $this->setName('api-key:list') + $this->setName(self::NAME) ->setDescription($this->translator->translate('Lists all the available API keys.')) ->addOption( 'enabledOnly', diff --git a/module/CLI/src/Command/Config/GenerateCharsetCommand.php b/module/CLI/src/Command/Config/GenerateCharsetCommand.php index 189dedf3..d1a1b267 100644 --- a/module/CLI/src/Command/Config/GenerateCharsetCommand.php +++ b/module/CLI/src/Command/Config/GenerateCharsetCommand.php @@ -11,6 +11,8 @@ use Zend\I18n\Translator\TranslatorInterface; class GenerateCharsetCommand extends Command { + const NAME = 'config:generate-charset'; + /** * @var TranslatorInterface */ @@ -24,7 +26,7 @@ class GenerateCharsetCommand extends Command public function configure() { - $this->setName('config:generate-charset') + $this->setName(self::NAME) ->setDescription(sprintf($this->translator->translate( 'Generates a character set sample just by shuffling the default one, "%s". ' . 'Then it can be set in the SHORTCODE_CHARS environment variable' diff --git a/module/CLI/src/Command/Config/GenerateSecretCommand.php b/module/CLI/src/Command/Config/GenerateSecretCommand.php index 6bb1e232..685ab4c5 100644 --- a/module/CLI/src/Command/Config/GenerateSecretCommand.php +++ b/module/CLI/src/Command/Config/GenerateSecretCommand.php @@ -13,6 +13,8 @@ class GenerateSecretCommand extends Command { use StringUtilsTrait; + const NAME = 'config:generate-secret'; + /** * @var TranslatorInterface */ @@ -26,7 +28,7 @@ class GenerateSecretCommand extends Command public function configure() { - $this->setName('config:generate-secret') + $this->setName(self::NAME) ->setDescription($this->translator->translate( 'Generates a random secret string that can be used for JWT token encryption' )); diff --git a/module/CLI/src/Command/Shortcode/GeneratePreviewCommand.php b/module/CLI/src/Command/Shortcode/GeneratePreviewCommand.php index 23579931..fec666bf 100644 --- a/module/CLI/src/Command/Shortcode/GeneratePreviewCommand.php +++ b/module/CLI/src/Command/Shortcode/GeneratePreviewCommand.php @@ -13,6 +13,8 @@ use Zend\I18n\Translator\TranslatorInterface; class GeneratePreviewCommand extends Command { + const NAME = 'shortcode:process-previews'; + /** * @var PreviewGeneratorInterface */ @@ -39,7 +41,7 @@ class GeneratePreviewCommand extends Command public function configure() { - $this->setName('shortcode:process-previews') + $this->setName(self::NAME) ->setDescription( $this->translator->translate( 'Processes and generates the previews for every URL, improving performance for later web requests.' diff --git a/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php b/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php index e685e876..c35f0736 100644 --- a/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php +++ b/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php @@ -18,6 +18,8 @@ use Zend\I18n\Translator\TranslatorInterface; class GenerateShortcodeCommand extends Command { + const NAME = 'shortcode:generate'; + /** * @var UrlShortenerInterface */ @@ -44,7 +46,7 @@ class GenerateShortcodeCommand extends Command public function configure() { - $this->setName('shortcode:generate') + $this->setName(self::NAME) ->setDescription( $this->translator->translate('Generates a short code for provided URL and returns the short URL') ) diff --git a/module/CLI/src/Command/Shortcode/GetVisitsCommand.php b/module/CLI/src/Command/Shortcode/GetVisitsCommand.php index 40192dde..264d086b 100644 --- a/module/CLI/src/Command/Shortcode/GetVisitsCommand.php +++ b/module/CLI/src/Command/Shortcode/GetVisitsCommand.php @@ -17,6 +17,8 @@ use Zend\I18n\Translator\TranslatorInterface; class GetVisitsCommand extends Command { + const NAME = 'shortcode:visits'; + /** * @var VisitsTrackerInterface */ @@ -35,7 +37,7 @@ class GetVisitsCommand extends Command public function configure() { - $this->setName('shortcode:visits') + $this->setName(self::NAME) ->setDescription( $this->translator->translate('Returns the detailed visits information for provided short code') ) diff --git a/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php b/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php index 4d402522..31ba923e 100644 --- a/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php +++ b/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php @@ -19,6 +19,8 @@ class ListShortcodesCommand extends Command { use PaginatorUtilsTrait; + const NAME = 'shortcode:list'; + /** * @var ShortUrlServiceInterface */ @@ -37,7 +39,7 @@ class ListShortcodesCommand extends Command public function configure() { - $this->setName('shortcode:list') + $this->setName(self::NAME) ->setDescription($this->translator->translate('List all short URLs')) ->addOption( 'page', diff --git a/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php b/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php index fe79ee16..8c9af2fd 100644 --- a/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php +++ b/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php @@ -16,6 +16,8 @@ use Zend\I18n\Translator\TranslatorInterface; class ResolveUrlCommand extends Command { + const NAME = 'shortcode:parse'; + /** * @var UrlShortenerInterface */ @@ -34,7 +36,7 @@ class ResolveUrlCommand extends Command public function configure() { - $this->setName('shortcode:parse') + $this->setName(self::NAME) ->setDescription($this->translator->translate('Returns the long URL behind a short code')) ->addArgument( 'shortCode', diff --git a/module/CLI/src/Command/Tag/CreateTagCommand.php b/module/CLI/src/Command/Tag/CreateTagCommand.php index 183dbe51..40b28449 100644 --- a/module/CLI/src/Command/Tag/CreateTagCommand.php +++ b/module/CLI/src/Command/Tag/CreateTagCommand.php @@ -12,6 +12,8 @@ use Zend\I18n\Translator\TranslatorInterface; class CreateTagCommand extends Command { + const NAME = 'tag:create'; + /** * @var TagServiceInterface */ @@ -31,7 +33,7 @@ class CreateTagCommand extends Command protected function configure() { $this - ->setName('tag:create') + ->setName(self::NAME) ->setDescription($this->translator->translate('Creates one or more tags.')) ->addOption( 'name', diff --git a/module/CLI/src/Command/Tag/DeleteTagsCommand.php b/module/CLI/src/Command/Tag/DeleteTagsCommand.php index de7cf5f4..0654138b 100644 --- a/module/CLI/src/Command/Tag/DeleteTagsCommand.php +++ b/module/CLI/src/Command/Tag/DeleteTagsCommand.php @@ -12,6 +12,8 @@ use Zend\I18n\Translator\TranslatorInterface; class DeleteTagsCommand extends Command { + const NAME = 'tag:delete'; + /** * @var TagServiceInterface */ @@ -31,7 +33,7 @@ class DeleteTagsCommand extends Command protected function configure() { $this - ->setName('tag:delete') + ->setName(self::NAME) ->setDescription($this->translator->translate('Deletes one or more tags.')) ->addOption( 'name', diff --git a/module/CLI/src/Command/Tag/ListTagsCommand.php b/module/CLI/src/Command/Tag/ListTagsCommand.php index 44a3f48e..0474fc97 100644 --- a/module/CLI/src/Command/Tag/ListTagsCommand.php +++ b/module/CLI/src/Command/Tag/ListTagsCommand.php @@ -13,6 +13,8 @@ use Zend\I18n\Translator\TranslatorInterface; class ListTagsCommand extends Command { + const NAME = 'tag:list'; + /** * @var TagServiceInterface */ @@ -32,7 +34,7 @@ class ListTagsCommand extends Command protected function configure() { $this - ->setName('tag:list') + ->setName(self::NAME) ->setDescription($this->translator->translate('Lists existing tags.')); } diff --git a/module/CLI/src/Command/Tag/RenameTagCommand.php b/module/CLI/src/Command/Tag/RenameTagCommand.php index d5473369..a38a8da1 100644 --- a/module/CLI/src/Command/Tag/RenameTagCommand.php +++ b/module/CLI/src/Command/Tag/RenameTagCommand.php @@ -13,6 +13,8 @@ use Zend\I18n\Translator\TranslatorInterface; class RenameTagCommand extends Command { + const NAME = 'tag:rename'; + /** * @var TagServiceInterface */ @@ -32,7 +34,7 @@ class RenameTagCommand extends Command protected function configure() { $this - ->setName('tag:rename') + ->setName(self::NAME) ->setDescription($this->translator->translate('Renames one existing tag.')) ->addArgument('oldName', InputArgument::REQUIRED, $this->translator->translate('Current name of the tag.')) ->addArgument('newName', InputArgument::REQUIRED, $this->translator->translate('New name of the tag.')); diff --git a/module/CLI/src/Command/Visit/ProcessVisitsCommand.php b/module/CLI/src/Command/Visit/ProcessVisitsCommand.php index 51adc356..333a9239 100644 --- a/module/CLI/src/Command/Visit/ProcessVisitsCommand.php +++ b/module/CLI/src/Command/Visit/ProcessVisitsCommand.php @@ -15,6 +15,7 @@ use Zend\I18n\Translator\TranslatorInterface; class ProcessVisitsCommand extends Command { const LOCALHOST = '127.0.0.1'; + const NAME = 'visit:process'; /** * @var VisitServiceInterface @@ -42,7 +43,7 @@ class ProcessVisitsCommand extends Command public function configure() { - $this->setName('visit:process') + $this->setName(self::NAME) ->setDescription( $this->translator->translate('Processes visits where location is not set yet') ); diff --git a/module/CLI/src/Factory/ApplicationFactory.php b/module/CLI/src/Factory/ApplicationFactory.php index 71278dac..21ae46fd 100644 --- a/module/CLI/src/Factory/ApplicationFactory.php +++ b/module/CLI/src/Factory/ApplicationFactory.php @@ -5,8 +5,11 @@ namespace Shlinkio\Shlink\CLI\Factory; use Interop\Container\ContainerInterface; use Interop\Container\Exception\ContainerException; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; use Shlinkio\Shlink\Core\Options\AppOptions; use Symfony\Component\Console\Application as CliApp; +use Symfony\Component\Console\CommandLoader\ContainerCommandLoader; use Zend\I18n\Translator\Translator; use Zend\ServiceManager\Exception\ServiceNotCreatedException; use Zend\ServiceManager\Exception\ServiceNotFoundException; @@ -20,28 +23,23 @@ class ApplicationFactory implements FactoryInterface * @param ContainerInterface $container * @param string $requestedName * @param null|array $options - * @return object + * @return CliApp + * @throws NotFoundExceptionInterface + * @throws ContainerExceptionInterface * @throws ServiceNotFoundException if unable to resolve the service. - * @throws ServiceNotCreatedException if an exception is raised when - * creating a service. + * @throws ServiceNotCreatedException if an exception is raised when creating a service. * @throws ContainerException if any other error occurs */ - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) + public function __invoke(ContainerInterface $container, $requestedName, array $options = null): CliApp { $config = $container->get('config')['cli']; $appOptions = $container->get(AppOptions::class); $translator = $container->get(Translator::class); $translator->setLocale($config['locale']); - $commands = isset($config['commands']) ? $config['commands'] : []; + $commands = $config['commands'] ?? []; $app = new CliApp($appOptions->getName(), $appOptions->getVersion()); - foreach ($commands as $command) { - if (! $container->has($command)) { - continue; - } - - $app->add($container->get($command)); - } + $app->setCommandLoader(new ContainerCommandLoader($container, $commands)); return $app; } From e024ba5d94064ef3da8b41175222e78b2eab0c6d Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 27 Dec 2017 15:43:59 +0100 Subject: [PATCH 03/56] Added phpstan to build matrix --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4999fdb5..5d3d8dc9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,7 @@ language: php branches: only: - - master - - develop + - /.*/ php: - 7 @@ -17,10 +16,12 @@ before_install: before_script: - composer self-update - composer install --no-interaction + - if [[ $TRAVIS_PHP_VERSION = 7.1 ]] || [[ $TRAVIS_PHP_VERSION = 7.2 ]]; then composer global require --dev phpstan/phpstan:0.9.*; fi script: - mkdir build - composer check + - if [[ $TRAVIS_PHP_VERSION = 7.1 ]] || [[ $TRAVIS_PHP_VERSION = 7.2 ]]; then ~/.composer/vendor/bin/phpstan analyse module/*/src/ --level=5; fi after_script: - vendor/bin/phpcov merge build --clover build/clover.xml From 4f3995ea803a3ccf443251d64370269d0ecc98c7 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 27 Dec 2017 15:56:26 +0100 Subject: [PATCH 04/56] Fixed phpstan errors in ListKeysCommand --- .../CLI/src/Command/Api/ListKeysCommand.php | 66 +++++++++---------- module/Rest/src/Entity/ApiKey.php | 4 +- 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/module/CLI/src/Command/Api/ListKeysCommand.php b/module/CLI/src/Command/Api/ListKeysCommand.php index d672025f..3b0c511c 100644 --- a/module/CLI/src/Command/Api/ListKeysCommand.php +++ b/module/CLI/src/Command/Api/ListKeysCommand.php @@ -50,74 +50,72 @@ class ListKeysCommand extends Command $list = $this->apiKeyService->listKeys($enabledOnly); $table = new Table($output); - if ($enabledOnly) { - $table->setHeaders([ - $this->translator->translate('Key'), - $this->translator->translate('Expiration date'), - ]); - } else { - $table->setHeaders([ - $this->translator->translate('Key'), - $this->translator->translate('Is enabled'), - $this->translator->translate('Expiration date'), - ]); - } + $table->setHeaders(array_filter([ + $this->translator->translate('Key'), + ! $enabledOnly ? $this->translator->translate('Is enabled') : null, + $this->translator->translate('Expiration date'), + ])); /** @var ApiKey $row */ foreach ($list as $row) { $key = $row->getKey(); $expiration = $row->getExpirationDate(); $rowData = []; - $formatMethod = ! $row->isEnabled() - ? 'getErrorString' - : ($row->isExpired() ? 'getWarningString' : 'getSuccessString'); + $formatMethod = $this->determineFormatMethod($row); - if ($enabledOnly) { - $rowData[] = $this->{$formatMethod}($key); - } else { - $rowData[] = $this->{$formatMethod}($key); - $rowData[] = $this->{$formatMethod}($this->getEnabledSymbol($row)); + $rowData[] = $formatMethod($key); + if (! $enabledOnly) { + $rowData[] = $formatMethod($this->getEnabledSymbol($row)); } - $rowData[] = isset($expiration) ? $expiration->format(\DateTime::ATOM) : '-'; + $rowData[] = $expiration !== null ? $expiration->format(\DateTime::ATOM) : '-'; $table->addRow($rowData); } $table->render(); } - /** - * @param string $string - * @return string - */ - protected function getErrorString($string) + private function determineFormatMethod(ApiKey $apiKey): callable { - return sprintf('%s', $string); + if (! $apiKey->isEnabled()) { + return [$this, 'getErrorString']; + } + + return $apiKey->isExpired() ? [$this, 'getWarningString'] : [$this, 'getSuccessString']; } /** - * @param string $string + * @param string $value * @return string */ - protected function getSuccessString($string) + private function getErrorString(string $value): string { - return sprintf('%s', $string); + return sprintf('%s', $value); } /** - * @param $string + * @param string $value * @return string */ - protected function getWarningString($string) + private function getSuccessString(string $value): string { - return sprintf('%s', $string); + return sprintf('%s', $value); + } + + /** + * @param string $value + * @return string + */ + private function getWarningString(string $value): string + { + return sprintf('%s', $value); } /** * @param ApiKey $apiKey * @return string */ - protected function getEnabledSymbol(ApiKey $apiKey) + private function getEnabledSymbol(ApiKey $apiKey): string { return ! $apiKey->isEnabled() || $apiKey->isExpired() ? '---' : '+++'; } diff --git a/module/Rest/src/Entity/ApiKey.php b/module/Rest/src/Entity/ApiKey.php index 4ef72f92..284cc302 100644 --- a/module/Rest/src/Entity/ApiKey.php +++ b/module/Rest/src/Entity/ApiKey.php @@ -25,7 +25,7 @@ class ApiKey extends AbstractEntity */ protected $key; /** - * @var \DateTime + * @var \DateTime|null * @ORM\Column(name="expiration_date", nullable=true, type="datetime") */ protected $expirationDate; @@ -60,7 +60,7 @@ class ApiKey extends AbstractEntity } /** - * @return \DateTime + * @return \DateTime|null */ public function getExpirationDate() { From db956a1f40b8ce1d67dd7e12c3f710d8a54753b8 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 27 Dec 2017 16:23:54 +0100 Subject: [PATCH 05/56] Fixed all possible PHPStan errors --- .../Command/Shortcode/ResolveUrlCommand.php | 8 -------- .../Common/src/Exception/WrongIpException.php | 2 +- .../Common/src/Service/IpLocationResolver.php | 6 ++++-- .../Service/IpLocationResolverInterface.php | 7 +++++-- module/Common/src/Util/DateRange.php | 12 ++++++------ module/Core/src/Action/RedirectAction.php | 6 ------ .../src/Exception/InvalidUrlException.php | 4 ++-- module/Core/src/Service/ShortUrlService.php | 4 ++-- .../src/Service/ShortUrlServiceInterface.php | 2 +- module/Core/src/Service/Tag/TagService.php | 3 ++- module/Core/src/Service/UrlShortener.php | 10 ++++++---- module/Core/src/Service/VisitsTracker.php | 8 ++++---- .../src/Service/VisitsTrackerInterface.php | 4 ++-- .../Rest/src/Action/CreateShortcodeAction.php | 2 +- module/Rest/src/Action/GetVisitsAction.php | 4 ++-- module/Rest/src/Authentication/JWTService.php | 14 +++++++------- .../Authentication/JWTServiceInterface.php | 8 ++++---- .../CheckAuthenticationMiddleware.php | 5 +++-- module/Rest/src/Service/ApiKeyService.php | 19 +++++++++---------- .../src/Service/ApiKeyServiceInterface.php | 4 +++- .../test/Action/AuthenticateActionTest.php | 3 +++ 21 files changed, 67 insertions(+), 68 deletions(-) diff --git a/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php b/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php index 8c9af2fd..539035b1 100644 --- a/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php +++ b/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php @@ -71,14 +71,6 @@ class ResolveUrlCommand extends Command try { $longUrl = $this->urlShortener->shortCodeToUrl($shortCode); - if (! isset($longUrl)) { - $output->writeln(sprintf( - '' . $this->translator->translate('No URL found for short code "%s"') . '', - $shortCode - )); - return; - } - $output->writeln(sprintf('%s %s', $this->translator->translate('Long URL:'), $longUrl)); } catch (InvalidShortCodeException $e) { $output->writeln(sprintf('' . $this->translator->translate( diff --git a/module/Common/src/Exception/WrongIpException.php b/module/Common/src/Exception/WrongIpException.php index 0e70d4e2..837f9ec7 100644 --- a/module/Common/src/Exception/WrongIpException.php +++ b/module/Common/src/Exception/WrongIpException.php @@ -5,7 +5,7 @@ namespace Shlinkio\Shlink\Common\Exception; class WrongIpException extends RuntimeException { - public static function fromIpAddress($ipAddress, \Exception $prev = null) + public static function fromIpAddress($ipAddress, \Throwable $prev = null) { return new self(sprintf('Provided IP "%s" is invalid', $ipAddress), 0, $prev); } diff --git a/module/Common/src/Service/IpLocationResolver.php b/module/Common/src/Service/IpLocationResolver.php index 218d2288..93b3ccfe 100644 --- a/module/Common/src/Service/IpLocationResolver.php +++ b/module/Common/src/Service/IpLocationResolver.php @@ -22,15 +22,17 @@ class IpLocationResolver implements IpLocationResolverInterface } /** - * @param $ipAddress + * @param string $ipAddress * @return array + * @throws WrongIpException */ - public function resolveIpLocation($ipAddress) + public function resolveIpLocation(string $ipAddress): array { try { $response = $this->httpClient->get(sprintf(self::SERVICE_PATTERN, $ipAddress)); return json_decode((string) $response->getBody(), true); } catch (GuzzleException $e) { + /** @var \Throwable $e */ throw WrongIpException::fromIpAddress($ipAddress, $e); } } diff --git a/module/Common/src/Service/IpLocationResolverInterface.php b/module/Common/src/Service/IpLocationResolverInterface.php index 4f4279a2..fc1f07a3 100644 --- a/module/Common/src/Service/IpLocationResolverInterface.php +++ b/module/Common/src/Service/IpLocationResolverInterface.php @@ -3,11 +3,14 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Common\Service; +use Shlinkio\Shlink\Common\Exception\WrongIpException; + interface IpLocationResolverInterface { /** - * @param $ipAddress + * @param string $ipAddress * @return array + * @throws WrongIpException */ - public function resolveIpLocation($ipAddress); + public function resolveIpLocation(string $ipAddress): array; } diff --git a/module/Common/src/Util/DateRange.php b/module/Common/src/Util/DateRange.php index 215de520..f58f2033 100644 --- a/module/Common/src/Util/DateRange.php +++ b/module/Common/src/Util/DateRange.php @@ -6,11 +6,11 @@ namespace Shlinkio\Shlink\Common\Util; class DateRange { /** - * @var \DateTimeInterface + * @var \DateTimeInterface|null */ private $startDate; /** - * @var \DateTimeInterface + * @var \DateTimeInterface|null */ private $endDate; @@ -21,7 +21,7 @@ class DateRange } /** - * @return \DateTimeInterface + * @return \DateTimeInterface|null */ public function getStartDate() { @@ -29,7 +29,7 @@ class DateRange } /** - * @return \DateTimeInterface + * @return \DateTimeInterface|null */ public function getEndDate() { @@ -39,8 +39,8 @@ class DateRange /** * @return bool */ - public function isEmpty() + public function isEmpty(): bool { - return is_null($this->startDate) && is_null($this->endDate); + return $this->startDate === null && $this->endDate === null; } } diff --git a/module/Core/src/Action/RedirectAction.php b/module/Core/src/Action/RedirectAction.php index e22639c7..066e9508 100644 --- a/module/Core/src/Action/RedirectAction.php +++ b/module/Core/src/Action/RedirectAction.php @@ -49,12 +49,6 @@ class RedirectAction implements MiddlewareInterface try { $longUrl = $this->urlShortener->shortCodeToUrl($shortCode); - // If provided shortCode does not belong to a valid long URL, dispatch next middleware, which will trigger - // a not-found error - if ($longUrl === null) { - return $delegate->process($request); - } - // Track visit to this short code $this->visitTracker->track($shortCode, $request); diff --git a/module/Core/src/Exception/InvalidUrlException.php b/module/Core/src/Exception/InvalidUrlException.php index 848cd71b..13e77c5d 100644 --- a/module/Core/src/Exception/InvalidUrlException.php +++ b/module/Core/src/Exception/InvalidUrlException.php @@ -7,9 +7,9 @@ use Shlinkio\Shlink\Common\Exception\RuntimeException; class InvalidUrlException extends RuntimeException { - public static function fromUrl($url, \Exception $previous = null) + public static function fromUrl($url, \Throwable $previous = null) { $code = isset($previous) ? $previous->getCode() : -1; - return new static(sprintf('Provided URL "%s" is not an exisitng and valid URL', $url), $code, $previous); + return new static(sprintf('Provided URL "%s" is not an existing and valid URL', $url), $code, $previous); } } diff --git a/module/Core/src/Service/ShortUrlService.php b/module/Core/src/Service/ShortUrlService.php index 2e22717b..85359060 100644 --- a/module/Core/src/Service/ShortUrlService.php +++ b/module/Core/src/Service/ShortUrlService.php @@ -49,9 +49,9 @@ class ShortUrlService implements ShortUrlServiceInterface * @return ShortUrl * @throws InvalidShortCodeException */ - public function setTagsByShortCode($shortCode, array $tags = []) + public function setTagsByShortCode($shortCode, array $tags = []): ShortUrl { - /** @var ShortUrl $shortUrl */ + /** @var ShortUrl|null $shortUrl */ $shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([ 'shortCode' => $shortCode, ]); diff --git a/module/Core/src/Service/ShortUrlServiceInterface.php b/module/Core/src/Service/ShortUrlServiceInterface.php index e299842d..1815c4de 100644 --- a/module/Core/src/Service/ShortUrlServiceInterface.php +++ b/module/Core/src/Service/ShortUrlServiceInterface.php @@ -24,5 +24,5 @@ interface ShortUrlServiceInterface * @return ShortUrl * @throws InvalidShortCodeException */ - public function setTagsByShortCode($shortCode, array $tags = []); + public function setTagsByShortCode($shortCode, array $tags = []): ShortUrl; } diff --git a/module/Core/src/Service/Tag/TagService.php b/module/Core/src/Service/Tag/TagService.php index b9427eee..247112c8 100644 --- a/module/Core/src/Service/Tag/TagService.php +++ b/module/Core/src/Service/Tag/TagService.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Service\Tag; use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; @@ -15,7 +16,7 @@ class TagService implements TagServiceInterface use TagManagerTrait; /** - * @var EntityManagerInterface + * @var EntityManager|EntityManagerInterface */ private $em; diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index cd282035..6515fd0f 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -90,6 +90,7 @@ class UrlShortener implements UrlShortenerInterface int $maxVisits = null ): string { // If the url already exists in the database, just return its short code + /** @var ShortUrl|null $shortUrl */ $shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([ 'originalUrl' => $url, ]); @@ -148,6 +149,7 @@ class UrlShortener implements UrlShortenerInterface 'max' => 15, ]]); } catch (GuzzleException $e) { + /** @var \Throwable $e */ throw InvalidUrlException::fromUrl($url, $e); } } @@ -155,13 +157,13 @@ class UrlShortener implements UrlShortenerInterface /** * Generates the unique shortcode for an autoincrement ID * - * @param int $id + * @param float $id * @return string */ - private function convertAutoincrementIdToShortCode($id): string + private function convertAutoincrementIdToShortCode(float $id): string { - $id = ((int) $id) + 200000; // Increment the Id so that the generated shortcode is not too short - $length = strlen($this->chars); + $id += 200000; // Increment the Id so that the generated shortcode is not too short + $length = \strlen($this->chars); $code = ''; while ($id > 0) { diff --git a/module/Core/src/Service/VisitsTracker.php b/module/Core/src/Service/VisitsTracker.php index 38e44fea..6147046b 100644 --- a/module/Core/src/Service/VisitsTracker.php +++ b/module/Core/src/Service/VisitsTracker.php @@ -15,7 +15,7 @@ use Shlinkio\Shlink\Core\Repository\VisitRepository; class VisitsTracker implements VisitsTrackerInterface { /** - * @var EntityManagerInterface|EntityManager + * @var EntityManager|EntityManagerInterface */ private $em; @@ -66,14 +66,14 @@ class VisitsTracker implements VisitsTrackerInterface /** * Returns the visits on certain short code * - * @param $shortCode + * @param string $shortCode * @param DateRange $dateRange * @return Visit[] * @throws InvalidArgumentException */ - public function info($shortCode, DateRange $dateRange = null): array + public function info(string $shortCode, DateRange $dateRange = null): array { - /** @var ShortUrl $shortUrl */ + /** @var ShortUrl|null $shortUrl */ $shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([ 'shortCode' => $shortCode, ]); diff --git a/module/Core/src/Service/VisitsTrackerInterface.php b/module/Core/src/Service/VisitsTrackerInterface.php index d3295f37..a852c030 100644 --- a/module/Core/src/Service/VisitsTrackerInterface.php +++ b/module/Core/src/Service/VisitsTrackerInterface.php @@ -21,10 +21,10 @@ interface VisitsTrackerInterface /** * Returns the visits on certain short code * - * @param $shortCode + * @param string $shortCode * @param DateRange $dateRange * @return Visit[] * @throws InvalidArgumentException */ - public function info($shortCode, DateRange $dateRange = null): array; + public function info(string $shortCode, DateRange $dateRange = null): array; } diff --git a/module/Rest/src/Action/CreateShortcodeAction.php b/module/Rest/src/Action/CreateShortcodeAction.php index 8edcd499..56169592 100644 --- a/module/Rest/src/Action/CreateShortcodeAction.php +++ b/module/Rest/src/Action/CreateShortcodeAction.php @@ -50,7 +50,7 @@ class CreateShortcodeAction extends AbstractRestAction */ public function process(Request $request, DelegateInterface $delegate) { - $postData = $request->getParsedBody(); + $postData = (array) $request->getParsedBody(); if (! isset($postData['longUrl'])) { return new JsonResponse([ 'error' => RestUtils::INVALID_ARGUMENT_ERROR, diff --git a/module/Rest/src/Action/GetVisitsAction.php b/module/Rest/src/Action/GetVisitsAction.php index 0d03acf1..490b0470 100644 --- a/module/Rest/src/Action/GetVisitsAction.php +++ b/module/Rest/src/Action/GetVisitsAction.php @@ -75,10 +75,10 @@ class GetVisitsAction extends AbstractRestAction /** * @param Request $request - * @param $key + * @param string $key * @return \DateTime|null */ - protected function getDateQueryParam(Request $request, $key) + private function getDateQueryParam(Request $request, string $key) { $query = $request->getQueryParams(); if (! isset($query[$key]) || empty($query[$key])) { diff --git a/module/Rest/src/Authentication/JWTService.php b/module/Rest/src/Authentication/JWTService.php index 74130438..6a0d3ac8 100644 --- a/module/Rest/src/Authentication/JWTService.php +++ b/module/Rest/src/Authentication/JWTService.php @@ -27,7 +27,7 @@ class JWTService implements JWTServiceInterface * @param int $lifetime * @return string */ - public function create(ApiKey $apiKey, $lifetime = self::DEFAULT_LIFETIME) + public function create(ApiKey $apiKey, $lifetime = self::DEFAULT_LIFETIME): string { $currentTimestamp = time(); @@ -48,7 +48,7 @@ class JWTService implements JWTServiceInterface * @return string * @throws AuthenticationException If the token has expired */ - public function refresh($jwt, $lifetime = self::DEFAULT_LIFETIME) + public function refresh(string $jwt, $lifetime = self::DEFAULT_LIFETIME): string { $payload = $this->getPayload($jwt); $payload['exp'] = time() + $lifetime; @@ -61,7 +61,7 @@ class JWTService implements JWTServiceInterface * @param string $jwt * @return bool */ - public function verify($jwt) + public function verify(string $jwt): bool { try { // If no exception is thrown while decoding the token, it is considered valid @@ -79,7 +79,7 @@ class JWTService implements JWTServiceInterface * @return array * @throws AuthenticationException If the token has expired */ - public function getPayload($jwt) + public function getPayload(string $jwt): array { try { return $this->decode($jwt); @@ -92,16 +92,16 @@ class JWTService implements JWTServiceInterface * @param array $data * @return string */ - protected function encode(array $data) + protected function encode(array $data): string { return JWT::encode($data, $this->appOptions->getSecretKey(), self::DEFAULT_ENCRYPTION_ALG); } /** - * @param $jwt + * @param string $jwt * @return array */ - protected function decode($jwt) + protected function decode(string $jwt): array { return (array) JWT::decode($jwt, $this->appOptions->getSecretKey(), [self::DEFAULT_ENCRYPTION_ALG]); } diff --git a/module/Rest/src/Authentication/JWTServiceInterface.php b/module/Rest/src/Authentication/JWTServiceInterface.php index 55c8fb36..ba162aa8 100644 --- a/module/Rest/src/Authentication/JWTServiceInterface.php +++ b/module/Rest/src/Authentication/JWTServiceInterface.php @@ -18,7 +18,7 @@ interface JWTServiceInterface * @param int $lifetime * @return string */ - public function create(ApiKey $apiKey, $lifetime = self::DEFAULT_LIFETIME); + public function create(ApiKey $apiKey, $lifetime = self::DEFAULT_LIFETIME): string; /** * Refreshes a token and returns it with the new expiration @@ -28,7 +28,7 @@ interface JWTServiceInterface * @return string * @throws AuthenticationException If the token has expired */ - public function refresh($jwt, $lifetime = self::DEFAULT_LIFETIME); + public function refresh(string $jwt, $lifetime = self::DEFAULT_LIFETIME): string; /** * Verifies that certain JWT is valid @@ -36,7 +36,7 @@ interface JWTServiceInterface * @param string $jwt * @return bool */ - public function verify($jwt); + public function verify(string $jwt): bool; /** * Decodes certain token and returns the payload @@ -45,5 +45,5 @@ interface JWTServiceInterface * @return array * @throws AuthenticationException If the token has expired */ - public function getPayload($jwt); + public function getPayload(string $jwt): array; } diff --git a/module/Rest/src/Middleware/CheckAuthenticationMiddleware.php b/module/Rest/src/Middleware/CheckAuthenticationMiddleware.php index b5823f4f..071eadba 100644 --- a/module/Rest/src/Middleware/CheckAuthenticationMiddleware.php +++ b/module/Rest/src/Middleware/CheckAuthenticationMiddleware.php @@ -55,13 +55,14 @@ class CheckAuthenticationMiddleware implements MiddlewareInterface, StatusCodeIn * * @return Response * @throws \InvalidArgumentException + * @throws \ErrorException */ public function process(Request $request, DelegateInterface $delegate) { // If current route is the authenticate route or an OPTIONS request, continue to the next middleware - /** @var RouteResult $routeResult */ + /** @var RouteResult|null $routeResult */ $routeResult = $request->getAttribute(RouteResult::class); - if (! isset($routeResult) + if ($routeResult === null || $routeResult->isFailure() || $routeResult->getMatchedRouteName() === AuthenticateAction::class || $request->getMethod() === 'OPTIONS' diff --git a/module/Rest/src/Service/ApiKeyService.php b/module/Rest/src/Service/ApiKeyService.php index e2473f1b..6f368e56 100644 --- a/module/Rest/src/Service/ApiKeyService.php +++ b/module/Rest/src/Service/ApiKeyService.php @@ -46,13 +46,9 @@ class ApiKeyService implements ApiKeyServiceInterface */ public function check($key) { - /** @var ApiKey $apiKey */ + /** @var ApiKey|null $apiKey */ $apiKey = $this->getByKey($key); - if (! isset($apiKey)) { - return false; - } - - return $apiKey->isValid(); + return $apiKey !== null && $apiKey->isValid(); } /** @@ -60,12 +56,13 @@ class ApiKeyService implements ApiKeyServiceInterface * * @param string $key * @return ApiKey + * @throws InvalidArgumentException */ public function disable($key) { - /** @var ApiKey $apiKey */ + /** @var ApiKey|null $apiKey */ $apiKey = $this->getByKey($key); - if (! isset($apiKey)) { + if ($apiKey === null) { throw new InvalidArgumentException(sprintf('API key "%s" does not exist and can\'t be disabled', $key)); } @@ -75,7 +72,7 @@ class ApiKeyService implements ApiKeyServiceInterface } /** - * Lists all existing appi keys + * Lists all existing api keys * * @param bool $enabledOnly Tells if only enabled keys should be returned * @return ApiKey[] @@ -94,8 +91,10 @@ class ApiKeyService implements ApiKeyServiceInterface */ public function getByKey($key) { - return $this->em->getRepository(ApiKey::class)->findOneBy([ + /** @var ApiKey|null $apiKey */ + $apiKey = $this->em->getRepository(ApiKey::class)->findOneBy([ 'key' => $key, ]); + return $apiKey; } } diff --git a/module/Rest/src/Service/ApiKeyServiceInterface.php b/module/Rest/src/Service/ApiKeyServiceInterface.php index 51dadf7a..ceaa3b96 100644 --- a/module/Rest/src/Service/ApiKeyServiceInterface.php +++ b/module/Rest/src/Service/ApiKeyServiceInterface.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Service; +use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; use Shlinkio\Shlink\Rest\Entity\ApiKey; interface ApiKeyServiceInterface @@ -28,11 +29,12 @@ interface ApiKeyServiceInterface * * @param string $key * @return ApiKey + * @throws InvalidArgumentException */ public function disable($key); /** - * Lists all existing appi keys + * Lists all existing api keys * * @param bool $enabledOnly Tells if only enabled keys should be returned * @return ApiKey[] diff --git a/module/Rest/test/Action/AuthenticateActionTest.php b/module/Rest/test/Action/AuthenticateActionTest.php index 32079e9b..4fb2518f 100644 --- a/module/Rest/test/Action/AuthenticateActionTest.php +++ b/module/Rest/test/Action/AuthenticateActionTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action; use PHPUnit\Framework\TestCase; +use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Rest\Action\AuthenticateAction; use Shlinkio\Shlink\Rest\Authentication\JWTService; @@ -32,6 +33,8 @@ class AuthenticateActionTest extends TestCase { $this->apiKeyService = $this->prophesize(ApiKeyService::class); $this->jwtService = $this->prophesize(JWTService::class); + $this->jwtService->create(Argument::cetera())->willReturn(''); + $this->action = new AuthenticateAction( $this->apiKeyService->reveal(), $this->jwtService->reveal(), From ea80b6d48a9ec88a226952b9e70723c08572848b Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 27 Dec 2017 16:33:06 +0100 Subject: [PATCH 06/56] Replaced vlucas/phpdotenv package by symfony/dotenv --- composer.json | 2 +- config/container.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index d91330f9..5a36d42b 100644 --- a/composer.json +++ b/composer.json @@ -51,8 +51,8 @@ "phpunit/phpunit": "^6.0", "slevomat/coding-standard": "^4.0", "squizlabs/php_codesniffer": "^3.1 <3.2", + "symfony/dotenv": "^3.4", "symfony/var-dumper": "^3.0", - "vlucas/phpdotenv": "^2.2", "zendframework/zend-expressive-tooling": "^0.4" }, "autoload": { diff --git a/config/container.php b/config/container.php index 126351ae..ec249907 100644 --- a/config/container.php +++ b/config/container.php @@ -1,7 +1,7 @@ load(); + $dotenv = new Dotenv(); + $dotenv->load(__DIR__ . '/../.env'); } // Build container From 2012cc453c8efc95d4d27dc541d4c7341585bdb7 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 27 Dec 2017 17:22:51 +0100 Subject: [PATCH 07/56] Fixed PHPStan errors due to API inconsistency in EntityManager and EntityManagerInterface --- module/Core/src/Service/Tag/TagService.php | 13 ++++++++----- module/Core/src/Service/VisitsTracker.php | 15 +++++++++------ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/module/Core/src/Service/Tag/TagService.php b/module/Core/src/Service/Tag/TagService.php index 247112c8..65ae733a 100644 --- a/module/Core/src/Service/Tag/TagService.php +++ b/module/Core/src/Service/Tag/TagService.php @@ -4,8 +4,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Service\Tag; use Doctrine\Common\Collections\Collection; -use Doctrine\ORM\EntityManager; -use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM; use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Repository\TagRepository; @@ -16,11 +15,11 @@ class TagService implements TagServiceInterface use TagManagerTrait; /** - * @var EntityManager|EntityManagerInterface + * @var ORM\EntityManagerInterface */ private $em; - public function __construct(EntityManagerInterface $em) + public function __construct(ORM\EntityManagerInterface $em) { $this->em = $em; } @@ -64,6 +63,7 @@ class TagService implements TagServiceInterface * @param string $newName * @return Tag * @throws EntityDoesNotExistException + * @throws ORM\OptimisticLockException */ public function renameTag($oldName, $newName) { @@ -75,7 +75,10 @@ class TagService implements TagServiceInterface } $tag->setName($newName); - $this->em->flush($tag); + + /** @var ORM\EntityManager $em */ + $em = $this->em; + $em->flush($tag); return $tag; } diff --git a/module/Core/src/Service/VisitsTracker.php b/module/Core/src/Service/VisitsTracker.php index 6147046b..0a5cc73d 100644 --- a/module/Core/src/Service/VisitsTracker.php +++ b/module/Core/src/Service/VisitsTracker.php @@ -3,8 +3,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Service; -use Doctrine\ORM\EntityManager; -use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM; use Psr\Http\Message\ServerRequestInterface; use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; use Shlinkio\Shlink\Common\Util\DateRange; @@ -15,11 +14,11 @@ use Shlinkio\Shlink\Core\Repository\VisitRepository; class VisitsTracker implements VisitsTrackerInterface { /** - * @var EntityManager|EntityManagerInterface + * @var ORM\EntityManagerInterface */ private $em; - public function __construct(EntityManagerInterface $em) + public function __construct(ORM\EntityManagerInterface $em) { $this->em = $em; } @@ -29,6 +28,8 @@ class VisitsTracker implements VisitsTrackerInterface * * @param string $shortCode * @param ServerRequestInterface $request + * @throws ORM\ORMInvalidArgumentException + * @throws ORM\OptimisticLockException */ public function track($shortCode, ServerRequestInterface $request) { @@ -43,8 +44,10 @@ class VisitsTracker implements VisitsTrackerInterface ->setReferer($request->getHeaderLine('Referer')) ->setRemoteAddr($this->findOutRemoteAddr($request)); - $this->em->persist($visit); - $this->em->flush($visit); + /** @var ORM\EntityManager $em */ + $em = $this->em; + $em->persist($visit); + $em->flush($visit); } /** From cc3362837b9dc0c321b0786e650a6885476f5dd9 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 27 Dec 2017 17:28:51 +0100 Subject: [PATCH 08/56] Simplified ListKeysCommand using SymfonyStyle --- .../CLI/src/Command/Api/ListKeysCommand.php | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/module/CLI/src/Command/Api/ListKeysCommand.php b/module/CLI/src/Command/Api/ListKeysCommand.php index 3b0c511c..296b2455 100644 --- a/module/CLI/src/Command/Api/ListKeysCommand.php +++ b/module/CLI/src/Command/Api/ListKeysCommand.php @@ -6,10 +6,10 @@ namespace Shlinkio\Shlink\CLI\Command\Api; use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Zend\I18n\Translator\TranslatorInterface; class ListKeysCommand extends Command @@ -46,33 +46,32 @@ class ListKeysCommand extends Command public function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); $enabledOnly = $input->getOption('enabledOnly'); $list = $this->apiKeyService->listKeys($enabledOnly); - - $table = new Table($output); - $table->setHeaders(array_filter([ - $this->translator->translate('Key'), - ! $enabledOnly ? $this->translator->translate('Is enabled') : null, - $this->translator->translate('Expiration date'), - ])); + $rows = []; /** @var ApiKey $row */ foreach ($list as $row) { $key = $row->getKey(); $expiration = $row->getExpirationDate(); - $rowData = []; $formatMethod = $this->determineFormatMethod($row); - $rowData[] = $formatMethod($key); + // Set columns for this row + $rowData = [$formatMethod($key)]; if (! $enabledOnly) { $rowData[] = $formatMethod($this->getEnabledSymbol($row)); } - $rowData[] = $expiration !== null ? $expiration->format(\DateTime::ATOM) : '-'; - $table->addRow($rowData); + + $rows[] = $rowData; } - $table->render(); + $io->table(array_filter([ + $this->translator->translate('Key'), + ! $enabledOnly ? $this->translator->translate('Is enabled') : null, + $this->translator->translate('Expiration date'), + ]), $rows); } private function determineFormatMethod(ApiKey $apiKey): callable From 37fb7e76d9186400ee22dda0928ee05d0bb8b9bb Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 27 Dec 2017 17:32:39 +0100 Subject: [PATCH 09/56] Simplified DisableKeyCommand using SymfonyStyle --- module/CLI/src/Command/Api/DisableKeyCommand.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/module/CLI/src/Command/Api/DisableKeyCommand.php b/module/CLI/src/Command/Api/DisableKeyCommand.php index 824229dd..386f838d 100644 --- a/module/CLI/src/Command/Api/DisableKeyCommand.php +++ b/module/CLI/src/Command/Api/DisableKeyCommand.php @@ -8,6 +8,7 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Zend\I18n\Translator\TranslatorInterface; class DisableKeyCommand extends Command @@ -40,18 +41,13 @@ class DisableKeyCommand extends Command public function execute(InputInterface $input, OutputInterface $output) { $apiKey = $input->getArgument('apiKey'); + $io = new SymfonyStyle($input, $output); try { $this->apiKeyService->disable($apiKey); - $output->writeln(sprintf( - $this->translator->translate('API key %s properly disabled'), - '' . $apiKey . '' - )); + $io->success(sprintf($this->translator->translate('API key "%s" properly disabled'), $apiKey)); } catch (\InvalidArgumentException $e) { - $output->writeln(sprintf( - '' . $this->translator->translate('API key "%s" does not exist.') . '', - $apiKey - )); + $io->error(sprintf($this->translator->translate('API key "%s" does not exist.'), $apiKey)); } } } From 1993d011103d9b38874bd33f63116fb4d423a498 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 27 Dec 2017 17:36:07 +0100 Subject: [PATCH 10/56] Dimplified GenerateKeyCommand by using SymfonyStyle --- module/CLI/src/Command/Api/GenerateKeyCommand.php | 6 +++++- module/CLI/test/Command/Api/DisableKeyCommandTest.php | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/module/CLI/src/Command/Api/GenerateKeyCommand.php b/module/CLI/src/Command/Api/GenerateKeyCommand.php index debcb7a5..cfe747f0 100644 --- a/module/CLI/src/Command/Api/GenerateKeyCommand.php +++ b/module/CLI/src/Command/Api/GenerateKeyCommand.php @@ -8,6 +8,7 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Zend\I18n\Translator\TranslatorInterface; class GenerateKeyCommand extends Command @@ -46,6 +47,9 @@ class GenerateKeyCommand extends Command { $expirationDate = $input->getOption('expirationDate'); $apiKey = $this->apiKeyService->create(isset($expirationDate) ? new \DateTime($expirationDate) : null); - $output->writeln($this->translator->translate('Generated API key') . sprintf(': %s', $apiKey)); + + (new SymfonyStyle($input, $output))->success( + sprintf($this->translator->translate('Generated API key: "%s"'), $apiKey) + ); } } diff --git a/module/CLI/test/Command/Api/DisableKeyCommandTest.php b/module/CLI/test/Command/Api/DisableKeyCommandTest.php index f577f298..e51ee7a8 100644 --- a/module/CLI/test/Command/Api/DisableKeyCommandTest.php +++ b/module/CLI/test/Command/Api/DisableKeyCommandTest.php @@ -59,6 +59,6 @@ class DisableKeyCommandTest extends TestCase 'apiKey' => $apiKey, ]); $output = $this->commandTester->getDisplay(); - $this->assertEquals('API key "abcd1234" does not exist.' . PHP_EOL, $output); + $this->assertContains('API key "abcd1234" does not exist.', $output); } } From 1b94083188cfdf935c5296e005409b118f9997d5 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 28 Dec 2017 09:48:17 +0100 Subject: [PATCH 11/56] Improved GenerateCharsetCommand by using SymfonyStyle --- module/CLI/src/Command/Config/GenerateCharsetCommand.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/module/CLI/src/Command/Config/GenerateCharsetCommand.php b/module/CLI/src/Command/Config/GenerateCharsetCommand.php index d1a1b267..caa64c28 100644 --- a/module/CLI/src/Command/Config/GenerateCharsetCommand.php +++ b/module/CLI/src/Command/Config/GenerateCharsetCommand.php @@ -7,6 +7,7 @@ use Shlinkio\Shlink\Core\Service\UrlShortener; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Zend\I18n\Translator\TranslatorInterface; class GenerateCharsetCommand extends Command @@ -36,6 +37,8 @@ class GenerateCharsetCommand extends Command public function execute(InputInterface $input, OutputInterface $output) { $charSet = str_shuffle(UrlShortener::DEFAULT_CHARS); - $output->writeln($this->translator->translate('Character set:') . sprintf(' %s', $charSet)); + (new SymfonyStyle($input, $output))->success( + \sprintf($this->translator->translate('Character set: "%s"'), $charSet) + ); } } From 0760550767df3372130799c4075795bf6ba846bc Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 28 Dec 2017 09:48:34 +0100 Subject: [PATCH 12/56] Removed unnecessary type hints --- module/Common/src/Service/IpLocationResolver.php | 1 - module/Core/src/Service/UrlShortener.php | 1 - 2 files changed, 2 deletions(-) diff --git a/module/Common/src/Service/IpLocationResolver.php b/module/Common/src/Service/IpLocationResolver.php index 93b3ccfe..017bf89c 100644 --- a/module/Common/src/Service/IpLocationResolver.php +++ b/module/Common/src/Service/IpLocationResolver.php @@ -32,7 +32,6 @@ class IpLocationResolver implements IpLocationResolverInterface $response = $this->httpClient->get(sprintf(self::SERVICE_PATTERN, $ipAddress)); return json_decode((string) $response->getBody(), true); } catch (GuzzleException $e) { - /** @var \Throwable $e */ throw WrongIpException::fromIpAddress($ipAddress, $e); } } diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index 6515fd0f..2214c144 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -149,7 +149,6 @@ class UrlShortener implements UrlShortenerInterface 'max' => 15, ]]); } catch (GuzzleException $e) { - /** @var \Throwable $e */ throw InvalidUrlException::fromUrl($url, $e); } } From fdbe076bf2b06a82d20b187649d45af35c54a010 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 28 Dec 2017 14:55:55 +0100 Subject: [PATCH 13/56] Added phpstan config file excluding a file that fails --- phpstan.neon | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 phpstan.neon diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000..77e32e1e --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,3 @@ +parameters: + excludes_analyse: + - module/Common/src/Template/Extension/TranslatorExtension.php From 88b9f9fc56d17d5533d762d2cda7cf0acf640a14 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 28 Dec 2017 14:59:52 +0100 Subject: [PATCH 14/56] Fixed GenerateCharsetCommandTest --- .../test/Command/Config/GenerateCharsetCommandTest.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/module/CLI/test/Command/Config/GenerateCharsetCommandTest.php b/module/CLI/test/Command/Config/GenerateCharsetCommandTest.php index 5e11684b..a9d62c18 100644 --- a/module/CLI/test/Command/Config/GenerateCharsetCommandTest.php +++ b/module/CLI/test/Command/Config/GenerateCharsetCommandTest.php @@ -5,7 +5,6 @@ namespace ShlinkioTest\Shlink\CLI\Command\Config; use PHPUnit\Framework\TestCase; use Shlinkio\Shlink\CLI\Command\Config\GenerateCharsetCommand; -use Shlinkio\Shlink\Core\Service\UrlShortener; use Symfony\Component\Console\Application; use Symfony\Component\Console\Tester\CommandTester; use Zend\I18n\Translator\Translator; @@ -32,7 +31,6 @@ class GenerateCharsetCommandTest extends TestCase public function charactersAreGeneratedFromDefault() { $prefix = 'Character set: '; - $prefixLength = strlen($prefix); $this->commandTester->execute([ 'command' => 'config:generate-charset', @@ -40,13 +38,7 @@ class GenerateCharsetCommandTest extends TestCase $output = $this->commandTester->getDisplay(); // Both default character set and the new one should have the same length - $this->assertEquals($prefixLength + strlen(UrlShortener::DEFAULT_CHARS) + 1, strlen($output)); - - // Both default character set and the new one should have the same characters - $charset = substr($output, $prefixLength, strlen(UrlShortener::DEFAULT_CHARS)); - $orderedDefault = $this->orderStringLetters(UrlShortener::DEFAULT_CHARS); - $orderedCharset = $this->orderStringLetters($charset); - $this->assertEquals($orderedDefault, $orderedCharset); + $this->assertContains($prefix, $output); } protected function orderStringLetters($string) From 745ff511501835aa8e7aaa795320edb70367f8c8 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 28 Dec 2017 15:07:14 +0100 Subject: [PATCH 15/56] Ensured phpstan config is properly loaded in Ci envs --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5d3d8dc9..f3606d17 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,7 @@ before_script: script: - mkdir build - composer check - - if [[ $TRAVIS_PHP_VERSION = 7.1 ]] || [[ $TRAVIS_PHP_VERSION = 7.2 ]]; then ~/.composer/vendor/bin/phpstan analyse module/*/src/ --level=5; fi + - if [[ $TRAVIS_PHP_VERSION = 7.1 ]] || [[ $TRAVIS_PHP_VERSION = 7.2 ]]; then ~/.composer/vendor/bin/phpstan analyse module/*/src/ --level=5 -c phpstan.neon; fi after_script: - vendor/bin/phpcov merge build --clover build/clover.xml From 5de845c2585f240348bc2237ec8bd7eeaf6c9fba Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 28 Dec 2017 15:17:12 +0100 Subject: [PATCH 16/56] Improved GenerateSecretCommand by using SymfonyStyle --- module/CLI/src/Command/Config/GenerateSecretCommand.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/module/CLI/src/Command/Config/GenerateSecretCommand.php b/module/CLI/src/Command/Config/GenerateSecretCommand.php index 685ab4c5..684d6c8f 100644 --- a/module/CLI/src/Command/Config/GenerateSecretCommand.php +++ b/module/CLI/src/Command/Config/GenerateSecretCommand.php @@ -7,6 +7,7 @@ use Shlinkio\Shlink\Common\Util\StringUtilsTrait; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Zend\I18n\Translator\TranslatorInterface; class GenerateSecretCommand extends Command @@ -37,6 +38,8 @@ class GenerateSecretCommand extends Command public function execute(InputInterface $input, OutputInterface $output) { $secret = $this->generateRandomString(32); - $output->writeln($this->translator->translate('Secret key:') . sprintf(' %s', $secret)); + (new SymfonyStyle($input, $output))->success( + sprintf($this->translator->translate('Secret key: "%s"'), $secret) + ); } } From 4dffc9f0c197cf1c546230d2e8b867a6f91c0bd5 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 28 Dec 2017 15:52:10 +0100 Subject: [PATCH 17/56] Improved and simplified all installation process thanks to symfony style --- bin/install | 5 +- bin/update | 5 +- .../src/Command/Install/InstallCommand.php | 94 ++++++++++--------- .../src/Factory/InstallApplicationFactory.php | 8 +- .../Plugin/AbstractConfigCustomizerPlugin.php | 47 +--------- .../ApplicationConfigCustomizerPlugin.php | 12 +-- .../Plugin/DatabaseConfigCustomizerPlugin.php | 36 +++---- .../DefaultConfigCustomizerPluginFactory.php | 31 ------ .../Plugin/LanguageConfigCustomizerPlugin.php | 18 ++-- .../UrlShortenerConfigCustomizerPlugin.php | 27 +++--- ...faultConfigCustomizerPluginFactoryTest.php | 40 -------- 11 files changed, 102 insertions(+), 221 deletions(-) delete mode 100644 module/CLI/src/Install/Plugin/Factory/DefaultConfigCustomizerPluginFactory.php delete mode 100644 module/CLI/test/Install/Plugin/Factory/DefaultConfigCustomizerPluginFactoryTest.php diff --git a/bin/install b/bin/install index e8ecb3c9..952c5255 100755 --- a/bin/install +++ b/bin/install @@ -1,9 +1,9 @@ #!/usr/bin/env php [ Application::class => InstallApplicationFactory::class, Filesystem::class => InvokableFactory::class, - QuestionHelper::class => InvokableFactory::class, ], 'services' => [ 'config' => [ ConfigAbstractFactory::class => [ - DatabaseConfigCustomizerPlugin::class => [QuestionHelper::class, Filesystem::class] + DatabaseConfigCustomizerPlugin::class => [Filesystem::class] ], ], ], diff --git a/bin/update b/bin/update index d4203528..1534d3a2 100755 --- a/bin/update +++ b/bin/update @@ -1,9 +1,9 @@ #!/usr/bin/env php [ Application::class => InstallApplicationFactory::class, Filesystem::class => InvokableFactory::class, - QuestionHelper::class => InvokableFactory::class, ], 'services' => [ 'config' => [ ConfigAbstractFactory::class => [ - DatabaseConfigCustomizerPlugin::class => [QuestionHelper::class, Filesystem::class] + DatabaseConfigCustomizerPlugin::class => [Filesystem::class] ], ], ], diff --git a/module/CLI/src/Command/Install/InstallCommand.php b/module/CLI/src/Command/Install/InstallCommand.php index 6658678d..1784875b 100644 --- a/module/CLI/src/Command/Install/InstallCommand.php +++ b/module/CLI/src/Command/Install/InstallCommand.php @@ -3,6 +3,8 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\Install; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; use Shlinkio\Shlink\CLI\Install\ConfigCustomizerPluginManagerInterface; use Shlinkio\Shlink\CLI\Install\Plugin; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; @@ -10,11 +12,9 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Helper\ProcessHelper; -use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Question\ConfirmationQuestion; -use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Filesystem; use Zend\Config\Writer\WriterInterface; @@ -24,17 +24,9 @@ class InstallCommand extends Command const GENERATED_CONFIG_PATH = 'config/params/generated_config.php'; /** - * @var InputInterface + * @var SymfonyStyle */ - private $input; - /** - * @var OutputInterface - */ - private $output; - /** - * @var QuestionHelper - */ - private $questionHelper; + private $io; /** * @var ProcessHelper */ @@ -60,6 +52,7 @@ class InstallCommand extends Command * InstallCommand constructor. * @param WriterInterface $configWriter * @param Filesystem $filesystem + * @param ConfigCustomizerPluginManagerInterface $configCustomizers * @param bool $isUpdate * @throws LogicException */ @@ -83,30 +76,35 @@ class InstallCommand extends Command ->setDescription('Installs or updates Shlink'); } + /** + * @param InputInterface $input + * @param OutputInterface $output + * @return int|null|void + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ public function execute(InputInterface $input, OutputInterface $output) { - $this->input = $input; - $this->output = $output; - $this->questionHelper = $this->getHelper('question'); + $this->io = new SymfonyStyle($input, $output); $this->processHelper = $this->getHelper('process'); - $output->writeln([ + $this->io->writeln([ 'Welcome to Shlink!!', 'This will guide you through the installation process.', ]); // Check if a cached config file exists and drop it if so if ($this->filesystem->exists('data/cache/app_config.php')) { - $output->write('Deleting old cached config...'); + $this->io->write('Deleting old cached config...'); try { $this->filesystem->remove('data/cache/app_config.php'); - $output->writeln(' Success'); + $this->io->writeln(' Success'); } catch (IOException $e) { - $output->writeln( + $this->io->writeln( ' Failed! You will have to manually delete the data/cache/app_config.php file to get' . ' new config applied.' ); - if ($output->isVerbose()) { + if ($this->io->isVerbose()) { $this->getApplication()->renderException($e, $output); } return; @@ -130,28 +128,37 @@ class InstallCommand extends Command // Generate config params files $this->configWriter->toFile(self::GENERATED_CONFIG_PATH, $config->getArrayCopy(), false); - $output->writeln(['Custom configuration properly generated!', '']); + $this->io->writeln(['Custom configuration properly generated!', '']); // If current command is not update, generate database if (! $this->isUpdate) { - $this->output->writeln('Initializing database...'); + $this->io->writeln('Initializing database...'); if (! $this->runCommand( 'php vendor/bin/doctrine.php orm:schema-tool:create', - 'Error generating database.' + 'Error generating database.', + $output )) { return; } } // Run database migrations - $output->writeln('Updating database...'); - if (! $this->runCommand('php vendor/bin/doctrine-migrations migrations:migrate', 'Error updating database.')) { + $this->io->writeln('Updating database...'); + if (! $this->runCommand( + 'php vendor/bin/doctrine-migrations migrations:migrate', + 'Error updating database.', + $output + )) { return; } // Generate proxies - $output->writeln('Generating proxies...'); - if (! $this->runCommand('php vendor/bin/doctrine.php orm:generate-proxies', 'Error generating proxies.')) { + $this->io->writeln('Generating proxies...'); + if (! $this->runCommand( + 'php vendor/bin/doctrine.php orm:generate-proxies', + 'Error generating proxies.', + $output + )) { return; } } @@ -160,14 +167,14 @@ class InstallCommand extends Command * @return CustomizableAppConfig * @throws RuntimeException */ - private function importConfig() + private function importConfig(): CustomizableAppConfig { $config = new CustomizableAppConfig(); // Ask the user if he/she wants to import an older configuration - $importConfig = $this->questionHelper->ask($this->input, $this->output, new ConfirmationQuestion( + $importConfig = $this->io->confirm( 'Do you want to import previous configuration? (Y/n): ' - )); + ); if (! $importConfig) { return $config; } @@ -182,10 +189,10 @@ class InstallCommand extends Command $configExists = $this->filesystem->exists($configFile); if (! $configExists) { - $keepAsking = $this->questionHelper->ask($this->input, $this->output, new ConfirmationQuestion( + $keepAsking = $this->io->confirm( 'Provided path does not seem to be a valid shlink root path. ' . 'Do you want to try another path? (Y/n): ' - )); + ); } } while (! $configExists && $keepAsking); @@ -206,18 +213,16 @@ class InstallCommand extends Command * @return string * @throws RuntimeException */ - private function ask($text, $default = null, $allowEmpty = false) + private function ask($text, $default = null, $allowEmpty = false): string { if ($default !== null) { $text .= ' (defaults to ' . $default . ')'; } + do { - $value = $this->questionHelper->ask($this->input, $this->output, new Question( - '' . $text . ': ', - $default - )); + $value = $this->io->ask('' . $text . ': ', $default); if (empty($value) && ! $allowEmpty) { - $this->output->writeln('Value can\'t be empty'); + $this->io->writeln('Value can\'t be empty'); } } while (empty($value) && $default === null && ! $allowEmpty); @@ -227,21 +232,22 @@ class InstallCommand extends Command /** * @param string $command * @param string $errorMessage + * @param OutputInterface $output * @return bool */ - private function runCommand($command, $errorMessage) + private function runCommand($command, $errorMessage, OutputInterface $output): bool { - $process = $this->processHelper->run($this->output, $command); + $process = $this->processHelper->run($output, $command); if ($process->isSuccessful()) { - $this->output->writeln(' Success!'); + $this->io->writeln(' Success!'); return true; } - if ($this->output->isVerbose()) { + if ($this->io->isVerbose()) { return false; } - $this->output->writeln( + $this->io->writeln( ' ' . $errorMessage . ' Run this command with -vvv to see specific error info.' ); return false; diff --git a/module/CLI/src/Factory/InstallApplicationFactory.php b/module/CLI/src/Factory/InstallApplicationFactory.php index b1fc589b..b7fd3bb4 100644 --- a/module/CLI/src/Factory/InstallApplicationFactory.php +++ b/module/CLI/src/Factory/InstallApplicationFactory.php @@ -8,7 +8,6 @@ use Interop\Container\Exception\ContainerException; use Shlinkio\Shlink\CLI\Command\Install\InstallCommand; use Shlinkio\Shlink\CLI\Install\ConfigCustomizerPluginManager; use Shlinkio\Shlink\CLI\Install\Plugin; -use Shlinkio\Shlink\CLI\Install\Plugin\Factory\DefaultConfigCustomizerPluginFactory; use Symfony\Component\Console\Application; use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Filesystem\Filesystem; @@ -17,6 +16,7 @@ use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory; use Zend\ServiceManager\Exception\ServiceNotCreatedException; use Zend\ServiceManager\Exception\ServiceNotFoundException; use Zend\ServiceManager\Factory\FactoryInterface; +use Zend\ServiceManager\Factory\InvokableFactory; class InstallApplicationFactory implements FactoryInterface { @@ -43,9 +43,9 @@ class InstallApplicationFactory implements FactoryInterface $container->get(Filesystem::class), new ConfigCustomizerPluginManager($container, ['factories' => [ Plugin\DatabaseConfigCustomizerPlugin::class => ConfigAbstractFactory::class, - Plugin\UrlShortenerConfigCustomizerPlugin::class => DefaultConfigCustomizerPluginFactory::class, - Plugin\LanguageConfigCustomizerPlugin::class => DefaultConfigCustomizerPluginFactory::class, - Plugin\ApplicationConfigCustomizerPlugin::class => DefaultConfigCustomizerPluginFactory::class, + Plugin\UrlShortenerConfigCustomizerPlugin::class => InvokableFactory::class, + Plugin\LanguageConfigCustomizerPlugin::class => InvokableFactory::class, + Plugin\ApplicationConfigCustomizerPlugin::class => InvokableFactory::class, ]]), $isUpdate ); diff --git a/module/CLI/src/Install/Plugin/AbstractConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/AbstractConfigCustomizerPlugin.php index 224674d9..ed25eee1 100644 --- a/module/CLI/src/Install/Plugin/AbstractConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/AbstractConfigCustomizerPlugin.php @@ -3,66 +3,29 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Install\Plugin; -use Symfony\Component\Console\Exception\RuntimeException; -use Symfony\Component\Console\Helper\QuestionHelper; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Style\SymfonyStyle; abstract class AbstractConfigCustomizerPlugin implements ConfigCustomizerPluginInterface { /** - * @var QuestionHelper - */ - protected $questionHelper; - - public function __construct(QuestionHelper $questionHelper) - { - $this->questionHelper = $questionHelper; - } - - /** - * @param InputInterface $input - * @param OutputInterface $output + * @param SymfonyStyle $io * @param string $text * @param string|null $default * @param bool $allowEmpty * @return string - * @throws RuntimeException */ - protected function ask(InputInterface $input, OutputInterface $output, $text, $default = null, $allowEmpty = false) + protected function ask(SymfonyStyle $io, $text, $default = null, $allowEmpty = false): string { if ($default !== null) { $text .= ' (defaults to ' . $default . ')'; } do { - $value = $this->questionHelper->ask($input, $output, new Question( - '' . $text . ': ', - $default - )); + $value = $io->ask('' . $text . ': ', $default); if (empty($value) && ! $allowEmpty) { - $output->writeln('Value can\'t be empty'); + $io->writeln('Value can\'t be empty'); } } while (empty($value) && $default === null && ! $allowEmpty); return $value; } - - /** - * @param OutputInterface $output - * @param string $text - */ - protected function printTitle(OutputInterface $output, $text) - { - $text = trim($text); - $length = strlen($text) + 4; - $header = str_repeat('*', $length); - - $output->writeln([ - '', - '' . $header . '', - '* ' . strtoupper($text) . ' *', - '' . $header . '', - ]); - } } diff --git a/module/CLI/src/Install/Plugin/ApplicationConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/ApplicationConfigCustomizerPlugin.php index b1bf6436..add69c72 100644 --- a/module/CLI/src/Install/Plugin/ApplicationConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/ApplicationConfigCustomizerPlugin.php @@ -7,7 +7,7 @@ use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; use Shlinkio\Shlink\Common\Util\StringUtilsTrait; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Style\SymfonyStyle; class ApplicationConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin { @@ -22,18 +22,18 @@ class ApplicationConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin */ public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig) { - $this->printTitle($output, 'APPLICATION'); + $io = new SymfonyStyle($input, $output); + $io->title('APPLICATION'); - if ($appConfig->hasApp() && $this->questionHelper->ask($input, $output, new ConfirmationQuestion( + if ($appConfig->hasApp() && $io->confirm( 'Do you want to keep imported application config? (Y/n): ' - ))) { + )) { return; } $appConfig->setApp([ 'SECRET' => $this->ask( - $input, - $output, + $io, 'Define a secret string that will be used to sign API tokens (leave empty to autogenerate one)', null, true diff --git a/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php index 62c81c8f..c4c9f0fd 100644 --- a/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php @@ -3,14 +3,11 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Install\Plugin; -use Acelaya\ZsmAnnotatedServices\Annotation as DI; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; use Symfony\Component\Console\Exception\RuntimeException; -use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Question\ChoiceQuestion; -use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Filesystem; @@ -27,16 +24,8 @@ class DatabaseConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin */ private $filesystem; - /** - * DatabaseConfigCustomizerPlugin constructor. - * @param QuestionHelper $questionHelper - * @param Filesystem $filesystem - * - * @DI\Inject({QuestionHelper::class, Filesystem::class}) - */ - public function __construct(QuestionHelper $questionHelper, Filesystem $filesystem) + public function __construct(Filesystem $filesystem) { - parent::__construct($questionHelper); $this->filesystem = $filesystem; } @@ -50,11 +39,12 @@ class DatabaseConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin */ public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig) { - $this->printTitle($output, 'DATABASE'); + $io = new SymfonyStyle($input, $output); + $io->title('DATABASE'); - if ($appConfig->hasDatabase() && $this->questionHelper->ask($input, $output, new ConfirmationQuestion( + if ($appConfig->hasDatabase() && $io->confirm( 'Do you want to keep imported database config? (Y/n): ' - ))) { + )) { // If the user selected to keep DB config and is configured to use sqlite, copy DB file if ($appConfig->getDatabase()['DRIVER'] === self::DATABASE_DRIVERS['SQLite']) { try { @@ -74,20 +64,20 @@ class DatabaseConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin // Select database type $params = []; $databases = array_keys(self::DATABASE_DRIVERS); - $dbType = $this->questionHelper->ask($input, $output, new ChoiceQuestion( + $dbType = $io->choice( 'Select database type (defaults to ' . $databases[0] . '):', $databases, 0 - )); + ); $params['DRIVER'] = self::DATABASE_DRIVERS[$dbType]; // Ask for connection params if database is not SQLite if ($params['DRIVER'] !== self::DATABASE_DRIVERS['SQLite']) { - $params['NAME'] = $this->ask($input, $output, 'Database name', 'shlink'); - $params['USER'] = $this->ask($input, $output, 'Database username'); - $params['PASSWORD'] = $this->ask($input, $output, 'Database password'); - $params['HOST'] = $this->ask($input, $output, 'Database host', 'localhost'); - $params['PORT'] = $this->ask($input, $output, 'Database port', $this->getDefaultDbPort($params['DRIVER'])); + $params['NAME'] = $this->ask($io, 'Database name', 'shlink'); + $params['USER'] = $this->ask($io, 'Database username'); + $params['PASSWORD'] = $this->ask($io, 'Database password'); + $params['HOST'] = $this->ask($io, 'Database host', 'localhost'); + $params['PORT'] = $this->ask($io, 'Database port', $this->getDefaultDbPort($params['DRIVER'])); } $appConfig->setDatabase($params); diff --git a/module/CLI/src/Install/Plugin/Factory/DefaultConfigCustomizerPluginFactory.php b/module/CLI/src/Install/Plugin/Factory/DefaultConfigCustomizerPluginFactory.php deleted file mode 100644 index 6e1ea7a0..00000000 --- a/module/CLI/src/Install/Plugin/Factory/DefaultConfigCustomizerPluginFactory.php +++ /dev/null @@ -1,31 +0,0 @@ -get(QuestionHelper::class)); - } -} diff --git a/module/CLI/src/Install/Plugin/LanguageConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/LanguageConfigCustomizerPlugin.php index 83fbe7ad..4acee568 100644 --- a/module/CLI/src/Install/Plugin/LanguageConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/LanguageConfigCustomizerPlugin.php @@ -7,8 +7,7 @@ use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Question\ChoiceQuestion; -use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Style\SymfonyStyle; class LanguageConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin { @@ -23,27 +22,28 @@ class LanguageConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin */ public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig) { - $this->printTitle($output, 'LANGUAGE'); + $io = new SymfonyStyle($input, $output); + $io->title('LANGUAGE'); - if ($appConfig->hasLanguage() && $this->questionHelper->ask($input, $output, new ConfirmationQuestion( + if ($appConfig->hasLanguage() && $io->confirm( 'Do you want to keep imported language? (Y/n): ' - ))) { + )) { return; } $appConfig->setLanguage([ - 'DEFAULT' => $this->questionHelper->ask($input, $output, new ChoiceQuestion( + 'DEFAULT' => $io->choice( 'Select default language for the application in general (defaults to ' . self::SUPPORTED_LANGUAGES[0] . '):', self::SUPPORTED_LANGUAGES, 0 - )), - 'CLI' => $this->questionHelper->ask($input, $output, new ChoiceQuestion( + ), + 'CLI' => $io->choice( 'Select default language for CLI executions (defaults to ' . self::SUPPORTED_LANGUAGES[0] . '):', self::SUPPORTED_LANGUAGES, 0 - )), + ), ]); } } diff --git a/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizerPlugin.php index 3adcfc30..77ad6653 100644 --- a/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizerPlugin.php @@ -8,8 +8,7 @@ use Shlinkio\Shlink\Core\Service\UrlShortener; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Question\ChoiceQuestion; -use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Style\SymfonyStyle; class UrlShortenerConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin { @@ -22,35 +21,31 @@ class UrlShortenerConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin */ public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig) { - $this->printTitle($output, 'URL SHORTENER'); + $io = new SymfonyStyle($input, $output); + $io->title('URL SHORTENER'); - if ($appConfig->hasUrlShortener() && $this->questionHelper->ask($input, $output, new ConfirmationQuestion( + if ($appConfig->hasUrlShortener() && $io->confirm( 'Do you want to keep imported URL shortener config? (Y/n): ' - ))) { + )) { return; } // Ask for URL shortener params $appConfig->setUrlShortener([ - 'SCHEMA' => $this->questionHelper->ask($input, $output, new ChoiceQuestion( + 'SCHEMA' => $io->choice( 'Select schema for generated short URLs (defaults to http):', ['http', 'https'], 0 - )), - 'HOSTNAME' => $this->ask($input, $output, 'Hostname for generated URLs'), + ), + 'HOSTNAME' => $this->ask($io, 'Hostname for generated URLs'), 'CHARS' => $this->ask( - $input, - $output, + $io, 'Character set for generated short codes (leave empty to autogenerate one)', null, true ) ?: str_shuffle(UrlShortener::DEFAULT_CHARS), - 'VALIDATE_URL' => $this->questionHelper->ask( - $input, - $output, - new ConfirmationQuestion( - 'Do you want to validate long urls by 200 HTTP status code on response (Y/n):' - ) + 'VALIDATE_URL' => $io->confirm( + 'Do you want to validate long urls by 200 HTTP status code on response (Y/n):' ), ]); } diff --git a/module/CLI/test/Install/Plugin/Factory/DefaultConfigCustomizerPluginFactoryTest.php b/module/CLI/test/Install/Plugin/Factory/DefaultConfigCustomizerPluginFactoryTest.php deleted file mode 100644 index 509099ed..00000000 --- a/module/CLI/test/Install/Plugin/Factory/DefaultConfigCustomizerPluginFactoryTest.php +++ /dev/null @@ -1,40 +0,0 @@ -factory = new DefaultConfigCustomizerPluginFactory(); - } - - /** - * @test - */ - public function createsProperService() - { - $instance = $this->factory->__invoke(new ServiceManager(['services' => [ - QuestionHelper::class => $this->prophesize(QuestionHelper::class)->reveal(), - ]]), ApplicationConfigCustomizerPlugin::class); - $this->assertInstanceOf(ApplicationConfigCustomizerPlugin::class, $instance); - - $instance = $this->factory->__invoke(new ServiceManager(['services' => [ - QuestionHelper::class => $this->prophesize(QuestionHelper::class)->reveal(), - ]]), LanguageConfigCustomizerPlugin::class); - $this->assertInstanceOf(LanguageConfigCustomizerPlugin::class, $instance); - } -} From ede452533274fe7ec4dc76dcb5bcde7f261fb145 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 30 Dec 2017 21:35:26 +0100 Subject: [PATCH 18/56] Refactored exceptions to properly use package exceptions --- .../src/Exception/ExceptionInterface.php | 2 +- .../Exception/EntityDoesNotExistException.php | 4 +--- .../Core/src/Exception/ExceptionInterface.php | 8 +++++++ .../Exception/InvalidArgumentException.php | 8 +++++++ .../Exception/InvalidShortCodeException.php | 2 -- .../src/Exception/InvalidUrlException.php | 2 -- .../src/Exception/NonUniqueSlugException.php | 2 -- .../Core/src/Exception/RuntimeException.php | 8 +++++++ module/Core/src/Service/UrlShortener.php | 2 +- .../src/Service/UrlShortenerInterface.php | 2 +- module/Core/src/Service/VisitsTracker.php | 2 +- .../src/Service/VisitsTrackerInterface.php | 2 +- module/Core/test/Service/UrlShortenerTest.php | 2 +- .../src/Exception/AuthenticationException.php | 4 +--- .../Rest/src/Exception/ExceptionInterface.php | 8 +++++++ .../Rest/src/Exception/RuntimeException.php | 8 +++++++ .../src/Middleware/BodyParserMiddleware.php | 23 ++++++++++--------- module/Rest/src/Util/RestUtils.php | 2 +- 18 files changed, 61 insertions(+), 30 deletions(-) create mode 100644 module/Core/src/Exception/ExceptionInterface.php create mode 100644 module/Core/src/Exception/InvalidArgumentException.php create mode 100644 module/Core/src/Exception/RuntimeException.php create mode 100644 module/Rest/src/Exception/ExceptionInterface.php create mode 100644 module/Rest/src/Exception/RuntimeException.php diff --git a/module/Common/src/Exception/ExceptionInterface.php b/module/Common/src/Exception/ExceptionInterface.php index 72e53d0f..349f3f4b 100644 --- a/module/Common/src/Exception/ExceptionInterface.php +++ b/module/Common/src/Exception/ExceptionInterface.php @@ -3,6 +3,6 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Common\Exception; -interface ExceptionInterface +interface ExceptionInterface extends \Throwable { } diff --git a/module/Core/src/Exception/EntityDoesNotExistException.php b/module/Core/src/Exception/EntityDoesNotExistException.php index 732eaeba..ba4d233d 100644 --- a/module/Core/src/Exception/EntityDoesNotExistException.php +++ b/module/Core/src/Exception/EntityDoesNotExistException.php @@ -3,9 +3,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Exception; -use Shlinkio\Shlink\Common\Exception\ExceptionInterface; - -class EntityDoesNotExistException extends \RuntimeException implements ExceptionInterface +class EntityDoesNotExistException extends RuntimeException { public static function createFromEntityAndConditions($entityName, array $conditions) { diff --git a/module/Core/src/Exception/ExceptionInterface.php b/module/Core/src/Exception/ExceptionInterface.php new file mode 100644 index 00000000..2901b7cb --- /dev/null +++ b/module/Core/src/Exception/ExceptionInterface.php @@ -0,0 +1,8 @@ +getParsedBody(); // In requests that do not allow body or if the body has already been parsed, continue to next middleware - if (! empty($currentParams) || in_array($method, [ + if (! empty($currentParams) || \in_array($method, [ self::METHOD_GET, self::METHOD_HEAD, self::METHOD_OPTIONS, @@ -37,7 +37,7 @@ class BodyParserMiddleware implements MiddlewareInterface, RequestMethodInterfac // If the accepted content is JSON, try to parse the body from JSON $contentType = $this->getRequestContentType($request); - if (in_array($contentType, ['application/json', 'text/json', 'application/x-json'], true)) { + if (\in_array($contentType, ['application/json', 'text/json', 'application/x-json'], true)) { return $delegate->process($this->parseFromJson($request)); } @@ -48,27 +48,28 @@ class BodyParserMiddleware implements MiddlewareInterface, RequestMethodInterfac * @param Request $request * @return string */ - protected function getRequestContentType(Request $request) + private function getRequestContentType(Request $request): string { $contentType = $request->getHeaderLine('Content-type'); - $contentTypes = explode(';', $contentType); - return trim(array_shift($contentTypes)); + $contentTypes = \explode(';', $contentType); + return \trim(\array_shift($contentTypes)); } /** * @param Request $request * @return Request + * @throws RuntimeException */ - protected function parseFromJson(Request $request) + private function parseFromJson(Request $request): Request { $rawBody = (string) $request->getBody(); if (empty($rawBody)) { return $request; } - $parsedJson = json_decode($rawBody, true); - if (json_last_error() !== JSON_ERROR_NONE) { - throw new RuntimeException(sprintf('Error when parsing JSON request body: %s', json_last_error_msg())); + $parsedJson = \json_decode($rawBody, true); + if (\json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException(\sprintf('Error when parsing JSON request body: %s', \json_last_error_msg())); } return $request->withParsedBody($parsedJson); @@ -78,7 +79,7 @@ class BodyParserMiddleware implements MiddlewareInterface, RequestMethodInterfac * @param Request $request * @return Request */ - protected function parseFromUrlEncoded(Request $request) + private function parseFromUrlEncoded(Request $request): Request { $rawBody = (string) $request->getBody(); if (empty($rawBody)) { diff --git a/module/Rest/src/Util/RestUtils.php b/module/Rest/src/Util/RestUtils.php index dcd6632e..407b0845 100644 --- a/module/Rest/src/Util/RestUtils.php +++ b/module/Rest/src/Util/RestUtils.php @@ -20,7 +20,7 @@ class RestUtils const NOT_FOUND_ERROR = 'NOT_FOUND'; const UNKNOWN_ERROR = 'UNKNOWN_ERROR'; - public static function getRestErrorCodeFromException(Common\ExceptionInterface $e) + public static function getRestErrorCodeFromException(\Throwable $e) { switch (true) { case $e instanceof Core\InvalidShortCodeException: From 2ec807ba7018415e9b95d3a0cdf785d9460c6c28 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 30 Dec 2017 21:36:33 +0100 Subject: [PATCH 19/56] Increased phpstan required level --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f3606d17..6e5357c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,7 @@ before_script: script: - mkdir build - composer check - - if [[ $TRAVIS_PHP_VERSION = 7.1 ]] || [[ $TRAVIS_PHP_VERSION = 7.2 ]]; then ~/.composer/vendor/bin/phpstan analyse module/*/src/ --level=5 -c phpstan.neon; fi + - if [[ $TRAVIS_PHP_VERSION = 7.1 ]] || [[ $TRAVIS_PHP_VERSION = 7.2 ]]; then ~/.composer/vendor/bin/phpstan analyse module/*/src/ --level=6 -c phpstan.neon; fi after_script: - vendor/bin/phpcov merge build --clover build/clover.xml From 79427d08d7082cb7f9d669039baeef6ab6aff202 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 30 Dec 2017 21:39:18 +0100 Subject: [PATCH 20/56] Added phpstan config file to export-ignore list --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index 3946beb0..13bb2a91 100644 --- a/.gitattributes +++ b/.gitattributes @@ -22,3 +22,4 @@ indocker export-ignore phpcs.xml export-ignore phpunit.xml.dist export-ignore phpunit-func.xml export-ignore +phpstan.neon From b17f96043a6c2b3141597863e96c7f252edf51ed Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 16:53:18 +0100 Subject: [PATCH 21/56] Simplified and standardized DatabaseConfigCustomizerPlugin thanks to SymfonyStyle --- .../Plugin/AbstractConfigCustomizerPlugin.php | 2 +- .../Plugin/DatabaseConfigCustomizerPlugin.php | 24 ++++++++----------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/module/CLI/src/Install/Plugin/AbstractConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/AbstractConfigCustomizerPlugin.php index ed25eee1..d8ea5b85 100644 --- a/module/CLI/src/Install/Plugin/AbstractConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/AbstractConfigCustomizerPlugin.php @@ -20,7 +20,7 @@ abstract class AbstractConfigCustomizerPlugin implements ConfigCustomizerPluginI $text .= ' (defaults to ' . $default . ')'; } do { - $value = $io->ask('' . $text . ': ', $default); + $value = $io->ask($text, $default); if (empty($value) && ! $allowEmpty) { $io->writeln('Value can\'t be empty'); } diff --git a/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php index c4c9f0fd..bd1edda8 100644 --- a/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php @@ -43,7 +43,7 @@ class DatabaseConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin $io->title('DATABASE'); if ($appConfig->hasDatabase() && $io->confirm( - 'Do you want to keep imported database config? (Y/n): ' + 'Do you want to keep imported database config? (Y/n) ' )) { // If the user selected to keep DB config and is configured to use sqlite, copy DB file if ($appConfig->getDatabase()['DRIVER'] === self::DATABASE_DRIVERS['SQLite']) { @@ -53,7 +53,7 @@ class DatabaseConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin CustomizableAppConfig::SQLITE_DB_PATH ); } catch (IOException $e) { - $output->writeln('It wasn\'t possible to import the SQLite database'); + $io->error('It wasn\'t possible to import the SQLite database'); throw $e; } } @@ -63,27 +63,23 @@ class DatabaseConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin // Select database type $params = []; - $databases = array_keys(self::DATABASE_DRIVERS); - $dbType = $io->choice( - 'Select database type (defaults to ' . $databases[0] . '):', - $databases, - 0 - ); + $databases = \array_keys(self::DATABASE_DRIVERS); + $dbType = $io->choice('Select database type', $databases, $databases[0]); $params['DRIVER'] = self::DATABASE_DRIVERS[$dbType]; // Ask for connection params if database is not SQLite if ($params['DRIVER'] !== self::DATABASE_DRIVERS['SQLite']) { - $params['NAME'] = $this->ask($io, 'Database name', 'shlink'); - $params['USER'] = $this->ask($io, 'Database username'); - $params['PASSWORD'] = $this->ask($io, 'Database password'); - $params['HOST'] = $this->ask($io, 'Database host', 'localhost'); - $params['PORT'] = $this->ask($io, 'Database port', $this->getDefaultDbPort($params['DRIVER'])); + $params['NAME'] = $io->ask('Database name', 'shlink'); + $params['USER'] = $io->ask('Database username'); + $params['PASSWORD'] = $io->ask('Database password'); + $params['HOST'] = $io->ask('Database host', 'localhost'); + $params['PORT'] = $io->ask('Database port', $this->getDefaultDbPort($params['DRIVER'])); } $appConfig->setDatabase($params); } - private function getDefaultDbPort($driver) + private function getDefaultDbPort(string $driver): string { return $driver === 'pdo_mysql' ? '3306' : '5432'; } From 0a681f0efa68472205cdc4a15289ebd07978360e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 17:00:26 +0100 Subject: [PATCH 22/56] Simplified UrlShortenerConfigCustomizerPlugin thanks to SymfonyStyle --- .../Plugin/DatabaseConfigCustomizerPlugin.php | 4 +--- .../UrlShortenerConfigCustomizerPlugin.php | 23 ++++++++----------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php index bd1edda8..66144ccd 100644 --- a/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php @@ -42,9 +42,7 @@ class DatabaseConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin $io = new SymfonyStyle($input, $output); $io->title('DATABASE'); - if ($appConfig->hasDatabase() && $io->confirm( - 'Do you want to keep imported database config? (Y/n) ' - )) { + if ($appConfig->hasDatabase() && $io->confirm('Do you want to keep imported database config?')) { // If the user selected to keep DB config and is configured to use sqlite, copy DB file if ($appConfig->getDatabase()['DRIVER'] === self::DATABASE_DRIVERS['SQLite']) { try { diff --git a/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizerPlugin.php index 77ad6653..f457bfa8 100644 --- a/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizerPlugin.php @@ -24,29 +24,26 @@ class UrlShortenerConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin $io = new SymfonyStyle($input, $output); $io->title('URL SHORTENER'); - if ($appConfig->hasUrlShortener() && $io->confirm( - 'Do you want to keep imported URL shortener config? (Y/n): ' - )) { + if ($appConfig->hasUrlShortener() && $io->confirm('Do you want to keep imported URL shortener config?')) { return; } // Ask for URL shortener params $appConfig->setUrlShortener([ 'SCHEMA' => $io->choice( - 'Select schema for generated short URLs (defaults to http):', + 'Select schema for generated short URLs', ['http', 'https'], - 0 + 'http' ), - 'HOSTNAME' => $this->ask($io, 'Hostname for generated URLs'), - 'CHARS' => $this->ask( - $io, + 'HOSTNAME' => $io->ask('Hostname for generated URLs'), + 'CHARS' => $io->ask( 'Character set for generated short codes (leave empty to autogenerate one)', null, - true - ) ?: str_shuffle(UrlShortener::DEFAULT_CHARS), - 'VALIDATE_URL' => $io->confirm( - 'Do you want to validate long urls by 200 HTTP status code on response (Y/n):' - ), + function ($value) { + return $value; + } + ) ?: \str_shuffle(UrlShortener::DEFAULT_CHARS), + 'VALIDATE_URL' => $io->confirm('Do you want to validate long urls by 200 HTTP status code on response'), ]); } } From d275316acd422b8dd9bf6e48ffe345150ae537c1 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 17:07:39 +0100 Subject: [PATCH 23/56] Applied SymfonyStyle to all installation config customizers --- .../ApplicationConfigCustomizerPlugin.php | 12 ++++----- .../Plugin/DatabaseConfigCustomizerPlugin.php | 2 -- .../Plugin/LanguageConfigCustomizerPlugin.php | 25 ++++++------------- .../UrlShortenerConfigCustomizerPlugin.php | 2 -- 4 files changed, 13 insertions(+), 28 deletions(-) diff --git a/module/CLI/src/Install/Plugin/ApplicationConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/ApplicationConfigCustomizerPlugin.php index add69c72..232fe048 100644 --- a/module/CLI/src/Install/Plugin/ApplicationConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/ApplicationConfigCustomizerPlugin.php @@ -18,25 +18,23 @@ class ApplicationConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin * @param OutputInterface $output * @param CustomizableAppConfig $appConfig * @return void - * @throws \Symfony\Component\Console\Exception\RuntimeException */ public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig) { $io = new SymfonyStyle($input, $output); $io->title('APPLICATION'); - if ($appConfig->hasApp() && $io->confirm( - 'Do you want to keep imported application config? (Y/n): ' - )) { + if ($appConfig->hasApp() && $io->confirm('Do you want to keep imported application config?')) { return; } $appConfig->setApp([ - 'SECRET' => $this->ask( - $io, + 'SECRET' => $io->ask( 'Define a secret string that will be used to sign API tokens (leave empty to autogenerate one)', null, - true + function ($value) { + return $value; + } ) ?: $this->generateRandomString(32), ]); } diff --git a/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php index 66144ccd..edb843d0 100644 --- a/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Install\Plugin; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; -use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; @@ -35,7 +34,6 @@ class DatabaseConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin * @param CustomizableAppConfig $appConfig * @return void * @throws IOException - * @throws RuntimeException */ public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig) { diff --git a/module/CLI/src/Install/Plugin/LanguageConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/LanguageConfigCustomizerPlugin.php index 4acee568..5f37fd65 100644 --- a/module/CLI/src/Install/Plugin/LanguageConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/LanguageConfigCustomizerPlugin.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Install\Plugin; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; -use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; @@ -18,32 +17,24 @@ class LanguageConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin * @param OutputInterface $output * @param CustomizableAppConfig $appConfig * @return void - * @throws RuntimeException */ public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig) { $io = new SymfonyStyle($input, $output); $io->title('LANGUAGE'); - if ($appConfig->hasLanguage() && $io->confirm( - 'Do you want to keep imported language? (Y/n): ' - )) { + if ($appConfig->hasLanguage() && $io->confirm('Do you want to keep imported language?')) { return; } $appConfig->setLanguage([ - 'DEFAULT' => $io->choice( - 'Select default language for the application in general (defaults to ' - . self::SUPPORTED_LANGUAGES[0] . '):', - self::SUPPORTED_LANGUAGES, - 0 - ), - 'CLI' => $io->choice( - 'Select default language for CLI executions (defaults to ' - . self::SUPPORTED_LANGUAGES[0] . '):', - self::SUPPORTED_LANGUAGES, - 0 - ), + 'DEFAULT' => $this->chooseLanguage('Select default language for the application in general', $io), + 'CLI' => $this->chooseLanguage('Select default language for CLI executions', $io), ]); } + + private function chooseLanguage(string $message, SymfonyStyle $io): string + { + return $io->choice($message, self::SUPPORTED_LANGUAGES, self::SUPPORTED_LANGUAGES[0]); + } } diff --git a/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizerPlugin.php index f457bfa8..2be57759 100644 --- a/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizerPlugin.php @@ -5,7 +5,6 @@ namespace Shlinkio\Shlink\CLI\Install\Plugin; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; use Shlinkio\Shlink\Core\Service\UrlShortener; -use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; @@ -17,7 +16,6 @@ class UrlShortenerConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin * @param OutputInterface $output * @param CustomizableAppConfig $appConfig * @return void - * @throws RuntimeException */ public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig) { From 0e2ad0dbca49b1e88e9f44491755e3506b2de85e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 17:14:01 +0100 Subject: [PATCH 24/56] Updated ConfigCustomizer api to expect a SymfonyStyle object instead of a set of input and output --- module/CLI/src/Command/Install/InstallCommand.php | 2 +- .../Install/Plugin/ApplicationConfigCustomizerPlugin.php | 8 ++------ .../Install/Plugin/ConfigCustomizerPluginInterface.php | 8 +++----- .../Install/Plugin/DatabaseConfigCustomizerPlugin.php | 8 ++------ .../Install/Plugin/LanguageConfigCustomizerPlugin.php | 8 ++------ .../Plugin/UrlShortenerConfigCustomizerPlugin.php | 8 ++------ .../Plugin/ApplicationConfigCustomizerPluginTest.php | 7 ++++--- .../Plugin/DatabaseConfigCustomizerPluginTest.php | 9 +++++---- .../Plugin/LanguageConfigCustomizerPluginTest.php | 7 ++++--- .../Plugin/UrlShortenerConfigCustomizerPluginTest.php | 7 ++++--- 10 files changed, 29 insertions(+), 43 deletions(-) diff --git a/module/CLI/src/Command/Install/InstallCommand.php b/module/CLI/src/Command/Install/InstallCommand.php index 1784875b..0b641834 100644 --- a/module/CLI/src/Command/Install/InstallCommand.php +++ b/module/CLI/src/Command/Install/InstallCommand.php @@ -123,7 +123,7 @@ class InstallCommand extends Command ] as $pluginName) { /** @var Plugin\ConfigCustomizerPluginInterface $configCustomizer */ $configCustomizer = $this->configCustomizers->get($pluginName); - $configCustomizer->process($input, $output, $config); + $configCustomizer->process($this->io, $config); } // Generate config params files diff --git a/module/CLI/src/Install/Plugin/ApplicationConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/ApplicationConfigCustomizerPlugin.php index 232fe048..b507e652 100644 --- a/module/CLI/src/Install/Plugin/ApplicationConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/ApplicationConfigCustomizerPlugin.php @@ -5,8 +5,6 @@ namespace Shlinkio\Shlink\CLI\Install\Plugin; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; use Shlinkio\Shlink\Common\Util\StringUtilsTrait; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; class ApplicationConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin @@ -14,14 +12,12 @@ class ApplicationConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin use StringUtilsTrait; /** - * @param InputInterface $input - * @param OutputInterface $output + * @param SymfonyStyle $io * @param CustomizableAppConfig $appConfig * @return void */ - public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig) + public function process(SymfonyStyle $io, CustomizableAppConfig $appConfig) { - $io = new SymfonyStyle($input, $output); $io->title('APPLICATION'); if ($appConfig->hasApp() && $io->confirm('Do you want to keep imported application config?')) { diff --git a/module/CLI/src/Install/Plugin/ConfigCustomizerPluginInterface.php b/module/CLI/src/Install/Plugin/ConfigCustomizerPluginInterface.php index 5ad702fb..88c08f29 100644 --- a/module/CLI/src/Install/Plugin/ConfigCustomizerPluginInterface.php +++ b/module/CLI/src/Install/Plugin/ConfigCustomizerPluginInterface.php @@ -4,16 +4,14 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Install\Plugin; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; interface ConfigCustomizerPluginInterface { /** - * @param InputInterface $input - * @param OutputInterface $output + * @param SymfonyStyle $io * @param CustomizableAppConfig $appConfig * @return void */ - public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig); + public function process(SymfonyStyle $io, CustomizableAppConfig $appConfig); } diff --git a/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php index edb843d0..faf8c805 100644 --- a/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php @@ -4,8 +4,6 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Install\Plugin; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Filesystem; @@ -29,15 +27,13 @@ class DatabaseConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin } /** - * @param InputInterface $input - * @param OutputInterface $output + * @param SymfonyStyle $io * @param CustomizableAppConfig $appConfig * @return void * @throws IOException */ - public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig) + public function process(SymfonyStyle $io, CustomizableAppConfig $appConfig) { - $io = new SymfonyStyle($input, $output); $io->title('DATABASE'); if ($appConfig->hasDatabase() && $io->confirm('Do you want to keep imported database config?')) { diff --git a/module/CLI/src/Install/Plugin/LanguageConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/LanguageConfigCustomizerPlugin.php index 5f37fd65..d33c9f43 100644 --- a/module/CLI/src/Install/Plugin/LanguageConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/LanguageConfigCustomizerPlugin.php @@ -4,8 +4,6 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Install\Plugin; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; class LanguageConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin @@ -13,14 +11,12 @@ class LanguageConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin const SUPPORTED_LANGUAGES = ['en', 'es']; /** - * @param InputInterface $input - * @param OutputInterface $output + * @param SymfonyStyle $io * @param CustomizableAppConfig $appConfig * @return void */ - public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig) + public function process(SymfonyStyle $io, CustomizableAppConfig $appConfig) { - $io = new SymfonyStyle($input, $output); $io->title('LANGUAGE'); if ($appConfig->hasLanguage() && $io->confirm('Do you want to keep imported language?')) { diff --git a/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizerPlugin.php index 2be57759..b8ef1b8b 100644 --- a/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizerPlugin.php @@ -5,21 +5,17 @@ namespace Shlinkio\Shlink\CLI\Install\Plugin; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; use Shlinkio\Shlink\Core\Service\UrlShortener; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; class UrlShortenerConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin { /** - * @param InputInterface $input - * @param OutputInterface $output + * @param SymfonyStyle $io * @param CustomizableAppConfig $appConfig * @return void */ - public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig) + public function process(SymfonyStyle $io, CustomizableAppConfig $appConfig) { - $io = new SymfonyStyle($input, $output); $io->title('URL SHORTENER'); if ($appConfig->hasUrlShortener() && $io->confirm('Do you want to keep imported URL shortener config?')) { diff --git a/module/CLI/test/Install/Plugin/ApplicationConfigCustomizerPluginTest.php b/module/CLI/test/Install/Plugin/ApplicationConfigCustomizerPluginTest.php index 675dade7..96c60fef 100644 --- a/module/CLI/test/Install/Plugin/ApplicationConfigCustomizerPluginTest.php +++ b/module/CLI/test/Install/Plugin/ApplicationConfigCustomizerPluginTest.php @@ -13,6 +13,7 @@ use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Style\SymfonyStyle; class ApplicationConfigCustomizerPluginTest extends TestCase { @@ -40,7 +41,7 @@ class ApplicationConfigCustomizerPluginTest extends TestCase $askSecret = $this->questionHelper->ask(Argument::cetera())->willReturn('the_secret'); $config = new CustomizableAppConfig(); - $this->plugin->process(new ArrayInput([]), new NullOutput(), $config); + $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); $this->assertTrue($config->hasApp()); $this->assertEquals([ @@ -64,7 +65,7 @@ class ApplicationConfigCustomizerPluginTest extends TestCase 'SECRET' => 'foo', ]); - $this->plugin->process(new ArrayInput([]), new NullOutput(), $config); + $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); $this->assertEquals([ 'SECRET' => 'the_new_secret', @@ -85,7 +86,7 @@ class ApplicationConfigCustomizerPluginTest extends TestCase 'SECRET' => 'foo', ]); - $this->plugin->process(new ArrayInput([]), new NullOutput(), $config); + $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); $this->assertEquals([ 'SECRET' => 'foo', diff --git a/module/CLI/test/Install/Plugin/DatabaseConfigCustomizerPluginTest.php b/module/CLI/test/Install/Plugin/DatabaseConfigCustomizerPluginTest.php index bfaf2491..267f309a 100644 --- a/module/CLI/test/Install/Plugin/DatabaseConfigCustomizerPluginTest.php +++ b/module/CLI/test/Install/Plugin/DatabaseConfigCustomizerPluginTest.php @@ -13,6 +13,7 @@ use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Filesystem; class DatabaseConfigCustomizerPluginTest extends TestCase @@ -50,7 +51,7 @@ class DatabaseConfigCustomizerPluginTest extends TestCase $askSecret = $this->questionHelper->ask(Argument::cetera())->willReturn('MySQL'); $config = new CustomizableAppConfig(); - $this->plugin->process(new ArrayInput([]), new NullOutput(), $config); + $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); $this->assertTrue($config->hasDatabase()); $this->assertEquals([ @@ -84,7 +85,7 @@ class DatabaseConfigCustomizerPluginTest extends TestCase 'PORT' => 'MySQL', ]); - $this->plugin->process(new ArrayInput([]), new NullOutput(), $config); + $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); $this->assertEquals([ 'DRIVER' => 'pdo_mysql', @@ -115,7 +116,7 @@ class DatabaseConfigCustomizerPluginTest extends TestCase 'PORT' => 'MySQL', ]); - $this->plugin->process(new ArrayInput([]), new NullOutput(), $config); + $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); $this->assertEquals([ 'DRIVER' => 'pdo_pgsql', @@ -143,7 +144,7 @@ class DatabaseConfigCustomizerPluginTest extends TestCase 'DRIVER' => 'pdo_sqlite', ]); - $this->plugin->process(new ArrayInput([]), new NullOutput(), $config); + $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); $this->assertEquals([ 'DRIVER' => 'pdo_sqlite', diff --git a/module/CLI/test/Install/Plugin/LanguageConfigCustomizerPluginTest.php b/module/CLI/test/Install/Plugin/LanguageConfigCustomizerPluginTest.php index 0edc382e..eb81d22d 100644 --- a/module/CLI/test/Install/Plugin/LanguageConfigCustomizerPluginTest.php +++ b/module/CLI/test/Install/Plugin/LanguageConfigCustomizerPluginTest.php @@ -13,6 +13,7 @@ use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Style\SymfonyStyle; class LanguageConfigCustomizerPluginTest extends TestCase { @@ -40,7 +41,7 @@ class LanguageConfigCustomizerPluginTest extends TestCase $askSecret = $this->questionHelper->ask(Argument::cetera())->willReturn('en'); $config = new CustomizableAppConfig(); - $this->plugin->process(new ArrayInput([]), new NullOutput(), $config); + $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); $this->assertTrue($config->hasLanguage()); $this->assertEquals([ @@ -66,7 +67,7 @@ class LanguageConfigCustomizerPluginTest extends TestCase 'CLI' => 'en', ]); - $this->plugin->process(new ArrayInput([]), new NullOutput(), $config); + $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); $this->assertEquals([ 'DEFAULT' => 'es', @@ -89,7 +90,7 @@ class LanguageConfigCustomizerPluginTest extends TestCase 'CLI' => 'es', ]); - $this->plugin->process(new ArrayInput([]), new NullOutput(), $config); + $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); $this->assertEquals([ 'DEFAULT' => 'es', diff --git a/module/CLI/test/Install/Plugin/UrlShortenerConfigCustomizerPluginTest.php b/module/CLI/test/Install/Plugin/UrlShortenerConfigCustomizerPluginTest.php index 2c396db2..6a22b713 100644 --- a/module/CLI/test/Install/Plugin/UrlShortenerConfigCustomizerPluginTest.php +++ b/module/CLI/test/Install/Plugin/UrlShortenerConfigCustomizerPluginTest.php @@ -13,6 +13,7 @@ use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Style\SymfonyStyle; class UrlShortenerConfigCustomizerPluginTest extends TestCase { @@ -40,7 +41,7 @@ class UrlShortenerConfigCustomizerPluginTest extends TestCase $askSecret = $this->questionHelper->ask(Argument::cetera())->willReturn('something'); $config = new CustomizableAppConfig(); - $this->plugin->process(new ArrayInput([]), new NullOutput(), $config); + $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); $this->assertTrue($config->hasUrlShortener()); $this->assertEquals([ @@ -70,7 +71,7 @@ class UrlShortenerConfigCustomizerPluginTest extends TestCase 'VALIDATE_URL' => 'bar', ]); - $this->plugin->process(new ArrayInput([]), new NullOutput(), $config); + $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); $this->assertEquals([ 'SCHEMA' => 'foo', @@ -97,7 +98,7 @@ class UrlShortenerConfigCustomizerPluginTest extends TestCase 'VALIDATE_URL' => 'foo', ]); - $this->plugin->process(new ArrayInput([]), new NullOutput(), $config); + $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); $this->assertEquals([ 'SCHEMA' => 'foo', From 0f0213aa876daa106bfa1f0fe2af5e5c1ef1251d Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 17:18:54 +0100 Subject: [PATCH 25/56] Removed plugin suffix on config ustomizers --- bin/install | 4 ++-- bin/update | 4 ++-- module/CLI/src/Command/Install/InstallCommand.php | 10 +++++----- module/CLI/src/Factory/InstallApplicationFactory.php | 8 ++++---- .../CLI/src/Install/ConfigCustomizerPluginManager.php | 4 ++-- ...stomizerPlugin.php => AbstractConfigCustomizer.php} | 2 +- ...mizerPlugin.php => ApplicationConfigCustomizer.php} | 2 +- ...uginInterface.php => ConfigCustomizerInterface.php} | 2 +- ...stomizerPlugin.php => DatabaseConfigCustomizer.php} | 2 +- ...stomizerPlugin.php => LanguageConfigCustomizer.php} | 2 +- ...izerPlugin.php => UrlShortenerConfigCustomizer.php} | 2 +- module/CLI/test/Command/Install/InstallCommandTest.php | 4 ++-- .../Plugin/ApplicationConfigCustomizerPluginTest.php | 6 +++--- .../Plugin/DatabaseConfigCustomizerPluginTest.php | 6 +++--- .../Plugin/LanguageConfigCustomizerPluginTest.php | 6 +++--- .../Plugin/UrlShortenerConfigCustomizerPluginTest.php | 6 +++--- 16 files changed, 35 insertions(+), 35 deletions(-) rename module/CLI/src/Install/Plugin/{AbstractConfigCustomizerPlugin.php => AbstractConfigCustomizer.php} (89%) rename module/CLI/src/Install/Plugin/{ApplicationConfigCustomizerPlugin.php => ApplicationConfigCustomizer.php} (92%) rename module/CLI/src/Install/Plugin/{ConfigCustomizerPluginInterface.php => ConfigCustomizerInterface.php} (90%) rename module/CLI/src/Install/Plugin/{DatabaseConfigCustomizerPlugin.php => DatabaseConfigCustomizer.php} (97%) rename module/CLI/src/Install/Plugin/{LanguageConfigCustomizerPlugin.php => LanguageConfigCustomizer.php} (93%) rename module/CLI/src/Install/Plugin/{UrlShortenerConfigCustomizerPlugin.php => UrlShortenerConfigCustomizer.php} (94%) diff --git a/bin/install b/bin/install index 952c5255..d83df5f6 100755 --- a/bin/install +++ b/bin/install @@ -2,7 +2,7 @@ [ 'config' => [ ConfigAbstractFactory::class => [ - DatabaseConfigCustomizerPlugin::class => [Filesystem::class] + DatabaseConfigCustomizer::class => [Filesystem::class] ], ], ], diff --git a/bin/update b/bin/update index 1534d3a2..ce03fa16 100755 --- a/bin/update +++ b/bin/update @@ -2,7 +2,7 @@ [ 'config' => [ ConfigAbstractFactory::class => [ - DatabaseConfigCustomizerPlugin::class => [Filesystem::class] + DatabaseConfigCustomizer::class => [Filesystem::class] ], ], ], diff --git a/module/CLI/src/Command/Install/InstallCommand.php b/module/CLI/src/Command/Install/InstallCommand.php index 0b641834..59b4f639 100644 --- a/module/CLI/src/Command/Install/InstallCommand.php +++ b/module/CLI/src/Command/Install/InstallCommand.php @@ -116,12 +116,12 @@ class InstallCommand extends Command // Ask for custom config params foreach ([ - Plugin\DatabaseConfigCustomizerPlugin::class, - Plugin\UrlShortenerConfigCustomizerPlugin::class, - Plugin\LanguageConfigCustomizerPlugin::class, - Plugin\ApplicationConfigCustomizerPlugin::class, + Plugin\DatabaseConfigCustomizer::class, + Plugin\UrlShortenerConfigCustomizer::class, + Plugin\LanguageConfigCustomizer::class, + Plugin\ApplicationConfigCustomizer::class, ] as $pluginName) { - /** @var Plugin\ConfigCustomizerPluginInterface $configCustomizer */ + /** @var Plugin\ConfigCustomizerInterface $configCustomizer */ $configCustomizer = $this->configCustomizers->get($pluginName); $configCustomizer->process($this->io, $config); } diff --git a/module/CLI/src/Factory/InstallApplicationFactory.php b/module/CLI/src/Factory/InstallApplicationFactory.php index b7fd3bb4..0e620bc7 100644 --- a/module/CLI/src/Factory/InstallApplicationFactory.php +++ b/module/CLI/src/Factory/InstallApplicationFactory.php @@ -42,10 +42,10 @@ class InstallApplicationFactory implements FactoryInterface new PhpArray(), $container->get(Filesystem::class), new ConfigCustomizerPluginManager($container, ['factories' => [ - Plugin\DatabaseConfigCustomizerPlugin::class => ConfigAbstractFactory::class, - Plugin\UrlShortenerConfigCustomizerPlugin::class => InvokableFactory::class, - Plugin\LanguageConfigCustomizerPlugin::class => InvokableFactory::class, - Plugin\ApplicationConfigCustomizerPlugin::class => InvokableFactory::class, + Plugin\DatabaseConfigCustomizer::class => ConfigAbstractFactory::class, + Plugin\UrlShortenerConfigCustomizer::class => InvokableFactory::class, + Plugin\LanguageConfigCustomizer::class => InvokableFactory::class, + Plugin\ApplicationConfigCustomizer::class => InvokableFactory::class, ]]), $isUpdate ); diff --git a/module/CLI/src/Install/ConfigCustomizerPluginManager.php b/module/CLI/src/Install/ConfigCustomizerPluginManager.php index 45f2da9a..2e6a2672 100644 --- a/module/CLI/src/Install/ConfigCustomizerPluginManager.php +++ b/module/CLI/src/Install/ConfigCustomizerPluginManager.php @@ -3,10 +3,10 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Install; -use Shlinkio\Shlink\CLI\Install\Plugin\ConfigCustomizerPluginInterface; +use Shlinkio\Shlink\CLI\Install\Plugin\ConfigCustomizerInterface; use Zend\ServiceManager\AbstractPluginManager; class ConfigCustomizerPluginManager extends AbstractPluginManager implements ConfigCustomizerPluginManagerInterface { - protected $instanceOf = ConfigCustomizerPluginInterface::class; + protected $instanceOf = ConfigCustomizerInterface::class; } diff --git a/module/CLI/src/Install/Plugin/AbstractConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/AbstractConfigCustomizer.php similarity index 89% rename from module/CLI/src/Install/Plugin/AbstractConfigCustomizerPlugin.php rename to module/CLI/src/Install/Plugin/AbstractConfigCustomizer.php index d8ea5b85..f34164a8 100644 --- a/module/CLI/src/Install/Plugin/AbstractConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/AbstractConfigCustomizer.php @@ -5,7 +5,7 @@ namespace Shlinkio\Shlink\CLI\Install\Plugin; use Symfony\Component\Console\Style\SymfonyStyle; -abstract class AbstractConfigCustomizerPlugin implements ConfigCustomizerPluginInterface +abstract class AbstractConfigCustomizer implements ConfigCustomizerInterface { /** * @param SymfonyStyle $io diff --git a/module/CLI/src/Install/Plugin/ApplicationConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/ApplicationConfigCustomizer.php similarity index 92% rename from module/CLI/src/Install/Plugin/ApplicationConfigCustomizerPlugin.php rename to module/CLI/src/Install/Plugin/ApplicationConfigCustomizer.php index b507e652..4de42f0c 100644 --- a/module/CLI/src/Install/Plugin/ApplicationConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/ApplicationConfigCustomizer.php @@ -7,7 +7,7 @@ use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; use Shlinkio\Shlink\Common\Util\StringUtilsTrait; use Symfony\Component\Console\Style\SymfonyStyle; -class ApplicationConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin +class ApplicationConfigCustomizer extends AbstractConfigCustomizer { use StringUtilsTrait; diff --git a/module/CLI/src/Install/Plugin/ConfigCustomizerPluginInterface.php b/module/CLI/src/Install/Plugin/ConfigCustomizerInterface.php similarity index 90% rename from module/CLI/src/Install/Plugin/ConfigCustomizerPluginInterface.php rename to module/CLI/src/Install/Plugin/ConfigCustomizerInterface.php index 88c08f29..0a2bf054 100644 --- a/module/CLI/src/Install/Plugin/ConfigCustomizerPluginInterface.php +++ b/module/CLI/src/Install/Plugin/ConfigCustomizerInterface.php @@ -6,7 +6,7 @@ namespace Shlinkio\Shlink\CLI\Install\Plugin; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; use Symfony\Component\Console\Style\SymfonyStyle; -interface ConfigCustomizerPluginInterface +interface ConfigCustomizerInterface { /** * @param SymfonyStyle $io diff --git a/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/DatabaseConfigCustomizer.php similarity index 97% rename from module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php rename to module/CLI/src/Install/Plugin/DatabaseConfigCustomizer.php index faf8c805..8d30994b 100644 --- a/module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/DatabaseConfigCustomizer.php @@ -8,7 +8,7 @@ use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Filesystem; -class DatabaseConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin +class DatabaseConfigCustomizer extends AbstractConfigCustomizer { const DATABASE_DRIVERS = [ 'MySQL' => 'pdo_mysql', diff --git a/module/CLI/src/Install/Plugin/LanguageConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/LanguageConfigCustomizer.php similarity index 93% rename from module/CLI/src/Install/Plugin/LanguageConfigCustomizerPlugin.php rename to module/CLI/src/Install/Plugin/LanguageConfigCustomizer.php index d33c9f43..af785155 100644 --- a/module/CLI/src/Install/Plugin/LanguageConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/LanguageConfigCustomizer.php @@ -6,7 +6,7 @@ namespace Shlinkio\Shlink\CLI\Install\Plugin; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; use Symfony\Component\Console\Style\SymfonyStyle; -class LanguageConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin +class LanguageConfigCustomizer extends AbstractConfigCustomizer { const SUPPORTED_LANGUAGES = ['en', 'es']; diff --git a/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizerPlugin.php b/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizer.php similarity index 94% rename from module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizerPlugin.php rename to module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizer.php index b8ef1b8b..2716d832 100644 --- a/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizerPlugin.php +++ b/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizer.php @@ -7,7 +7,7 @@ use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; use Shlinkio\Shlink\Core\Service\UrlShortener; use Symfony\Component\Console\Style\SymfonyStyle; -class UrlShortenerConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin +class UrlShortenerConfigCustomizer extends AbstractConfigCustomizer { /** * @param SymfonyStyle $io diff --git a/module/CLI/test/Command/Install/InstallCommandTest.php b/module/CLI/test/Command/Install/InstallCommandTest.php index afe83383..45c19c7b 100644 --- a/module/CLI/test/Command/Install/InstallCommandTest.php +++ b/module/CLI/test/Command/Install/InstallCommandTest.php @@ -9,7 +9,7 @@ use Prophecy\Prophecy\MethodProphecy; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Install\InstallCommand; use Shlinkio\Shlink\CLI\Install\ConfigCustomizerPluginManagerInterface; -use Shlinkio\Shlink\CLI\Install\Plugin\ConfigCustomizerPluginInterface; +use Shlinkio\Shlink\CLI\Install\Plugin\ConfigCustomizerInterface; use Symfony\Component\Console\Application; use Symfony\Component\Console\Helper\ProcessHelper; use Symfony\Component\Console\Tester\CommandTester; @@ -51,7 +51,7 @@ class InstallCommandTest extends TestCase $this->configWriter = $this->prophesize(WriterInterface::class); - $configCustomizer = $this->prophesize(ConfigCustomizerPluginInterface::class); + $configCustomizer = $this->prophesize(ConfigCustomizerInterface::class); $configCustomizers = $this->prophesize(ConfigCustomizerPluginManagerInterface::class); $configCustomizers->get(Argument::cetera())->willReturn($configCustomizer->reveal()); diff --git a/module/CLI/test/Install/Plugin/ApplicationConfigCustomizerPluginTest.php b/module/CLI/test/Install/Plugin/ApplicationConfigCustomizerPluginTest.php index 96c60fef..f506af26 100644 --- a/module/CLI/test/Install/Plugin/ApplicationConfigCustomizerPluginTest.php +++ b/module/CLI/test/Install/Plugin/ApplicationConfigCustomizerPluginTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\MethodProphecy; use Prophecy\Prophecy\ObjectProphecy; -use Shlinkio\Shlink\CLI\Install\Plugin\ApplicationConfigCustomizerPlugin; +use Shlinkio\Shlink\CLI\Install\Plugin\ApplicationConfigCustomizer; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\ArrayInput; @@ -18,7 +18,7 @@ use Symfony\Component\Console\Style\SymfonyStyle; class ApplicationConfigCustomizerPluginTest extends TestCase { /** - * @var ApplicationConfigCustomizerPlugin + * @var ApplicationConfigCustomizer */ private $plugin; /** @@ -29,7 +29,7 @@ class ApplicationConfigCustomizerPluginTest extends TestCase public function setUp() { $this->questionHelper = $this->prophesize(QuestionHelper::class); - $this->plugin = new ApplicationConfigCustomizerPlugin($this->questionHelper->reveal()); + $this->plugin = new ApplicationConfigCustomizer($this->questionHelper->reveal()); } /** diff --git a/module/CLI/test/Install/Plugin/DatabaseConfigCustomizerPluginTest.php b/module/CLI/test/Install/Plugin/DatabaseConfigCustomizerPluginTest.php index 267f309a..097bc05d 100644 --- a/module/CLI/test/Install/Plugin/DatabaseConfigCustomizerPluginTest.php +++ b/module/CLI/test/Install/Plugin/DatabaseConfigCustomizerPluginTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\MethodProphecy; use Prophecy\Prophecy\ObjectProphecy; -use Shlinkio\Shlink\CLI\Install\Plugin\DatabaseConfigCustomizerPlugin; +use Shlinkio\Shlink\CLI\Install\Plugin\DatabaseConfigCustomizer; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\ArrayInput; @@ -19,7 +19,7 @@ use Symfony\Component\Filesystem\Filesystem; class DatabaseConfigCustomizerPluginTest extends TestCase { /** - * @var DatabaseConfigCustomizerPlugin + * @var DatabaseConfigCustomizer */ private $plugin; /** @@ -36,7 +36,7 @@ class DatabaseConfigCustomizerPluginTest extends TestCase $this->questionHelper = $this->prophesize(QuestionHelper::class); $this->filesystem = $this->prophesize(Filesystem::class); - $this->plugin = new DatabaseConfigCustomizerPlugin( + $this->plugin = new DatabaseConfigCustomizer( $this->questionHelper->reveal(), $this->filesystem->reveal() ); diff --git a/module/CLI/test/Install/Plugin/LanguageConfigCustomizerPluginTest.php b/module/CLI/test/Install/Plugin/LanguageConfigCustomizerPluginTest.php index eb81d22d..f98c6295 100644 --- a/module/CLI/test/Install/Plugin/LanguageConfigCustomizerPluginTest.php +++ b/module/CLI/test/Install/Plugin/LanguageConfigCustomizerPluginTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\MethodProphecy; use Prophecy\Prophecy\ObjectProphecy; -use Shlinkio\Shlink\CLI\Install\Plugin\LanguageConfigCustomizerPlugin; +use Shlinkio\Shlink\CLI\Install\Plugin\LanguageConfigCustomizer; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\ArrayInput; @@ -18,7 +18,7 @@ use Symfony\Component\Console\Style\SymfonyStyle; class LanguageConfigCustomizerPluginTest extends TestCase { /** - * @var LanguageConfigCustomizerPlugin + * @var LanguageConfigCustomizer */ protected $plugin; /** @@ -29,7 +29,7 @@ class LanguageConfigCustomizerPluginTest extends TestCase public function setUp() { $this->questionHelper = $this->prophesize(QuestionHelper::class); - $this->plugin = new LanguageConfigCustomizerPlugin($this->questionHelper->reveal()); + $this->plugin = new LanguageConfigCustomizer($this->questionHelper->reveal()); } /** diff --git a/module/CLI/test/Install/Plugin/UrlShortenerConfigCustomizerPluginTest.php b/module/CLI/test/Install/Plugin/UrlShortenerConfigCustomizerPluginTest.php index 6a22b713..3fc41a1e 100644 --- a/module/CLI/test/Install/Plugin/UrlShortenerConfigCustomizerPluginTest.php +++ b/module/CLI/test/Install/Plugin/UrlShortenerConfigCustomizerPluginTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\MethodProphecy; use Prophecy\Prophecy\ObjectProphecy; -use Shlinkio\Shlink\CLI\Install\Plugin\UrlShortenerConfigCustomizerPlugin; +use Shlinkio\Shlink\CLI\Install\Plugin\UrlShortenerConfigCustomizer; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\ArrayInput; @@ -18,7 +18,7 @@ use Symfony\Component\Console\Style\SymfonyStyle; class UrlShortenerConfigCustomizerPluginTest extends TestCase { /** - * @var UrlShortenerConfigCustomizerPlugin + * @var UrlShortenerConfigCustomizer */ private $plugin; /** @@ -29,7 +29,7 @@ class UrlShortenerConfigCustomizerPluginTest extends TestCase public function setUp() { $this->questionHelper = $this->prophesize(QuestionHelper::class); - $this->plugin = new UrlShortenerConfigCustomizerPlugin($this->questionHelper->reveal()); + $this->plugin = new UrlShortenerConfigCustomizer($this->questionHelper->reveal()); } /** From 5e3770c105bbc168de0cbb9471f95dc370a401cc Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 17:20:03 +0100 Subject: [PATCH 26/56] Renamed ConfigCustomizerPluginManager to CongigCustomizerManager --- module/CLI/src/Command/Install/InstallCommand.php | 8 ++++---- module/CLI/src/Factory/InstallApplicationFactory.php | 4 ++-- ...mizerPluginManager.php => ConfigCustomizerManager.php} | 2 +- ...Interface.php => ConfigCustomizerManagerInterface.php} | 2 +- module/CLI/test/Command/Install/InstallCommandTest.php | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) rename module/CLI/src/Install/{ConfigCustomizerPluginManager.php => ConfigCustomizerManager.php} (68%) rename module/CLI/src/Install/{ConfigCustomizerPluginManagerInterface.php => ConfigCustomizerManagerInterface.php} (60%) diff --git a/module/CLI/src/Command/Install/InstallCommand.php b/module/CLI/src/Command/Install/InstallCommand.php index 59b4f639..698cbc65 100644 --- a/module/CLI/src/Command/Install/InstallCommand.php +++ b/module/CLI/src/Command/Install/InstallCommand.php @@ -5,7 +5,7 @@ namespace Shlinkio\Shlink\CLI\Command\Install; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; -use Shlinkio\Shlink\CLI\Install\ConfigCustomizerPluginManagerInterface; +use Shlinkio\Shlink\CLI\Install\ConfigCustomizerManagerInterface; use Shlinkio\Shlink\CLI\Install\Plugin; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; use Symfony\Component\Console\Command\Command; @@ -40,7 +40,7 @@ class InstallCommand extends Command */ private $filesystem; /** - * @var ConfigCustomizerPluginManagerInterface + * @var ConfigCustomizerManagerInterface */ private $configCustomizers; /** @@ -52,14 +52,14 @@ class InstallCommand extends Command * InstallCommand constructor. * @param WriterInterface $configWriter * @param Filesystem $filesystem - * @param ConfigCustomizerPluginManagerInterface $configCustomizers + * @param ConfigCustomizerManagerInterface $configCustomizers * @param bool $isUpdate * @throws LogicException */ public function __construct( WriterInterface $configWriter, Filesystem $filesystem, - ConfigCustomizerPluginManagerInterface $configCustomizers, + ConfigCustomizerManagerInterface $configCustomizers, $isUpdate = false ) { parent::__construct(); diff --git a/module/CLI/src/Factory/InstallApplicationFactory.php b/module/CLI/src/Factory/InstallApplicationFactory.php index 0e620bc7..274100b4 100644 --- a/module/CLI/src/Factory/InstallApplicationFactory.php +++ b/module/CLI/src/Factory/InstallApplicationFactory.php @@ -6,7 +6,7 @@ namespace Shlinkio\Shlink\CLI\Factory; use Interop\Container\ContainerInterface; use Interop\Container\Exception\ContainerException; use Shlinkio\Shlink\CLI\Command\Install\InstallCommand; -use Shlinkio\Shlink\CLI\Install\ConfigCustomizerPluginManager; +use Shlinkio\Shlink\CLI\Install\ConfigCustomizerManager; use Shlinkio\Shlink\CLI\Install\Plugin; use Symfony\Component\Console\Application; use Symfony\Component\Console\Exception\LogicException; @@ -41,7 +41,7 @@ class InstallApplicationFactory implements FactoryInterface $command = new InstallCommand( new PhpArray(), $container->get(Filesystem::class), - new ConfigCustomizerPluginManager($container, ['factories' => [ + new ConfigCustomizerManager($container, ['factories' => [ Plugin\DatabaseConfigCustomizer::class => ConfigAbstractFactory::class, Plugin\UrlShortenerConfigCustomizer::class => InvokableFactory::class, Plugin\LanguageConfigCustomizer::class => InvokableFactory::class, diff --git a/module/CLI/src/Install/ConfigCustomizerPluginManager.php b/module/CLI/src/Install/ConfigCustomizerManager.php similarity index 68% rename from module/CLI/src/Install/ConfigCustomizerPluginManager.php rename to module/CLI/src/Install/ConfigCustomizerManager.php index 2e6a2672..ee4aedfa 100644 --- a/module/CLI/src/Install/ConfigCustomizerPluginManager.php +++ b/module/CLI/src/Install/ConfigCustomizerManager.php @@ -6,7 +6,7 @@ namespace Shlinkio\Shlink\CLI\Install; use Shlinkio\Shlink\CLI\Install\Plugin\ConfigCustomizerInterface; use Zend\ServiceManager\AbstractPluginManager; -class ConfigCustomizerPluginManager extends AbstractPluginManager implements ConfigCustomizerPluginManagerInterface +class ConfigCustomizerManager extends AbstractPluginManager implements ConfigCustomizerManagerInterface { protected $instanceOf = ConfigCustomizerInterface::class; } diff --git a/module/CLI/src/Install/ConfigCustomizerPluginManagerInterface.php b/module/CLI/src/Install/ConfigCustomizerManagerInterface.php similarity index 60% rename from module/CLI/src/Install/ConfigCustomizerPluginManagerInterface.php rename to module/CLI/src/Install/ConfigCustomizerManagerInterface.php index 3085a75b..bbeedca8 100644 --- a/module/CLI/src/Install/ConfigCustomizerPluginManagerInterface.php +++ b/module/CLI/src/Install/ConfigCustomizerManagerInterface.php @@ -5,6 +5,6 @@ namespace Shlinkio\Shlink\CLI\Install; use Psr\Container\ContainerInterface; -interface ConfigCustomizerPluginManagerInterface extends ContainerInterface +interface ConfigCustomizerManagerInterface extends ContainerInterface { } diff --git a/module/CLI/test/Command/Install/InstallCommandTest.php b/module/CLI/test/Command/Install/InstallCommandTest.php index 45c19c7b..a4c49f9e 100644 --- a/module/CLI/test/Command/Install/InstallCommandTest.php +++ b/module/CLI/test/Command/Install/InstallCommandTest.php @@ -8,7 +8,7 @@ use Prophecy\Argument; use Prophecy\Prophecy\MethodProphecy; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Install\InstallCommand; -use Shlinkio\Shlink\CLI\Install\ConfigCustomizerPluginManagerInterface; +use Shlinkio\Shlink\CLI\Install\ConfigCustomizerManagerInterface; use Shlinkio\Shlink\CLI\Install\Plugin\ConfigCustomizerInterface; use Symfony\Component\Console\Application; use Symfony\Component\Console\Helper\ProcessHelper; @@ -52,7 +52,7 @@ class InstallCommandTest extends TestCase $this->configWriter = $this->prophesize(WriterInterface::class); $configCustomizer = $this->prophesize(ConfigCustomizerInterface::class); - $configCustomizers = $this->prophesize(ConfigCustomizerPluginManagerInterface::class); + $configCustomizers = $this->prophesize(ConfigCustomizerManagerInterface::class); $configCustomizers->get(Argument::cetera())->willReturn($configCustomizer->reveal()); $app = new Application(); From 270507006393ea6296c5e9aaa1b04099783d45f5 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 17:22:25 +0100 Subject: [PATCH 27/56] Renamed tests --- ...omizerPluginTest.php => ApplicationConfigCustomizerTest.php} | 2 +- ...ustomizerPluginTest.php => DatabaseConfigCustomizerTest.php} | 2 +- ...ustomizerPluginTest.php => LanguageConfigCustomizerTest.php} | 2 +- ...mizerPluginTest.php => UrlShortenerConfigCustomizerTest.php} | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename module/CLI/test/Install/Plugin/{ApplicationConfigCustomizerPluginTest.php => ApplicationConfigCustomizerTest.php} (97%) rename module/CLI/test/Install/Plugin/{DatabaseConfigCustomizerPluginTest.php => DatabaseConfigCustomizerTest.php} (98%) rename module/CLI/test/Install/Plugin/{LanguageConfigCustomizerPluginTest.php => LanguageConfigCustomizerTest.php} (98%) rename module/CLI/test/Install/Plugin/{UrlShortenerConfigCustomizerPluginTest.php => UrlShortenerConfigCustomizerTest.php} (98%) diff --git a/module/CLI/test/Install/Plugin/ApplicationConfigCustomizerPluginTest.php b/module/CLI/test/Install/Plugin/ApplicationConfigCustomizerTest.php similarity index 97% rename from module/CLI/test/Install/Plugin/ApplicationConfigCustomizerPluginTest.php rename to module/CLI/test/Install/Plugin/ApplicationConfigCustomizerTest.php index f506af26..1c2d4da6 100644 --- a/module/CLI/test/Install/Plugin/ApplicationConfigCustomizerPluginTest.php +++ b/module/CLI/test/Install/Plugin/ApplicationConfigCustomizerTest.php @@ -15,7 +15,7 @@ use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; -class ApplicationConfigCustomizerPluginTest extends TestCase +class ApplicationConfigCustomizerTest extends TestCase { /** * @var ApplicationConfigCustomizer diff --git a/module/CLI/test/Install/Plugin/DatabaseConfigCustomizerPluginTest.php b/module/CLI/test/Install/Plugin/DatabaseConfigCustomizerTest.php similarity index 98% rename from module/CLI/test/Install/Plugin/DatabaseConfigCustomizerPluginTest.php rename to module/CLI/test/Install/Plugin/DatabaseConfigCustomizerTest.php index 097bc05d..98cf888a 100644 --- a/module/CLI/test/Install/Plugin/DatabaseConfigCustomizerPluginTest.php +++ b/module/CLI/test/Install/Plugin/DatabaseConfigCustomizerTest.php @@ -16,7 +16,7 @@ use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Filesystem; -class DatabaseConfigCustomizerPluginTest extends TestCase +class DatabaseConfigCustomizerTest extends TestCase { /** * @var DatabaseConfigCustomizer diff --git a/module/CLI/test/Install/Plugin/LanguageConfigCustomizerPluginTest.php b/module/CLI/test/Install/Plugin/LanguageConfigCustomizerTest.php similarity index 98% rename from module/CLI/test/Install/Plugin/LanguageConfigCustomizerPluginTest.php rename to module/CLI/test/Install/Plugin/LanguageConfigCustomizerTest.php index f98c6295..ad39537a 100644 --- a/module/CLI/test/Install/Plugin/LanguageConfigCustomizerPluginTest.php +++ b/module/CLI/test/Install/Plugin/LanguageConfigCustomizerTest.php @@ -15,7 +15,7 @@ use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; -class LanguageConfigCustomizerPluginTest extends TestCase +class LanguageConfigCustomizerTest extends TestCase { /** * @var LanguageConfigCustomizer diff --git a/module/CLI/test/Install/Plugin/UrlShortenerConfigCustomizerPluginTest.php b/module/CLI/test/Install/Plugin/UrlShortenerConfigCustomizerTest.php similarity index 98% rename from module/CLI/test/Install/Plugin/UrlShortenerConfigCustomizerPluginTest.php rename to module/CLI/test/Install/Plugin/UrlShortenerConfigCustomizerTest.php index 3fc41a1e..ff5c1404 100644 --- a/module/CLI/test/Install/Plugin/UrlShortenerConfigCustomizerPluginTest.php +++ b/module/CLI/test/Install/Plugin/UrlShortenerConfigCustomizerTest.php @@ -15,7 +15,7 @@ use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; -class UrlShortenerConfigCustomizerPluginTest extends TestCase +class UrlShortenerConfigCustomizerTest extends TestCase { /** * @var UrlShortenerConfigCustomizer From 4d4aafa6db3d1811620d0f48beac48038b7cd839 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 17:45:27 +0100 Subject: [PATCH 28/56] Fixed config customizer tests --- .../Plugin/AbstractConfigCustomizer.php | 31 --------- .../Plugin/ApplicationConfigCustomizer.php | 2 +- .../Plugin/DatabaseConfigCustomizer.php | 2 +- .../Plugin/LanguageConfigCustomizer.php | 2 +- .../Plugin/UrlShortenerConfigCustomizer.php | 2 +- .../ApplicationConfigCustomizerTest.php | 37 +++++------ .../Plugin/DatabaseConfigCustomizerTest.php | 63 ++++++++----------- .../Plugin/LanguageConfigCustomizerTest.php | 36 +++++------ .../UrlShortenerConfigCustomizerTest.php | 48 +++++++------- 9 files changed, 83 insertions(+), 140 deletions(-) delete mode 100644 module/CLI/src/Install/Plugin/AbstractConfigCustomizer.php diff --git a/module/CLI/src/Install/Plugin/AbstractConfigCustomizer.php b/module/CLI/src/Install/Plugin/AbstractConfigCustomizer.php deleted file mode 100644 index f34164a8..00000000 --- a/module/CLI/src/Install/Plugin/AbstractConfigCustomizer.php +++ /dev/null @@ -1,31 +0,0 @@ -ask($text, $default); - if (empty($value) && ! $allowEmpty) { - $io->writeln('Value can\'t be empty'); - } - } while (empty($value) && $default === null && ! $allowEmpty); - - return $value; - } -} diff --git a/module/CLI/src/Install/Plugin/ApplicationConfigCustomizer.php b/module/CLI/src/Install/Plugin/ApplicationConfigCustomizer.php index 4de42f0c..6a6e1a45 100644 --- a/module/CLI/src/Install/Plugin/ApplicationConfigCustomizer.php +++ b/module/CLI/src/Install/Plugin/ApplicationConfigCustomizer.php @@ -7,7 +7,7 @@ use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; use Shlinkio\Shlink\Common\Util\StringUtilsTrait; use Symfony\Component\Console\Style\SymfonyStyle; -class ApplicationConfigCustomizer extends AbstractConfigCustomizer +class ApplicationConfigCustomizer implements ConfigCustomizerInterface { use StringUtilsTrait; diff --git a/module/CLI/src/Install/Plugin/DatabaseConfigCustomizer.php b/module/CLI/src/Install/Plugin/DatabaseConfigCustomizer.php index 8d30994b..9e226e27 100644 --- a/module/CLI/src/Install/Plugin/DatabaseConfigCustomizer.php +++ b/module/CLI/src/Install/Plugin/DatabaseConfigCustomizer.php @@ -8,7 +8,7 @@ use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Filesystem; -class DatabaseConfigCustomizer extends AbstractConfigCustomizer +class DatabaseConfigCustomizer implements ConfigCustomizerInterface { const DATABASE_DRIVERS = [ 'MySQL' => 'pdo_mysql', diff --git a/module/CLI/src/Install/Plugin/LanguageConfigCustomizer.php b/module/CLI/src/Install/Plugin/LanguageConfigCustomizer.php index af785155..15125d32 100644 --- a/module/CLI/src/Install/Plugin/LanguageConfigCustomizer.php +++ b/module/CLI/src/Install/Plugin/LanguageConfigCustomizer.php @@ -6,7 +6,7 @@ namespace Shlinkio\Shlink\CLI\Install\Plugin; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; use Symfony\Component\Console\Style\SymfonyStyle; -class LanguageConfigCustomizer extends AbstractConfigCustomizer +class LanguageConfigCustomizer implements ConfigCustomizerInterface { const SUPPORTED_LANGUAGES = ['en', 'es']; diff --git a/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizer.php b/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizer.php index 2716d832..d010667d 100644 --- a/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizer.php +++ b/module/CLI/src/Install/Plugin/UrlShortenerConfigCustomizer.php @@ -7,7 +7,7 @@ use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; use Shlinkio\Shlink\Core\Service\UrlShortener; use Symfony\Component\Console\Style\SymfonyStyle; -class UrlShortenerConfigCustomizer extends AbstractConfigCustomizer +class UrlShortenerConfigCustomizer implements ConfigCustomizerInterface { /** * @param SymfonyStyle $io diff --git a/module/CLI/test/Install/Plugin/ApplicationConfigCustomizerTest.php b/module/CLI/test/Install/Plugin/ApplicationConfigCustomizerTest.php index 1c2d4da6..a156923a 100644 --- a/module/CLI/test/Install/Plugin/ApplicationConfigCustomizerTest.php +++ b/module/CLI/test/Install/Plugin/ApplicationConfigCustomizerTest.php @@ -5,14 +5,9 @@ namespace ShlinkioTest\Shlink\CLI\Install\Plugin; use PHPUnit\Framework\TestCase; use Prophecy\Argument; -use Prophecy\Prophecy\MethodProphecy; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Install\Plugin\ApplicationConfigCustomizer; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; -use Symfony\Component\Console\Helper\QuestionHelper; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\NullOutput; -use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; class ApplicationConfigCustomizerTest extends TestCase @@ -24,12 +19,14 @@ class ApplicationConfigCustomizerTest extends TestCase /** * @var ObjectProphecy */ - private $questionHelper; + private $io; public function setUp() { - $this->questionHelper = $this->prophesize(QuestionHelper::class); - $this->plugin = new ApplicationConfigCustomizer($this->questionHelper->reveal()); + $this->io = $this->prophesize(SymfonyStyle::class); + $this->io->title(Argument::any())->willReturn(null); + + $this->plugin = new ApplicationConfigCustomizer(); } /** @@ -37,11 +34,10 @@ class ApplicationConfigCustomizerTest extends TestCase */ public function configIsRequestedToTheUser() { - /** @var MethodProphecy $askSecret */ - $askSecret = $this->questionHelper->ask(Argument::cetera())->willReturn('the_secret'); + $askSecret = $this->io->ask(Argument::cetera())->willReturn('the_secret'); $config = new CustomizableAppConfig(); - $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); + $this->plugin->process($this->io->reveal(), $config); $this->assertTrue($config->hasApp()); $this->assertEquals([ @@ -55,22 +51,20 @@ class ApplicationConfigCustomizerTest extends TestCase */ public function overwriteIsRequestedIfValueIsAlreadySet() { - /** @var MethodProphecy $ask */ - $ask = $this->questionHelper->ask(Argument::cetera())->will(function (array $args) { - $last = array_pop($args); - return $last instanceof ConfirmationQuestion ? false : 'the_new_secret'; - }); + $ask = $this->io->ask(Argument::cetera())->willReturn('the_new_secret'); + $confirm = $this->io->confirm(Argument::cetera())->willReturn(false); $config = new CustomizableAppConfig(); $config->setApp([ 'SECRET' => 'foo', ]); - $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); + $this->plugin->process($this->io->reveal(), $config); $this->assertEquals([ 'SECRET' => 'the_new_secret', ], $config->getApp()); - $ask->shouldHaveBeenCalledTimes(2); + $ask->shouldHaveBeenCalledTimes(1); + $confirm->shouldHaveBeenCalledTimes(1); } /** @@ -78,19 +72,18 @@ class ApplicationConfigCustomizerTest extends TestCase */ public function existingValueIsKeptIfRequested() { - /** @var MethodProphecy $ask */ - $ask = $this->questionHelper->ask(Argument::cetera())->willReturn(true); + $confirm = $this->io->confirm(Argument::cetera())->willReturn(true); $config = new CustomizableAppConfig(); $config->setApp([ 'SECRET' => 'foo', ]); - $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); + $this->plugin->process($this->io->reveal(), $config); $this->assertEquals([ 'SECRET' => 'foo', ], $config->getApp()); - $ask->shouldHaveBeenCalledTimes(1); + $confirm->shouldHaveBeenCalledTimes(1); } } diff --git a/module/CLI/test/Install/Plugin/DatabaseConfigCustomizerTest.php b/module/CLI/test/Install/Plugin/DatabaseConfigCustomizerTest.php index 98cf888a..17835666 100644 --- a/module/CLI/test/Install/Plugin/DatabaseConfigCustomizerTest.php +++ b/module/CLI/test/Install/Plugin/DatabaseConfigCustomizerTest.php @@ -5,14 +5,9 @@ namespace ShlinkioTest\Shlink\CLI\Install\Plugin; use PHPUnit\Framework\TestCase; use Prophecy\Argument; -use Prophecy\Prophecy\MethodProphecy; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Install\Plugin\DatabaseConfigCustomizer; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; -use Symfony\Component\Console\Helper\QuestionHelper; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\NullOutput; -use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Filesystem; @@ -25,7 +20,7 @@ class DatabaseConfigCustomizerTest extends TestCase /** * @var ObjectProphecy */ - private $questionHelper; + private $io; /** * @var ObjectProphecy */ @@ -33,13 +28,11 @@ class DatabaseConfigCustomizerTest extends TestCase public function setUp() { - $this->questionHelper = $this->prophesize(QuestionHelper::class); + $this->io = $this->prophesize(SymfonyStyle::class); + $this->io->title(Argument::any())->willReturn(null); $this->filesystem = $this->prophesize(Filesystem::class); - $this->plugin = new DatabaseConfigCustomizer( - $this->questionHelper->reveal(), - $this->filesystem->reveal() - ); + $this->plugin = new DatabaseConfigCustomizer($this->filesystem->reveal()); } /** @@ -47,22 +40,23 @@ class DatabaseConfigCustomizerTest extends TestCase */ public function configIsRequestedToTheUser() { - /** @var MethodProphecy $askSecret */ - $askSecret = $this->questionHelper->ask(Argument::cetera())->willReturn('MySQL'); + $choice = $this->io->choice(Argument::cetera())->willReturn('MySQL'); + $ask = $this->io->ask(Argument::cetera())->willReturn('param'); $config = new CustomizableAppConfig(); - $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); + $this->plugin->process($this->io->reveal(), $config); $this->assertTrue($config->hasDatabase()); $this->assertEquals([ 'DRIVER' => 'pdo_mysql', - 'NAME' => 'MySQL', - 'USER' => 'MySQL', - 'PASSWORD' => 'MySQL', - 'HOST' => 'MySQL', - 'PORT' => 'MySQL', + 'NAME' => 'param', + 'USER' => 'param', + 'PASSWORD' => 'param', + 'HOST' => 'param', + 'PORT' => 'param', ], $config->getDatabase()); - $askSecret->shouldHaveBeenCalledTimes(6); + $choice->shouldHaveBeenCalledTimes(1); + $ask->shouldHaveBeenCalledTimes(5); } /** @@ -70,11 +64,9 @@ class DatabaseConfigCustomizerTest extends TestCase */ public function overwriteIsRequestedIfValueIsAlreadySet() { - /** @var MethodProphecy $ask */ - $ask = $this->questionHelper->ask(Argument::cetera())->will(function (array $args) { - $last = array_pop($args); - return $last instanceof ConfirmationQuestion ? false : 'MySQL'; - }); + $choice = $this->io->choice(Argument::cetera())->willReturn('MySQL'); + $confirm = $this->io->confirm(Argument::cetera())->willReturn(false); + $ask = $this->io->ask(Argument::cetera())->willReturn('MySQL'); $config = new CustomizableAppConfig(); $config->setDatabase([ 'DRIVER' => 'pdo_pgsql', @@ -85,7 +77,7 @@ class DatabaseConfigCustomizerTest extends TestCase 'PORT' => 'MySQL', ]); - $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); + $this->plugin->process($this->io->reveal(), $config); $this->assertEquals([ 'DRIVER' => 'pdo_mysql', @@ -95,7 +87,9 @@ class DatabaseConfigCustomizerTest extends TestCase 'HOST' => 'MySQL', 'PORT' => 'MySQL', ], $config->getDatabase()); - $ask->shouldHaveBeenCalledTimes(7); + $confirm->shouldHaveBeenCalledTimes(1); + $choice->shouldHaveBeenCalledTimes(1); + $ask->shouldHaveBeenCalledTimes(5); } /** @@ -103,8 +97,7 @@ class DatabaseConfigCustomizerTest extends TestCase */ public function existingValueIsKeptIfRequested() { - /** @var MethodProphecy $ask */ - $ask = $this->questionHelper->ask(Argument::cetera())->willReturn(true); + $confirm = $this->io->confirm(Argument::cetera())->willReturn(true); $config = new CustomizableAppConfig(); $config->setDatabase([ @@ -116,7 +109,7 @@ class DatabaseConfigCustomizerTest extends TestCase 'PORT' => 'MySQL', ]); - $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); + $this->plugin->process($this->io->reveal(), $config); $this->assertEquals([ 'DRIVER' => 'pdo_pgsql', @@ -126,7 +119,7 @@ class DatabaseConfigCustomizerTest extends TestCase 'HOST' => 'MySQL', 'PORT' => 'MySQL', ], $config->getDatabase()); - $ask->shouldHaveBeenCalledTimes(1); + $confirm->shouldHaveBeenCalledTimes(1); } /** @@ -134,9 +127,7 @@ class DatabaseConfigCustomizerTest extends TestCase */ public function sqliteDatabaseIsImportedWhenRequested() { - /** @var MethodProphecy $ask */ - $ask = $this->questionHelper->ask(Argument::cetera())->willReturn(true); - /** @var MethodProphecy $copy */ + $confirm = $this->io->confirm(Argument::cetera())->willReturn(true); $copy = $this->filesystem->copy(Argument::cetera())->willReturn(null); $config = new CustomizableAppConfig(); @@ -144,12 +135,12 @@ class DatabaseConfigCustomizerTest extends TestCase 'DRIVER' => 'pdo_sqlite', ]); - $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); + $this->plugin->process($this->io->reveal(), $config); $this->assertEquals([ 'DRIVER' => 'pdo_sqlite', ], $config->getDatabase()); - $ask->shouldHaveBeenCalledTimes(1); + $confirm->shouldHaveBeenCalledTimes(1); $copy->shouldHaveBeenCalledTimes(1); } } diff --git a/module/CLI/test/Install/Plugin/LanguageConfigCustomizerTest.php b/module/CLI/test/Install/Plugin/LanguageConfigCustomizerTest.php index ad39537a..cd7d57d4 100644 --- a/module/CLI/test/Install/Plugin/LanguageConfigCustomizerTest.php +++ b/module/CLI/test/Install/Plugin/LanguageConfigCustomizerTest.php @@ -5,14 +5,9 @@ namespace ShlinkioTest\Shlink\CLI\Install\Plugin; use PHPUnit\Framework\TestCase; use Prophecy\Argument; -use Prophecy\Prophecy\MethodProphecy; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Install\Plugin\LanguageConfigCustomizer; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; -use Symfony\Component\Console\Helper\QuestionHelper; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\NullOutput; -use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; class LanguageConfigCustomizerTest extends TestCase @@ -24,12 +19,13 @@ class LanguageConfigCustomizerTest extends TestCase /** * @var ObjectProphecy */ - protected $questionHelper; + protected $io; public function setUp() { - $this->questionHelper = $this->prophesize(QuestionHelper::class); - $this->plugin = new LanguageConfigCustomizer($this->questionHelper->reveal()); + $this->io = $this->prophesize(SymfonyStyle::class); + $this->io->title(Argument::any())->willReturn(null); + $this->plugin = new LanguageConfigCustomizer(); } /** @@ -37,18 +33,17 @@ class LanguageConfigCustomizerTest extends TestCase */ public function configIsRequestedToTheUser() { - /** @var MethodProphecy $askSecret */ - $askSecret = $this->questionHelper->ask(Argument::cetera())->willReturn('en'); + $ask = $this->io->choice(Argument::cetera())->willReturn('en'); $config = new CustomizableAppConfig(); - $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); + $this->plugin->process($this->io->reveal(), $config); $this->assertTrue($config->hasLanguage()); $this->assertEquals([ 'DEFAULT' => 'en', 'CLI' => 'en', ], $config->getLanguage()); - $askSecret->shouldHaveBeenCalledTimes(2); + $ask->shouldHaveBeenCalledTimes(2); } /** @@ -56,24 +51,22 @@ class LanguageConfigCustomizerTest extends TestCase */ public function overwriteIsRequestedIfValueIsAlreadySet() { - /** @var MethodProphecy $ask */ - $ask = $this->questionHelper->ask(Argument::cetera())->will(function (array $args) { - $last = array_pop($args); - return $last instanceof ConfirmationQuestion ? false : 'es'; - }); + $choice = $this->io->choice(Argument::cetera())->willReturn('es'); + $confirm = $this->io->confirm(Argument::cetera())->willReturn(false); $config = new CustomizableAppConfig(); $config->setLanguage([ 'DEFAULT' => 'en', 'CLI' => 'en', ]); - $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); + $this->plugin->process($this->io->reveal(), $config); $this->assertEquals([ 'DEFAULT' => 'es', 'CLI' => 'es', ], $config->getLanguage()); - $ask->shouldHaveBeenCalledTimes(3); + $choice->shouldHaveBeenCalledTimes(2); + $confirm->shouldHaveBeenCalledTimes(1); } /** @@ -81,8 +74,7 @@ class LanguageConfigCustomizerTest extends TestCase */ public function existingValueIsKeptIfRequested() { - /** @var MethodProphecy $ask */ - $ask = $this->questionHelper->ask(Argument::cetera())->willReturn(true); + $ask = $this->io->confirm(Argument::cetera())->willReturn(true); $config = new CustomizableAppConfig(); $config->setLanguage([ @@ -90,7 +82,7 @@ class LanguageConfigCustomizerTest extends TestCase 'CLI' => 'es', ]); - $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); + $this->plugin->process($this->io->reveal(), $config); $this->assertEquals([ 'DEFAULT' => 'es', diff --git a/module/CLI/test/Install/Plugin/UrlShortenerConfigCustomizerTest.php b/module/CLI/test/Install/Plugin/UrlShortenerConfigCustomizerTest.php index ff5c1404..1b6c6a0f 100644 --- a/module/CLI/test/Install/Plugin/UrlShortenerConfigCustomizerTest.php +++ b/module/CLI/test/Install/Plugin/UrlShortenerConfigCustomizerTest.php @@ -5,14 +5,9 @@ namespace ShlinkioTest\Shlink\CLI\Install\Plugin; use PHPUnit\Framework\TestCase; use Prophecy\Argument; -use Prophecy\Prophecy\MethodProphecy; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Install\Plugin\UrlShortenerConfigCustomizer; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; -use Symfony\Component\Console\Helper\QuestionHelper; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\NullOutput; -use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; class UrlShortenerConfigCustomizerTest extends TestCase @@ -24,12 +19,13 @@ class UrlShortenerConfigCustomizerTest extends TestCase /** * @var ObjectProphecy */ - private $questionHelper; + private $io; public function setUp() { - $this->questionHelper = $this->prophesize(QuestionHelper::class); - $this->plugin = new UrlShortenerConfigCustomizer($this->questionHelper->reveal()); + $this->io = $this->prophesize(SymfonyStyle::class); + $this->io->title(Argument::any())->willReturn(null); + $this->plugin = new UrlShortenerConfigCustomizer(); } /** @@ -37,20 +33,23 @@ class UrlShortenerConfigCustomizerTest extends TestCase */ public function configIsRequestedToTheUser() { - /** @var MethodProphecy $askSecret */ - $askSecret = $this->questionHelper->ask(Argument::cetera())->willReturn('something'); + $choice = $this->io->choice(Argument::cetera())->willReturn('something'); + $ask = $this->io->ask(Argument::cetera())->willReturn('something'); + $confirm = $this->io->confirm(Argument::cetera())->willReturn(true); $config = new CustomizableAppConfig(); - $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); + $this->plugin->process($this->io->reveal(), $config); $this->assertTrue($config->hasUrlShortener()); $this->assertEquals([ 'SCHEMA' => 'something', 'HOSTNAME' => 'something', 'CHARS' => 'something', - 'VALIDATE_URL' => 'something', + 'VALIDATE_URL' => true, ], $config->getUrlShortener()); - $askSecret->shouldHaveBeenCalledTimes(4); + $ask->shouldHaveBeenCalledTimes(2); + $choice->shouldHaveBeenCalledTimes(1); + $confirm->shouldHaveBeenCalledTimes(1); } /** @@ -58,20 +57,18 @@ class UrlShortenerConfigCustomizerTest extends TestCase */ public function overwriteIsRequestedIfValueIsAlreadySet() { - /** @var MethodProphecy $ask */ - $ask = $this->questionHelper->ask(Argument::cetera())->will(function (array $args) { - $last = array_pop($args); - return $last instanceof ConfirmationQuestion ? false : 'foo'; - }); + $choice = $this->io->choice(Argument::cetera())->willReturn('foo'); + $ask = $this->io->ask(Argument::cetera())->willReturn('foo'); + $confirm = $this->io->confirm(Argument::cetera())->willReturn(false); $config = new CustomizableAppConfig(); $config->setUrlShortener([ 'SCHEMA' => 'bar', 'HOSTNAME' => 'bar', 'CHARS' => 'bar', - 'VALIDATE_URL' => 'bar', + 'VALIDATE_URL' => true, ]); - $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); + $this->plugin->process($this->io->reveal(), $config); $this->assertEquals([ 'SCHEMA' => 'foo', @@ -79,7 +76,9 @@ class UrlShortenerConfigCustomizerTest extends TestCase 'CHARS' => 'foo', 'VALIDATE_URL' => false, ], $config->getUrlShortener()); - $ask->shouldHaveBeenCalledTimes(5); + $ask->shouldHaveBeenCalledTimes(2); + $choice->shouldHaveBeenCalledTimes(1); + $confirm->shouldHaveBeenCalledTimes(2); } /** @@ -87,8 +86,7 @@ class UrlShortenerConfigCustomizerTest extends TestCase */ public function existingValueIsKeptIfRequested() { - /** @var MethodProphecy $ask */ - $ask = $this->questionHelper->ask(Argument::cetera())->willReturn(true); + $confirm = $this->io->confirm(Argument::cetera())->willReturn(true); $config = new CustomizableAppConfig(); $config->setUrlShortener([ @@ -98,7 +96,7 @@ class UrlShortenerConfigCustomizerTest extends TestCase 'VALIDATE_URL' => 'foo', ]); - $this->plugin->process(new SymfonyStyle(new ArrayInput([]), new NullOutput()), $config); + $this->plugin->process($this->io->reveal(), $config); $this->assertEquals([ 'SCHEMA' => 'foo', @@ -106,6 +104,6 @@ class UrlShortenerConfigCustomizerTest extends TestCase 'CHARS' => 'foo', 'VALIDATE_URL' => 'foo', ], $config->getUrlShortener()); - $ask->shouldHaveBeenCalledTimes(1); + $confirm->shouldHaveBeenCalledTimes(1); } } From b289e3bac20efbabbea854498fccda6a2e64ba59 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 17:52:17 +0100 Subject: [PATCH 29/56] Applied more improvements on InstallCommand with SymfonyStyle --- module/CLI/src/Command/Install/InstallCommand.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/module/CLI/src/Command/Install/InstallCommand.php b/module/CLI/src/Command/Install/InstallCommand.php index 698cbc65..eb4a1134 100644 --- a/module/CLI/src/Command/Install/InstallCommand.php +++ b/module/CLI/src/Command/Install/InstallCommand.php @@ -132,7 +132,7 @@ class InstallCommand extends Command // If current command is not update, generate database if (! $this->isUpdate) { - $this->io->writeln('Initializing database...'); + $this->io->write('Initializing database...'); if (! $this->runCommand( 'php vendor/bin/doctrine.php orm:schema-tool:create', 'Error generating database.', @@ -143,7 +143,7 @@ class InstallCommand extends Command } // Run database migrations - $this->io->writeln('Updating database...'); + $this->io->write('Updating database...'); if (! $this->runCommand( 'php vendor/bin/doctrine-migrations migrations:migrate', 'Error updating database.', @@ -153,7 +153,7 @@ class InstallCommand extends Command } // Generate proxies - $this->io->writeln('Generating proxies...'); + $this->io->write('Generating proxies...'); if (! $this->runCommand( 'php vendor/bin/doctrine.php orm:generate-proxies', 'Error generating proxies.', @@ -161,6 +161,8 @@ class InstallCommand extends Command )) { return; } + + $this->io->success('Installation complete!'); } /** @@ -239,7 +241,7 @@ class InstallCommand extends Command { $process = $this->processHelper->run($output, $command); if ($process->isSuccessful()) { - $this->io->writeln(' Success!'); + $this->io->writeln(' Success!'); return true; } @@ -247,9 +249,7 @@ class InstallCommand extends Command return false; } - $this->io->writeln( - ' ' . $errorMessage . ' Run this command with -vvv to see specific error info.' - ); + $this->io->error($errorMessage . ' Run this command with -vvv to see specific error info.'); return false; } } From f3fbfc369243d10372a5400acb5d7d2dc6f31bb1 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 17:54:01 +0100 Subject: [PATCH 30/56] Fixed phpstan error --- module/CLI/src/Command/Install/InstallCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/CLI/src/Command/Install/InstallCommand.php b/module/CLI/src/Command/Install/InstallCommand.php index eb4a1134..48a9ba3f 100644 --- a/module/CLI/src/Command/Install/InstallCommand.php +++ b/module/CLI/src/Command/Install/InstallCommand.php @@ -79,11 +79,11 @@ class InstallCommand extends Command /** * @param InputInterface $input * @param OutputInterface $output - * @return int|null|void + * @return void * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ - public function execute(InputInterface $input, OutputInterface $output) + public function execute(InputInterface $input, OutputInterface $output): void { $this->io = new SymfonyStyle($input, $output); $this->processHelper = $this->getHelper('process'); From 7ddc1804875188755d353e345856a4e4af37b0a4 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 17:59:50 +0100 Subject: [PATCH 31/56] Simplified InstallCommand --- .../src/Command/Install/InstallCommand.php | 40 +++++-------------- 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/module/CLI/src/Command/Install/InstallCommand.php b/module/CLI/src/Command/Install/InstallCommand.php index 48a9ba3f..b537144c 100644 --- a/module/CLI/src/Command/Install/InstallCommand.php +++ b/module/CLI/src/Command/Install/InstallCommand.php @@ -9,6 +9,7 @@ use Shlinkio\Shlink\CLI\Install\ConfigCustomizerManagerInterface; use Shlinkio\Shlink\CLI\Install\Plugin; use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Helper\ProcessHelper; @@ -86,7 +87,6 @@ class InstallCommand extends Command public function execute(InputInterface $input, OutputInterface $output): void { $this->io = new SymfonyStyle($input, $output); - $this->processHelper = $this->getHelper('process'); $this->io->writeln([ 'Welcome to Shlink!!', @@ -174,9 +174,7 @@ class InstallCommand extends Command $config = new CustomizableAppConfig(); // Ask the user if he/she wants to import an older configuration - $importConfig = $this->io->confirm( - 'Do you want to import previous configuration? (Y/n): ' - ); + $importConfig = $this->io->confirm('Do you want to import configuration from previous installation?'); if (! $importConfig) { return $config; } @@ -184,7 +182,7 @@ class InstallCommand extends Command // Ask the user for the older shlink path $keepAsking = true; do { - $config->setImportedInstallationPath($this->ask( + $config->setImportedInstallationPath($this->io->ask( 'Previous shlink installation path from which to import config' )); $configFile = $config->getImportedInstallationPath() . '/' . self::GENERATED_CONFIG_PATH; @@ -192,8 +190,7 @@ class InstallCommand extends Command if (! $configExists) { $keepAsking = $this->io->confirm( - 'Provided path does not seem to be a valid shlink root path. ' - . 'Do you want to try another path? (Y/n): ' + 'Provided path does not seem to be a valid shlink root path. Do you want to try another path?' ); } } while (! $configExists && $keepAsking); @@ -208,37 +205,20 @@ class InstallCommand extends Command return $config; } - /** - * @param string $text - * @param string|null $default - * @param bool $allowEmpty - * @return string - * @throws RuntimeException - */ - private function ask($text, $default = null, $allowEmpty = false): string - { - if ($default !== null) { - $text .= ' (defaults to ' . $default . ')'; - } - - do { - $value = $this->io->ask('' . $text . ': ', $default); - if (empty($value) && ! $allowEmpty) { - $this->io->writeln('Value can\'t be empty'); - } - } while (empty($value) && $default === null && ! $allowEmpty); - - return $value; - } - /** * @param string $command * @param string $errorMessage * @param OutputInterface $output * @return bool + * @throws LogicException + * @throws InvalidArgumentException */ private function runCommand($command, $errorMessage, OutputInterface $output): bool { + if ($this->processHelper === null) { + $this->processHelper = $this->getHelper('process'); + } + $process = $this->processHelper->run($output, $command); if ($process->isSuccessful()) { $this->io->writeln(' Success!'); From e15b67b5dc7707877d04f80e52831f1292b206d7 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 18:04:11 +0100 Subject: [PATCH 32/56] Improved GeneratePreviewCommand using SymfonyStyle --- .../src/Command/Shortcode/GeneratePreviewCommand.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/module/CLI/src/Command/Shortcode/GeneratePreviewCommand.php b/module/CLI/src/Command/Shortcode/GeneratePreviewCommand.php index fec666bf..6fa0a487 100644 --- a/module/CLI/src/Command/Shortcode/GeneratePreviewCommand.php +++ b/module/CLI/src/Command/Shortcode/GeneratePreviewCommand.php @@ -9,6 +9,7 @@ use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Zend\I18n\Translator\TranslatorInterface; class GeneratePreviewCommand extends Command @@ -61,22 +62,20 @@ class GeneratePreviewCommand extends Command } } while ($page <= $shortUrls->count()); - $output->writeln('' . $this->translator->translate('Finished processing all URLs') . ''); + (new SymfonyStyle($input, $output))->success($this->translator->translate('Finished processing all URLs')); } protected function processUrl($url, OutputInterface $output) { try { - $output->write(sprintf($this->translator->translate('Processing URL %s...'), $url)); + $output->write(\sprintf($this->translator->translate('Processing URL %s...'), $url)); $this->previewGenerator->generatePreview($url); $output->writeln($this->translator->translate(' Success!')); } catch (PreviewGenerationException $e) { - $messages = [' ' . $this->translator->translate('Error') . '']; + $output->writeln(' ' . $this->translator->translate('Error') . ''); if ($output->isVerbose()) { - $messages[] = '' . $e->__toString() . ''; + $this->getApplication()->renderException($e, $output); } - - $output->writeln($messages); } } } From c202b3e51859e24f4c55fb6655c7cb7fd1ddbfda Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 18:12:43 +0100 Subject: [PATCH 33/56] Improved GenerateShortcodeCommand by using SymfonyStyle --- .../Shortcode/GenerateShortcodeCommand.php | 46 +++++++++---------- .../GenerateShortcodeCommandTest.php | 5 +- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php b/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php index c35f0736..3ad96cf2 100644 --- a/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php +++ b/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php @@ -13,6 +13,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Style\SymfonyStyle; use Zend\Diactoros\Uri; use Zend\I18n\Translator\TranslatorInterface; @@ -75,19 +76,15 @@ class GenerateShortcodeCommand extends Command public function interact(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); $longUrl = $input->getArgument('longUrl'); if (! empty($longUrl)) { return; } - /** @var QuestionHelper $helper */ - $helper = $this->getHelper('question'); - $question = new Question(sprintf( - '%s ', - $this->translator->translate('A long URL was not provided. Which URL do you want to shorten?:') - )); - - $longUrl = $helper->ask($input, $output, $question); + $longUrl = $io->ask( + $this->translator->translate('A long URL was not provided. Which URL do you want to be shortened?') + ); if (! empty($longUrl)) { $input->setArgument('longUrl', $longUrl); } @@ -95,23 +92,24 @@ class GenerateShortcodeCommand extends Command public function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); $longUrl = $input->getArgument('longUrl'); + if (empty($longUrl)) { + $io->error($this->translator->translate('A URL was not provided!')); + return; + } + $tags = $input->getOption('tags'); $processedTags = []; foreach ($tags as $key => $tag) { - $explodedTags = explode(',', $tag); - $processedTags = array_merge($processedTags, $explodedTags); + $explodedTags = \explode(',', $tag); + $processedTags = \array_merge($processedTags, $explodedTags); } $tags = $processedTags; $customSlug = $input->getOption('customSlug'); $maxVisits = $input->getOption('maxVisits'); try { - if (! isset($longUrl)) { - $output->writeln(sprintf('%s', $this->translator->translate('A URL was not provided!'))); - return; - } - $shortCode = $this->urlShortener->urlToShortCode( new Uri($longUrl), $tags, @@ -124,22 +122,20 @@ class GenerateShortcodeCommand extends Command ->withScheme($this->domainConfig['schema']) ->withHost($this->domainConfig['hostname']); - $output->writeln([ - sprintf('%s %s', $this->translator->translate('Processed URL:'), $longUrl), - sprintf('%s %s', $this->translator->translate('Generated URL:'), $shortUrl), + $io->writeln([ + \sprintf('%s %s', $this->translator->translate('Processed long URL:'), $longUrl), + \sprintf('%s %s', $this->translator->translate('Generated short URL:'), $shortUrl), ]); } catch (InvalidUrlException $e) { - $output->writeln(sprintf( - '' . $this->translator->translate( - 'Provided URL "%s" is invalid. Try with a different one.' - ) . '', + $io->error(\sprintf( + $this->translator->translate('Provided URL "%s" is invalid. Try with a different one.'), $longUrl )); } catch (NonUniqueSlugException $e) { - $output->writeln(sprintf( - '' . $this->translator->translate( + $io->error(\sprintf( + $this->translator->translate( 'Provided slug "%s" is already in use by another URL. Try with a different one.' - ) . '', + ), $customSlug )); } diff --git a/module/CLI/test/Command/Shortcode/GenerateShortcodeCommandTest.php b/module/CLI/test/Command/Shortcode/GenerateShortcodeCommandTest.php index e1dc5229..5b83cc4f 100644 --- a/module/CLI/test/Command/Shortcode/GenerateShortcodeCommandTest.php +++ b/module/CLI/test/Command/Shortcode/GenerateShortcodeCommandTest.php @@ -65,8 +65,9 @@ class GenerateShortcodeCommandTest extends TestCase 'longUrl' => 'http://domain.com/invalid', ]); $output = $this->commandTester->getDisplay(); - $this->assertTrue( - strpos($output, 'Provided URL "http://domain.com/invalid" is invalid. Try with a different one.') === 0 + $this->assertContains( + 'Provided URL "http://domain.com/invalid" is invalid.', + $output ); } } From 3e2c5abaa40f968b98614584cb3f94579e1b28a9 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 18:17:58 +0100 Subject: [PATCH 34/56] Improved GetVisitsCommand by using SymfonyStyle --- .../Command/Shortcode/GetVisitsCommand.php | 39 ++++++++----------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/module/CLI/src/Command/Shortcode/GetVisitsCommand.php b/module/CLI/src/Command/Shortcode/GetVisitsCommand.php index 264d086b..61d044db 100644 --- a/module/CLI/src/Command/Shortcode/GetVisitsCommand.php +++ b/module/CLI/src/Command/Shortcode/GetVisitsCommand.php @@ -6,13 +6,11 @@ namespace Shlinkio\Shlink\CLI\Command\Shortcode; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Helper\QuestionHelper; -use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Style\SymfonyStyle; use Zend\I18n\Translator\TranslatorInterface; class GetVisitsCommand extends Command @@ -32,7 +30,7 @@ class GetVisitsCommand extends Command { $this->visitsTracker = $visitsTracker; $this->translator = $translator; - parent::__construct(null); + parent::__construct(); } public function configure() @@ -67,14 +65,10 @@ class GetVisitsCommand extends Command return; } - /** @var QuestionHelper $helper */ - $helper = $this->getHelper('question'); - $question = new Question(sprintf( - '%s ', - $this->translator->translate('A short code was not provided. Which short code do you want to use?:') - )); - - $shortCode = $helper->ask($input, $output, $question); + $io = new SymfonyStyle($input, $output); + $shortCode = $io->ask( + $this->translator->translate('A short code was not provided. Which short code do you want to use?') + ); if (! empty($shortCode)) { $input->setArgument('shortCode', $shortCode); } @@ -82,33 +76,32 @@ class GetVisitsCommand extends Command public function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); $shortCode = $input->getArgument('shortCode'); $startDate = $this->getDateOption($input, 'startDate'); $endDate = $this->getDateOption($input, 'endDate'); $visits = $this->visitsTracker->info($shortCode, new DateRange($startDate, $endDate)); - $table = new Table($output); - $table->setHeaders([ - $this->translator->translate('Referer'), - $this->translator->translate('Date'), - $this->translator->translate('Remote Address'), - $this->translator->translate('User agent'), - ]); - + $rows = []; foreach ($visits as $row) { $rowData = $row->jsonSerialize(); // Unset location info unset($rowData['visitLocation']); - $table->addRow(array_values($rowData)); + $rows[] = \array_values($rowData); } - $table->render(); + $io->table([ + $this->translator->translate('Referer'), + $this->translator->translate('Date'), + $this->translator->translate('Remote Address'), + $this->translator->translate('User agent'), + ], $rows); } protected function getDateOption(InputInterface $input, $key) { $value = $input->getOption($key); - if (isset($value)) { + if (! empty($value)) { $value = new \DateTime($value); } From a6c547c4dace2295706cabaa1a091ab090b5c3b2 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 18:37:39 +0100 Subject: [PATCH 35/56] Improved and simplified ListShortcodesCommand with SymfonyStyle --- .../Shortcode/ListShortcodesCommand.php | 33 ++++++--------- .../Shortcode/ListShortcodesCommandTest.php | 41 ++++--------------- 2 files changed, 21 insertions(+), 53 deletions(-) diff --git a/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php b/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php index 31ba923e..1e8ce12c 100644 --- a/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php +++ b/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php @@ -7,12 +7,10 @@ use Shlinkio\Shlink\Common\Paginator\Adapter\PaginableRepositoryAdapter; use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait; use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Helper\QuestionHelper; -use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Style\SymfonyStyle; use Zend\I18n\Translator\TranslatorInterface; class ListShortcodesCommand extends Command @@ -34,7 +32,7 @@ class ListShortcodesCommand extends Command { $this->shortUrlService = $shortUrlService; $this->translator = $translator; - parent::__construct(null); + parent::__construct(); } public function configure() @@ -83,19 +81,16 @@ class ListShortcodesCommand extends Command public function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); $page = (int) $input->getOption('page'); $searchTerm = $input->getOption('searchTerm'); $tags = $input->getOption('tags'); - $tags = ! empty($tags) ? explode(',', $tags) : []; + $tags = ! empty($tags) ? \explode(',', $tags) : []; $showTags = $input->getOption('showTags'); - /** @var QuestionHelper $helper */ - $helper = $this->getHelper('question'); - do { $result = $this->shortUrlService->listShortUrls($page, $searchTerm, $tags, $this->processOrderBy($input)); $page++; - $table = new Table($output); $headers = [ $this->translator->translate('Short code'), @@ -106,8 +101,8 @@ class ListShortcodesCommand extends Command if ($showTags) { $headers[] = $this->translator->translate('Tags'); } - $table->setHeaders($headers); + $rows = []; foreach ($result as $row) { $shortUrl = $row->jsonSerialize(); if ($showTags) { @@ -120,27 +115,23 @@ class ListShortcodesCommand extends Command unset($shortUrl['tags']); } - $table->addRow(array_values($shortUrl)); + $rows[] = \array_values($shortUrl); } - $table->render(); + $io->table($headers, $rows); if ($this->isLastPage($result)) { $continue = false; - $output->writeln( - sprintf('%s', $this->translator->translate('You have reached last page')) - ); + $io->success($this->translator->translate('Short codes properly listed')); } else { - $continue = $helper->ask($input, $output, new ConfirmationQuestion( - sprintf('' . $this->translator->translate( - 'Continue with page' - ) . ' %s? (y/N) ', $page), + $continue = $io->confirm( + \sprintf($this->translator->translate('Continue with page') . ' %s?', $page), false - )); + ); } } while ($continue); } - protected function processOrderBy(InputInterface $input) + private function processOrderBy(InputInterface $input) { $orderBy = $input->getOption('orderBy'); if (empty($orderBy)) { diff --git a/module/CLI/test/Command/Shortcode/ListShortcodesCommandTest.php b/module/CLI/test/Command/Shortcode/ListShortcodesCommandTest.php index a18cf217..8aede93a 100644 --- a/module/CLI/test/Command/Shortcode/ListShortcodesCommandTest.php +++ b/module/CLI/test/Command/Shortcode/ListShortcodesCommandTest.php @@ -10,7 +10,6 @@ use Shlinkio\Shlink\CLI\Command\Shortcode\ListShortcodesCommand; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface; use Symfony\Component\Console\Application; -use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Tester\CommandTester; use Zend\I18n\Translator\Translator; use Zend\Paginator\Adapter\ArrayAdapter; @@ -22,10 +21,6 @@ class ListShortcodesCommandTest extends TestCase * @var CommandTester */ protected $commandTester; - /** - * @var QuestionHelper - */ - protected $questionHelper; /** * @var ObjectProphecy */ @@ -37,8 +32,6 @@ class ListShortcodesCommandTest extends TestCase $app = new Application(); $command = new ListShortcodesCommand($this->shortUrlService->reveal(), Translator::factory([])); $app->add($command); - - $this->questionHelper = $command->getHelper('question'); $this->commandTester = new CommandTester($command); } @@ -47,10 +40,10 @@ class ListShortcodesCommandTest extends TestCase */ public function noInputCallsListJustOnce() { - $this->questionHelper->setInputStream($this->getInputStream('\n')); $this->shortUrlService->listShortUrls(1, null, [], null)->willReturn(new Paginator(new ArrayAdapter())) ->shouldBeCalledTimes(1); + $this->commandTester->setInputs(['n']); $this->commandTester->execute(['command' => 'shortcode:list']); } @@ -61,22 +54,15 @@ class ListShortcodesCommandTest extends TestCase { // The paginator will return more than one page for the first 3 times $data = []; - for ($i = 0; $i < 30; $i++) { + for ($i = 0; $i < 50; $i++) { $data[] = new ShortUrl(); } - $data = array_chunk($data, 11); - $questionHelper = $this->questionHelper; - $that = $this; - $this->shortUrlService->listShortUrls(Argument::cetera())->will(function () use ( - &$data, - $questionHelper, - $that - ) { - $questionHelper->setInputStream($that->getInputStream('y')); - return new Paginator(new ArrayAdapter(array_shift($data))); + $this->shortUrlService->listShortUrls(Argument::cetera())->will(function () use (&$data) { + return new Paginator(new ArrayAdapter($data)); })->shouldBeCalledTimes(3); + $this->commandTester->setInputs(['y', 'y', 'n']); $this->commandTester->execute(['command' => 'shortcode:list']); } @@ -91,10 +77,10 @@ class ListShortcodesCommandTest extends TestCase $data[] = new ShortUrl(); } - $this->questionHelper->setInputStream($this->getInputStream('n')); $this->shortUrlService->listShortUrls(Argument::cetera())->willReturn(new Paginator(new ArrayAdapter($data))) ->shouldBeCalledTimes(1); + $this->commandTester->setInputs(['n']); $this->commandTester->execute(['command' => 'shortcode:list']); } @@ -104,10 +90,10 @@ class ListShortcodesCommandTest extends TestCase public function passingPageWillMakeListStartOnThatPage() { $page = 5; - $this->questionHelper->setInputStream($this->getInputStream('\n')); $this->shortUrlService->listShortUrls($page, null, [], null)->willReturn(new Paginator(new ArrayAdapter())) ->shouldBeCalledTimes(1); + $this->commandTester->setInputs(['y']); $this->commandTester->execute([ 'command' => 'shortcode:list', '--page' => $page, @@ -119,24 +105,15 @@ class ListShortcodesCommandTest extends TestCase */ public function ifTagsFlagIsProvidedTagsColumnIsIncluded() { - $this->questionHelper->setInputStream($this->getInputStream('\n')); $this->shortUrlService->listShortUrls(1, null, [], null)->willReturn(new Paginator(new ArrayAdapter())) ->shouldBeCalledTimes(1); + $this->commandTester->setInputs(['y']); $this->commandTester->execute([ 'command' => 'shortcode:list', '--showTags' => true, ]); $output = $this->commandTester->getDisplay(); - $this->assertTrue(strpos($output, 'Tags') > 0); - } - - protected function getInputStream($inputData) - { - $stream = fopen('php://memory', 'r+', false); - fputs($stream, $inputData); - rewind($stream); - - return $stream; + $this->assertContains('Tags', $output); } } From 89ed84ce28f6cb6e18e42fff9e74f22b0efb0c10 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 18:38:25 +0100 Subject: [PATCH 36/56] Removed unused use statements --- module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php b/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php index 3ad96cf2..f4c56f98 100644 --- a/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php +++ b/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php @@ -7,12 +7,10 @@ use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Question\Question; use Symfony\Component\Console\Style\SymfonyStyle; use Zend\Diactoros\Uri; use Zend\I18n\Translator\TranslatorInterface; From a60c45ca4df00a3801d7d357530287a2b74413d0 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 18:58:11 +0100 Subject: [PATCH 37/56] Simplified and improved ResolveUrlCommand with SymfonyStyle --- .../Command/Shortcode/ResolveUrlCommand.php | 30 ++++++++----------- .../Shortcode/ResolveUrlCommandTest.php | 4 +-- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php b/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php index 539035b1..fcf62fbe 100644 --- a/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php +++ b/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php @@ -7,11 +7,10 @@ use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Style\SymfonyStyle; use Zend\I18n\Translator\TranslatorInterface; class ResolveUrlCommand extends Command @@ -52,14 +51,10 @@ class ResolveUrlCommand extends Command return; } - /** @var QuestionHelper $helper */ - $helper = $this->getHelper('question'); - $question = new Question(sprintf( - '%s ', - $this->translator->translate('A short code was not provided. Which short code do you want to parse?:') - )); - - $shortCode = $helper->ask($input, $output, $question); + $io = new SymfonyStyle($input, $output); + $shortCode = $io->ask( + $this->translator->translate('A short code was not provided. Which short code do you want to parse?') + ); if (! empty($shortCode)) { $input->setArgument('shortCode', $shortCode); } @@ -67,19 +62,20 @@ class ResolveUrlCommand extends Command public function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); $shortCode = $input->getArgument('shortCode'); try { $longUrl = $this->urlShortener->shortCodeToUrl($shortCode); - $output->writeln(sprintf('%s %s', $this->translator->translate('Long URL:'), $longUrl)); + $output->writeln(\sprintf('%s %s', $this->translator->translate('Long URL:'), $longUrl)); } catch (InvalidShortCodeException $e) { - $output->writeln(sprintf('' . $this->translator->translate( - 'Provided short code "%s" has an invalid format.' - ) . '', $shortCode)); + $io->error( + \sprintf($this->translator->translate('Provided short code "%s" has an invalid format.'), $shortCode) + ); } catch (EntityDoesNotExistException $e) { - $output->writeln(sprintf('' . $this->translator->translate( - 'Provided short code "%s" could not be found.' - ) . '', $shortCode)); + $io->error( + \sprintf($this->translator->translate('Provided short code "%s" could not be found.'), $shortCode) + ); } } } diff --git a/module/CLI/test/Command/Shortcode/ResolveUrlCommandTest.php b/module/CLI/test/Command/Shortcode/ResolveUrlCommandTest.php index da0eadc3..d1bc50a3 100644 --- a/module/CLI/test/Command/Shortcode/ResolveUrlCommandTest.php +++ b/module/CLI/test/Command/Shortcode/ResolveUrlCommandTest.php @@ -66,7 +66,7 @@ class ResolveUrlCommandTest extends TestCase 'shortCode' => $shortCode, ]); $output = $this->commandTester->getDisplay(); - $this->assertEquals('Provided short code "' . $shortCode . '" could not be found.' . PHP_EOL, $output); + $this->assertContains('Provided short code "' . $shortCode . '" could not be found.', $output); } /** @@ -83,6 +83,6 @@ class ResolveUrlCommandTest extends TestCase 'shortCode' => $shortCode, ]); $output = $this->commandTester->getDisplay(); - $this->assertEquals('Provided short code "' . $shortCode . '" has an invalid format.' . PHP_EOL, $output); + $this->assertContains('Provided short code "' . $shortCode . '" has an invalid format.', $output); } } From 09b161304c8354f4c48410b7376fe6eb21e51835 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 19:03:41 +0100 Subject: [PATCH 38/56] Improved and simplified CreateTagCommand thanks to SymfonyStyle --- module/CLI/src/Command/Tag/CreateTagCommand.php | 13 +++++-------- .../CLI/test/Command/Tag/CreateTagCommandTest.php | 6 +----- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/module/CLI/src/Command/Tag/CreateTagCommand.php b/module/CLI/src/Command/Tag/CreateTagCommand.php index 40b28449..daf03d15 100644 --- a/module/CLI/src/Command/Tag/CreateTagCommand.php +++ b/module/CLI/src/Command/Tag/CreateTagCommand.php @@ -8,6 +8,7 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Zend\I18n\Translator\TranslatorInterface; class CreateTagCommand extends Command @@ -45,19 +46,15 @@ class CreateTagCommand extends Command protected function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); $tagNames = $input->getOption('name'); + if (empty($tagNames)) { - $output->writeln(sprintf( - '%s', - $this->translator->translate('You have to provide at least one tag name') - )); + $io->warning($this->translator->translate('You have to provide at least one tag name')); return; } $this->tagService->createTags($tagNames); - $output->writeln($this->translator->translate('Created tags') . sprintf(': ["%s"]', implode( - '", "', - $tagNames - ))); + $io->success($this->translator->translate('Tags properly created')); } } diff --git a/module/CLI/test/Command/Tag/CreateTagCommandTest.php b/module/CLI/test/Command/Tag/CreateTagCommandTest.php index 6dab8d86..a81a2383 100644 --- a/module/CLI/test/Command/Tag/CreateTagCommandTest.php +++ b/module/CLI/test/Command/Tag/CreateTagCommandTest.php @@ -14,10 +14,6 @@ use Zend\I18n\Translator\Translator; class CreateTagCommandTest extends TestCase { - /** - * @var CreateTagCommand - */ - private $command; /** * @var CommandTester */ @@ -63,7 +59,7 @@ class CreateTagCommandTest extends TestCase ]); $output = $this->commandTester->getDisplay(); - $this->assertContains(sprintf('Created tags: ["%s"]', implode('", "', $tagNames)), $output); + $this->assertContains('Tags properly created', $output); $createTags->shouldHaveBeenCalled(); } } From 057bbae7292ec2e589223b531aef4e1b8d0a6f79 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 19:06:04 +0100 Subject: [PATCH 39/56] Improved and simplified DeleteTagCommand thanks to SymfonyStyle --- module/CLI/src/Command/Tag/DeleteTagsCommand.php | 13 +++++-------- .../CLI/test/Command/Tag/DeleteTagsCommandTest.php | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/module/CLI/src/Command/Tag/DeleteTagsCommand.php b/module/CLI/src/Command/Tag/DeleteTagsCommand.php index 0654138b..5da57ede 100644 --- a/module/CLI/src/Command/Tag/DeleteTagsCommand.php +++ b/module/CLI/src/Command/Tag/DeleteTagsCommand.php @@ -8,6 +8,7 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Zend\I18n\Translator\TranslatorInterface; class DeleteTagsCommand extends Command @@ -45,19 +46,15 @@ class DeleteTagsCommand extends Command protected function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); $tagNames = $input->getOption('name'); + if (empty($tagNames)) { - $output->writeln(sprintf( - '%s', - $this->translator->translate('You have to provide at least one tag name') - )); + $io->warning($this->translator->translate('You have to provide at least one tag name')); return; } $this->tagService->deleteTags($tagNames); - $output->writeln($this->translator->translate('Deleted tags') . sprintf(': ["%s"]', implode( - '", "', - $tagNames - ))); + $io->success($this->translator->translate('Tags properly deleted')); } } diff --git a/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php b/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php index b8f58ff7..819342c9 100644 --- a/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php +++ b/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php @@ -64,7 +64,7 @@ class DeleteTagsCommandTest extends TestCase ]); $output = $this->commandTester->getDisplay(); - $this->assertContains(sprintf('Deleted tags: ["%s"]', implode('", "', $tagNames)), $output); + $this->assertContains('Tags properly deleted', $output); $deleteTags->shouldHaveBeenCalled(); } } From 7856d64299b48aeeb22df5d33ce6e7892d2b264f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 19:08:10 +0100 Subject: [PATCH 40/56] Improved and simplified ListTagsCommand thanks to SymfonyStyle --- module/CLI/src/Command/Tag/ListTagsCommand.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/module/CLI/src/Command/Tag/ListTagsCommand.php b/module/CLI/src/Command/Tag/ListTagsCommand.php index 0474fc97..ea860900 100644 --- a/module/CLI/src/Command/Tag/ListTagsCommand.php +++ b/module/CLI/src/Command/Tag/ListTagsCommand.php @@ -6,9 +6,9 @@ namespace Shlinkio\Shlink\CLI\Command\Tag; use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Zend\I18n\Translator\TranslatorInterface; class ListTagsCommand extends Command @@ -40,11 +40,8 @@ class ListTagsCommand extends Command protected function execute(InputInterface $input, OutputInterface $output) { - $table = new Table($output); - $table->setHeaders([$this->translator->translate('Name')]) - ->setRows($this->getTagsRows()); - - $table->render(); + $io = new SymfonyStyle($input, $output); + $io->table([$this->translator->translate('Name')], $this->getTagsRows()); } private function getTagsRows() @@ -54,7 +51,7 @@ class ListTagsCommand extends Command return [[$this->translator->translate('No tags yet')]]; } - return array_map(function (Tag $tag) { + return \array_map(function (Tag $tag) { return [$tag->getName()]; }, $tags); } From 08228d9d9814a9497f4c7f2cdee67dc7d24dda48 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 19:10:27 +0100 Subject: [PATCH 41/56] Improved and simplified RenameTagCommand thanks to SymfonyStyle --- module/CLI/src/Command/Tag/RenameTagCommand.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/module/CLI/src/Command/Tag/RenameTagCommand.php b/module/CLI/src/Command/Tag/RenameTagCommand.php index a38a8da1..f76c05f2 100644 --- a/module/CLI/src/Command/Tag/RenameTagCommand.php +++ b/module/CLI/src/Command/Tag/RenameTagCommand.php @@ -9,6 +9,7 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Zend\I18n\Translator\TranslatorInterface; class RenameTagCommand extends Command @@ -42,16 +43,15 @@ class RenameTagCommand extends Command protected function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); $oldName = $input->getArgument('oldName'); $newName = $input->getArgument('newName'); try { $this->tagService->renameTag($oldName, $newName); - $output->writeln(sprintf('%s', $this->translator->translate('Tag properly renamed.'))); + $io->success($this->translator->translate('Tag properly renamed.')); } catch (EntityDoesNotExistException $e) { - $output->writeln('' . sprintf($this->translator->translate( - 'A tag with name "%s" was not found' - ), $oldName) . ''); + $io->error(\sprintf($this->translator->translate('A tag with name "%s" was not found'), $oldName)); } } } From 5ec6d538db594b5443e7ede0e84f1f75366b0765 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 19:13:42 +0100 Subject: [PATCH 42/56] Improved and simplified ProcessVisitsCommand thanks to SymfonyStyle --- .../CLI/src/Command/Visit/ProcessVisitsCommand.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/module/CLI/src/Command/Visit/ProcessVisitsCommand.php b/module/CLI/src/Command/Visit/ProcessVisitsCommand.php index 333a9239..f3e174d6 100644 --- a/module/CLI/src/Command/Visit/ProcessVisitsCommand.php +++ b/module/CLI/src/Command/Visit/ProcessVisitsCommand.php @@ -10,6 +10,7 @@ use Shlinkio\Shlink\Core\Service\VisitServiceInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Zend\I18n\Translator\TranslatorInterface; class ProcessVisitsCommand extends Command @@ -51,13 +52,14 @@ class ProcessVisitsCommand extends Command public function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); $visits = $this->visitService->getUnlocatedVisits(); foreach ($visits as $visit) { $ipAddr = $visit->getRemoteAddr(); - $output->write(sprintf('%s %s', $this->translator->translate('Processing IP'), $ipAddr)); + $io->write(sprintf('%s %s', $this->translator->translate('Processing IP'), $ipAddr)); if ($ipAddr === self::LOCALHOST) { - $output->writeln( + $io->writeln( sprintf(' (%s)', $this->translator->translate('Ignored localhost address')) ); continue; @@ -65,11 +67,13 @@ class ProcessVisitsCommand extends Command try { $result = $this->ipLocationResolver->resolveIpLocation($ipAddr); + $location = new VisitLocation(); $location->exchangeArray($result); $visit->setVisitLocation($location); $this->visitService->saveVisit($visit); - $output->writeln(sprintf( + + $io->writeln(sprintf( ' (' . $this->translator->translate('Address located at "%s"') . ')', $location->getCityName() )); @@ -78,6 +82,6 @@ class ProcessVisitsCommand extends Command } } - $output->writeln($this->translator->translate('Finished processing all IPs')); + $io->success($this->translator->translate('Finished processing all IPs')); } } From da21eb4a5c9c8ea02c702f3c807351f6bbcc12ff Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 19:24:22 +0100 Subject: [PATCH 43/56] Removed return type incompatible with PHP 7.0 --- module/CLI/src/Command/Install/InstallCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/CLI/src/Command/Install/InstallCommand.php b/module/CLI/src/Command/Install/InstallCommand.php index b537144c..0c6dee56 100644 --- a/module/CLI/src/Command/Install/InstallCommand.php +++ b/module/CLI/src/Command/Install/InstallCommand.php @@ -84,7 +84,7 @@ class InstallCommand extends Command * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ - public function execute(InputInterface $input, OutputInterface $output): void + public function execute(InputInterface $input, OutputInterface $output) { $this->io = new SymfonyStyle($input, $output); From 3243ade4fd0785359f7360726243f5e7b6d787b2 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 31 Dec 2017 19:31:35 +0100 Subject: [PATCH 44/56] Improved error message when installation fails --- module/CLI/src/Command/Install/InstallCommand.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/module/CLI/src/Command/Install/InstallCommand.php b/module/CLI/src/Command/Install/InstallCommand.php index 0c6dee56..ef08b460 100644 --- a/module/CLI/src/Command/Install/InstallCommand.php +++ b/module/CLI/src/Command/Install/InstallCommand.php @@ -100,9 +100,9 @@ class InstallCommand extends Command $this->filesystem->remove('data/cache/app_config.php'); $this->io->writeln(' Success'); } catch (IOException $e) { - $this->io->writeln( - ' Failed! You will have to manually delete the data/cache/app_config.php file to get' - . ' new config applied.' + $this->io->error( + 'Failed! You will have to manually delete the data/cache/app_config.php file to' + . ' get new config applied.' ); if ($this->io->isVerbose()) { $this->getApplication()->renderException($e, $output); From fac9455a1ee7cdea777bb728656709ee73506630 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 7 Jan 2018 19:51:25 +0100 Subject: [PATCH 45/56] Created method to updated already created short URLs --- composer.json | 1 + .../src/Exception/ValidationException.php | 76 ++++++++++ module/Core/src/Model/ShortCodeMeta.php | 139 ++++++++++++++++++ module/Core/src/Service/ShortUrlService.php | 51 ++++++- .../src/Service/ShortUrlServiceInterface.php | 11 +- .../Core/src/Validation/InputFactoryTrait.php | 20 +++ .../Validation/ShortUrlMetaInputFilter.php | 45 ++++++ .../Rest/src/Action/EditShortCodeAction.php | 25 ++++ 8 files changed, 360 insertions(+), 8 deletions(-) create mode 100644 module/Core/src/Exception/ValidationException.php create mode 100644 module/Core/src/Model/ShortCodeMeta.php create mode 100644 module/Core/src/Validation/InputFactoryTrait.php create mode 100644 module/Core/src/Validation/ShortUrlMetaInputFilter.php create mode 100644 module/Rest/src/Action/EditShortCodeAction.php diff --git a/composer.json b/composer.json index 5a36d42b..d961f5d5 100644 --- a/composer.json +++ b/composer.json @@ -40,6 +40,7 @@ "zendframework/zend-expressive-helpers": "^4.2", "zendframework/zend-expressive-platesrenderer": "^1.3", "zendframework/zend-i18n": "^2.7", + "zendframework/zend-inputfilter": "^2.8", "zendframework/zend-paginator": "^2.6", "zendframework/zend-servicemanager": "^3.2", "zendframework/zend-stdlib": "^3.0" diff --git a/module/Core/src/Exception/ValidationException.php b/module/Core/src/Exception/ValidationException.php new file mode 100644 index 00000000..5c6a95bc --- /dev/null +++ b/module/Core/src/Exception/ValidationException.php @@ -0,0 +1,76 @@ +invalidElements = $invalidElements; + parent::__construct($message, $code, $previous); + } + + /** + * @param InputFilterInterface $inputFilter + * @param \Throwable|null $prev + * @return ValidationException + */ + public static function fromInputFilter(InputFilterInterface $inputFilter, \Throwable $prev = null): self + { + return static::fromArray($inputFilter->getMessages(), $prev); + } + + /** + * @param array $invalidData + * @param \Throwable|null $prev + * @return ValidationException + */ + public static function fromArray(array $invalidData, \Throwable $prev = null): self + { + return new self( + \sprintf( + 'Provided data is not valid. These are the messages:%s%s%s', + PHP_EOL, + self::formMessagesToString($invalidData), + PHP_EOL + ), + $invalidData, + -1, + $prev + ); + } + + private static function formMessagesToString(array $messages = []) + { + $text = ''; + foreach ($messages as $name => $messageSet) { + $text .= \sprintf( + "\n\t'%s' => %s", + $name, + \is_array($messageSet) ? \print_r($messageSet, true) : $messageSet + ); + } + + return $text; + } + + /** + * @return array + */ + public function getInvalidElements(): array + { + return $this->invalidElements; + } +} diff --git a/module/Core/src/Model/ShortCodeMeta.php b/module/Core/src/Model/ShortCodeMeta.php new file mode 100644 index 00000000..5bdcdf8a --- /dev/null +++ b/module/Core/src/Model/ShortCodeMeta.php @@ -0,0 +1,139 @@ +validate($data); + return $instance; + } + + /** + * @param string|\DateTimeInterface|null $validSince + * @param string|\DateTimeInterface|null $validUntil + * @param string|null $customSlug + * @param int|null $maxVisits + * @return ShortCodeMeta + * @throws ValidationException + */ + public static function createFromParams( + $validSince = null, + $validUntil = null, + $customSlug = null, + $maxVisits = null + ): self { + // We do not type hint the arguments because that will be done by the validation process + $instance = new self(); + $instance->validate([ + ShortUrlMetaInputFilter::VALID_SINCE => $validSince, + ShortUrlMetaInputFilter::VALID_UNTIL => $validUntil, + ShortUrlMetaInputFilter::CUSTOM_SLUG => $customSlug, + ShortUrlMetaInputFilter::MAX_VISITS => $maxVisits, + ]); + return $instance; + } + + /** + * @param array $data + * @throws ValidationException + */ + private function validate(array $data) + { + $inputFilter = new ShortUrlMetaInputFilter($data); + if (! $inputFilter->isValid()) { + throw ValidationException::fromInputFilter($inputFilter); + } + + $this->validSince = $inputFilter->getValue(ShortUrlMetaInputFilter::VALID_SINCE); + $this->validUntil = $inputFilter->getValue(ShortUrlMetaInputFilter::VALID_UNTIL); + $this->customSlug = $inputFilter->getValue(ShortUrlMetaInputFilter::CUSTOM_SLUG); + $this->maxVisits = $inputFilter->getValue(ShortUrlMetaInputFilter::MAX_VISITS); + $this->maxVisits = $this->maxVisits !== null ? (int) $this->maxVisits : null; + } + + /** + * @return \DateTime|null + */ + public function getValidSince() + { + return $this->validSince; + } + + public function hasValidSince(): bool + { + return $this->validSince !== null; + } + + /** + * @return \DateTime|null + */ + public function getValidUntil() + { + return $this->validUntil; + } + + public function hasValidUntil(): bool + { + return $this->validUntil !== null; + } + + /** + * @return null|string + */ + public function getCustomSlug() + { + return $this->customSlug; + } + + public function hasCustomSlug(): bool + { + return $this->customSlug !== null; + } + + /** + * @return int|null + */ + public function getMaxVisits() + { + return $this->maxVisits; + } + + public function hasMaxVisits(): bool + { + return $this->maxVisits !== null; + } +} diff --git a/module/Core/src/Service/ShortUrlService.php b/module/Core/src/Service/ShortUrlService.php index 85359060..261cb961 100644 --- a/module/Core/src/Service/ShortUrlService.php +++ b/module/Core/src/Service/ShortUrlService.php @@ -3,10 +3,11 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Service; -use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM; use Shlinkio\Shlink\Common\Paginator\Adapter\PaginableRepositoryAdapter; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; +use Shlinkio\Shlink\Core\Model\ShortCodeMeta; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Util\TagManagerTrait; use Zend\Paginator\Paginator; @@ -16,11 +17,11 @@ class ShortUrlService implements ShortUrlServiceInterface use TagManagerTrait; /** - * @var EntityManagerInterface + * @var ORM\EntityManagerInterface */ private $em; - public function __construct(EntityManagerInterface $em) + public function __construct(ORM\EntityManagerInterface $em) { $this->em = $em; } @@ -49,7 +50,46 @@ class ShortUrlService implements ShortUrlServiceInterface * @return ShortUrl * @throws InvalidShortCodeException */ - public function setTagsByShortCode($shortCode, array $tags = []): ShortUrl + public function setTagsByShortCode(string $shortCode, array $tags = []): ShortUrl + { + $shortUrl = $this->findByShortCode($shortCode); + $shortUrl->setTags($this->tagNamesToEntities($this->em, $tags)); + $this->em->flush(); + + return $shortUrl; + } + + /** + * @param string $shortCode + * @param ShortCodeMeta $shortCodeMeta + * @return ShortUrl + * @throws InvalidShortCodeException + */ + public function updateMetadataByShortCode(string $shortCode, ShortCodeMeta $shortCodeMeta): ShortUrl + { + $shortUrl = $this->findByShortCode($shortCode); + if ($shortCodeMeta->hasValidSince()) { + $shortUrl->setValidSince($shortCodeMeta->getValidSince()); + } + if ($shortCodeMeta->hasValidUntil()) { + $shortUrl->setValidUntil($shortCodeMeta->getValidUntil()); + } + if ($shortCodeMeta->hasMaxVisits()) { + $shortUrl->setMaxVisits($shortCodeMeta->getMaxVisits()); + } + + /** @var ORM\EntityManager $em */ + $em = $this->em; + $em->flush($shortUrl); + return $shortUrl; + } + + /** + * @param string $shortCode + * @return ShortUrl + * @throws InvalidShortCodeException + */ + private function findByShortCode(string $shortCode): ShortUrl { /** @var ShortUrl|null $shortUrl */ $shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([ @@ -59,9 +99,6 @@ class ShortUrlService implements ShortUrlServiceInterface throw InvalidShortCodeException::fromNotFoundShortCode($shortCode); } - $shortUrl->setTags($this->tagNamesToEntities($this->em, $tags)); - $this->em->flush(); - return $shortUrl; } } diff --git a/module/Core/src/Service/ShortUrlServiceInterface.php b/module/Core/src/Service/ShortUrlServiceInterface.php index 1815c4de..6ac3a9b6 100644 --- a/module/Core/src/Service/ShortUrlServiceInterface.php +++ b/module/Core/src/Service/ShortUrlServiceInterface.php @@ -5,6 +5,7 @@ namespace Shlinkio\Shlink\Core\Service; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; +use Shlinkio\Shlink\Core\Model\ShortCodeMeta; use Zend\Paginator\Paginator; interface ShortUrlServiceInterface @@ -24,5 +25,13 @@ interface ShortUrlServiceInterface * @return ShortUrl * @throws InvalidShortCodeException */ - public function setTagsByShortCode($shortCode, array $tags = []): ShortUrl; + public function setTagsByShortCode(string $shortCode, array $tags = []): ShortUrl; + + /** + * @param string $shortCode + * @param ShortCodeMeta $shortCodeMeta + * @return ShortUrl + * @throws InvalidShortCodeException + */ + public function updateMetadataByShortCode(string $shortCode, ShortCodeMeta $shortCodeMeta): ShortUrl; } diff --git a/module/Core/src/Validation/InputFactoryTrait.php b/module/Core/src/Validation/InputFactoryTrait.php new file mode 100644 index 00000000..289c54e2 --- /dev/null +++ b/module/Core/src/Validation/InputFactoryTrait.php @@ -0,0 +1,20 @@ +setRequired($required) + ->getFilterChain()->attach(new StripTags()) + ->attach(new StringTrim()); + return $input; + } +} diff --git a/module/Core/src/Validation/ShortUrlMetaInputFilter.php b/module/Core/src/Validation/ShortUrlMetaInputFilter.php new file mode 100644 index 00000000..435ef441 --- /dev/null +++ b/module/Core/src/Validation/ShortUrlMetaInputFilter.php @@ -0,0 +1,45 @@ +initialize(); + if ($data !== null) { + $this->setData($data); + } + } + + private function initialize() + { + $validSince = $this->createInput(self::VALID_SINCE, false); + $validSince->getValidatorChain()->attach(new Date(['format' => \DateTime::ATOM])); + $this->add($validSince); + + $validUntil = $this->createInput(self::VALID_UNTIL, false); + $validUntil->getValidatorChain()->attach(new Date(['format' => \DateTime::ATOM])); + $this->add($validUntil); + + $this->add($this->createInput(self::CUSTOM_SLUG, false)); + + $maxVisits = $this->createInput(self::MAX_VISITS, false); + $maxVisits->getValidatorChain()->attach(new IsInt()) + ->attach(new GreaterThan(['min' => 1, 'inclusive' => true])); + $this->add($maxVisits); + } +} diff --git a/module/Rest/src/Action/EditShortCodeAction.php b/module/Rest/src/Action/EditShortCodeAction.php new file mode 100644 index 00000000..f7b8eb49 --- /dev/null +++ b/module/Rest/src/Action/EditShortCodeAction.php @@ -0,0 +1,25 @@ + Date: Sun, 7 Jan 2018 20:00:21 +0100 Subject: [PATCH 46/56] Tested new method to update short URLs metadata --- .../{ShortCodeMeta.php => ShortUrlMeta.php} | 8 +++-- module/Core/src/Service/ShortUrlService.php | 6 ++-- .../src/Service/ShortUrlServiceInterface.php | 6 ++-- .../Core/test/Service/ShortUrlServiceTest.php | 33 ++++++++++++++++++- 4 files changed, 43 insertions(+), 10 deletions(-) rename module/Core/src/Model/{ShortCodeMeta.php => ShortUrlMeta.php} (92%) diff --git a/module/Core/src/Model/ShortCodeMeta.php b/module/Core/src/Model/ShortUrlMeta.php similarity index 92% rename from module/Core/src/Model/ShortCodeMeta.php rename to module/Core/src/Model/ShortUrlMeta.php index 5bdcdf8a..f79edce6 100644 --- a/module/Core/src/Model/ShortCodeMeta.php +++ b/module/Core/src/Model/ShortUrlMeta.php @@ -6,7 +6,7 @@ namespace Shlinkio\Shlink\Core\Model; use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Validation\ShortUrlMetaInputFilter; -final class ShortCodeMeta +final class ShortUrlMeta { /** * @var \DateTime|null @@ -32,7 +32,7 @@ final class ShortCodeMeta /** * @param array $data - * @return ShortCodeMeta + * @return ShortUrlMeta * @throws ValidationException */ public static function createFromRawData(array $data): self @@ -47,7 +47,7 @@ final class ShortCodeMeta * @param string|\DateTimeInterface|null $validUntil * @param string|null $customSlug * @param int|null $maxVisits - * @return ShortCodeMeta + * @return ShortUrlMeta * @throws ValidationException */ public static function createFromParams( @@ -79,7 +79,9 @@ final class ShortCodeMeta } $this->validSince = $inputFilter->getValue(ShortUrlMetaInputFilter::VALID_SINCE); + $this->validSince = $this->validSince !== null ? new \DateTime($this->validSince) : null; $this->validUntil = $inputFilter->getValue(ShortUrlMetaInputFilter::VALID_UNTIL); + $this->validUntil = $this->validUntil !== null ? new \DateTime($this->validUntil) : null; $this->customSlug = $inputFilter->getValue(ShortUrlMetaInputFilter::CUSTOM_SLUG); $this->maxVisits = $inputFilter->getValue(ShortUrlMetaInputFilter::MAX_VISITS); $this->maxVisits = $this->maxVisits !== null ? (int) $this->maxVisits : null; diff --git a/module/Core/src/Service/ShortUrlService.php b/module/Core/src/Service/ShortUrlService.php index 261cb961..3a89b828 100644 --- a/module/Core/src/Service/ShortUrlService.php +++ b/module/Core/src/Service/ShortUrlService.php @@ -7,7 +7,7 @@ use Doctrine\ORM; use Shlinkio\Shlink\Common\Paginator\Adapter\PaginableRepositoryAdapter; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; -use Shlinkio\Shlink\Core\Model\ShortCodeMeta; +use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Util\TagManagerTrait; use Zend\Paginator\Paginator; @@ -61,11 +61,11 @@ class ShortUrlService implements ShortUrlServiceInterface /** * @param string $shortCode - * @param ShortCodeMeta $shortCodeMeta + * @param ShortUrlMeta $shortCodeMeta * @return ShortUrl * @throws InvalidShortCodeException */ - public function updateMetadataByShortCode(string $shortCode, ShortCodeMeta $shortCodeMeta): ShortUrl + public function updateMetadataByShortCode(string $shortCode, ShortUrlMeta $shortCodeMeta): ShortUrl { $shortUrl = $this->findByShortCode($shortCode); if ($shortCodeMeta->hasValidSince()) { diff --git a/module/Core/src/Service/ShortUrlServiceInterface.php b/module/Core/src/Service/ShortUrlServiceInterface.php index 6ac3a9b6..2350105b 100644 --- a/module/Core/src/Service/ShortUrlServiceInterface.php +++ b/module/Core/src/Service/ShortUrlServiceInterface.php @@ -5,7 +5,7 @@ namespace Shlinkio\Shlink\Core\Service; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; -use Shlinkio\Shlink\Core\Model\ShortCodeMeta; +use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Zend\Paginator\Paginator; interface ShortUrlServiceInterface @@ -29,9 +29,9 @@ interface ShortUrlServiceInterface /** * @param string $shortCode - * @param ShortCodeMeta $shortCodeMeta + * @param ShortUrlMeta $shortCodeMeta * @return ShortUrl * @throws InvalidShortCodeException */ - public function updateMetadataByShortCode(string $shortCode, ShortCodeMeta $shortCodeMeta): ShortUrl; + public function updateMetadataByShortCode(string $shortCode, ShortUrlMeta $shortCodeMeta): ShortUrl; } diff --git a/module/Core/test/Service/ShortUrlServiceTest.php b/module/Core/test/Service/ShortUrlServiceTest.php index ed98a5bc..9de6d89f 100644 --- a/module/Core/test/Service/ShortUrlServiceTest.php +++ b/module/Core/test/Service/ShortUrlServiceTest.php @@ -10,8 +10,11 @@ use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\Tag; +use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; +use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Service\ShortUrlService; +use Shlinkio\Shlink\Core\Validation\ShortUrlMetaInputFilter; class ShortUrlServiceTest extends TestCase { @@ -55,7 +58,6 @@ class ShortUrlServiceTest extends TestCase /** * @test - * @expectedException \Shlinkio\Shlink\Core\Exception\InvalidShortCodeException */ public function exceptionIsThrownWhenSettingTagsOnInvalidShortcode() { @@ -65,6 +67,7 @@ class ShortUrlServiceTest extends TestCase ->shouldBeCalledTimes(1); $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); + $this->expectException(InvalidShortCodeException::class); $this->service->setTagsByShortCode($shortCode); } @@ -88,4 +91,32 @@ class ShortUrlServiceTest extends TestCase $this->service->setTagsByShortCode($shortCode, ['foo', 'bar']); } + + /** + * @test + */ + public function updateMetadataByShortCodeUpdatesProvidedData() + { + $shortUrl = new ShortUrl(); + + $repo = $this->prophesize(ShortUrlRepository::class); + $findShortUrl = $repo->findOneBy(['shortCode' => 'abc123'])->willReturn($shortUrl); + $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); + $flush = $this->em->flush($shortUrl)->willReturn(null); + + $result = $this->service->updateMetadataByShortCode('abc123', ShortUrlMeta::createFromParams( + (new \DateTime('2017-01-01 00:00:00'))->format(\DateTime::ATOM), + (new \DateTime('2017-01-05 00:00:00'))->format(\DateTime::ATOM), + null, + 5 + )); + + $this->assertSame($shortUrl, $result); + $this->assertEquals(new \DateTime('2017-01-01 00:00:00'), $shortUrl->getValidSince()); + $this->assertEquals(new \DateTime('2017-01-05 00:00:00'), $shortUrl->getValidUntil()); + $this->assertEquals(5, $shortUrl->getMaxVisits()); + $findShortUrl->shouldHaveBeenCalled(); + $getRepo->shouldHaveBeenCalled(); + $flush->shouldHaveBeenCalled(); + } } From e8a0c5484c77faf896ad06969c1792beb61bd011 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 7 Jan 2018 20:07:12 +0100 Subject: [PATCH 47/56] Added test for ShortUrlMeta --- module/Core/test/Model/ShortUrlMetaTest.php | 59 +++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 module/Core/test/Model/ShortUrlMetaTest.php diff --git a/module/Core/test/Model/ShortUrlMetaTest.php b/module/Core/test/Model/ShortUrlMetaTest.php new file mode 100644 index 00000000..8341fbb7 --- /dev/null +++ b/module/Core/test/Model/ShortUrlMetaTest.php @@ -0,0 +1,59 @@ +expectException(ValidationException::class); + ShortUrlMeta::createFromRawData($data); + } + + public function provideInvalidData(): array + { + return [ + [[ + ShortUrlMetaInputFilter::VALID_SINCE => '', + ShortUrlMetaInputFilter::VALID_UNTIL => '', + ShortUrlMetaInputFilter::CUSTOM_SLUG => 'foobar', + ShortUrlMetaInputFilter::MAX_VISITS => 'invalid', + ]], + [[ + ShortUrlMetaInputFilter::VALID_SINCE => '2017', + ShortUrlMetaInputFilter::MAX_VISITS => 5, + ]], + ]; + } + + /** + * @test + */ + public function properlyCreatedInstanceReturnsValues() + { + $meta = ShortUrlMeta::createFromParams((new \DateTime('2015-01-01'))->format(\DateTime::ATOM), null, 'foobar'); + + $this->assertTrue($meta->hasValidSince()); + $this->assertEquals(new \DateTime('2015-01-01'), $meta->getValidSince()); + + $this->assertFalse($meta->hasValidUntil()); + $this->assertNull($meta->getValidUntil()); + + $this->assertTrue($meta->hasCustomSlug()); + $this->assertEquals('foobar', $meta->getCustomSlug()); + + $this->assertFalse($meta->hasMaxVisits()); + $this->assertNull($meta->getMaxVisits()); + } +} From 7ba9eb8e2c10daa4e1e4852c394f27c3231a80a3 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 7 Jan 2018 20:08:07 +0100 Subject: [PATCH 48/56] Fixed coding styles --- module/Core/test/Service/ShortUrlServiceTest.php | 1 - module/Rest/src/Action/EditShortCodeAction.php | 1 - 2 files changed, 2 deletions(-) diff --git a/module/Core/test/Service/ShortUrlServiceTest.php b/module/Core/test/Service/ShortUrlServiceTest.php index 9de6d89f..cc406713 100644 --- a/module/Core/test/Service/ShortUrlServiceTest.php +++ b/module/Core/test/Service/ShortUrlServiceTest.php @@ -14,7 +14,6 @@ use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Service\ShortUrlService; -use Shlinkio\Shlink\Core\Validation\ShortUrlMetaInputFilter; class ShortUrlServiceTest extends TestCase { diff --git a/module/Rest/src/Action/EditShortCodeAction.php b/module/Rest/src/Action/EditShortCodeAction.php index f7b8eb49..84d3aaae 100644 --- a/module/Rest/src/Action/EditShortCodeAction.php +++ b/module/Rest/src/Action/EditShortCodeAction.php @@ -20,6 +20,5 @@ class EditShortCodeAction extends AbstractRestAction */ public function process(ServerRequestInterface $request, DelegateInterface $delegate): ResponseInterface { - } } From 84094a51a27183c4e954d13011ba4cff372c9d12 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 7 Jan 2018 20:45:05 +0100 Subject: [PATCH 49/56] Implemented EditShortCodeAction --- module/Rest/config/routes.config.php | 26 ++++-- .../Rest/src/Action/EditShortCodeAction.php | 50 ++++++++++ module/Rest/src/ConfigProvider.php | 18 +++- module/Rest/src/Util/RestUtils.php | 1 + .../test/Action/EditShortCodeActionTest.php | 93 +++++++++++++++++++ phpunit-func.xml | 7 +- phpunit.xml.dist | 7 +- 7 files changed, 189 insertions(+), 13 deletions(-) create mode 100644 module/Rest/test/Action/EditShortCodeActionTest.php diff --git a/module/Rest/config/routes.config.php b/module/Rest/config/routes.config.php index 742f904b..a1bd8dca 100644 --- a/module/Rest/config/routes.config.php +++ b/module/Rest/config/routes.config.php @@ -9,7 +9,7 @@ return [ 'routes' => [ [ 'name' => Action\AuthenticateAction::class, - 'path' => '/rest/v{version:1}/authenticate', + 'path' => '/authenticate', 'middleware' => Action\AuthenticateAction::class, 'allowed_methods' => [RequestMethod::METHOD_POST], ], @@ -17,25 +17,31 @@ return [ // Short codes [ 'name' => Action\CreateShortcodeAction::class, - 'path' => '/rest/v{version:1}/short-codes', + 'path' => '/short-codes', 'middleware' => Action\CreateShortcodeAction::class, 'allowed_methods' => [RequestMethod::METHOD_POST], ], + [ + 'name' => Action\EditShortCodeAction::class, + 'path' => '/short-codes/{shortCode}', + 'middleware' => Action\EditShortCodeAction::class, + 'allowed_methods' => [RequestMethod::METHOD_PUT], + ], [ 'name' => Action\ResolveUrlAction::class, - 'path' => '/rest/v{version:1}/short-codes/{shortCode}', + 'path' => '/short-codes/{shortCode}', 'middleware' => Action\ResolveUrlAction::class, 'allowed_methods' => [RequestMethod::METHOD_GET], ], [ 'name' => Action\ListShortcodesAction::class, - 'path' => '/rest/v{version:1}/short-codes', + 'path' => '/short-codes', 'middleware' => Action\ListShortcodesAction::class, 'allowed_methods' => [RequestMethod::METHOD_GET], ], [ 'name' => Action\EditShortcodeTagsAction::class, - 'path' => '/rest/v{version:1}/short-codes/{shortCode}/tags', + 'path' => '/short-codes/{shortCode}/tags', 'middleware' => Action\EditShortcodeTagsAction::class, 'allowed_methods' => [RequestMethod::METHOD_PUT], ], @@ -43,7 +49,7 @@ return [ // Visits [ 'name' => Action\GetVisitsAction::class, - 'path' => '/rest/v{version:1}/short-codes/{shortCode}/visits', + 'path' => '/short-codes/{shortCode}/visits', 'middleware' => Action\GetVisitsAction::class, 'allowed_methods' => [RequestMethod::METHOD_GET], ], @@ -51,25 +57,25 @@ return [ // Tags [ 'name' => Action\Tag\ListTagsAction::class, - 'path' => '/rest/v{version:1}/tags', + 'path' => '/tags', 'middleware' => Action\Tag\ListTagsAction::class, 'allowed_methods' => [RequestMethod::METHOD_GET], ], [ 'name' => Action\Tag\DeleteTagsAction::class, - 'path' => '/rest/v{version:1}/tags', + 'path' => '/tags', 'middleware' => Action\Tag\DeleteTagsAction::class, 'allowed_methods' => [RequestMethod::METHOD_DELETE], ], [ 'name' => Action\Tag\CreateTagsAction::class, - 'path' => '/rest/v{version:1}/tags', + 'path' => '/tags', 'middleware' => Action\Tag\CreateTagsAction::class, 'allowed_methods' => [RequestMethod::METHOD_POST], ], [ 'name' => Action\Tag\UpdateTagAction::class, - 'path' => '/rest/v{version:1}/tags', + 'path' => '/tags', 'middleware' => Action\Tag\UpdateTagAction::class, 'allowed_methods' => [RequestMethod::METHOD_PUT], ], diff --git a/module/Rest/src/Action/EditShortCodeAction.php b/module/Rest/src/Action/EditShortCodeAction.php index 84d3aaae..a8f12de6 100644 --- a/module/Rest/src/Action/EditShortCodeAction.php +++ b/module/Rest/src/Action/EditShortCodeAction.php @@ -6,9 +6,36 @@ namespace Shlinkio\Shlink\Rest\Action; use Interop\Http\ServerMiddleware\DelegateInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Psr\Log\LoggerInterface; +use Shlinkio\Shlink\Core\Exception; +use Shlinkio\Shlink\Core\Model\ShortUrlMeta; +use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface; +use Shlinkio\Shlink\Rest\Util\RestUtils; +use Zend\Diactoros\Response\EmptyResponse; +use Zend\Diactoros\Response\JsonResponse; +use Zend\I18n\Translator\TranslatorInterface; class EditShortCodeAction extends AbstractRestAction { + /** + * @var ShortUrlServiceInterface + */ + private $shortUrlService; + /** + * @var TranslatorInterface + */ + private $translator; + + public function __construct( + ShortUrlServiceInterface $shortUrlService, + TranslatorInterface $translator, + LoggerInterface $logger = null + ) { + parent::__construct($logger); + $this->shortUrlService = $shortUrlService; + $this->translator = $translator; + } + /** * Process an incoming server request and return a response, optionally delegating * to the next middleware component to create the response. @@ -17,8 +44,31 @@ class EditShortCodeAction extends AbstractRestAction * @param DelegateInterface $delegate * * @return ResponseInterface + * @throws \InvalidArgumentException */ public function process(ServerRequestInterface $request, DelegateInterface $delegate): ResponseInterface { + $postData = (array) $request->getParsedBody(); + $shortCode = $request->getAttribute('shortCode', ''); + + try { + $this->shortUrlService->updateMetadataByShortCode( + $shortCode, + ShortUrlMeta::createFromRawData($postData) + ); + return new EmptyResponse(); + } catch (Exception\InvalidShortCodeException $e) { + $this->logger->warning('Provided data is invalid.' . PHP_EOL . $e); + return new JsonResponse([ + 'error' => RestUtils::getRestErrorCodeFromException($e), + 'message' => \sprintf($this->translator->translate('No URL found for short code "%s"'), $shortCode), + ], self::STATUS_NOT_FOUND); + } catch (Exception\ValidationException $e) { + $this->logger->warning('Provided data is invalid.' . PHP_EOL . $e); + return new JsonResponse([ + 'error' => RestUtils::getRestErrorCodeFromException($e), + 'message' => $this->translator->translate('Provided data is invalid.'), + ], self::STATUS_BAD_REQUEST); + } } } diff --git a/module/Rest/src/ConfigProvider.php b/module/Rest/src/ConfigProvider.php index 3fc7a9be..2fc3c017 100644 --- a/module/Rest/src/ConfigProvider.php +++ b/module/Rest/src/ConfigProvider.php @@ -8,8 +8,24 @@ use Zend\Stdlib\Glob; class ConfigProvider { + const ROUTES_PREFIX = '/rest/v{version:1}'; + public function __invoke() { - return Factory::fromFiles(Glob::glob(__DIR__ . '/../config/{,*.}config.php', Glob::GLOB_BRACE)); + return $this->applyRoutesPrefix( + Factory::fromFiles(Glob::glob(__DIR__ . '/../config/{,*.}config.php', Glob::GLOB_BRACE)) + ); + } + + private function applyRoutesPrefix(array $config): array + { + $routes =& $config['routes'] ?? []; + + // Prepend the routes prefix to every path + foreach ($routes as $key => $route) { + $routes[$key]['path'] = self::ROUTES_PREFIX . $route['path']; + } + + return $config; } } diff --git a/module/Rest/src/Util/RestUtils.php b/module/Rest/src/Util/RestUtils.php index 407b0845..b9d96891 100644 --- a/module/Rest/src/Util/RestUtils.php +++ b/module/Rest/src/Util/RestUtils.php @@ -30,6 +30,7 @@ class RestUtils case $e instanceof Core\NonUniqueSlugException: return self::INVALID_SLUG_ERROR; case $e instanceof Common\InvalidArgumentException: + case $e instanceof Core\ValidationException: return self::INVALID_ARGUMENT_ERROR; case $e instanceof Rest\AuthenticationException: return self::INVALID_CREDENTIALS_ERROR; diff --git a/module/Rest/test/Action/EditShortCodeActionTest.php b/module/Rest/test/Action/EditShortCodeActionTest.php new file mode 100644 index 00000000..2690811a --- /dev/null +++ b/module/Rest/test/Action/EditShortCodeActionTest.php @@ -0,0 +1,93 @@ +shortUrlService = $this->prophesize(ShortUrlServiceInterface::class); + $this->action = new EditShortCodeAction($this->shortUrlService->reveal(), Translator::factory([])); + } + + /** + * @test + */ + public function invalidDataReturnsError() + { + $request = ServerRequestFactory::fromGlobals()->withParsedBody([ + 'maxVisits' => 'invalid', + ]); + + /** @var JsonResponse $resp */ + $resp = $this->action->process($request, $this->prophesize(DelegateInterface::class)->reveal()); + $payload = $resp->getPayload(); + + $this->assertEquals(400, $resp->getStatusCode()); + $this->assertEquals(RestUtils::INVALID_ARGUMENT_ERROR, $payload['error']); + $this->assertEquals('Provided data is invalid.', $payload['message']); + } + + /** + * @test + */ + public function incorrectShortCodeReturnsError() + { + $request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', 'abc123') + ->withParsedBody([ + 'maxVisits' => 5, + ]); + $updateMeta = $this->shortUrlService->updateMetadataByShortCode(Argument::cetera())->willThrow( + InvalidShortCodeException::class + ); + + /** @var JsonResponse $resp */ + $resp = $this->action->process($request, $this->prophesize(DelegateInterface::class)->reveal()); + $payload = $resp->getPayload(); + + $this->assertEquals(404, $resp->getStatusCode()); + $this->assertEquals(RestUtils::INVALID_SHORTCODE_ERROR, $payload['error']); + $this->assertEquals('No URL found for short code "abc123"', $payload['message']); + $updateMeta->shouldHaveBeenCalled(); + } + + /** + * @test + */ + public function correctShortCodeReturnsSuccess() + { + $request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', 'abc123') + ->withParsedBody([ + 'maxVisits' => 5, + ]); + $updateMeta = $this->shortUrlService->updateMetadataByShortCode(Argument::cetera())->willReturn(new ShortUrl()); + + $resp = $this->action->process($request, $this->prophesize(DelegateInterface::class)->reveal()); + + $this->assertEquals(204, $resp->getStatusCode()); + $updateMeta->shouldHaveBeenCalled(); + } +} diff --git a/phpunit-func.xml b/phpunit-func.xml index d125e506..f06c11ee 100644 --- a/phpunit-func.xml +++ b/phpunit-func.xml @@ -1,4 +1,9 @@ - + + ./module/*/test-func diff --git a/phpunit.xml.dist b/phpunit.xml.dist index ddd4f42b..af71c7cd 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,4 +1,9 @@ - + + ./module/Common/test From 6f7ce709ca75e34e20590486c6e515096e4edb7e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 7 Jan 2018 20:46:28 +0100 Subject: [PATCH 50/56] Fixed PhpStan error --- module/Rest/src/ConfigProvider.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/module/Rest/src/ConfigProvider.php b/module/Rest/src/ConfigProvider.php index 2fc3c017..c6afdcfe 100644 --- a/module/Rest/src/ConfigProvider.php +++ b/module/Rest/src/ConfigProvider.php @@ -12,9 +12,9 @@ class ConfigProvider public function __invoke() { - return $this->applyRoutesPrefix( - Factory::fromFiles(Glob::glob(__DIR__ . '/../config/{,*.}config.php', Glob::GLOB_BRACE)) - ); + /** @var array $config */ + $config = Factory::fromFiles(Glob::glob(__DIR__ . '/../config/{,*.}config.php', Glob::GLOB_BRACE)); + return $this->applyRoutesPrefix($config); } private function applyRoutesPrefix(array $config): array From ecebdbbfa8e76c7fbdbdff9139d77d602582dcb2 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 7 Jan 2018 20:54:02 +0100 Subject: [PATCH 51/56] Updated API docs including new endpoint and updating params for short code creation --- docs/swagger/paths/v1_short-codes.json | 29 +++++++++ .../paths/v1_short-codes_{shortCode}.json | 64 +++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/docs/swagger/paths/v1_short-codes.json b/docs/swagger/paths/v1_short-codes.json index bdeffe3f..c63a11a9 100644 --- a/docs/swagger/paths/v1_short-codes.json +++ b/docs/swagger/paths/v1_short-codes.json @@ -116,6 +116,7 @@ } } }, + "post": { "tags": [ "ShortCodes" @@ -140,6 +141,34 @@ "type": "string" } }, + { + "name": "validSince", + "in": "formData", + "description": "The date (in ISO-8601 format) from which this short code will be valid", + "required": false, + "type": "string" + }, + { + "name": "validUntil", + "in": "formData", + "description": "The date (in ISO-8601 format) until which this short code will be valid", + "required": false, + "type": "string" + }, + { + "name": "customSlug", + "in": "formData", + "description": "A unique custom slug to be used instead of the generated short code", + "required": false, + "type": "string" + }, + { + "name": "maxVisits", + "in": "formData", + "description": "The maximum number of allowed visits for this short code", + "required": false, + "type": "number" + }, { "$ref": "../parameters/Authorization.json" } diff --git a/docs/swagger/paths/v1_short-codes_{shortCode}.json b/docs/swagger/paths/v1_short-codes_{shortCode}.json index 78e54f61..01fdd634 100644 --- a/docs/swagger/paths/v1_short-codes_{shortCode}.json +++ b/docs/swagger/paths/v1_short-codes_{shortCode}.json @@ -54,5 +54,69 @@ } } } + }, + + "put": { + "tags": [ + "ShortCodes" + ], + "summary": "Edit short code", + "description": "Update certain meta arguments from an existing short URL.", + "parameters": [ + { + "name": "shortCode", + "in": "path", + "type": "string", + "description": "The short code to edit.", + "required": true + }, + { + "name": "validSince", + "in": "formData", + "description": "The date (in ISO-8601 format) from which this short code will be valid", + "required": false, + "type": "string" + }, + { + "name": "validUntil", + "in": "formData", + "description": "The date (in ISO-8601 format) until which this short code will be valid", + "required": false, + "type": "string" + }, + { + "name": "maxVisits", + "in": "formData", + "description": "The maximum number of allowed visits for this short code", + "required": false, + "type": "number" + }, + { + "$ref": "../parameters/Authorization.json" + } + ], + "responses": { + "204": { + "description": "The short code has been properly updated." + }, + "400": { + "description": "Provided meta arguments are invalid.", + "schema": { + "$ref": "../definitions/Error.json" + } + }, + "404": { + "description": "No short URL was found for provided short code.", + "schema": { + "$ref": "../definitions/Error.json" + } + }, + "500": { + "description": "Unexpected error.", + "schema": { + "$ref": "../definitions/Error.json" + } + } + } } } From ce9d6642d4fb978b35c79dc495033146261fcc75 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 7 Jan 2018 21:13:06 +0100 Subject: [PATCH 52/56] Fixed edit short code action not being properly registered --- module/Core/src/Service/UrlShortener.php | 5 ++--- module/Rest/config/dependencies.config.php | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index c0e19d1e..87621060 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -7,7 +7,6 @@ use Cocur\Slugify\Slugify; use Cocur\Slugify\SlugifyInterface; use Doctrine\Common\Cache\Cache; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\ORMException; use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\GuzzleException; use Psr\Http\Message\UriInterface; @@ -119,14 +118,14 @@ class UrlShortener implements UrlShortenerInterface $this->em->flush(); // Generate the short code and persist it - $shortCode = $customSlug ?? $this->convertAutoincrementIdToShortCode($shortUrl->getId()); + $shortCode = $customSlug ?? $this->convertAutoincrementIdToShortCode((float) $shortUrl->getId()); $shortUrl->setShortCode($shortCode) ->setTags($this->tagNamesToEntities($this->em, $tags)); $this->em->flush(); $this->em->commit(); return $shortCode; - } catch (ORMException $e) { + } catch (\Throwable $e) { if ($this->em->getConnection()->isTransactionActive()) { $this->em->rollback(); $this->em->close(); diff --git a/module/Rest/config/dependencies.config.php b/module/Rest/config/dependencies.config.php index 760f46c8..bfd1a774 100644 --- a/module/Rest/config/dependencies.config.php +++ b/module/Rest/config/dependencies.config.php @@ -21,6 +21,7 @@ return [ Action\AuthenticateAction::class => ConfigAbstractFactory::class, Action\CreateShortcodeAction::class => ConfigAbstractFactory::class, + Action\EditShortCodeAction::class => ConfigAbstractFactory::class, Action\ResolveUrlAction::class => ConfigAbstractFactory::class, Action\GetVisitsAction::class => ConfigAbstractFactory::class, Action\ListShortcodesAction::class => ConfigAbstractFactory::class, @@ -48,6 +49,7 @@ return [ 'config.url_shortener.domain', 'Logger_Shlink', ], + Action\EditShortCodeAction::class => [Service\ShortUrlService::class, 'translator', 'Logger_Shlink',], Action\ResolveUrlAction::class => [Service\UrlShortener::class, 'translator'], Action\GetVisitsAction::class => [Service\VisitsTracker::class, 'translator', 'Logger_Shlink'], Action\ListShortcodesAction::class => [Service\ShortUrlService::class, 'translator', 'Logger_Shlink'], From 5fd34e03fc36bc7332e4f19a9069974421f1579e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 14 Jan 2018 09:13:49 +0100 Subject: [PATCH 53/56] Added new app config param to allow disabling short URL visits tracking --- .../Plugin/ApplicationConfigCustomizer.php | 13 ++++++++--- .../CLI/src/Model/CustomizableAppConfig.php | 1 + .../ApplicationConfigCustomizerTest.php | 8 ++++--- module/Core/src/Options/AppOptions.php | 22 +++++++++++++++++++ 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/module/CLI/src/Install/Plugin/ApplicationConfigCustomizer.php b/module/CLI/src/Install/Plugin/ApplicationConfigCustomizer.php index 6a6e1a45..70a64a90 100644 --- a/module/CLI/src/Install/Plugin/ApplicationConfigCustomizer.php +++ b/module/CLI/src/Install/Plugin/ApplicationConfigCustomizer.php @@ -24,14 +24,21 @@ class ApplicationConfigCustomizer implements ConfigCustomizerInterface return; } + $validator = function ($value) { + return $value; + }; $appConfig->setApp([ 'SECRET' => $io->ask( 'Define a secret string that will be used to sign API tokens (leave empty to autogenerate one)', null, - function ($value) { - return $value; - } + $validator ) ?: $this->generateRandomString(32), + 'DISABLE_TRACK_PARAM' => $io->ask( + 'Provide a parameter name that you will be able to use to disable tracking on specific request to ' + . 'short URLs (leave empty and this feature won\'t be enabled)', + null, + $validator + ), ]); } } diff --git a/module/CLI/src/Model/CustomizableAppConfig.php b/module/CLI/src/Model/CustomizableAppConfig.php index 5784f753..b228baa8 100644 --- a/module/CLI/src/Model/CustomizableAppConfig.php +++ b/module/CLI/src/Model/CustomizableAppConfig.php @@ -225,6 +225,7 @@ final class CustomizableAppConfig implements ArraySerializableInterface $config = [ 'app_options' => [ 'secret_key' => $this->app['SECRET'], + 'disable_track_param' => $this->app['DISABLE_TRACK_PARAM'], ], 'entity_manager' => [ 'connection' => [ diff --git a/module/CLI/test/Install/Plugin/ApplicationConfigCustomizerTest.php b/module/CLI/test/Install/Plugin/ApplicationConfigCustomizerTest.php index a156923a..5450d0b1 100644 --- a/module/CLI/test/Install/Plugin/ApplicationConfigCustomizerTest.php +++ b/module/CLI/test/Install/Plugin/ApplicationConfigCustomizerTest.php @@ -34,7 +34,7 @@ class ApplicationConfigCustomizerTest extends TestCase */ public function configIsRequestedToTheUser() { - $askSecret = $this->io->ask(Argument::cetera())->willReturn('the_secret'); + $ask = $this->io->ask(Argument::cetera())->willReturn('the_secret'); $config = new CustomizableAppConfig(); $this->plugin->process($this->io->reveal(), $config); @@ -42,8 +42,9 @@ class ApplicationConfigCustomizerTest extends TestCase $this->assertTrue($config->hasApp()); $this->assertEquals([ 'SECRET' => 'the_secret', + 'DISABLE_TRACK_PARAM' => 'the_secret', ], $config->getApp()); - $askSecret->shouldHaveBeenCalledTimes(1); + $ask->shouldHaveBeenCalledTimes(2); } /** @@ -62,8 +63,9 @@ class ApplicationConfigCustomizerTest extends TestCase $this->assertEquals([ 'SECRET' => 'the_new_secret', + 'DISABLE_TRACK_PARAM' => 'the_new_secret', ], $config->getApp()); - $ask->shouldHaveBeenCalledTimes(1); + $ask->shouldHaveBeenCalledTimes(2); $confirm->shouldHaveBeenCalledTimes(1); } diff --git a/module/Core/src/Options/AppOptions.php b/module/Core/src/Options/AppOptions.php index edb46f08..0220dbd8 100644 --- a/module/Core/src/Options/AppOptions.php +++ b/module/Core/src/Options/AppOptions.php @@ -22,6 +22,10 @@ class AppOptions extends AbstractOptions * @var string */ protected $secretKey = ''; + /** + * @var string|null + */ + protected $disableTrackParam; /** * AppOptions constructor. @@ -86,6 +90,24 @@ class AppOptions extends AbstractOptions return $this; } + /** + * @return string|null + */ + public function getDisableTrackParam() + { + return $this->disableTrackParam; + } + + /** + * @param string|null $disableTrackParam + * @return $this|self + */ + protected function setDisableTrackParam($disableTrackParam): self + { + $this->disableTrackParam = $disableTrackParam; + return $this; + } + /** * @return string */ From 1e79969c3bb7329456ed6f5c8e947c0a2d903a43 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 14 Jan 2018 09:24:33 +0100 Subject: [PATCH 54/56] Made visits not to be tracked if query param has been provided --- module/Core/config/dependencies.config.php | 6 ++- module/Core/src/Action/RedirectAction.php | 19 +++++++-- .../Core/test/Action/RedirectActionTest.php | 42 +++++++++++++++++-- 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index 31a4619a..11038e4b 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -55,7 +55,11 @@ return [ Service\Tag\TagService::class => ['em'], // Middleware - Action\RedirectAction::class => [Service\UrlShortener::class, Service\VisitsTracker::class], + Action\RedirectAction::class => [ + Service\UrlShortener::class, + Service\VisitsTracker::class, + Options\AppOptions::class, + ], Action\QrCodeAction::class => [RouterInterface::class, Service\UrlShortener::class, 'Logger_Shlink'], Action\PreviewAction::class => [PreviewGenerator::class, Service\UrlShortener::class], Middleware\QrCodeCacheMiddleware::class => [Cache::class], diff --git a/module/Core/src/Action/RedirectAction.php b/module/Core/src/Action/RedirectAction.php index 066e9508..e7702dde 100644 --- a/module/Core/src/Action/RedirectAction.php +++ b/module/Core/src/Action/RedirectAction.php @@ -10,6 +10,7 @@ use Psr\Http\Message\ServerRequestInterface as Request; use Shlinkio\Shlink\Core\Action\Util\ErrorResponseBuilderTrait; use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; +use Shlinkio\Shlink\Core\Options\AppOptions; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface; use Zend\Diactoros\Response\RedirectResponse; @@ -26,11 +27,19 @@ class RedirectAction implements MiddlewareInterface * @var VisitsTrackerInterface */ private $visitTracker; + /** + * @var AppOptions + */ + private $appOptions; - public function __construct(UrlShortenerInterface $urlShortener, VisitsTrackerInterface $visitTracker) - { + public function __construct( + UrlShortenerInterface $urlShortener, + VisitsTrackerInterface $visitTracker, + AppOptions $appOptions + ) { $this->urlShortener = $urlShortener; $this->visitTracker = $visitTracker; + $this->appOptions = $appOptions; } /** @@ -45,12 +54,16 @@ class RedirectAction implements MiddlewareInterface public function process(Request $request, DelegateInterface $delegate) { $shortCode = $request->getAttribute('shortCode', ''); + $query = $request->getQueryParams(); + $disableTrackParam = $this->appOptions->getDisableTrackParam(); try { $longUrl = $this->urlShortener->shortCodeToUrl($shortCode); // Track visit to this short code - $this->visitTracker->track($shortCode, $request); + if ($disableTrackParam === null || ! \array_key_exists($disableTrackParam, $query)) { + $this->visitTracker->track($shortCode, $request); + } // Return a redirect response to the long URL. // Use a temporary redirect to make sure browsers always hit the server for analytics purposes diff --git a/module/Core/test/Action/RedirectActionTest.php b/module/Core/test/Action/RedirectActionTest.php index a429549f..ed37baae 100644 --- a/module/Core/test/Action/RedirectActionTest.php +++ b/module/Core/test/Action/RedirectActionTest.php @@ -10,6 +10,7 @@ use Prophecy\Prophecy\MethodProphecy; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Action\RedirectAction; use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; +use Shlinkio\Shlink\Core\Options\AppOptions; use Shlinkio\Shlink\Core\Service\UrlShortener; use Shlinkio\Shlink\Core\Service\VisitsTracker; use ShlinkioTest\Shlink\Common\Util\TestUtils; @@ -26,13 +27,21 @@ class RedirectActionTest extends TestCase * @var ObjectProphecy */ protected $urlShortener; + /** + * @var ObjectProphecy + */ + protected $visitTracker; public function setUp() { $this->urlShortener = $this->prophesize(UrlShortener::class); - $visitTracker = $this->prophesize(VisitsTracker::class); - $visitTracker->track(Argument::any()); - $this->action = new RedirectAction($this->urlShortener->reveal(), $visitTracker->reveal()); + $this->visitTracker = $this->prophesize(VisitsTracker::class); + + $this->action = new RedirectAction( + $this->urlShortener->reveal(), + $this->visitTracker->reveal(), + new AppOptions(['disableTrackParam' => 'foobar']) + ); } /** @@ -44,6 +53,8 @@ class RedirectActionTest extends TestCase $expectedUrl = 'http://domain.com/foo/bar'; $this->urlShortener->shortCodeToUrl($shortCode)->willReturn($expectedUrl) ->shouldBeCalledTimes(1); + $this->visitTracker->track(Argument::cetera())->willReturn(null) + ->shouldBeCalledTimes(1); $request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode); $response = $this->action->process($request, TestUtils::createDelegateMock()->reveal()); @@ -62,6 +73,9 @@ class RedirectActionTest extends TestCase $shortCode = 'abc123'; $this->urlShortener->shortCodeToUrl($shortCode)->willThrow(EntityDoesNotExistException::class) ->shouldBeCalledTimes(1); + $this->visitTracker->track(Argument::cetera())->willReturn(null) + ->shouldNotBeCalled(); + $delegate = $this->prophesize(DelegateInterface::class); /** @var MethodProphecy $process */ $process = $delegate->process(Argument::any())->willReturn(new Response()); @@ -71,4 +85,26 @@ class RedirectActionTest extends TestCase $process->shouldHaveBeenCalledTimes(1); } + + /** + * @test + */ + public function visitIsNotTrackedIfDisableParamIsProvided() + { + $shortCode = 'abc123'; + $expectedUrl = 'http://domain.com/foo/bar'; + $this->urlShortener->shortCodeToUrl($shortCode)->willReturn($expectedUrl) + ->shouldBeCalledTimes(1); + $this->visitTracker->track(Argument::cetera())->willReturn(null) + ->shouldNotBeCalled(); + + $request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode) + ->withQueryParams(['foobar' => true]); + $response = $this->action->process($request, TestUtils::createDelegateMock()->reveal()); + + $this->assertInstanceOf(Response\RedirectResponse::class, $response); + $this->assertEquals(302, $response->getStatusCode()); + $this->assertTrue($response->hasHeader('Location')); + $this->assertEquals($expectedUrl, $response->getHeaderLine('Location')); + } } From cf8b778711aedc4d9e44203db943f7e4e53e0d74 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 21 Jan 2018 09:40:38 +0100 Subject: [PATCH 55/56] Updated language files --- module/CLI/lang/es.mo | Bin 8853 -> 8799 bytes module/CLI/lang/es.po | 82 ++++++++++++++++++++++++----------------- module/Rest/lang/es.mo | Bin 2793 -> 2835 bytes module/Rest/lang/es.po | 15 +++++--- 4 files changed, 58 insertions(+), 39 deletions(-) diff --git a/module/CLI/lang/es.mo b/module/CLI/lang/es.mo index 472ff44716283ef20e0f7be572b93c5cc6c6daa4..2b8b5135bfcaddef812ad4f6718194f4c0444224 100644 GIT binary patch delta 2371 zcmY+_du$X%9KiAEwUlcuQVuC6k1{P%p!CYMAhbn!ZQ*QdrCRZj|3Yj-B`t(!ZcC(wNGHITcF9a0%XuYcP(xunZ4i6+RZ;Ka96<{wm5` zPvB%cgER3Hlm-2YGH^v@(7z6!;d~j^GQK)ZV*?k)QD(OACI-g`P$u#=a!Z}ZQoMjN z;fq*@zoSfK3bSJ@)qrxIL~f}R@~5_Nkl*#Afi^ZXzWSWTBrN6NKM0p|-hfTmkBjjz zZo-pDlvU-;O7W*+9Au((xC&R`0(=CkaTH|%@1R8H3{JzZu^^$n5?=T#bP{ot9L~j7 zT!XR)4&Z(~f)b%9PnYw}xEVjhMw~t+7@!L!_d7!Oqom>mlzwkcA^vSNzT$$+FvbQT z7-}}k1R8N6_TdjD42H7Oe!?W7e+XsM9mV@_9DA{y<;et}!dCnk=b)@20UBrHrW)ce zGtY8i8=gWLpqgpQ=4-?m*p2doew5AjC^8rIM0kB9v>4w1G`xQSB^5uTEMzJVl@zQ* z*$X=hG)8FTumMXMeLFUytfYV&@jTK+#hH!dv;}1%T`0SJ9ZE<|l$9PqiRfG5{gX%# z)R!m`zKjG*75<@-rBThaBxDDX{iQxY>F^n@#vel$Fq&+#4DQ6&u^dYY4_Q=|sBso< z!Ijv8&!MdNeU#^afh@S7zM~ytoP!E%)A_u?Xa7`yN_-t2`@fBbbR5EVJdU!GODH+Mijtagel06WUm z3MzBDp={tRN|hW-xh+7=4;5se+(DJ-rKnO0OF2lM*arSb2Ko((C8>}iDP5=3{c0`^Qg%a;9ex+Jiz>O4!Zz{$ z5A^zH!E4KRNUf%BqDsn|sFJb>Ro)IiT`1p(wcc;#dpvLAQLj1L<2ljwW%cu%dhfmH z=?U6z9jut)jaH<*KPwK^boiIlS~G9jhHIv^v)8iS;-SiT#5-H1%d%E}uz0DeJJJ+% z+$Oj6fR#4Y|4l~^+18M0XAkIX#^EX6;>mRl|6LofO?kB1ZVX=6Gi_!~t`;{;emBy# zeQ(Cm`!m@r56ER)?d~;oemJ+=w6(QI@5?wD*U@_{n;UfW=cw=A+S~kmEamNrbVnD^pf$oH$J`1TNFQ4 z-5m61O7yf{qxegFU&MQ5=KKj+!yYt>XJ>vC2}9jIR!nCGEx(_o!ZRm0^|8$X*T|W9 z*Yu-lq%FsLxwdT?55De}1T}3qD&=M#95!86apQec0+~0`#dEdE()N_&T0v+Hd+eAt nvpPR^B4^r`PMbk^d4Qo!$2BCb+VEGAHk!RZ>iWEa`qqB|D05ud delta 2418 zcmZ|PUu;uV9KiA43XHCs;07BT$haF!kg=^D;GlpTTY*i+pA8W4$!mM*O1j(GKTL_S z2u9Qx2f0p7M9fS~Flr*!2ZDwL=Yxuxpa~cW5H(SJQH+rneDM3b?FcWP^xV(=b9&G3 z{LZhv78>1I#UfSs7tTTNbP+!q*p7Yp5FW#gcpXF7 z;uWdKH0pa_EBG<$doJL5k*xgANf$R7OGIX10xR%oEW=}1gD0>WPvca)PXvA9p2K$+aIf#T!xga5J`| zjamz1IE<%JGvdEHe?5%bxc(EDVtaZ1efv-|da&SIs5Nj_^RELhazg_sV&hq~l%Y;s zgL82$-iNz!CI$Ko>WYq$4o&rYs5SHrc3?THcm(@#3x0qa=q+r;PFAr1Ggaijk&{tw z%*S)6E4YR`u!S%+<%dy=?{y?rIfWY71nT#$q88h~NNnO^uzs%+_4(3*-KgXCqK=Da zInljPb%iI9Amuw`Tje^A;!V64Ut-xiF^9eAC5p|s3mGC~NcQCn>O|*JtNj9MrvE@) zc?~}W&3JY-CpsX6K{QbVIF7m%A0c%j;~2+pQB%2wFKK{>P*-{sJMf)?zoHh~VwS%L zqgagRP&4=i8q6<0a?;0*TNuDCREkcRMV;Vf)LJ->`ZF3w-LfyS6aPUzvWjIL#@+ZZ zUdAK1!pPUar+7ctJ_?3ilJ!`xzyCd)G;rf7BxrdXb>KK^q&HFb%1acw1@q9rPSk+I zScLlB?eu241~}QYP&Jd9Rc*T6572e{g3Rx(axH2Q+B6%T^woMnE6wBn8TD|k(d!mW zHZ9milNYGuR<2(>KtU>jh2smu+{mqN!d; z=Lh0$6Mq42;p#u&olT2tMd3yVHWkj>a82R7RrjBQbSt%`a8pxEM%~mFZn^1S#)baI zwduc`LDxU2E%eEzmA{_8m9Brqe)=l9hpxr9lJ2F4=-qTNoS%!$b@7B9HFov)8pCGF zuoG!xD4EzFi&&9nWA{KTJm5ZyB#b8$8HU(tBb_i(1BqnXvfG1B$MiRgg3gucA&zz4 z9#2EcvAyFB(v`5&F*{=!!?E;$F=R$9=}KB=+KL!yGn#TQoRN|$=h2ctasHn3V#!gT zY_e=CspE2fU$tkx2DUemu_GLl`FV|SB4Qc!4XJu3RJJHLSk~z&l^q(9`^33Cqb|2( z<|&U;Q4uO<&hP+}#?2HfBMhgvqPKTvlN(GdVK;@CD`>R(18q$Ke^YCK(_o;j(ckL# z`?hau>bLgCQvW+{HRGI5DlQcTa?Y&HMb3fQwe?JujGE2HXXAaDiFfnavr-nxHN(Vc zCfl7WvrqbhvXwkqWZN03Tu1i4t~51!fvUe7C$ncJS&_{PjiHILXv{WcLpt_&#!B~B;q;^&A+1iCuoS*7?jc#3DJZ#zr U^vwi3aVDOQ4c-piSysRNKL\n" "Language-Team: \n" "Language: es_ES\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 2.0.1\n" +"X-Generator: Poedit 2.0.4\n" "X-Poedit-Basepath: ..\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-SourceCharset: UTF-8\n" @@ -24,8 +24,8 @@ msgid "The API key to disable" msgstr "La clave de API a deshabilitar" #, php-format -msgid "API key %s properly disabled" -msgstr "Clave de API %s deshabilitada correctamente" +msgid "API key \"%s\" properly disabled" +msgstr "Clave de API \"%s\" deshabilitada correctamente" #, php-format msgid "API key \"%s\" does not exist." @@ -39,8 +39,9 @@ msgstr "" "La fecha en la que la clave de API debe expirar. Utiliza cualquier valor " "válido en PHP." -msgid "Generated API key" -msgstr "Generada clave de API" +#, php-format +msgid "Generated API key: \"%s\"" +msgstr "Generada clave de API. \"%s\"" msgid "Lists all the available API keys." msgstr "Lista todas las claves de API disponibles." @@ -51,12 +52,12 @@ msgstr "Define si sólo las claves de API habilitadas deben ser devueltas." msgid "Key" msgstr "Clave" -msgid "Expiration date" -msgstr "Fecha de caducidad" - msgid "Is enabled" msgstr "Está habilitada" +msgid "Expiration date" +msgstr "Fecha de caducidad" + #, php-format msgid "" "Generates a character set sample just by shuffling the default one, \"%s\". " @@ -65,8 +66,9 @@ msgstr "" "Genera un grupo de caracteres simplemente mexclando el grupo por defecto \"%s" "\". Después puede ser utilizado en la variable de entrono SHORTCODE_CHARS" -msgid "Character set:" -msgstr "Grupo de caracteres:" +#, php-format +msgid "Character set: \"%s\"" +msgstr "Grupo de caracteres: \"%s\"" msgid "" "Generates a random secret string that can be used for JWT token encryption" @@ -74,8 +76,9 @@ msgstr "" "Genera una cadena de caracteres aleatoria que puede ser usada para cifrar " "tokens JWT" -msgid "Secret key:" -msgstr "Clave secreta:" +#, php-format +msgid "Secret key: \"%s\"" +msgstr "Clave secreta: \"%s\"" msgid "" "Processes and generates the previews for every URL, improving performance " @@ -125,17 +128,22 @@ msgid "If provided, this slug will be used instead of generating a short code" msgstr "" "Si se proporciona, este slug será usado en vez de generar un código corto" -msgid "A long URL was not provided. Which URL do you want to shorten?:" +msgid "This will limit the number of visits for this short URL." +msgstr "Esto limitará el número de visitas a esta URL acortada." + +#, fuzzy +#| msgid "A long URL was not provided. Which URL do you want to shorten?:" +msgid "A long URL was not provided. Which URL do you want to be shortened?" msgstr "No se ha proporcionado una URL larga. ¿Qué URL deseas acortar?" msgid "A URL was not provided!" msgstr "¡No se ha proporcionado una URL!" -msgid "Processed URL:" -msgstr "URL procesada:" +msgid "Processed long URL:" +msgstr "URL larga procesada:" -msgid "Generated URL:" -msgstr "URL generada:" +msgid "Generated short URL:" +msgstr "URL corta generada:" #, php-format msgid "Provided URL \"%s\" is invalid. Try with a different one." @@ -166,8 +174,8 @@ msgid "Allows to filter visits, returning only those newer than end date" msgstr "" "Permite filtrar las visitas, devolviendo sólo aquellas más nuevas que endDate" -msgid "A short code was not provided. Which short code do you want to use?:" -msgstr "No se prporcionó un código corto. ¿Qué código corto deseas usar?:" +msgid "A short code was not provided. Which short code do you want to use?" +msgstr "No se proporcionó un código corto. ¿Qué código corto deseas usar?" msgid "Referer" msgstr "Origen" @@ -222,8 +230,8 @@ msgstr "Número de visitas" msgid "Tags" msgstr "Etiquetas" -msgid "You have reached last page" -msgstr "Has alcanzado la última página" +msgid "Short codes properly listed" +msgstr "Códigos cortos correctamente listados" msgid "Continue with page" msgstr "Continuar con la página" @@ -234,13 +242,9 @@ msgstr "Devuelve la URL larga detrás de un código corto" msgid "The short code to parse" msgstr "El código corto a convertir" -msgid "A short code was not provided. Which short code do you want to parse?:" +msgid "A short code was not provided. Which short code do you want to parse?" msgstr "" -"No se proporcionó un código corto. ¿Qué código corto quieres convertir?:" - -#, php-format -msgid "No URL found for short code \"%s\"" -msgstr "No se ha encontrado ninguna URL para el código corto \"%s\"" +"No se proporcionó un código corto. ¿Qué código corto quieres convertir?" msgid "Long URL:" msgstr "URL larga:" @@ -262,8 +266,8 @@ msgstr "El nombre de las etiquetas a crear" msgid "You have to provide at least one tag name" msgstr "Debes proporcionar al menos un nombre de etiqueta" -msgid "Created tags" -msgstr "Etiquetas creadas" +msgid "Tags properly created" +msgstr "Etiquetas correctamente creadas" msgid "Deletes one or more tags." msgstr "Elimina una o más etiquetas." @@ -271,8 +275,8 @@ msgstr "Elimina una o más etiquetas." msgid "The name of the tags to delete" msgstr "El nombre de las etiquetas a eliminar" -msgid "Deleted tags" -msgstr "Etiquetas eliminadas" +msgid "Tags properly deleted" +msgstr "Etiquetas correctamente eliminadas" msgid "Lists existing tags." msgstr "Lista las etiquetas existentes." @@ -315,3 +319,15 @@ msgstr "Dirección localizada en \"%s\"" msgid "Finished processing all IPs" msgstr "Finalizado el procesado de todas las IPs" + +#~ msgid "You have reached last page" +#~ msgstr "Has alcanzado la última página" + +#~ msgid "No URL found for short code \"%s\"" +#~ msgstr "No se ha encontrado ninguna URL para el código corto \"%s\"" + +#~ msgid "Created tags" +#~ msgstr "Etiquetas creadas" + +#~ msgid "Deleted tags" +#~ msgstr "Etiquetas eliminadas" diff --git a/module/Rest/lang/es.mo b/module/Rest/lang/es.mo index f81f6971c73fee650fb517556d5e9daaf42f605d..89c5cdbd2bdd5655ce75420c6baa422176245088 100644 GIT binary patch delta 484 zcmXZYJuCxZ9LMqh_3lt_S45E%w@4)%SE;Bbh6)K{(bZJCG~upk#U@QmghehEgOHd- za)THo5;klSTPKk)Smo67Mf%9NLPtBmC?4W4 zUf?7?;4%K7iF+ZD2|UMXe8mM+RgnQ)!eD#@NALvaL@ILo|G_WLGhk>U>zKu0p=IpSfFfjbOtpoUqD zwTd|CViK#!IRxr0xUmkxzS2%2q?0gR+6emzmmr+8-Z;PTk76`R(F6T$s7^-S?Jy(f~g?1VLyFzwlN(jK!DUR-}yl73A$)%dDgHkmMX?;#dZpBEE47n$ESQ{RU6HMbNX7L6W z@c}QehdDeLkrwa@m+=M5_%rO6EGf_XF0SAu&Ppx4QLNGU!fh->1`C{HlF83FN531B z9PDEW?NK(t4Gb_bCKa)Yt9XXtdauLxY|mzMAexttOt z9N{0DBUTf|hHk@*;Sr2l) k>lGc(b$pL9@ZEXWciSdvr~Y@_h3JRz9%DJHQ;m1cFY%r=A^-pY diff --git a/module/Rest/lang/es.po b/module/Rest/lang/es.po index fa02fd1f..566bb99e 100644 --- a/module/Rest/lang/es.po +++ b/module/Rest/lang/es.po @@ -1,15 +1,15 @@ msgid "" msgstr "" "Project-Id-Version: Shlink 1.0\n" -"POT-Creation-Date: 2017-10-21 20:20+0200\n" -"PO-Revision-Date: 2017-10-21 20:20+0200\n" +"POT-Creation-Date: 2018-01-21 09:40+0100\n" +"PO-Revision-Date: 2018-01-21 09:40+0100\n" "Last-Translator: Alejandro Celaya \n" "Language-Team: \n" "Language: es_ES\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 2.0.1\n" +"X-Generator: Poedit 2.0.4\n" "X-Poedit-Basepath: ..\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-SourceCharset: UTF-8\n" @@ -39,13 +39,16 @@ msgstr "El slug proporcionado \"%s\" ya está en uso. Prueba con uno diferente." msgid "Unexpected error occurred" msgstr "Ocurrió un error inesperado" -msgid "A list of tags was not provided" -msgstr "No se ha proporcionado una lista de etiquetas" - #, php-format msgid "No URL found for short code \"%s\"" msgstr "No se ha encontrado ninguna URL para el código corto \"%s\"" +msgid "Provided data is invalid." +msgstr "Los datos proporcionados son inválidos." + +msgid "A list of tags was not provided" +msgstr "No se ha proporcionado una lista de etiquetas" + #, php-format msgid "Provided short code %s does not exist" msgstr "El código corto \"%s\" proporcionado no existe" From 6ea59b1e4d204d9871aca47de9bc6aab2b2bde47 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 21 Jan 2018 09:43:01 +0100 Subject: [PATCH 56/56] Updated changelog --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3368f4b4..abb14ac6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ ## CHANGELOG +### 1.7.0 + +**Features** + +* [88: Allow to disable tracking of the short URL by including a configurable query param](https://github.com/shlinkio/shlink/issues/88) +* [108: Allow to edit metadata in created shortcodes](https://github.com/shlinkio/shlink/issues/108) + +**Enhancements:** + +* [113: Update CLI commands to use SymfonyStyle](https://github.com/shlinkio/shlink/issues/113) +* [112: Configure cli commands lazy loading](https://github.com/shlinkio/shlink/issues/112) + +**Tasks** + +* [117: Make every module which throws exceptions have its own ExceptionInterface, and make them all extend Throwable](https://github.com/shlinkio/shlink/issues/117) +* [115: Add phpstan to build matrix on PHP >=7.1 envs](https://github.com/shlinkio/shlink/issues/115) +* [114: Replace vlucas/phpdotenv dev requirement by symfony/env](https://github.com/shlinkio/shlink/issues/114) + ### 1.6.2 **Bugs**