From 8256f0c7575e043abce10d432389fa9eadce17b1 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Jul 2017 13:33:32 +0200 Subject: [PATCH 01/64] Dropped AnnotatedFactory in commands and replaced by ConfigAbstractFactory --- composer.json | 2 + module/CLI/config/dependencies.config.php | 68 ++++++++++++---- .../CLI/src/Command/Api/DisableKeyCommand.php | 9 --- .../src/Command/Api/GenerateKeyCommand.php | 11 +-- .../CLI/src/Command/Api/ListKeysCommand.php | 11 +-- .../Command/Config/GenerateCharsetCommand.php | 9 +-- .../Command/Config/GenerateSecretCommand.php | 9 +-- .../Shortcode/GeneratePreviewCommand.php | 11 --- .../Shortcode/GenerateShortcodeCommand.php | 10 --- .../Command/Shortcode/GetVisitsCommand.php | 9 --- .../Shortcode/ListShortcodesCommand.php | 12 +-- .../Command/Shortcode/ResolveUrlCommand.php | 9 --- .../CLI/src/Command/Tag/CreateTagCommand.php | 10 --- .../CLI/src/Command/Tag/DeleteTagsCommand.php | 10 --- .../CLI/src/Command/Tag/ListTagsCommand.php | 10 --- .../CLI/src/Command/Tag/RenameTagCommand.php | 10 --- .../Command/Visit/ProcessVisitsCommand.php | 11 --- module/Common/config/dependencies.config.php | 3 + .../DottedAccessConfigAbstractFactory.php | 79 +++++++++++++++++++ 19 files changed, 141 insertions(+), 162 deletions(-) create mode 100644 module/Common/src/Factory/DottedAccessConfigAbstractFactory.php diff --git a/composer.json b/composer.json index 637a66ab..4e909e5e 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,8 @@ "acelaya/zsm-annotated-services": "^1.0", "acelaya/ze-content-based-error-handler": "^2.0", "doctrine/orm": "^2.5", + "doctrine/collections": "^1.4 <1.5", + "doctrine/common": "^2.7 <2.8", "guzzlehttp/guzzle": "^6.2", "symfony/console": "^3.0", "symfony/process": "^3.0", diff --git a/module/CLI/config/dependencies.config.php b/module/CLI/config/dependencies.config.php index 565ab8bc..641b58db 100644 --- a/module/CLI/config/dependencies.config.php +++ b/module/CLI/config/dependencies.config.php @@ -1,8 +1,14 @@ [ Application::class => ApplicationFactory::class, - Command\Shortcode\GenerateShortcodeCommand::class => AnnotatedFactory::class, - Command\Shortcode\ResolveUrlCommand::class => AnnotatedFactory::class, - Command\Shortcode\ListShortcodesCommand::class => AnnotatedFactory::class, - Command\Shortcode\GetVisitsCommand::class => AnnotatedFactory::class, - Command\Shortcode\GeneratePreviewCommand::class => AnnotatedFactory::class, - Command\Visit\ProcessVisitsCommand::class => AnnotatedFactory::class, - Command\Config\GenerateCharsetCommand::class => AnnotatedFactory::class, - Command\Config\GenerateSecretCommand::class => AnnotatedFactory::class, - Command\Api\GenerateKeyCommand::class => AnnotatedFactory::class, - Command\Api\DisableKeyCommand::class => AnnotatedFactory::class, - Command\Api\ListKeysCommand::class => AnnotatedFactory::class, - Command\Tag\ListTagsCommand::class => AnnotatedFactory::class, - Command\Tag\CreateTagCommand::class => AnnotatedFactory::class, - Command\Tag\RenameTagCommand::class => AnnotatedFactory::class, - Command\Tag\DeleteTagsCommand::class => AnnotatedFactory::class, + Command\Shortcode\GenerateShortcodeCommand::class => ConfigAbstractFactory::class, + Command\Shortcode\ResolveUrlCommand::class => ConfigAbstractFactory::class, + Command\Shortcode\ListShortcodesCommand::class => ConfigAbstractFactory::class, + Command\Shortcode\GetVisitsCommand::class => ConfigAbstractFactory::class, + Command\Shortcode\GeneratePreviewCommand::class => ConfigAbstractFactory::class, + Command\Visit\ProcessVisitsCommand::class => ConfigAbstractFactory::class, + Command\Config\GenerateCharsetCommand::class => ConfigAbstractFactory::class, + Command\Config\GenerateSecretCommand::class => ConfigAbstractFactory::class, + Command\Api\GenerateKeyCommand::class => ConfigAbstractFactory::class, + Command\Api\DisableKeyCommand::class => ConfigAbstractFactory::class, + Command\Api\ListKeysCommand::class => ConfigAbstractFactory::class, + Command\Tag\ListTagsCommand::class => ConfigAbstractFactory::class, + Command\Tag\CreateTagCommand::class => ConfigAbstractFactory::class, + Command\Tag\RenameTagCommand::class => ConfigAbstractFactory::class, + Command\Tag\DeleteTagsCommand::class => ConfigAbstractFactory::class, ], ], + ConfigAbstractFactory::class => [ + Command\Shortcode\GenerateShortcodeCommand::class => [ + Service\UrlShortener::class, + 'translator', + 'config.url_shortener.domain', + ], + Command\Shortcode\ResolveUrlCommand::class => [Service\UrlShortener::class, 'translator'], + Command\Shortcode\ListShortcodesCommand::class => [Service\ShortUrlService::class, 'translator'], + Command\Shortcode\GetVisitsCommand::class => [Service\VisitsTracker::class, 'translator'], + Command\Shortcode\GeneratePreviewCommand::class => [ + Service\ShortUrlService::class, + PreviewGenerator::class, + 'translator' + ], + Command\Visit\ProcessVisitsCommand::class => [ + Service\VisitService::class, + IpLocationResolver::class, + 'translator' + ], + Command\Config\GenerateCharsetCommand::class => ['translator'], + Command\Config\GenerateSecretCommand::class => ['translator'], + Command\Api\GenerateKeyCommand::class => [ApiKeyService::class, 'translator'], + Command\Api\DisableKeyCommand::class => [ApiKeyService::class, 'translator'], + Command\Api\ListKeysCommand::class => [ApiKeyService::class, 'translator'], + Command\Tag\ListTagsCommand::class => [Service\Tag\TagService::class, Translator::class], + Command\Tag\CreateTagCommand::class => [Service\Tag\TagService::class, Translator::class], + Command\Tag\RenameTagCommand::class => [Service\Tag\TagService::class, Translator::class], + Command\Tag\DeleteTagsCommand::class => [Service\Tag\TagService::class, Translator::class], + ], + ]; diff --git a/module/CLI/src/Command/Api/DisableKeyCommand.php b/module/CLI/src/Command/Api/DisableKeyCommand.php index 48d9d564..fbf02992 100644 --- a/module/CLI/src/Command/Api/DisableKeyCommand.php +++ b/module/CLI/src/Command/Api/DisableKeyCommand.php @@ -1,8 +1,6 @@ apiKeyService = $apiKeyService; diff --git a/module/CLI/src/Command/Api/GenerateKeyCommand.php b/module/CLI/src/Command/Api/GenerateKeyCommand.php index 75d94e65..7d436669 100644 --- a/module/CLI/src/Command/Api/GenerateKeyCommand.php +++ b/module/CLI/src/Command/Api/GenerateKeyCommand.php @@ -1,8 +1,6 @@ apiKeyService = $apiKeyService; $this->translator = $translator; - parent::__construct(null); + parent::__construct(); } public function configure() diff --git a/module/CLI/src/Command/Api/ListKeysCommand.php b/module/CLI/src/Command/Api/ListKeysCommand.php index b08c1ece..8be23ec1 100644 --- a/module/CLI/src/Command/Api/ListKeysCommand.php +++ b/module/CLI/src/Command/Api/ListKeysCommand.php @@ -1,9 +1,7 @@ apiKeyService = $apiKeyService; $this->translator = $translator; - parent::__construct(null); + parent::__construct(); } public function configure() diff --git a/module/CLI/src/Command/Config/GenerateCharsetCommand.php b/module/CLI/src/Command/Config/GenerateCharsetCommand.php index 22369934..bf0f2b6d 100644 --- a/module/CLI/src/Command/Config/GenerateCharsetCommand.php +++ b/module/CLI/src/Command/Config/GenerateCharsetCommand.php @@ -1,7 +1,6 @@ translator = $translator; - parent::__construct(null); + parent::__construct(); } public function configure() diff --git a/module/CLI/src/Command/Config/GenerateSecretCommand.php b/module/CLI/src/Command/Config/GenerateSecretCommand.php index bef5c86a..e27bf751 100644 --- a/module/CLI/src/Command/Config/GenerateSecretCommand.php +++ b/module/CLI/src/Command/Config/GenerateSecretCommand.php @@ -1,7 +1,6 @@ translator = $translator; - parent::__construct(null); + parent::__construct(); } public function configure() diff --git a/module/CLI/src/Command/Shortcode/GeneratePreviewCommand.php b/module/CLI/src/Command/Shortcode/GeneratePreviewCommand.php index b224ad61..68b7189e 100644 --- a/module/CLI/src/Command/Shortcode/GeneratePreviewCommand.php +++ b/module/CLI/src/Command/Shortcode/GeneratePreviewCommand.php @@ -1,11 +1,8 @@ visitsTracker = $visitsTracker; diff --git a/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php b/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php index a8594db7..1c8e17f8 100644 --- a/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php +++ b/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php @@ -1,10 +1,8 @@ shortUrlService = $shortUrlService; @@ -88,12 +79,11 @@ class ListShortcodesCommand extends Command public function execute(InputInterface $input, OutputInterface $output) { - $page = intval($input->getOption('page')); + $page = (int) $input->getOption('page'); $searchTerm = $input->getOption('searchTerm'); $tags = $input->getOption('tags'); $tags = ! empty($tags) ? explode(',', $tags) : []; $showTags = $input->getOption('showTags'); - $orderBy = $input->getOption('orderBy'); /** @var QuestionHelper $helper */ $helper = $this->getHelper('question'); diff --git a/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php b/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php index a94b47ad..3344b7af 100644 --- a/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php +++ b/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php @@ -1,9 +1,7 @@ urlShortener = $urlShortener; diff --git a/module/CLI/src/Command/Tag/CreateTagCommand.php b/module/CLI/src/Command/Tag/CreateTagCommand.php index 8a06d38c..530ee569 100644 --- a/module/CLI/src/Command/Tag/CreateTagCommand.php +++ b/module/CLI/src/Command/Tag/CreateTagCommand.php @@ -1,14 +1,11 @@ tagService = $tagService; diff --git a/module/CLI/src/Command/Tag/DeleteTagsCommand.php b/module/CLI/src/Command/Tag/DeleteTagsCommand.php index 0a4e271b..394eac40 100644 --- a/module/CLI/src/Command/Tag/DeleteTagsCommand.php +++ b/module/CLI/src/Command/Tag/DeleteTagsCommand.php @@ -1,14 +1,11 @@ tagService = $tagService; diff --git a/module/CLI/src/Command/Tag/ListTagsCommand.php b/module/CLI/src/Command/Tag/ListTagsCommand.php index eb120226..97037406 100644 --- a/module/CLI/src/Command/Tag/ListTagsCommand.php +++ b/module/CLI/src/Command/Tag/ListTagsCommand.php @@ -1,15 +1,12 @@ tagService = $tagService; diff --git a/module/CLI/src/Command/Tag/RenameTagCommand.php b/module/CLI/src/Command/Tag/RenameTagCommand.php index e3ee678e..89f267d3 100644 --- a/module/CLI/src/Command/Tag/RenameTagCommand.php +++ b/module/CLI/src/Command/Tag/RenameTagCommand.php @@ -1,15 +1,12 @@ tagService = $tagService; diff --git a/module/CLI/src/Command/Visit/ProcessVisitsCommand.php b/module/CLI/src/Command/Visit/ProcessVisitsCommand.php index 195d891c..8b4f82a8 100644 --- a/module/CLI/src/Command/Visit/ProcessVisitsCommand.php +++ b/module/CLI/src/Command/Visit/ProcessVisitsCommand.php @@ -1,12 +1,9 @@ 'Logger_Shlink', LoggerInterface::class => 'Logger_Shlink', ], + 'abstract_factories' => [ + Factory\DottedAccessConfigAbstractFactory::class, + ], ], ]; diff --git a/module/Common/src/Factory/DottedAccessConfigAbstractFactory.php b/module/Common/src/Factory/DottedAccessConfigAbstractFactory.php new file mode 100644 index 00000000..7d0e03b6 --- /dev/null +++ b/module/Common/src/Factory/DottedAccessConfigAbstractFactory.php @@ -0,0 +1,79 @@ + 0; + } + + /** + * Create an object + * + * @param ContainerInterface $container + * @param string $requestedName + * @param null|array $options + * @return object + * @throws InvalidArgumentException + * @throws ServiceNotFoundException if unable to resolve the 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) + { + $parts = explode('.', $requestedName); + $serviceName = array_shift($parts); + if (! $container->has($serviceName)) { + throw new ServiceNotCreatedException(sprintf( + 'Defined service "%s" could not be found in container after resolving dotted expression "%s".', + $serviceName, + $requestedName + )); + } + + $array = $container->get($serviceName); + return $this->readKeysFromArray($parts, $array); + } + + /** + * @param array $keys + * @param array|\ArrayAccess $array + * @return mixed|null + * @throws InvalidArgumentException + */ + private function readKeysFromArray(array $keys, $array) + { + $key = array_shift($keys); + + // When one of the provided keys is not found, throw an exception + if (! isset($array[$key])) { + throw new InvalidArgumentException(sprintf( + 'The key "%s" provided in the dotted notation could not be found in the array service', + $key + )); + } + + $value = $array[$key]; + if (! empty($keys) && (is_array($value) || $value instanceof \ArrayAccess)) { + $value = $this->readKeysFromArray($keys, $value); + } + + return $value; + } +} From 54cb40f6ed0e8e4a3f389a26dd422353a3010439 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Jul 2017 13:41:41 +0200 Subject: [PATCH 02/64] Replaced more ussages of AnnotatedFactory by ConfigAbstractFactory --- bin/install | 21 ++++++++++---- bin/update | 21 ++++++++++---- .../src/Factory/InstallApplicationFactory.php | 4 +-- module/Common/config/dependencies.config.php | 28 +++++++++++++------ .../src/Middleware/LocaleMiddleware.php | 7 ----- .../Common/src/Service/IpLocationResolver.php | 7 ----- .../Common/src/Service/PreviewGenerator.php | 10 ------- .../Twig/Extension/TranslatorExtension.php | 7 ----- 8 files changed, 53 insertions(+), 52 deletions(-) diff --git a/bin/install b/bin/install index 43a07cd3..e8ecb3c9 100755 --- a/bin/install +++ b/bin/install @@ -1,9 +1,11 @@ #!/usr/bin/env php [ - Application::class => InstallApplicationFactory::class, - Filesystem::class => InvokableFactory::class, - QuestionHelper::class => InvokableFactory::class, -]]); +$container = new ServiceManager([ + 'factories' => [ + Application::class => InstallApplicationFactory::class, + Filesystem::class => InvokableFactory::class, + QuestionHelper::class => InvokableFactory::class, + ], + 'services' => [ + 'config' => [ + ConfigAbstractFactory::class => [ + DatabaseConfigCustomizerPlugin::class => [QuestionHelper::class, Filesystem::class] + ], + ], + ], +]); $container->build(Application::class)->run(); diff --git a/bin/update b/bin/update index 164e20b0..d4203528 100755 --- a/bin/update +++ b/bin/update @@ -1,9 +1,11 @@ #!/usr/bin/env php [ - Application::class => InstallApplicationFactory::class, - Filesystem::class => InvokableFactory::class, - QuestionHelper::class => InvokableFactory::class, -]]); +$container = new ServiceManager([ + 'factories' => [ + Application::class => InstallApplicationFactory::class, + Filesystem::class => InvokableFactory::class, + QuestionHelper::class => InvokableFactory::class, + ], + 'services' => [ + 'config' => [ + ConfigAbstractFactory::class => [ + DatabaseConfigCustomizerPlugin::class => [QuestionHelper::class, Filesystem::class] + ], + ], + ], +]); $container->build(Application::class, ['isUpdate' => true])->run(); diff --git a/module/CLI/src/Factory/InstallApplicationFactory.php b/module/CLI/src/Factory/InstallApplicationFactory.php index 742b5b70..387b2321 100644 --- a/module/CLI/src/Factory/InstallApplicationFactory.php +++ b/module/CLI/src/Factory/InstallApplicationFactory.php @@ -1,7 +1,6 @@ get(Filesystem::class), new ConfigCustomizerPluginManager($container, ['factories' => [ - Plugin\DatabaseConfigCustomizerPlugin::class => AnnotatedFactory::class, + Plugin\DatabaseConfigCustomizerPlugin::class => ConfigAbstractFactory::class, Plugin\UrlShortenerConfigCustomizerPlugin::class => DefaultConfigCustomizerPluginFactory::class, Plugin\LanguageConfigCustomizerPlugin::class => DefaultConfigCustomizerPluginFactory::class, Plugin\ApplicationConfigCustomizerPlugin::class => DefaultConfigCustomizerPluginFactory::class, diff --git a/module/Common/config/dependencies.config.php b/module/Common/config/dependencies.config.php index 06250b3a..dd64b026 100644 --- a/module/Common/config/dependencies.config.php +++ b/module/Common/config/dependencies.config.php @@ -1,45 +1,44 @@ [ - 'invokables' => [ - Filesystem::class => Filesystem::class, - ], 'factories' => [ EntityManager::class => Factory\EntityManagerFactory::class, GuzzleHttp\Client::class => InvokableFactory::class, Cache::class => Factory\CacheFactory::class, 'Logger_Shlink' => Factory\LoggerFactory::class, + Filesystem::class => InvokableFactory::class, Translator::class => Factory\TranslatorFactory::class, - TranslatorExtension::class => AnnotatedFactory::class, - LocaleMiddleware::class => AnnotatedFactory::class, + TranslatorExtension::class => ConfigAbstractFactory::class, + LocaleMiddleware::class => ConfigAbstractFactory::class, Image\ImageBuilder::class => Image\ImageBuilderFactory::class, - Service\IpLocationResolver::class => AnnotatedFactory::class, - Service\PreviewGenerator::class => AnnotatedFactory::class, + Service\IpLocationResolver::class => ConfigAbstractFactory::class, + Service\PreviewGenerator::class => ConfigAbstractFactory::class, ], 'aliases' => [ 'em' => EntityManager::class, 'httpClient' => GuzzleHttp\Client::class, 'translator' => Translator::class, 'logger' => LoggerInterface::class, - AnnotatedFactory::CACHE_SERVICE => Cache::class, Logger::class => 'Logger_Shlink', LoggerInterface::class => 'Logger_Shlink', ], @@ -48,4 +47,15 @@ return [ ], ], + ConfigAbstractFactory::class => [ + TranslatorExtension::class => ['translator'], + LocaleMiddleware::class => ['translator'], + Service\IpLocationResolver::class => ['httpClient'], + Service\PreviewGenerator::class => [ + ImageBuilder::class, + Filesystem::class, + 'config.preview_generation.files_location', + ], + ], + ]; diff --git a/module/Common/src/Middleware/LocaleMiddleware.php b/module/Common/src/Middleware/LocaleMiddleware.php index 0cb81502..fe0ff2c6 100644 --- a/module/Common/src/Middleware/LocaleMiddleware.php +++ b/module/Common/src/Middleware/LocaleMiddleware.php @@ -1,7 +1,6 @@ translator = $translator; diff --git a/module/Common/src/Service/IpLocationResolver.php b/module/Common/src/Service/IpLocationResolver.php index 0bfa80ce..287760a3 100644 --- a/module/Common/src/Service/IpLocationResolver.php +++ b/module/Common/src/Service/IpLocationResolver.php @@ -1,7 +1,6 @@ httpClient = $httpClient; diff --git a/module/Common/src/Service/PreviewGenerator.php b/module/Common/src/Service/PreviewGenerator.php index 03fa392f..d85bf50b 100644 --- a/module/Common/src/Service/PreviewGenerator.php +++ b/module/Common/src/Service/PreviewGenerator.php @@ -1,10 +1,8 @@ location = $location; diff --git a/module/Common/src/Twig/Extension/TranslatorExtension.php b/module/Common/src/Twig/Extension/TranslatorExtension.php index 48ee3f11..807c3099 100644 --- a/module/Common/src/Twig/Extension/TranslatorExtension.php +++ b/module/Common/src/Twig/Extension/TranslatorExtension.php @@ -1,7 +1,6 @@ translator = $translator; From 6300982b071490520b7eaab659172da57621ad81 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Jul 2017 13:48:30 +0200 Subject: [PATCH 03/64] Replaced more ussages of AnnotatedFactory by ConfigAbstractFactory --- module/Core/config/dependencies.config.php | 39 ++++++++++++++----- module/Core/src/Action/PreviewAction.php | 10 ----- module/Core/src/Action/QrCodeAction.php | 10 ----- module/Core/src/Action/RedirectAction.php | 11 ------ .../src/Middleware/QrCodeCacheMiddleware.php | 7 ---- module/Core/src/Service/ShortUrlService.php | 9 +---- module/Core/src/Service/Tag/TagService.php | 7 ---- module/Core/src/Service/UrlShortener.php | 18 ++------- module/Core/src/Service/VisitService.php | 7 ---- module/Core/src/Service/VisitsTracker.php | 7 ---- 10 files changed, 34 insertions(+), 91 deletions(-) diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index 1ef7257c..a25c82fb 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -1,9 +1,13 @@ Options\AppOptionsFactory::class, // Services - Service\UrlShortener::class => AnnotatedFactory::class, - Service\VisitsTracker::class => AnnotatedFactory::class, - Service\ShortUrlService::class => AnnotatedFactory::class, - Service\VisitService::class => AnnotatedFactory::class, - Service\Tag\TagService::class => AnnotatedFactory::class, + Service\UrlShortener::class => ConfigAbstractFactory::class, + Service\VisitsTracker::class => ConfigAbstractFactory::class, + Service\ShortUrlService::class => ConfigAbstractFactory::class, + Service\VisitService::class => ConfigAbstractFactory::class, + Service\Tag\TagService::class => ConfigAbstractFactory::class, // Middleware - Action\RedirectAction::class => AnnotatedFactory::class, - Action\QrCodeAction::class => AnnotatedFactory::class, - Action\PreviewAction::class => AnnotatedFactory::class, - Middleware\QrCodeCacheMiddleware::class => AnnotatedFactory::class, + Action\RedirectAction::class => ConfigAbstractFactory::class, + Action\QrCodeAction::class => ConfigAbstractFactory::class, + Action\PreviewAction::class => ConfigAbstractFactory::class, + Middleware\QrCodeCacheMiddleware::class => ConfigAbstractFactory::class, ], ], + ConfigAbstractFactory::class => [ + // Services + Service\UrlShortener::class => ['httpClient', 'em', Cache::class, 'config.url_shortener.shortcode_chars'], + Service\VisitsTracker::class => ['em'], + Service\ShortUrlService::class => ['em'], + Service\VisitService::class => ['em'], + Service\Tag\TagService::class => ['em'], + + // Middleware + Action\RedirectAction::class => [Service\UrlShortener::class, Service\VisitsTracker::class, 'Logger_Shlink'], + 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/PreviewAction.php b/module/Core/src/Action/PreviewAction.php index 291225be..12c57435 100644 --- a/module/Core/src/Action/PreviewAction.php +++ b/module/Core/src/Action/PreviewAction.php @@ -1,16 +1,13 @@ previewGenerator = $previewGenerator; diff --git a/module/Core/src/Action/QrCodeAction.php b/module/Core/src/Action/QrCodeAction.php index 3970d740..efef3400 100644 --- a/module/Core/src/Action/QrCodeAction.php +++ b/module/Core/src/Action/QrCodeAction.php @@ -1,7 +1,6 @@ cache = $cache; diff --git a/module/Core/src/Service/ShortUrlService.php b/module/Core/src/Service/ShortUrlService.php index 59c76565..c7ae94c9 100644 --- a/module/Core/src/Service/ShortUrlService.php +++ b/module/Core/src/Service/ShortUrlService.php @@ -1,7 +1,6 @@ em = $em; @@ -60,7 +53,7 @@ class ShortUrlService implements ShortUrlServiceInterface $shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([ 'shortCode' => $shortCode, ]); - if (! isset($shortUrl)) { + if ($shortUrl === null) { throw InvalidShortCodeException::fromNotFoundShortCode($shortCode); } diff --git a/module/Core/src/Service/Tag/TagService.php b/module/Core/src/Service/Tag/TagService.php index 52708a39..97fdca2d 100644 --- a/module/Core/src/Service/Tag/TagService.php +++ b/module/Core/src/Service/Tag/TagService.php @@ -1,7 +1,6 @@ em = $em; diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index 9d6927bf..67e32626 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -1,7 +1,6 @@ chars); $code = ''; while ($id > 0) { // Determine the value of the next higher character in the short code and prepend it - $code = $this->chars[intval(fmod($id, $length))] . $code; + $code = $this->chars[(int) fmod($id, $length)] . $code; $id = floor($id / $length); } - return $this->chars[intval($id)] . $code; + return $this->chars[(int) $id] . $code; } /** diff --git a/module/Core/src/Service/VisitService.php b/module/Core/src/Service/VisitService.php index d8e46c31..375cc57e 100644 --- a/module/Core/src/Service/VisitService.php +++ b/module/Core/src/Service/VisitService.php @@ -1,7 +1,6 @@ em = $em; diff --git a/module/Core/src/Service/VisitsTracker.php b/module/Core/src/Service/VisitsTracker.php index 94647086..92bafa71 100644 --- a/module/Core/src/Service/VisitsTracker.php +++ b/module/Core/src/Service/VisitsTracker.php @@ -1,7 +1,6 @@ em = $em; From 7ca22f862928579aa3a97f18c4bbeaef930299de Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Jul 2017 14:20:40 +0200 Subject: [PATCH 04/64] Removed any remaining reference to AnnotatedFactory --- composer.json | 2 +- module/Rest/config/dependencies.config.php | 60 ++++++++++++++----- module/Rest/src/Action/AuthenticateAction.php | 11 ---- .../Rest/src/Action/CreateShortcodeAction.php | 12 ---- .../src/Action/EditShortcodeTagsAction.php | 10 ---- module/Rest/src/Action/GetVisitsAction.php | 10 ---- .../Rest/src/Action/ListShortcodesAction.php | 10 ---- module/Rest/src/Action/ResolveUrlAction.php | 10 ---- .../Rest/src/Action/Tag/CreateTagsAction.php | 9 --- .../Rest/src/Action/Tag/DeleteTagsAction.php | 9 --- module/Rest/src/Action/Tag/ListTagsAction.php | 9 --- .../Rest/src/Action/Tag/UpdateTagAction.php | 11 ---- module/Rest/src/Authentication/JWTService.php | 7 --- .../CheckAuthenticationMiddleware.php | 11 +--- module/Rest/src/Service/ApiKeyService.php | 7 --- 15 files changed, 46 insertions(+), 142 deletions(-) diff --git a/composer.json b/composer.json index 4e909e5e..3b8a744e 100644 --- a/composer.json +++ b/composer.json @@ -22,9 +22,9 @@ "zendframework/zend-config": "^3.0", "zendframework/zend-i18n": "^2.7", "zendframework/zend-config-aggregator": "^0.1", - "acelaya/zsm-annotated-services": "^1.0", "acelaya/ze-content-based-error-handler": "^2.0", "doctrine/orm": "^2.5", + "doctrine/annotations": "^1.4 <1.5", "doctrine/collections": "^1.4 <1.5", "doctrine/common": "^2.7 <2.8", "guzzlehttp/guzzle": "^6.2", diff --git a/module/Rest/config/dependencies.config.php b/module/Rest/config/dependencies.config.php index a36f7590..8f2227c0 100644 --- a/module/Rest/config/dependencies.config.php +++ b/module/Rest/config/dependencies.config.php @@ -1,34 +1,62 @@ [ 'factories' => [ - JWTService::class => AnnotatedFactory::class, - Service\ApiKeyService::class => AnnotatedFactory::class, + JWTService::class => ConfigAbstractFactory::class, + ApiKeyService::class => ConfigAbstractFactory::class, - Action\AuthenticateAction::class => AnnotatedFactory::class, - Action\CreateShortcodeAction::class => AnnotatedFactory::class, - Action\ResolveUrlAction::class => AnnotatedFactory::class, - Action\GetVisitsAction::class => AnnotatedFactory::class, - Action\ListShortcodesAction::class => AnnotatedFactory::class, - Action\EditShortcodeTagsAction::class => AnnotatedFactory::class, - Action\Tag\ListTagsAction::class => AnnotatedFactory::class, - Action\Tag\DeleteTagsAction::class => AnnotatedFactory::class, - Action\Tag\CreateTagsAction::class => AnnotatedFactory::class, - Action\Tag\UpdateTagAction::class => AnnotatedFactory::class, + Action\AuthenticateAction::class => ConfigAbstractFactory::class, + Action\CreateShortcodeAction::class => ConfigAbstractFactory::class, + Action\ResolveUrlAction::class => ConfigAbstractFactory::class, + Action\GetVisitsAction::class => ConfigAbstractFactory::class, + Action\ListShortcodesAction::class => ConfigAbstractFactory::class, + Action\EditShortcodeTagsAction::class => ConfigAbstractFactory::class, + Action\Tag\ListTagsAction::class => ConfigAbstractFactory::class, + Action\Tag\DeleteTagsAction::class => ConfigAbstractFactory::class, + Action\Tag\CreateTagsAction::class => ConfigAbstractFactory::class, + Action\Tag\UpdateTagAction::class => ConfigAbstractFactory::class, - Middleware\BodyParserMiddleware::class => AnnotatedFactory::class, + Middleware\BodyParserMiddleware::class => InvokableFactory::class, Middleware\CrossDomainMiddleware::class => InvokableFactory::class, Middleware\PathVersionMiddleware::class => InvokableFactory::class, - Middleware\CheckAuthenticationMiddleware::class => AnnotatedFactory::class, + Middleware\CheckAuthenticationMiddleware::class => ConfigAbstractFactory::class, ], ], + ConfigAbstractFactory::class => [ + JWTService::class => [AppOptions::class], + ApiKeyService::class => ['em'], + + Action\AuthenticateAction::class => [ApiKeyService::class, JWTService::class, 'translator', 'Logger_Shlink'], + Action\CreateShortcodeAction::class => [ + Service\UrlShortener::class, + 'translator', + 'config.url_shortener.domain', + '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'], + Action\EditShortcodeTagsAction::class => [Service\ShortUrlService::class, 'translator', 'Logger_Shlink'], + Action\Tag\ListTagsAction::class => [Service\Tag\TagService::class, LoggerInterface::class], + Action\Tag\DeleteTagsAction::class => [Service\Tag\TagService::class, LoggerInterface::class], + Action\Tag\CreateTagsAction::class => [Service\Tag\TagService::class, LoggerInterface::class], + Action\Tag\UpdateTagAction::class => [Service\Tag\TagService::class, Translator::class, LoggerInterface::class], + + Middleware\CheckAuthenticationMiddleware::class => [JWTService::class, 'translator', 'Logger_Shlink'], + ], + ]; diff --git a/module/Rest/src/Action/AuthenticateAction.php b/module/Rest/src/Action/AuthenticateAction.php index a4b15920..1403e88e 100644 --- a/module/Rest/src/Action/AuthenticateAction.php +++ b/module/Rest/src/Action/AuthenticateAction.php @@ -1,12 +1,10 @@ appOptions = $appOptions; diff --git a/module/Rest/src/Middleware/CheckAuthenticationMiddleware.php b/module/Rest/src/Middleware/CheckAuthenticationMiddleware.php index d7d922ad..93537d35 100644 --- a/module/Rest/src/Middleware/CheckAuthenticationMiddleware.php +++ b/module/Rest/src/Middleware/CheckAuthenticationMiddleware.php @@ -1,7 +1,6 @@ em = $em; From abf802093c04339b05390bb9e271b448e15d730c Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Jul 2017 08:42:57 +0200 Subject: [PATCH 05/64] Ensured all doctrine components are not updated to latest releases, which require PHP 7.1 --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3b8a744e..60856299 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,8 @@ "zendframework/zend-i18n": "^2.7", "zendframework/zend-config-aggregator": "^0.1", "acelaya/ze-content-based-error-handler": "^2.0", - "doctrine/orm": "^2.5", + "doctrine/orm": "^2.5 <2.6", + "doctrine/dbal": "^2.5 <2.6", "doctrine/annotations": "^1.4 <1.5", "doctrine/collections": "^1.4 <1.5", "doctrine/common": "^2.7 <2.8", From 574f407a906e7266d99626e9d5a0160f14b6c656 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Jul 2017 11:51:47 +0200 Subject: [PATCH 06/64] Ensured a doctrine/cache version which requires PHP 7.1 is not installed --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 60856299..e7fd3a4a 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,7 @@ "doctrine/annotations": "^1.4 <1.5", "doctrine/collections": "^1.4 <1.5", "doctrine/common": "^2.7 <2.8", + "doctrine/cache": "^1.6 <1.7", "guzzlehttp/guzzle": "^6.2", "symfony/console": "^3.0", "symfony/process": "^3.0", From 960c7a08356d0da85d7fbdaf890766aaba2cd827 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 19 Aug 2017 19:41:07 +0200 Subject: [PATCH 07/64] Added security-advisories dependency --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e7fd3a4a..faab4cb7 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ ], "require": { "php": "^5.6 || ^7.0", + "roave/security-advisories": "dev-master", "zendframework/zend-expressive": "^2.0", "zendframework/zend-expressive-fastroute": "^2.0", "zendframework/zend-expressive-twigrenderer": "^1.4", @@ -43,7 +44,6 @@ "require-dev": { "phpunit/phpunit": "^5.7 || ^6.0", "squizlabs/php_codesniffer": "^2.3", - "roave/security-advisories": "dev-master", "filp/whoops": "^2.0", "symfony/var-dumper": "^3.0", "vlucas/phpdotenv": "^2.2", From 1260da85a765939cac6114ae4c3dad8191ce8376 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 30 Sep 2017 12:13:12 +0200 Subject: [PATCH 08/64] Added PHP 7.2 to the build matrix --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 83d37309..617ac22b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ php: - 5.6 - 7 - 7.1 + - 7.2 before_install: phpenv config-add .travis-php.ini From ba2053bd3a0f0cfd9b734d4ab0a6ea8ad5ff2a39 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Jul 2017 13:33:32 +0200 Subject: [PATCH 09/64] Dropped AnnotatedFactory in commands and replaced by ConfigAbstractFactory --- composer.json | 2 + module/CLI/config/dependencies.config.php | 68 ++++++++++++---- .../CLI/src/Command/Api/DisableKeyCommand.php | 9 --- .../src/Command/Api/GenerateKeyCommand.php | 11 +-- .../CLI/src/Command/Api/ListKeysCommand.php | 11 +-- .../Command/Config/GenerateCharsetCommand.php | 9 +-- .../Command/Config/GenerateSecretCommand.php | 9 +-- .../Shortcode/GeneratePreviewCommand.php | 11 --- .../Shortcode/GenerateShortcodeCommand.php | 10 --- .../Command/Shortcode/GetVisitsCommand.php | 9 --- .../Shortcode/ListShortcodesCommand.php | 12 +-- .../Command/Shortcode/ResolveUrlCommand.php | 9 --- .../CLI/src/Command/Tag/CreateTagCommand.php | 10 --- .../CLI/src/Command/Tag/DeleteTagsCommand.php | 10 --- .../CLI/src/Command/Tag/ListTagsCommand.php | 10 --- .../CLI/src/Command/Tag/RenameTagCommand.php | 10 --- .../Command/Visit/ProcessVisitsCommand.php | 11 --- module/Common/config/dependencies.config.php | 3 + .../DottedAccessConfigAbstractFactory.php | 79 +++++++++++++++++++ 19 files changed, 141 insertions(+), 162 deletions(-) create mode 100644 module/Common/src/Factory/DottedAccessConfigAbstractFactory.php diff --git a/composer.json b/composer.json index 637a66ab..4e909e5e 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,8 @@ "acelaya/zsm-annotated-services": "^1.0", "acelaya/ze-content-based-error-handler": "^2.0", "doctrine/orm": "^2.5", + "doctrine/collections": "^1.4 <1.5", + "doctrine/common": "^2.7 <2.8", "guzzlehttp/guzzle": "^6.2", "symfony/console": "^3.0", "symfony/process": "^3.0", diff --git a/module/CLI/config/dependencies.config.php b/module/CLI/config/dependencies.config.php index 565ab8bc..641b58db 100644 --- a/module/CLI/config/dependencies.config.php +++ b/module/CLI/config/dependencies.config.php @@ -1,8 +1,14 @@ [ Application::class => ApplicationFactory::class, - Command\Shortcode\GenerateShortcodeCommand::class => AnnotatedFactory::class, - Command\Shortcode\ResolveUrlCommand::class => AnnotatedFactory::class, - Command\Shortcode\ListShortcodesCommand::class => AnnotatedFactory::class, - Command\Shortcode\GetVisitsCommand::class => AnnotatedFactory::class, - Command\Shortcode\GeneratePreviewCommand::class => AnnotatedFactory::class, - Command\Visit\ProcessVisitsCommand::class => AnnotatedFactory::class, - Command\Config\GenerateCharsetCommand::class => AnnotatedFactory::class, - Command\Config\GenerateSecretCommand::class => AnnotatedFactory::class, - Command\Api\GenerateKeyCommand::class => AnnotatedFactory::class, - Command\Api\DisableKeyCommand::class => AnnotatedFactory::class, - Command\Api\ListKeysCommand::class => AnnotatedFactory::class, - Command\Tag\ListTagsCommand::class => AnnotatedFactory::class, - Command\Tag\CreateTagCommand::class => AnnotatedFactory::class, - Command\Tag\RenameTagCommand::class => AnnotatedFactory::class, - Command\Tag\DeleteTagsCommand::class => AnnotatedFactory::class, + Command\Shortcode\GenerateShortcodeCommand::class => ConfigAbstractFactory::class, + Command\Shortcode\ResolveUrlCommand::class => ConfigAbstractFactory::class, + Command\Shortcode\ListShortcodesCommand::class => ConfigAbstractFactory::class, + Command\Shortcode\GetVisitsCommand::class => ConfigAbstractFactory::class, + Command\Shortcode\GeneratePreviewCommand::class => ConfigAbstractFactory::class, + Command\Visit\ProcessVisitsCommand::class => ConfigAbstractFactory::class, + Command\Config\GenerateCharsetCommand::class => ConfigAbstractFactory::class, + Command\Config\GenerateSecretCommand::class => ConfigAbstractFactory::class, + Command\Api\GenerateKeyCommand::class => ConfigAbstractFactory::class, + Command\Api\DisableKeyCommand::class => ConfigAbstractFactory::class, + Command\Api\ListKeysCommand::class => ConfigAbstractFactory::class, + Command\Tag\ListTagsCommand::class => ConfigAbstractFactory::class, + Command\Tag\CreateTagCommand::class => ConfigAbstractFactory::class, + Command\Tag\RenameTagCommand::class => ConfigAbstractFactory::class, + Command\Tag\DeleteTagsCommand::class => ConfigAbstractFactory::class, ], ], + ConfigAbstractFactory::class => [ + Command\Shortcode\GenerateShortcodeCommand::class => [ + Service\UrlShortener::class, + 'translator', + 'config.url_shortener.domain', + ], + Command\Shortcode\ResolveUrlCommand::class => [Service\UrlShortener::class, 'translator'], + Command\Shortcode\ListShortcodesCommand::class => [Service\ShortUrlService::class, 'translator'], + Command\Shortcode\GetVisitsCommand::class => [Service\VisitsTracker::class, 'translator'], + Command\Shortcode\GeneratePreviewCommand::class => [ + Service\ShortUrlService::class, + PreviewGenerator::class, + 'translator' + ], + Command\Visit\ProcessVisitsCommand::class => [ + Service\VisitService::class, + IpLocationResolver::class, + 'translator' + ], + Command\Config\GenerateCharsetCommand::class => ['translator'], + Command\Config\GenerateSecretCommand::class => ['translator'], + Command\Api\GenerateKeyCommand::class => [ApiKeyService::class, 'translator'], + Command\Api\DisableKeyCommand::class => [ApiKeyService::class, 'translator'], + Command\Api\ListKeysCommand::class => [ApiKeyService::class, 'translator'], + Command\Tag\ListTagsCommand::class => [Service\Tag\TagService::class, Translator::class], + Command\Tag\CreateTagCommand::class => [Service\Tag\TagService::class, Translator::class], + Command\Tag\RenameTagCommand::class => [Service\Tag\TagService::class, Translator::class], + Command\Tag\DeleteTagsCommand::class => [Service\Tag\TagService::class, Translator::class], + ], + ]; diff --git a/module/CLI/src/Command/Api/DisableKeyCommand.php b/module/CLI/src/Command/Api/DisableKeyCommand.php index 48d9d564..fbf02992 100644 --- a/module/CLI/src/Command/Api/DisableKeyCommand.php +++ b/module/CLI/src/Command/Api/DisableKeyCommand.php @@ -1,8 +1,6 @@ apiKeyService = $apiKeyService; diff --git a/module/CLI/src/Command/Api/GenerateKeyCommand.php b/module/CLI/src/Command/Api/GenerateKeyCommand.php index 75d94e65..7d436669 100644 --- a/module/CLI/src/Command/Api/GenerateKeyCommand.php +++ b/module/CLI/src/Command/Api/GenerateKeyCommand.php @@ -1,8 +1,6 @@ apiKeyService = $apiKeyService; $this->translator = $translator; - parent::__construct(null); + parent::__construct(); } public function configure() diff --git a/module/CLI/src/Command/Api/ListKeysCommand.php b/module/CLI/src/Command/Api/ListKeysCommand.php index b08c1ece..8be23ec1 100644 --- a/module/CLI/src/Command/Api/ListKeysCommand.php +++ b/module/CLI/src/Command/Api/ListKeysCommand.php @@ -1,9 +1,7 @@ apiKeyService = $apiKeyService; $this->translator = $translator; - parent::__construct(null); + parent::__construct(); } public function configure() diff --git a/module/CLI/src/Command/Config/GenerateCharsetCommand.php b/module/CLI/src/Command/Config/GenerateCharsetCommand.php index 22369934..bf0f2b6d 100644 --- a/module/CLI/src/Command/Config/GenerateCharsetCommand.php +++ b/module/CLI/src/Command/Config/GenerateCharsetCommand.php @@ -1,7 +1,6 @@ translator = $translator; - parent::__construct(null); + parent::__construct(); } public function configure() diff --git a/module/CLI/src/Command/Config/GenerateSecretCommand.php b/module/CLI/src/Command/Config/GenerateSecretCommand.php index bef5c86a..e27bf751 100644 --- a/module/CLI/src/Command/Config/GenerateSecretCommand.php +++ b/module/CLI/src/Command/Config/GenerateSecretCommand.php @@ -1,7 +1,6 @@ translator = $translator; - parent::__construct(null); + parent::__construct(); } public function configure() diff --git a/module/CLI/src/Command/Shortcode/GeneratePreviewCommand.php b/module/CLI/src/Command/Shortcode/GeneratePreviewCommand.php index b224ad61..68b7189e 100644 --- a/module/CLI/src/Command/Shortcode/GeneratePreviewCommand.php +++ b/module/CLI/src/Command/Shortcode/GeneratePreviewCommand.php @@ -1,11 +1,8 @@ visitsTracker = $visitsTracker; diff --git a/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php b/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php index a8594db7..1c8e17f8 100644 --- a/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php +++ b/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php @@ -1,10 +1,8 @@ shortUrlService = $shortUrlService; @@ -88,12 +79,11 @@ class ListShortcodesCommand extends Command public function execute(InputInterface $input, OutputInterface $output) { - $page = intval($input->getOption('page')); + $page = (int) $input->getOption('page'); $searchTerm = $input->getOption('searchTerm'); $tags = $input->getOption('tags'); $tags = ! empty($tags) ? explode(',', $tags) : []; $showTags = $input->getOption('showTags'); - $orderBy = $input->getOption('orderBy'); /** @var QuestionHelper $helper */ $helper = $this->getHelper('question'); diff --git a/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php b/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php index a94b47ad..3344b7af 100644 --- a/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php +++ b/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php @@ -1,9 +1,7 @@ urlShortener = $urlShortener; diff --git a/module/CLI/src/Command/Tag/CreateTagCommand.php b/module/CLI/src/Command/Tag/CreateTagCommand.php index 8a06d38c..530ee569 100644 --- a/module/CLI/src/Command/Tag/CreateTagCommand.php +++ b/module/CLI/src/Command/Tag/CreateTagCommand.php @@ -1,14 +1,11 @@ tagService = $tagService; diff --git a/module/CLI/src/Command/Tag/DeleteTagsCommand.php b/module/CLI/src/Command/Tag/DeleteTagsCommand.php index 0a4e271b..394eac40 100644 --- a/module/CLI/src/Command/Tag/DeleteTagsCommand.php +++ b/module/CLI/src/Command/Tag/DeleteTagsCommand.php @@ -1,14 +1,11 @@ tagService = $tagService; diff --git a/module/CLI/src/Command/Tag/ListTagsCommand.php b/module/CLI/src/Command/Tag/ListTagsCommand.php index eb120226..97037406 100644 --- a/module/CLI/src/Command/Tag/ListTagsCommand.php +++ b/module/CLI/src/Command/Tag/ListTagsCommand.php @@ -1,15 +1,12 @@ tagService = $tagService; diff --git a/module/CLI/src/Command/Tag/RenameTagCommand.php b/module/CLI/src/Command/Tag/RenameTagCommand.php index e3ee678e..89f267d3 100644 --- a/module/CLI/src/Command/Tag/RenameTagCommand.php +++ b/module/CLI/src/Command/Tag/RenameTagCommand.php @@ -1,15 +1,12 @@ tagService = $tagService; diff --git a/module/CLI/src/Command/Visit/ProcessVisitsCommand.php b/module/CLI/src/Command/Visit/ProcessVisitsCommand.php index 195d891c..8b4f82a8 100644 --- a/module/CLI/src/Command/Visit/ProcessVisitsCommand.php +++ b/module/CLI/src/Command/Visit/ProcessVisitsCommand.php @@ -1,12 +1,9 @@ 'Logger_Shlink', LoggerInterface::class => 'Logger_Shlink', ], + 'abstract_factories' => [ + Factory\DottedAccessConfigAbstractFactory::class, + ], ], ]; diff --git a/module/Common/src/Factory/DottedAccessConfigAbstractFactory.php b/module/Common/src/Factory/DottedAccessConfigAbstractFactory.php new file mode 100644 index 00000000..7d0e03b6 --- /dev/null +++ b/module/Common/src/Factory/DottedAccessConfigAbstractFactory.php @@ -0,0 +1,79 @@ + 0; + } + + /** + * Create an object + * + * @param ContainerInterface $container + * @param string $requestedName + * @param null|array $options + * @return object + * @throws InvalidArgumentException + * @throws ServiceNotFoundException if unable to resolve the 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) + { + $parts = explode('.', $requestedName); + $serviceName = array_shift($parts); + if (! $container->has($serviceName)) { + throw new ServiceNotCreatedException(sprintf( + 'Defined service "%s" could not be found in container after resolving dotted expression "%s".', + $serviceName, + $requestedName + )); + } + + $array = $container->get($serviceName); + return $this->readKeysFromArray($parts, $array); + } + + /** + * @param array $keys + * @param array|\ArrayAccess $array + * @return mixed|null + * @throws InvalidArgumentException + */ + private function readKeysFromArray(array $keys, $array) + { + $key = array_shift($keys); + + // When one of the provided keys is not found, throw an exception + if (! isset($array[$key])) { + throw new InvalidArgumentException(sprintf( + 'The key "%s" provided in the dotted notation could not be found in the array service', + $key + )); + } + + $value = $array[$key]; + if (! empty($keys) && (is_array($value) || $value instanceof \ArrayAccess)) { + $value = $this->readKeysFromArray($keys, $value); + } + + return $value; + } +} From 9ef9da087021c897fb08a4b248e42de09b1e903a Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Jul 2017 13:41:41 +0200 Subject: [PATCH 10/64] Replaced more ussages of AnnotatedFactory by ConfigAbstractFactory --- bin/install | 21 ++++++++++---- bin/update | 21 ++++++++++---- .../src/Factory/InstallApplicationFactory.php | 4 +-- module/Common/config/dependencies.config.php | 28 +++++++++++++------ .../src/Middleware/LocaleMiddleware.php | 7 ----- .../Common/src/Service/IpLocationResolver.php | 7 ----- .../Common/src/Service/PreviewGenerator.php | 10 ------- .../Twig/Extension/TranslatorExtension.php | 7 ----- 8 files changed, 53 insertions(+), 52 deletions(-) diff --git a/bin/install b/bin/install index 43a07cd3..e8ecb3c9 100755 --- a/bin/install +++ b/bin/install @@ -1,9 +1,11 @@ #!/usr/bin/env php [ - Application::class => InstallApplicationFactory::class, - Filesystem::class => InvokableFactory::class, - QuestionHelper::class => InvokableFactory::class, -]]); +$container = new ServiceManager([ + 'factories' => [ + Application::class => InstallApplicationFactory::class, + Filesystem::class => InvokableFactory::class, + QuestionHelper::class => InvokableFactory::class, + ], + 'services' => [ + 'config' => [ + ConfigAbstractFactory::class => [ + DatabaseConfigCustomizerPlugin::class => [QuestionHelper::class, Filesystem::class] + ], + ], + ], +]); $container->build(Application::class)->run(); diff --git a/bin/update b/bin/update index 164e20b0..d4203528 100755 --- a/bin/update +++ b/bin/update @@ -1,9 +1,11 @@ #!/usr/bin/env php [ - Application::class => InstallApplicationFactory::class, - Filesystem::class => InvokableFactory::class, - QuestionHelper::class => InvokableFactory::class, -]]); +$container = new ServiceManager([ + 'factories' => [ + Application::class => InstallApplicationFactory::class, + Filesystem::class => InvokableFactory::class, + QuestionHelper::class => InvokableFactory::class, + ], + 'services' => [ + 'config' => [ + ConfigAbstractFactory::class => [ + DatabaseConfigCustomizerPlugin::class => [QuestionHelper::class, Filesystem::class] + ], + ], + ], +]); $container->build(Application::class, ['isUpdate' => true])->run(); diff --git a/module/CLI/src/Factory/InstallApplicationFactory.php b/module/CLI/src/Factory/InstallApplicationFactory.php index 742b5b70..387b2321 100644 --- a/module/CLI/src/Factory/InstallApplicationFactory.php +++ b/module/CLI/src/Factory/InstallApplicationFactory.php @@ -1,7 +1,6 @@ get(Filesystem::class), new ConfigCustomizerPluginManager($container, ['factories' => [ - Plugin\DatabaseConfigCustomizerPlugin::class => AnnotatedFactory::class, + Plugin\DatabaseConfigCustomizerPlugin::class => ConfigAbstractFactory::class, Plugin\UrlShortenerConfigCustomizerPlugin::class => DefaultConfigCustomizerPluginFactory::class, Plugin\LanguageConfigCustomizerPlugin::class => DefaultConfigCustomizerPluginFactory::class, Plugin\ApplicationConfigCustomizerPlugin::class => DefaultConfigCustomizerPluginFactory::class, diff --git a/module/Common/config/dependencies.config.php b/module/Common/config/dependencies.config.php index 06250b3a..dd64b026 100644 --- a/module/Common/config/dependencies.config.php +++ b/module/Common/config/dependencies.config.php @@ -1,45 +1,44 @@ [ - 'invokables' => [ - Filesystem::class => Filesystem::class, - ], 'factories' => [ EntityManager::class => Factory\EntityManagerFactory::class, GuzzleHttp\Client::class => InvokableFactory::class, Cache::class => Factory\CacheFactory::class, 'Logger_Shlink' => Factory\LoggerFactory::class, + Filesystem::class => InvokableFactory::class, Translator::class => Factory\TranslatorFactory::class, - TranslatorExtension::class => AnnotatedFactory::class, - LocaleMiddleware::class => AnnotatedFactory::class, + TranslatorExtension::class => ConfigAbstractFactory::class, + LocaleMiddleware::class => ConfigAbstractFactory::class, Image\ImageBuilder::class => Image\ImageBuilderFactory::class, - Service\IpLocationResolver::class => AnnotatedFactory::class, - Service\PreviewGenerator::class => AnnotatedFactory::class, + Service\IpLocationResolver::class => ConfigAbstractFactory::class, + Service\PreviewGenerator::class => ConfigAbstractFactory::class, ], 'aliases' => [ 'em' => EntityManager::class, 'httpClient' => GuzzleHttp\Client::class, 'translator' => Translator::class, 'logger' => LoggerInterface::class, - AnnotatedFactory::CACHE_SERVICE => Cache::class, Logger::class => 'Logger_Shlink', LoggerInterface::class => 'Logger_Shlink', ], @@ -48,4 +47,15 @@ return [ ], ], + ConfigAbstractFactory::class => [ + TranslatorExtension::class => ['translator'], + LocaleMiddleware::class => ['translator'], + Service\IpLocationResolver::class => ['httpClient'], + Service\PreviewGenerator::class => [ + ImageBuilder::class, + Filesystem::class, + 'config.preview_generation.files_location', + ], + ], + ]; diff --git a/module/Common/src/Middleware/LocaleMiddleware.php b/module/Common/src/Middleware/LocaleMiddleware.php index 0cb81502..fe0ff2c6 100644 --- a/module/Common/src/Middleware/LocaleMiddleware.php +++ b/module/Common/src/Middleware/LocaleMiddleware.php @@ -1,7 +1,6 @@ translator = $translator; diff --git a/module/Common/src/Service/IpLocationResolver.php b/module/Common/src/Service/IpLocationResolver.php index 0bfa80ce..287760a3 100644 --- a/module/Common/src/Service/IpLocationResolver.php +++ b/module/Common/src/Service/IpLocationResolver.php @@ -1,7 +1,6 @@ httpClient = $httpClient; diff --git a/module/Common/src/Service/PreviewGenerator.php b/module/Common/src/Service/PreviewGenerator.php index 03fa392f..d85bf50b 100644 --- a/module/Common/src/Service/PreviewGenerator.php +++ b/module/Common/src/Service/PreviewGenerator.php @@ -1,10 +1,8 @@ location = $location; diff --git a/module/Common/src/Twig/Extension/TranslatorExtension.php b/module/Common/src/Twig/Extension/TranslatorExtension.php index 48ee3f11..807c3099 100644 --- a/module/Common/src/Twig/Extension/TranslatorExtension.php +++ b/module/Common/src/Twig/Extension/TranslatorExtension.php @@ -1,7 +1,6 @@ translator = $translator; From b93d65ddc18bfbc4bf99773215cd8047b86583ec Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Jul 2017 13:48:30 +0200 Subject: [PATCH 11/64] Replaced more ussages of AnnotatedFactory by ConfigAbstractFactory --- module/Core/config/dependencies.config.php | 39 ++++++++++++++----- module/Core/src/Action/PreviewAction.php | 10 ----- module/Core/src/Action/QrCodeAction.php | 10 ----- module/Core/src/Action/RedirectAction.php | 11 ------ .../src/Middleware/QrCodeCacheMiddleware.php | 7 ---- module/Core/src/Service/ShortUrlService.php | 9 +---- module/Core/src/Service/Tag/TagService.php | 7 ---- module/Core/src/Service/UrlShortener.php | 18 ++------- module/Core/src/Service/VisitService.php | 7 ---- module/Core/src/Service/VisitsTracker.php | 7 ---- 10 files changed, 34 insertions(+), 91 deletions(-) diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index 1ef7257c..a25c82fb 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -1,9 +1,13 @@ Options\AppOptionsFactory::class, // Services - Service\UrlShortener::class => AnnotatedFactory::class, - Service\VisitsTracker::class => AnnotatedFactory::class, - Service\ShortUrlService::class => AnnotatedFactory::class, - Service\VisitService::class => AnnotatedFactory::class, - Service\Tag\TagService::class => AnnotatedFactory::class, + Service\UrlShortener::class => ConfigAbstractFactory::class, + Service\VisitsTracker::class => ConfigAbstractFactory::class, + Service\ShortUrlService::class => ConfigAbstractFactory::class, + Service\VisitService::class => ConfigAbstractFactory::class, + Service\Tag\TagService::class => ConfigAbstractFactory::class, // Middleware - Action\RedirectAction::class => AnnotatedFactory::class, - Action\QrCodeAction::class => AnnotatedFactory::class, - Action\PreviewAction::class => AnnotatedFactory::class, - Middleware\QrCodeCacheMiddleware::class => AnnotatedFactory::class, + Action\RedirectAction::class => ConfigAbstractFactory::class, + Action\QrCodeAction::class => ConfigAbstractFactory::class, + Action\PreviewAction::class => ConfigAbstractFactory::class, + Middleware\QrCodeCacheMiddleware::class => ConfigAbstractFactory::class, ], ], + ConfigAbstractFactory::class => [ + // Services + Service\UrlShortener::class => ['httpClient', 'em', Cache::class, 'config.url_shortener.shortcode_chars'], + Service\VisitsTracker::class => ['em'], + Service\ShortUrlService::class => ['em'], + Service\VisitService::class => ['em'], + Service\Tag\TagService::class => ['em'], + + // Middleware + Action\RedirectAction::class => [Service\UrlShortener::class, Service\VisitsTracker::class, 'Logger_Shlink'], + 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/PreviewAction.php b/module/Core/src/Action/PreviewAction.php index 291225be..12c57435 100644 --- a/module/Core/src/Action/PreviewAction.php +++ b/module/Core/src/Action/PreviewAction.php @@ -1,16 +1,13 @@ previewGenerator = $previewGenerator; diff --git a/module/Core/src/Action/QrCodeAction.php b/module/Core/src/Action/QrCodeAction.php index 3970d740..efef3400 100644 --- a/module/Core/src/Action/QrCodeAction.php +++ b/module/Core/src/Action/QrCodeAction.php @@ -1,7 +1,6 @@ cache = $cache; diff --git a/module/Core/src/Service/ShortUrlService.php b/module/Core/src/Service/ShortUrlService.php index 59c76565..c7ae94c9 100644 --- a/module/Core/src/Service/ShortUrlService.php +++ b/module/Core/src/Service/ShortUrlService.php @@ -1,7 +1,6 @@ em = $em; @@ -60,7 +53,7 @@ class ShortUrlService implements ShortUrlServiceInterface $shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([ 'shortCode' => $shortCode, ]); - if (! isset($shortUrl)) { + if ($shortUrl === null) { throw InvalidShortCodeException::fromNotFoundShortCode($shortCode); } diff --git a/module/Core/src/Service/Tag/TagService.php b/module/Core/src/Service/Tag/TagService.php index 52708a39..97fdca2d 100644 --- a/module/Core/src/Service/Tag/TagService.php +++ b/module/Core/src/Service/Tag/TagService.php @@ -1,7 +1,6 @@ em = $em; diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index 9d6927bf..67e32626 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -1,7 +1,6 @@ chars); $code = ''; while ($id > 0) { // Determine the value of the next higher character in the short code and prepend it - $code = $this->chars[intval(fmod($id, $length))] . $code; + $code = $this->chars[(int) fmod($id, $length)] . $code; $id = floor($id / $length); } - return $this->chars[intval($id)] . $code; + return $this->chars[(int) $id] . $code; } /** diff --git a/module/Core/src/Service/VisitService.php b/module/Core/src/Service/VisitService.php index d8e46c31..375cc57e 100644 --- a/module/Core/src/Service/VisitService.php +++ b/module/Core/src/Service/VisitService.php @@ -1,7 +1,6 @@ em = $em; diff --git a/module/Core/src/Service/VisitsTracker.php b/module/Core/src/Service/VisitsTracker.php index 94647086..92bafa71 100644 --- a/module/Core/src/Service/VisitsTracker.php +++ b/module/Core/src/Service/VisitsTracker.php @@ -1,7 +1,6 @@ em = $em; From 01a4f9f867af4f16d8866c8916dd792a3c6228d3 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Jul 2017 14:20:40 +0200 Subject: [PATCH 12/64] Removed any remaining reference to AnnotatedFactory --- composer.json | 2 +- module/Rest/config/dependencies.config.php | 60 ++++++++++++++----- module/Rest/src/Action/AuthenticateAction.php | 11 ---- .../Rest/src/Action/CreateShortcodeAction.php | 12 ---- .../src/Action/EditShortcodeTagsAction.php | 10 ---- module/Rest/src/Action/GetVisitsAction.php | 10 ---- .../Rest/src/Action/ListShortcodesAction.php | 10 ---- module/Rest/src/Action/ResolveUrlAction.php | 10 ---- .../Rest/src/Action/Tag/CreateTagsAction.php | 9 --- .../Rest/src/Action/Tag/DeleteTagsAction.php | 9 --- module/Rest/src/Action/Tag/ListTagsAction.php | 9 --- .../Rest/src/Action/Tag/UpdateTagAction.php | 11 ---- module/Rest/src/Authentication/JWTService.php | 7 --- .../CheckAuthenticationMiddleware.php | 11 +--- module/Rest/src/Service/ApiKeyService.php | 7 --- 15 files changed, 46 insertions(+), 142 deletions(-) diff --git a/composer.json b/composer.json index 4e909e5e..3b8a744e 100644 --- a/composer.json +++ b/composer.json @@ -22,9 +22,9 @@ "zendframework/zend-config": "^3.0", "zendframework/zend-i18n": "^2.7", "zendframework/zend-config-aggregator": "^0.1", - "acelaya/zsm-annotated-services": "^1.0", "acelaya/ze-content-based-error-handler": "^2.0", "doctrine/orm": "^2.5", + "doctrine/annotations": "^1.4 <1.5", "doctrine/collections": "^1.4 <1.5", "doctrine/common": "^2.7 <2.8", "guzzlehttp/guzzle": "^6.2", diff --git a/module/Rest/config/dependencies.config.php b/module/Rest/config/dependencies.config.php index a36f7590..8f2227c0 100644 --- a/module/Rest/config/dependencies.config.php +++ b/module/Rest/config/dependencies.config.php @@ -1,34 +1,62 @@ [ 'factories' => [ - JWTService::class => AnnotatedFactory::class, - Service\ApiKeyService::class => AnnotatedFactory::class, + JWTService::class => ConfigAbstractFactory::class, + ApiKeyService::class => ConfigAbstractFactory::class, - Action\AuthenticateAction::class => AnnotatedFactory::class, - Action\CreateShortcodeAction::class => AnnotatedFactory::class, - Action\ResolveUrlAction::class => AnnotatedFactory::class, - Action\GetVisitsAction::class => AnnotatedFactory::class, - Action\ListShortcodesAction::class => AnnotatedFactory::class, - Action\EditShortcodeTagsAction::class => AnnotatedFactory::class, - Action\Tag\ListTagsAction::class => AnnotatedFactory::class, - Action\Tag\DeleteTagsAction::class => AnnotatedFactory::class, - Action\Tag\CreateTagsAction::class => AnnotatedFactory::class, - Action\Tag\UpdateTagAction::class => AnnotatedFactory::class, + Action\AuthenticateAction::class => ConfigAbstractFactory::class, + Action\CreateShortcodeAction::class => ConfigAbstractFactory::class, + Action\ResolveUrlAction::class => ConfigAbstractFactory::class, + Action\GetVisitsAction::class => ConfigAbstractFactory::class, + Action\ListShortcodesAction::class => ConfigAbstractFactory::class, + Action\EditShortcodeTagsAction::class => ConfigAbstractFactory::class, + Action\Tag\ListTagsAction::class => ConfigAbstractFactory::class, + Action\Tag\DeleteTagsAction::class => ConfigAbstractFactory::class, + Action\Tag\CreateTagsAction::class => ConfigAbstractFactory::class, + Action\Tag\UpdateTagAction::class => ConfigAbstractFactory::class, - Middleware\BodyParserMiddleware::class => AnnotatedFactory::class, + Middleware\BodyParserMiddleware::class => InvokableFactory::class, Middleware\CrossDomainMiddleware::class => InvokableFactory::class, Middleware\PathVersionMiddleware::class => InvokableFactory::class, - Middleware\CheckAuthenticationMiddleware::class => AnnotatedFactory::class, + Middleware\CheckAuthenticationMiddleware::class => ConfigAbstractFactory::class, ], ], + ConfigAbstractFactory::class => [ + JWTService::class => [AppOptions::class], + ApiKeyService::class => ['em'], + + Action\AuthenticateAction::class => [ApiKeyService::class, JWTService::class, 'translator', 'Logger_Shlink'], + Action\CreateShortcodeAction::class => [ + Service\UrlShortener::class, + 'translator', + 'config.url_shortener.domain', + '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'], + Action\EditShortcodeTagsAction::class => [Service\ShortUrlService::class, 'translator', 'Logger_Shlink'], + Action\Tag\ListTagsAction::class => [Service\Tag\TagService::class, LoggerInterface::class], + Action\Tag\DeleteTagsAction::class => [Service\Tag\TagService::class, LoggerInterface::class], + Action\Tag\CreateTagsAction::class => [Service\Tag\TagService::class, LoggerInterface::class], + Action\Tag\UpdateTagAction::class => [Service\Tag\TagService::class, Translator::class, LoggerInterface::class], + + Middleware\CheckAuthenticationMiddleware::class => [JWTService::class, 'translator', 'Logger_Shlink'], + ], + ]; diff --git a/module/Rest/src/Action/AuthenticateAction.php b/module/Rest/src/Action/AuthenticateAction.php index a4b15920..1403e88e 100644 --- a/module/Rest/src/Action/AuthenticateAction.php +++ b/module/Rest/src/Action/AuthenticateAction.php @@ -1,12 +1,10 @@ appOptions = $appOptions; diff --git a/module/Rest/src/Middleware/CheckAuthenticationMiddleware.php b/module/Rest/src/Middleware/CheckAuthenticationMiddleware.php index d7d922ad..93537d35 100644 --- a/module/Rest/src/Middleware/CheckAuthenticationMiddleware.php +++ b/module/Rest/src/Middleware/CheckAuthenticationMiddleware.php @@ -1,7 +1,6 @@ em = $em; From 4ad167eb30b0826d17ba16f4df3c74f4440d138b Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Jul 2017 08:42:57 +0200 Subject: [PATCH 13/64] Ensured all doctrine components are not updated to latest releases, which require PHP 7.1 --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3b8a744e..60856299 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,8 @@ "zendframework/zend-i18n": "^2.7", "zendframework/zend-config-aggregator": "^0.1", "acelaya/ze-content-based-error-handler": "^2.0", - "doctrine/orm": "^2.5", + "doctrine/orm": "^2.5 <2.6", + "doctrine/dbal": "^2.5 <2.6", "doctrine/annotations": "^1.4 <1.5", "doctrine/collections": "^1.4 <1.5", "doctrine/common": "^2.7 <2.8", From 3e5f0b2451b522024f0e4fec20b4797bfee547c1 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Jul 2017 11:51:47 +0200 Subject: [PATCH 14/64] Ensured a doctrine/cache version which requires PHP 7.1 is not installed --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 60856299..e7fd3a4a 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,7 @@ "doctrine/annotations": "^1.4 <1.5", "doctrine/collections": "^1.4 <1.5", "doctrine/common": "^2.7 <2.8", + "doctrine/cache": "^1.6 <1.7", "guzzlehttp/guzzle": "^6.2", "symfony/console": "^3.0", "symfony/process": "^3.0", From 6321cc0f2d0264c9043fd1eb31d4f2e55549a711 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 19 Aug 2017 19:41:07 +0200 Subject: [PATCH 15/64] Added security-advisories dependency --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e7fd3a4a..faab4cb7 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ ], "require": { "php": "^5.6 || ^7.0", + "roave/security-advisories": "dev-master", "zendframework/zend-expressive": "^2.0", "zendframework/zend-expressive-fastroute": "^2.0", "zendframework/zend-expressive-twigrenderer": "^1.4", @@ -43,7 +44,6 @@ "require-dev": { "phpunit/phpunit": "^5.7 || ^6.0", "squizlabs/php_codesniffer": "^2.3", - "roave/security-advisories": "dev-master", "filp/whoops": "^2.0", "symfony/var-dumper": "^3.0", "vlucas/phpdotenv": "^2.2", From d10583c7c5aa79c774dfe75e7cf9cf64af7f6631 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 12 Oct 2017 09:24:47 +0200 Subject: [PATCH 16/64] Udated minimum PHP version and docker stuff --- .gitignore | 1 + composer.json | 7 ++++--- data/infra/php.Dockerfile | 5 ++++- docker-compose.override.yml.dist | 8 ++++++++ 4 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 docker-compose.override.yml.dist diff --git a/.gitignore b/.gitignore index 2a7fb730..e3bcd671 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ vendor/ .env data/database.sqlite docs/swagger-ui +docker-compose.override.yml diff --git a/composer.json b/composer.json index faab4cb7..226419e7 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ } ], "require": { - "php": "^5.6 || ^7.0", + "php": "^7.0", "roave/security-advisories": "dev-master", "zendframework/zend-expressive": "^2.0", "zendframework/zend-expressive-fastroute": "^2.0", @@ -39,11 +39,12 @@ "theorchard/monolog-cascade": "^0.4", "endroid/qrcode": "^1.7", "mikehaertl/phpwkhtmltopdf": "^2.2", - "doctrine/migrations": "^1.4" + "doctrine/migrations": "^1.4", + "http-interop/http-middleware": "^0.4.1" }, "require-dev": { "phpunit/phpunit": "^5.7 || ^6.0", - "squizlabs/php_codesniffer": "^2.3", + "squizlabs/php_codesniffer": "^3.1", "filp/whoops": "^2.0", "symfony/var-dumper": "^3.0", "vlucas/phpdotenv": "^2.2", diff --git a/data/infra/php.Dockerfile b/data/infra/php.Dockerfile index 28d9d6f3..fac0b498 100644 --- a/data/infra/php.Dockerfile +++ b/data/infra/php.Dockerfile @@ -26,7 +26,7 @@ RUN apk add --no-cache --virtual libpng-dev RUN docker-php-ext-install gd # Install redis extension -ADD https://github.com/phpredis/phpredis/archive/php7.tar.gz /tmp/phpredis.tar.gz +ADD https://github.com/phpredis/phpredis/archive/3.1.4.tar.gz /tmp/phpredis.tar.gz RUN mkdir -p /usr/src/php/ext/redis\ && tar xf /tmp/phpredis.tar.gz -C /usr/src/php/ext/redis --strip-components=1 # configure and install @@ -85,3 +85,6 @@ RUN rm /tmp/xdebug.tar.gz RUN php -r "readfile('https://getcomposer.org/installer');" | php RUN chmod +x composer.phar RUN mv composer.phar /usr/local/bin/composer + +# Make home directory writable by anyone +RUN chmod 777 /home diff --git a/docker-compose.override.yml.dist b/docker-compose.override.yml.dist new file mode 100644 index 00000000..b347b9e8 --- /dev/null +++ b/docker-compose.override.yml.dist @@ -0,0 +1,8 @@ +version: '2' + +services: + shlink_php: + user: 1000:1000 + volumes: + - /etc/passwd:/etc/passwd:ro + - /etc/group:/etc/group:ro From 453ca1728ebee38c28ad2d38fb997555316a9705 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 12 Oct 2017 09:40:42 +0200 Subject: [PATCH 17/64] Updated system to use plates instead of twig --- .travis.yml | 1 - composer.json | 54 +++++++------- config/autoload/dependencies.global.php | 12 +++- config/autoload/templates.global.php | 4 +- module/Common/config/dependencies.config.php | 2 +- module/Common/config/templates.config.php | 6 +- .../Extension/TranslatorExtension.php | 25 +++++++ .../Twig/Extension/TranslatorExtension.php | 68 ------------------ module/Common/test/ConfigProviderTest.php | 2 +- .../Extension/TranslatorExtensionTest.php | 34 +++++++++ .../Extension/TranslatorExtensionTest.php | 70 ------------------- module/Core/config/templates.config.php | 2 +- module/Core/config/zend-expressive.config.php | 4 +- .../templates/{core => }/error/404.html.twig | 0 .../{core => }/error/error.html.twig | 0 .../{core => }/layout/default.html.twig | 0 16 files changed, 107 insertions(+), 177 deletions(-) create mode 100644 module/Common/src/Template/Extension/TranslatorExtension.php delete mode 100644 module/Common/src/Twig/Extension/TranslatorExtension.php create mode 100644 module/Common/test/Template/Extension/TranslatorExtensionTest.php delete mode 100644 module/Common/test/Twig/Extension/TranslatorExtensionTest.php rename module/Core/templates/{core => }/error/404.html.twig (100%) rename module/Core/templates/{core => }/error/error.html.twig (100%) rename module/Core/templates/{core => }/layout/default.html.twig (100%) diff --git a/.travis.yml b/.travis.yml index 617ac22b..f9e19b89 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ branches: - develop php: - - 5.6 - 7 - 7.1 - 7.2 diff --git a/composer.json b/composer.json index 226419e7..1f1d5303 100644 --- a/composer.json +++ b/composer.json @@ -13,39 +13,40 @@ ], "require": { "php": "^7.0", - "roave/security-advisories": "dev-master", - "zendframework/zend-expressive": "^2.0", - "zendframework/zend-expressive-fastroute": "^2.0", - "zendframework/zend-expressive-twigrenderer": "^1.4", - "zendframework/zend-stdlib": "^3.0", - "zendframework/zend-servicemanager": "^3.0", - "zendframework/zend-paginator": "^2.6", - "zendframework/zend-config": "^3.0", - "zendframework/zend-i18n": "^2.7", - "zendframework/zend-config-aggregator": "^0.1", "acelaya/ze-content-based-error-handler": "^2.0", - "doctrine/orm": "^2.5 <2.6", - "doctrine/dbal": "^2.5 <2.6", "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/cache": "^1.6 <1.7", - "guzzlehttp/guzzle": "^6.2", - "symfony/console": "^3.0", - "symfony/process": "^3.0", - "symfony/filesystem": "^3.0", - "firebase/php-jwt": "^4.0", - "monolog/monolog": "^1.21", - "theorchard/monolog-cascade": "^0.4", - "endroid/qrcode": "^1.7", - "mikehaertl/phpwkhtmltopdf": "^2.2", + "doctrine/dbal": "^2.5 <2.6", "doctrine/migrations": "^1.4", - "http-interop/http-middleware": "^0.4.1" + "doctrine/orm": "^2.5 <2.6", + "endroid/qrcode": "^1.7", + "firebase/php-jwt": "^4.0", + "guzzlehttp/guzzle": "^6.2", + "http-interop/http-middleware": "^0.4.1", + "mikehaertl/phpwkhtmltopdf": "^2.2", + "monolog/monolog": "^1.21", + "roave/security-advisories": "dev-master", + "symfony/console": "^3.0", + "symfony/filesystem": "^3.0", + "symfony/process": "^3.0", + "theorchard/monolog-cascade": "^0.4", + "zendframework/zend-config": "^3.0", + "zendframework/zend-config-aggregator": "^0.1", + "zendframework/zend-expressive": "^2.0", + "zendframework/zend-expressive-fastroute": "^2.0", + "zendframework/zend-expressive-helpers": "^4.2", + "zendframework/zend-expressive-platesrenderer": "^1.3", + "zendframework/zend-i18n": "^2.7", + "zendframework/zend-paginator": "^2.6", + "zendframework/zend-servicemanager": "^3.0", + "zendframework/zend-stdlib": "^3.0" }, "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.0", - "squizlabs/php_codesniffer": "^3.1", "filp/whoops": "^2.0", + "phpunit/phpunit": "^6.0", + "squizlabs/php_codesniffer": "^3.1", "symfony/var-dumper": "^3.0", "vlucas/phpdotenv": "^2.2", "zendframework/zend-expressive-tooling": "^0.4" @@ -81,6 +82,7 @@ "pretty-test": "phpunit --coverage-html build/coverage" }, "config": { - "process-timeout": 0 + "process-timeout": 0, + "sort-packages": true } } diff --git a/config/autoload/dependencies.global.php b/config/autoload/dependencies.global.php index 83a893dc..c918544d 100644 --- a/config/autoload/dependencies.global.php +++ b/config/autoload/dependencies.global.php @@ -1,11 +1,15 @@ [ 'factories' => [ Expressive\Application::class => Container\ApplicationFactory::class, - Template\TemplateRendererInterface::class => Twig\TwigRendererFactory::class, - \Twig_Environment::class => Twig\TwigEnvironmentFactory::class, + Template\TemplateRendererInterface::class => Plates\PlatesRendererFactory::class, Router\RouterInterface::class => Router\FastRouteRouterFactory::class, ErrorHandler::class => Container\ErrorHandlerFactory::class, Middleware\ImplicitOptionsMiddleware::class => EmptyResponseImplicitOptionsMiddlewareFactory::class, + + Helper\UrlHelper::class => Helper\UrlHelperFactory::class, + Helper\ServerUrlHelper::class => InvokableFactory::class, ], ], diff --git a/config/autoload/templates.global.php b/config/autoload/templates.global.php index f102baa0..cf8e404b 100644 --- a/config/autoload/templates.global.php +++ b/config/autoload/templates.global.php @@ -2,8 +2,8 @@ return [ - 'twig' => [ - 'cache_dir' => 'data/cache/twig', + 'templates' => [ + 'extension' => 'phtml', 'extensions' => [ // extension service names or instances ], diff --git a/module/Common/config/dependencies.config.php b/module/Common/config/dependencies.config.php index dd64b026..dd89b481 100644 --- a/module/Common/config/dependencies.config.php +++ b/module/Common/config/dependencies.config.php @@ -9,7 +9,7 @@ use Shlinkio\Shlink\Common\Image; use Shlinkio\Shlink\Common\Image\ImageBuilder; use Shlinkio\Shlink\Common\Middleware\LocaleMiddleware; use Shlinkio\Shlink\Common\Service; -use Shlinkio\Shlink\Common\Twig\Extension\TranslatorExtension; +use Shlinkio\Shlink\Common\Template\Extension\TranslatorExtension; use Symfony\Component\Filesystem\Filesystem; use Zend\I18n\Translator\Translator; use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory; diff --git a/module/Common/config/templates.config.php b/module/Common/config/templates.config.php index 903c1e8c..37a6714d 100644 --- a/module/Common/config/templates.config.php +++ b/module/Common/config/templates.config.php @@ -1,9 +1,11 @@ [ + 'templates' => [ 'extensions' => [ TranslatorExtension::class, ], diff --git a/module/Common/src/Template/Extension/TranslatorExtension.php b/module/Common/src/Template/Extension/TranslatorExtension.php new file mode 100644 index 00000000..01fbeeed --- /dev/null +++ b/module/Common/src/Template/Extension/TranslatorExtension.php @@ -0,0 +1,25 @@ +translator = $translator; + } + + public function register(Engine $engine) + { + $engine->registerFunction('translate', [$this->translator, 'translate']); + $engine->registerFunction('translate_plural', [$this->translator, 'translatePlural']); + } +} diff --git a/module/Common/src/Twig/Extension/TranslatorExtension.php b/module/Common/src/Twig/Extension/TranslatorExtension.php deleted file mode 100644 index 807c3099..00000000 --- a/module/Common/src/Twig/Extension/TranslatorExtension.php +++ /dev/null @@ -1,68 +0,0 @@ -translator = $translator; - } - - /** - * Returns the name of the extension. - * - * @return string The extension name - */ - public function getName() - { - return __CLASS__; - } - - public function getFunctions() - { - return [ - new \Twig_SimpleFunction('translate', [$this, 'translate']), - new \Twig_SimpleFunction('translate_plural', [$this, 'translatePlural']), - ]; - } - - /** - * Translate a message. - * - * @param string $message - * @param string $textDomain - * @param string $locale - * @return string - */ - public function translate($message, $textDomain = 'default', $locale = null) - { - return $this->translator->translate($message, $textDomain, $locale); - } - - /** - * Translate a plural message. - * - * @param string $singular - * @param string $plural - * @param int $number - * @param string $textDomain - * @param string|null $locale - * @return string - */ - public function translatePlural( - $singular, - $plural, - $number, - $textDomain = 'default', - $locale = null - ) { - $this->translator->translatePlural($singular, $plural, $number, $textDomain, $locale); - } -} diff --git a/module/Common/test/ConfigProviderTest.php b/module/Common/test/ConfigProviderTest.php index 05b5da49..65a62a01 100644 --- a/module/Common/test/ConfigProviderTest.php +++ b/module/Common/test/ConfigProviderTest.php @@ -24,6 +24,6 @@ class ConfigProviderTest extends TestCase $config = $this->configProvider->__invoke(); $this->assertArrayHasKey('dependencies', $config); - $this->assertArrayHasKey('twig', $config); + $this->assertArrayHasKey('templates', $config); } } diff --git a/module/Common/test/Template/Extension/TranslatorExtensionTest.php b/module/Common/test/Template/Extension/TranslatorExtensionTest.php new file mode 100644 index 00000000..e39e2b24 --- /dev/null +++ b/module/Common/test/Template/Extension/TranslatorExtensionTest.php @@ -0,0 +1,34 @@ +extension = new TranslatorExtension($this->prophesize(Translator::class)->reveal()); + } + + /** + * @test + */ + public function properFunctionsAreReturned() + { + $engine = $this->prophesize(Engine::class); + + $engine->registerFunction('translate', Argument::type('callable'))->shouldBeCalledTimes(1); + $engine->registerFunction('translate_plural', Argument::type('callable'))->shouldBeCalledTimes(1); + + $funcs = $this->extension->register($engine->reveal()); + } +} diff --git a/module/Common/test/Twig/Extension/TranslatorExtensionTest.php b/module/Common/test/Twig/Extension/TranslatorExtensionTest.php deleted file mode 100644 index 46dc2ec8..00000000 --- a/module/Common/test/Twig/Extension/TranslatorExtensionTest.php +++ /dev/null @@ -1,70 +0,0 @@ -translator = $this->prophesize(Translator::class); - $this->extension = new TranslatorExtension($this->translator->reveal()); - } - - /** - * @test - */ - public function extensionNameIsClassName() - { - $this->assertEquals(TranslatorExtension::class, $this->extension->getName()); - } - - /** - * @test - */ - public function properFunctionsAreReturned() - { - $funcs = $this->extension->getFunctions(); - $this->assertCount(2, $funcs); - foreach ($funcs as $func) { - $this->assertInstanceOf(\Twig_SimpleFunction::class, $func); - } - } - - /** - * @test - */ - public function translateFallbacksToTranslator() - { - $this->translator->translate('foo', 'default', null)->shouldBeCalledTimes(1); - $this->extension->translate('foo'); - - $this->translator->translate('bar', 'baz', 'en')->shouldBeCalledTimes(1); - $this->extension->translate('bar', 'baz', 'en'); - } - - /** - * @test - */ - public function translatePluralFallbacksToTranslator() - { - $this->translator->translatePlural('foo', 'bar', 'baz', 'default', null)->shouldBeCalledTimes(1); - $this->extension->translatePlural('foo', 'bar', 'baz'); - - $this->translator->translatePlural('foo', 'bar', 'baz', 'another', 'en')->shouldBeCalledTimes(1); - $this->extension->translatePlural('foo', 'bar', 'baz', 'another', 'en'); - } -} diff --git a/module/Core/config/templates.config.php b/module/Core/config/templates.config.php index da3623c1..d5e6677e 100644 --- a/module/Core/config/templates.config.php +++ b/module/Core/config/templates.config.php @@ -4,7 +4,7 @@ return [ 'templates' => [ 'paths' => [ - 'module/Core/templates', + 'ShlinkCore' => __DIR__ . '/../templates', ], ], diff --git a/module/Core/config/zend-expressive.config.php b/module/Core/config/zend-expressive.config.php index c5fefe8f..3dfd091f 100644 --- a/module/Core/config/zend-expressive.config.php +++ b/module/Core/config/zend-expressive.config.php @@ -4,8 +4,8 @@ return [ 'zend-expressive' => [ 'error_handler' => [ - 'template_404' => 'core/error/404.html.twig', - 'template_error' => 'core/error/error.html.twig', + 'template_404' => 'ShlinkCore::error/404', + 'template_error' => 'ShlinkCore::error/error', ], ], diff --git a/module/Core/templates/core/error/404.html.twig b/module/Core/templates/error/404.html.twig similarity index 100% rename from module/Core/templates/core/error/404.html.twig rename to module/Core/templates/error/404.html.twig diff --git a/module/Core/templates/core/error/error.html.twig b/module/Core/templates/error/error.html.twig similarity index 100% rename from module/Core/templates/core/error/error.html.twig rename to module/Core/templates/error/error.html.twig diff --git a/module/Core/templates/core/layout/default.html.twig b/module/Core/templates/layout/default.html.twig similarity index 100% rename from module/Core/templates/core/layout/default.html.twig rename to module/Core/templates/layout/default.html.twig From e53ffc8d431ca75876d9155d1cdc4357552320a2 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 12 Oct 2017 10:03:14 +0200 Subject: [PATCH 18/64] Migrated templates to plates --- config/autoload/templates.global.php | 3 +++ module/Common/config/templates.config.php | 2 +- module/Core/templates/error/404.html.twig | 17 ------------- module/Core/templates/error/404.phtml | 19 ++++++++++++++ module/Core/templates/error/error.html.twig | 21 ---------------- module/Core/templates/error/error.phtml | 25 +++++++++++++++++++ .../{default.html.twig => default.phtml} | 16 ++++++------ .../src/Middleware/PathVersionMiddleware.php | 2 +- public/index.php | 6 ++--- 9 files changed, 61 insertions(+), 50 deletions(-) delete mode 100644 module/Core/templates/error/404.html.twig create mode 100644 module/Core/templates/error/404.phtml delete mode 100644 module/Core/templates/error/error.html.twig create mode 100644 module/Core/templates/error/error.phtml rename module/Core/templates/layout/{default.html.twig => default.phtml} (70%) diff --git a/config/autoload/templates.global.php b/config/autoload/templates.global.php index cf8e404b..24296d41 100644 --- a/config/autoload/templates.global.php +++ b/config/autoload/templates.global.php @@ -4,6 +4,9 @@ return [ 'templates' => [ 'extension' => 'phtml', + ], + + 'plates' => [ 'extensions' => [ // extension service names or instances ], diff --git a/module/Common/config/templates.config.php b/module/Common/config/templates.config.php index 37a6714d..eb25ef78 100644 --- a/module/Common/config/templates.config.php +++ b/module/Common/config/templates.config.php @@ -5,7 +5,7 @@ use Shlinkio\Shlink\Common\Template\Extension\TranslatorExtension; return [ - 'templates' => [ + 'plates' => [ 'extensions' => [ TranslatorExtension::class, ], diff --git a/module/Core/templates/error/404.html.twig b/module/Core/templates/error/404.html.twig deleted file mode 100644 index fe36f047..00000000 --- a/module/Core/templates/error/404.html.twig +++ /dev/null @@ -1,17 +0,0 @@ -{% extends 'core/layout/default.html.twig' %} - -{% block title %}{{ translate('URL Not Found') }}{% endblock %} - -{% block stylesheets %} - -{% endblock %} - -{% block content %} -

{{ translate('Oops!') }}

-
-

{{ translate('This short URL doesn\'t seem to be valid.') }}

-

{{ translate('Make sure you included all the characters, with no extra punctuation.') }}

-{% endblock %} diff --git a/module/Core/templates/error/404.phtml b/module/Core/templates/error/404.phtml new file mode 100644 index 00000000..369a168b --- /dev/null +++ b/module/Core/templates/error/404.phtml @@ -0,0 +1,19 @@ +layout('ShlinkCore::layout/default') ?> + +start('title') ?> + translate('URL Not Found') ?> +end() ?> + +start('stylesheets') ?> + +end() ?> + +start('main') ?> +

translate('Oops!') ?>

+
+

translate('This short URL doesn\'t seem to be valid.') ?>

+

translate('Make sure you included all the characters, with no extra punctuation.') ?>

+end() ?> diff --git a/module/Core/templates/error/error.html.twig b/module/Core/templates/error/error.html.twig deleted file mode 100644 index 5cb66c57..00000000 --- a/module/Core/templates/error/error.html.twig +++ /dev/null @@ -1,21 +0,0 @@ -{% extends 'core/layout/default.html.twig' %} - -{% block title %}{{ status }} {{ reason }}{% endblock %} - -{% block stylesheets %} - -{% endblock %} - -{% block content %} -

{{ translate('Oops!') }}

-
- {% if status != 404 %} -

{{ translate('We encountered a %s %s error.') | format(status, reason) }}

- {% else %} -

{{ translate('This short URL doesn\'t seem to be valid.') }}

-

{{ translate('Make sure you included all the characters, with no extra punctuation.') }}

- {% endif %} -{% endblock %} diff --git a/module/Core/templates/error/error.phtml b/module/Core/templates/error/error.phtml new file mode 100644 index 00000000..6d22ed9f --- /dev/null +++ b/module/Core/templates/error/error.phtml @@ -0,0 +1,25 @@ +layout('ShlinkCore::layout/default') ?> + +start('title') ?> + e($status . ' ' . $reason) ?> +end() ?> + +start('stylesheets') ?> + +end() ?> + +start('main') ?> +

translate('Oops!') ?>

+
+ + +

translate('We encountered a %s %s error.'), $status, $reason) ?>

+ +

translate('This short URL doesn\'t seem to be valid.') ?>

+

translate('Make sure you included all the characters, with no extra punctuation.') ?>

+ +end() ?> + diff --git a/module/Core/templates/layout/default.html.twig b/module/Core/templates/layout/default.phtml similarity index 70% rename from module/Core/templates/layout/default.html.twig rename to module/Core/templates/layout/default.phtml index 4b405c5b..802bf466 100644 --- a/module/Core/templates/layout/default.html.twig +++ b/module/Core/templates/layout/default.phtml @@ -3,7 +3,7 @@ - {% block title %}{% endblock %} | URL shortener + <?= $this->section('title', '') ?> | URL shortener @@ -13,28 +13,30 @@ .app-content {flex: 1;} .app-footer p {margin-bottom: 20px;} - {% block stylesheets %}{% endblock %} + section('stylesheets', '') ?>
- {% block content %}{% endblock %} + section('main', '') ?>

- {% block footer %} + section('footer')): ?> + section('footer') ?> +

- © {{ "now" | date("Y") }} Shlink + © Shlink

- {% endblock %} +
- {% block javascript %}{% endblock %} + section('javascript', '') ?> diff --git a/module/Rest/src/Middleware/PathVersionMiddleware.php b/module/Rest/src/Middleware/PathVersionMiddleware.php index 3220bfaf..b247f50c 100644 --- a/module/Rest/src/Middleware/PathVersionMiddleware.php +++ b/module/Rest/src/Middleware/PathVersionMiddleware.php @@ -37,7 +37,7 @@ class PathVersionMiddleware implements MiddlewareInterface $uri = $request->getUri(); $path = $uri->getPath(); - // If the path does not begin with the version number, prepend v1 by default for retrocompatibility purposes + // If the path does not begin with the version number, prepend v1 by default for BC compatibility purposes if (strpos($path, '/v') !== 0) { $parts = explode('/', $path); // Remove the first empty part and the diff --git a/public/index.php b/public/index.php index 6b310943..3f2c0f84 100644 --- a/public/index.php +++ b/public/index.php @@ -1,9 +1,9 @@ get(Application::class); -$app->run(); +$app = $container->get(Application::class)->run(); From fbeb9593171041626c3b9fafae9d20e2e6d101e7 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 12 Oct 2017 10:03:43 +0200 Subject: [PATCH 19/64] Fixed tests --- module/Common/test/ConfigProviderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/Common/test/ConfigProviderTest.php b/module/Common/test/ConfigProviderTest.php index 65a62a01..551d2137 100644 --- a/module/Common/test/ConfigProviderTest.php +++ b/module/Common/test/ConfigProviderTest.php @@ -24,6 +24,6 @@ class ConfigProviderTest extends TestCase $config = $this->configProvider->__invoke(); $this->assertArrayHasKey('dependencies', $config); - $this->assertArrayHasKey('templates', $config); + $this->assertArrayHasKey('plates', $config); } } From c422a14c5cc8eb44d42121fe12421bfa8025ac42 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 12 Oct 2017 10:13:20 +0200 Subject: [PATCH 20/64] Improved coding styles --- composer.json | 1 + config/autoload/app_options.global.php | 2 ++ config/autoload/dependencies.global.php | 2 +- config/autoload/entity-manager.global.php | 2 ++ config/autoload/logger.global.php | 2 ++ .../autoload/middleware-pipeline.global.php | 2 ++ config/autoload/phpwkhtmltopdf.global.php | 2 ++ config/autoload/preview-generation.global.php | 2 ++ config/autoload/router.global.php | 2 ++ config/autoload/templates.global.php | 1 + config/autoload/translator.global.php | 2 ++ config/autoload/url-shortener.global.php | 2 ++ config/autoload/zend-expressive.global.php | 1 + config/cli-config.php | 2 ++ config/config.php | 2 ++ config/container.php | 2 ++ data/migrations/Version20160819142757.php | 1 + data/migrations/Version20160820191203.php | 1 + module/CLI/config/cli.config.php | 4 ++- module/CLI/config/dependencies.config.php | 5 ++-- module/CLI/config/translator.config.php | 2 ++ .../CLI/src/Command/Api/DisableKeyCommand.php | 2 ++ .../src/Command/Api/GenerateKeyCommand.php | 2 ++ .../CLI/src/Command/Api/ListKeysCommand.php | 2 ++ .../Command/Config/GenerateCharsetCommand.php | 2 ++ .../Command/Config/GenerateSecretCommand.php | 2 ++ .../src/Command/Install/InstallCommand.php | 2 ++ .../Shortcode/GeneratePreviewCommand.php | 2 ++ .../Shortcode/GenerateShortcodeCommand.php | 2 ++ .../Command/Shortcode/GetVisitsCommand.php | 2 ++ .../Shortcode/ListShortcodesCommand.php | 2 ++ .../Command/Shortcode/ResolveUrlCommand.php | 2 ++ .../CLI/src/Command/Tag/CreateTagCommand.php | 2 ++ .../CLI/src/Command/Tag/DeleteTagsCommand.php | 2 ++ .../CLI/src/Command/Tag/ListTagsCommand.php | 2 ++ .../CLI/src/Command/Tag/RenameTagCommand.php | 2 ++ .../Command/Visit/ProcessVisitsCommand.php | 2 ++ module/CLI/src/ConfigProvider.php | 2 ++ module/CLI/src/Factory/ApplicationFactory.php | 2 ++ .../src/Factory/InstallApplicationFactory.php | 2 ++ .../Install/ConfigCustomizerPluginManager.php | 2 ++ ...ConfigCustomizerPluginManagerInterface.php | 2 ++ .../Plugin/AbstractConfigCustomizerPlugin.php | 2 ++ .../ApplicationConfigCustomizerPlugin.php | 2 ++ .../ConfigCustomizerPluginInterface.php | 2 ++ .../Plugin/DatabaseConfigCustomizerPlugin.php | 2 ++ .../DefaultConfigCustomizerPluginFactory.php | 2 ++ .../Plugin/LanguageConfigCustomizerPlugin.php | 2 ++ .../UrlShortenerConfigCustomizerPlugin.php | 4 ++- .../CLI/src/Model/CustomizableAppConfig.php | 2 ++ .../Command/Api/DisableKeyCommandTest.php | 2 ++ .../Command/Api/GenerateKeyCommandTest.php | 2 ++ .../test/Command/Api/ListKeysCommandTest.php | 2 ++ .../Config/GenerateCharsetCommandTest.php | 2 ++ .../Command/Install/InstallCommandTest.php | 2 ++ .../Shortcode/GeneratePreviewCommandTest.php | 6 +++-- .../GenerateShortcodeCommandTest.php | 8 +++--- .../Shortcode/GetVisitsCommandTest.php | 2 ++ .../Shortcode/ListShortcodesCommandTest.php | 2 ++ .../Shortcode/ResolveUrlCommandTest.php | 2 ++ .../test/Command/Tag/CreateTagCommandTest.php | 2 ++ .../Command/Tag/DeleteTagsCommandTest.php | 2 ++ .../test/Command/Tag/ListTagsCommandTest.php | 4 ++- .../test/Command/Tag/RenameTagCommandTest.php | 2 ++ .../Visit/ProcessVisitsCommandTest.php | 2 ++ module/CLI/test/ConfigProviderTest.php | 2 ++ .../test/Factory/ApplicationFactoryTest.php | 2 ++ .../Factory/InstallApplicationFactoryTest.php | 2 ++ .../ApplicationConfigCustomizerPluginTest.php | 2 ++ .../DatabaseConfigCustomizerPluginTest.php | 2 ++ ...faultConfigCustomizerPluginFactoryTest.php | 2 ++ .../LanguageConfigCustomizerPluginTest.php | 2 ++ ...UrlShortenerConfigCustomizerPluginTest.php | 2 ++ module/Common/config/dependencies.config.php | 1 + module/Common/functions/functions.php | 2 ++ module/Common/src/ConfigProvider.php | 2 ++ module/Common/src/Entity/AbstractEntity.php | 2 ++ .../src/Exception/ExceptionInterface.php | 2 ++ .../Exception/InvalidArgumentException.php | 2 ++ .../Exception/PreviewGenerationException.php | 2 ++ .../Common/src/Exception/RuntimeException.php | 2 ++ .../Common/src/Exception/WrongIpException.php | 2 ++ module/Common/src/Factory/CacheFactory.php | 2 ++ .../DottedAccessConfigAbstractFactory.php | 2 ++ ...sponseImplicitOptionsMiddlewareFactory.php | 2 ++ .../src/Factory/EntityManagerFactory.php | 2 ++ module/Common/src/Factory/LoggerFactory.php | 2 ++ .../Common/src/Factory/TranslatorFactory.php | 2 ++ module/Common/src/Image/ImageBuilder.php | 2 ++ .../Common/src/Image/ImageBuilderFactory.php | 2 ++ .../src/Image/ImageBuilderInterface.php | 2 ++ module/Common/src/Image/ImageFactory.php | 2 ++ .../src/Middleware/LocaleMiddleware.php | 2 ++ .../Adapter/PaginableRepositoryAdapter.php | 4 ++- .../Paginator/Util/PaginatorUtilsTrait.php | 2 ++ .../PaginableRepositoryInterface.php | 2 ++ module/Common/src/Response/QrCodeResponse.php | 2 ++ .../Common/src/Service/IpLocationResolver.php | 4 ++- .../Service/IpLocationResolverInterface.php | 2 ++ .../Common/src/Service/PreviewGenerator.php | 2 ++ .../src/Service/PreviewGeneratorInterface.php | 2 ++ .../Extension/TranslatorExtension.php | 2 ++ module/Common/src/Util/DateRange.php | 2 ++ module/Common/src/Util/ResponseUtilsTrait.php | 2 ++ module/Common/src/Util/StringUtilsTrait.php | 2 ++ module/Common/test/ConfigProviderTest.php | 2 ++ .../Common/test/Factory/CacheFactoryTest.php | 6 +++-- ...seImplicitOptionsMiddlewareFactoryTest.php | 2 ++ .../test/Factory/EntityManagerFactoryTest.php | 2 ++ .../Common/test/Factory/LoggerFactoryTest.php | 2 ++ .../test/Factory/TranslatorFactoryTest.php | 2 ++ .../test/Image/ImageBuilderFactoryTest.php | 2 ++ module/Common/test/Image/ImageFactoryTest.php | 2 ++ .../test/Middleware/LocaleMiddlewareTest.php | 2 ++ .../PaginableRepositoryAdapterTest.php | 2 ++ .../test/Service/IpLocationResolverTest.php | 2 ++ .../test/Service/PreviewGeneratorTest.php | 4 ++- .../Extension/TranslatorExtensionTest.php | 2 ++ module/Common/test/Util/DateRangeTest.php | 2 ++ module/Common/test/Util/TestUtils.php | 2 ++ module/Core/config/app_options.config.php | 2 ++ module/Core/config/dependencies.config.php | 1 + module/Core/config/entity-manager.config.php | 2 ++ module/Core/config/routes.config.php | 2 ++ module/Core/config/templates.config.php | 1 + module/Core/config/translator.config.php | 2 ++ module/Core/config/zend-expressive.config.php | 1 + module/Core/src/Action/PreviewAction.php | 2 ++ module/Core/src/Action/QrCodeAction.php | 2 ++ module/Core/src/Action/RedirectAction.php | 2 ++ module/Core/src/ConfigProvider.php | 2 ++ module/Core/src/Entity/ShortUrl.php | 2 ++ module/Core/src/Entity/Tag.php | 2 ++ module/Core/src/Entity/Visit.php | 2 ++ module/Core/src/Entity/VisitLocation.php | 4 ++- .../Exception/EntityDoesNotExistException.php | 2 ++ .../Exception/InvalidShortCodeException.php | 2 ++ .../src/Exception/InvalidUrlException.php | 2 ++ .../src/Middleware/QrCodeCacheMiddleware.php | 2 ++ module/Core/src/Options/AppOptions.php | 2 ++ module/Core/src/Options/AppOptionsFactory.php | 2 ++ .../src/Repository/ShortUrlRepository.php | 2 ++ .../ShortUrlRepositoryInterface.php | 2 ++ module/Core/src/Repository/TagRepository.php | 2 ++ .../src/Repository/TagRepositoryInterface.php | 2 ++ .../Core/src/Repository/VisitRepository.php | 2 ++ .../Repository/VisitRepositoryInterface.php | 2 ++ module/Core/src/Service/ShortUrlService.php | 2 ++ .../src/Service/ShortUrlServiceInterface.php | 2 ++ module/Core/src/Service/Tag/TagService.php | 2 ++ .../src/Service/Tag/TagServiceInterface.php | 2 ++ module/Core/src/Service/UrlShortener.php | 4 ++- .../src/Service/UrlShortenerInterface.php | 2 ++ module/Core/src/Service/VisitService.php | 2 ++ .../src/Service/VisitServiceInterface.php | 2 ++ module/Core/src/Service/VisitsTracker.php | 2 ++ .../src/Service/VisitsTrackerInterface.php | 2 ++ module/Core/src/Util/TagManagerTrait.php | 2 ++ module/Core/test/Action/PreviewActionTest.php | 2 ++ module/Core/test/Action/QrCodeActionTest.php | 2 ++ .../Core/test/Action/RedirectActionTest.php | 2 ++ module/Core/test/ConfigProviderTest.php | 2 ++ module/Core/test/Entity/TagTest.php | 2 ++ .../Middleware/QrCodeCacheMiddlewareTest.php | 2 ++ .../test/Options/AppOptionsFactoryTest.php | 2 ++ .../Core/test/Service/ShortUrlServiceTest.php | 2 ++ .../Core/test/Service/Tag/TagServiceTest.php | 4 ++- module/Core/test/Service/UrlShortenerTest.php | 2 ++ module/Core/test/Service/VisitServiceTest.php | 2 ++ .../Core/test/Service/VisitsTrackerTest.php | 2 ++ module/Rest/config/dependencies.config.php | 3 ++- module/Rest/config/entity-manager.config.php | 2 ++ module/Rest/config/error-handler.config.php | 2 ++ module/Rest/config/routes.config.php | 4 ++- module/Rest/config/translator.config.php | 2 ++ module/Rest/src/Action/AbstractRestAction.php | 2 ++ module/Rest/src/Action/AuthenticateAction.php | 2 ++ .../Rest/src/Action/CreateShortcodeAction.php | 2 ++ .../src/Action/EditShortcodeTagsAction.php | 2 ++ module/Rest/src/Action/GetVisitsAction.php | 8 +++--- .../Rest/src/Action/ListShortcodesAction.php | 2 ++ module/Rest/src/Action/ResolveUrlAction.php | 2 ++ .../Rest/src/Action/Tag/CreateTagsAction.php | 2 ++ .../Rest/src/Action/Tag/DeleteTagsAction.php | 2 ++ module/Rest/src/Action/Tag/ListTagsAction.php | 2 ++ .../Rest/src/Action/Tag/UpdateTagAction.php | 2 ++ module/Rest/src/Authentication/JWTService.php | 2 ++ .../Authentication/JWTServiceInterface.php | 2 ++ module/Rest/src/ConfigProvider.php | 2 ++ module/Rest/src/Entity/ApiKey.php | 2 ++ .../JsonErrorResponseGenerator.php | 3 ++- .../src/Exception/AuthenticationException.php | 2 ++ .../src/Middleware/BodyParserMiddleware.php | 4 ++- .../CheckAuthenticationMiddleware.php | 2 ++ .../src/Middleware/CrossDomainMiddleware.php | 2 ++ .../src/Middleware/PathVersionMiddleware.php | 2 ++ module/Rest/src/Service/ApiKeyService.php | 2 ++ .../src/Service/ApiKeyServiceInterface.php | 2 ++ module/Rest/src/Util/RestUtils.php | 2 ++ .../test/Action/AuthenticateActionTest.php | 2 ++ .../test/Action/CreateShortcodeActionTest.php | 2 ++ .../Action/EditShortcodeTagsActionTest.php | 2 ++ .../Rest/test/Action/GetVisitsActionTest.php | 2 ++ .../test/Action/ListShortcodesActionTest.php | 2 ++ .../Rest/test/Action/ResolveUrlActionTest.php | 2 ++ .../test/Action/Tag/CreateTagsActionTest.php | 2 ++ .../test/Action/Tag/DeleteTagsActionTest.php | 2 ++ .../test/Action/Tag/ListTagsActionTest.php | 2 ++ .../test/Action/Tag/UpdateTagActionTest.php | 2 ++ .../test/Authentication/JWTServiceTest.php | 2 ++ module/Rest/test/ConfigProviderTest.php | 2 ++ .../JsonErrorResponseGeneratorTest.php | 2 ++ .../Middleware/BodyParserMiddlewareTest.php | 2 ++ .../CheckAuthenticationMiddlewareTest.php | 2 ++ .../Middleware/CrossDomainMiddlewareTest.php | 2 ++ .../Middleware/PathVersionMiddlewareTest.php | 2 ++ .../Rest/test/Service/ApiKeyServiceTest.php | 2 ++ module/Rest/test/Util/RestUtilsTest.php | 2 ++ phpcs.xml | 25 +++++++++++++++++++ 219 files changed, 473 insertions(+), 26 deletions(-) diff --git a/composer.json b/composer.json index 1f1d5303..d58a88aa 100644 --- a/composer.json +++ b/composer.json @@ -46,6 +46,7 @@ "require-dev": { "filp/whoops": "^2.0", "phpunit/phpunit": "^6.0", + "slevomat/coding-standard": "^4.0", "squizlabs/php_codesniffer": "^3.1", "symfony/var-dumper": "^3.0", "vlucas/phpdotenv": "^2.2", diff --git a/config/autoload/app_options.global.php b/config/autoload/app_options.global.php index fd7147a1..0f8163bf 100644 --- a/config/autoload/app_options.global.php +++ b/config/autoload/app_options.global.php @@ -1,4 +1,6 @@ [ diff --git a/config/autoload/preview-generation.global.php b/config/autoload/preview-generation.global.php index b4f14da3..4a4fa9d1 100644 --- a/config/autoload/preview-generation.global.php +++ b/config/autoload/preview-generation.global.php @@ -1,4 +1,6 @@ [ diff --git a/config/autoload/router.global.php b/config/autoload/router.global.php index fb026248..deb875f3 100644 --- a/config/autoload/router.global.php +++ b/config/autoload/router.global.php @@ -1,4 +1,6 @@ [ Service\ShortUrlService::class, PreviewGenerator::class, - 'translator' + 'translator', ], Command\Visit\ProcessVisitsCommand::class => [ Service\VisitService::class, IpLocationResolver::class, - 'translator' + 'translator', ], Command\Config\GenerateCharsetCommand::class => ['translator'], Command\Config\GenerateSecretCommand::class => ['translator'], diff --git a/module/CLI/config/translator.config.php b/module/CLI/config/translator.config.php index ae120db3..659d4cae 100644 --- a/module/CLI/config/translator.config.php +++ b/module/CLI/config/translator.config.php @@ -1,4 +1,6 @@ [ diff --git a/module/CLI/src/Command/Api/DisableKeyCommand.php b/module/CLI/src/Command/Api/DisableKeyCommand.php index fbf02992..e4390203 100644 --- a/module/CLI/src/Command/Api/DisableKeyCommand.php +++ b/module/CLI/src/Command/Api/DisableKeyCommand.php @@ -1,4 +1,6 @@ previewGenerator->generatePreview('http://baz.com/something')->shouldBeCalledTimes(1); $this->commandTester->execute([ - 'command' => 'shortcode:process-previews' + 'command' => 'shortcode:process-previews', ]); } @@ -83,7 +85,7 @@ class GeneratePreviewCommandTest extends TestCase ->shouldBeCalledTimes(count($items)); $this->commandTester->execute([ - 'command' => 'shortcode:process-previews' + 'command' => 'shortcode:process-previews', ]); $output = $this->commandTester->getDisplay(); $this->assertEquals(count($items), substr_count($output, 'Error')); diff --git a/module/CLI/test/Command/Shortcode/GenerateShortcodeCommandTest.php b/module/CLI/test/Command/Shortcode/GenerateShortcodeCommandTest.php index 11a1385d..e1dc5229 100644 --- a/module/CLI/test/Command/Shortcode/GenerateShortcodeCommandTest.php +++ b/module/CLI/test/Command/Shortcode/GenerateShortcodeCommandTest.php @@ -1,4 +1,6 @@ urlShortener = $this->prophesize(UrlShortener::class); $command = new GenerateShortcodeCommand($this->urlShortener->reveal(), Translator::factory([]), [ 'schema' => 'http', - 'hostname' => 'foo.com' + 'hostname' => 'foo.com', ]); $app = new Application(); $app->add($command); @@ -44,7 +46,7 @@ class GenerateShortcodeCommandTest extends TestCase $this->commandTester->execute([ 'command' => 'shortcode:generate', - 'longUrl' => 'http://domain.com/foo/bar' + 'longUrl' => 'http://domain.com/foo/bar', ]); $output = $this->commandTester->getDisplay(); $this->assertTrue(strpos($output, 'http://foo.com/abc123') > 0); @@ -60,7 +62,7 @@ class GenerateShortcodeCommandTest extends TestCase $this->commandTester->execute([ 'command' => 'shortcode:generate', - 'longUrl' => 'http://domain.com/invalid' + 'longUrl' => 'http://domain.com/invalid', ]); $output = $this->commandTester->getDisplay(); $this->assertTrue( diff --git a/module/CLI/test/Command/Shortcode/GetVisitsCommandTest.php b/module/CLI/test/Command/Shortcode/GetVisitsCommandTest.php index 4ec77e71..d80d6d4f 100644 --- a/module/CLI/test/Command/Shortcode/GetVisitsCommandTest.php +++ b/module/CLI/test/Command/Shortcode/GetVisitsCommandTest.php @@ -1,4 +1,6 @@ paginableRepository = $paginableRepository; - $this->searchTerm = trim(strip_tags($searchTerm)); + $this->searchTerm = $searchTerm !== null ? trim(strip_tags($searchTerm)) : null; $this->orderBy = $orderBy; $this->tags = $tags; } diff --git a/module/Common/src/Paginator/Util/PaginatorUtilsTrait.php b/module/Common/src/Paginator/Util/PaginatorUtilsTrait.php index a1e0ec5c..167de806 100644 --- a/module/Common/src/Paginator/Util/PaginatorUtilsTrait.php +++ b/module/Common/src/Paginator/Util/PaginatorUtilsTrait.php @@ -1,4 +1,6 @@ httpClient->get(sprintf(self::SERVICE_PATTERN, $ipAddress)); - return json_decode($response->getBody(), true); + return json_decode((string) $response->getBody(), true); } catch (GuzzleException $e) { throw WrongIpException::fromIpAddress($ipAddress, $e); } diff --git a/module/Common/src/Service/IpLocationResolverInterface.php b/module/Common/src/Service/IpLocationResolverInterface.php index 350c2b9c..4f4279a2 100644 --- a/module/Common/src/Service/IpLocationResolverInterface.php +++ b/module/Common/src/Service/IpLocationResolverInterface.php @@ -1,4 +1,6 @@ '1.2.3.4', - 'port' => 123 + 'port' => 123, ], [ 'host' => '4.3.2.1', - 'port' => 321 + 'port' => 321, ], ]; /** @var MemcachedCache $instance */ diff --git a/module/Common/test/Factory/EmptyResponseImplicitOptionsMiddlewareFactoryTest.php b/module/Common/test/Factory/EmptyResponseImplicitOptionsMiddlewareFactoryTest.php index aca35f62..dd4b6301 100644 --- a/module/Common/test/Factory/EmptyResponseImplicitOptionsMiddlewareFactoryTest.php +++ b/module/Common/test/Factory/EmptyResponseImplicitOptionsMiddlewareFactoryTest.php @@ -1,4 +1,6 @@ function () { return $this->image->reveal(); }, - ] + ], ]), $this->filesystem->reveal(), 'dir'); } diff --git a/module/Common/test/Template/Extension/TranslatorExtensionTest.php b/module/Common/test/Template/Extension/TranslatorExtensionTest.php index e39e2b24..04d36188 100644 --- a/module/Common/test/Template/Extension/TranslatorExtensionTest.php +++ b/module/Common/test/Template/Extension/TranslatorExtensionTest.php @@ -1,4 +1,6 @@ [], diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index a25c82fb..a21a92b8 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -1,4 +1,5 @@ [ diff --git a/module/Core/config/routes.config.php b/module/Core/config/routes.config.php index a3c70aeb..e18cb448 100644 --- a/module/Core/config/routes.config.php +++ b/module/Core/config/routes.config.php @@ -1,4 +1,6 @@ [ diff --git a/module/Core/config/zend-expressive.config.php b/module/Core/config/zend-expressive.config.php index 3dfd091f..81f6676f 100644 --- a/module/Core/config/zend-expressive.config.php +++ b/module/Core/config/zend-expressive.config.php @@ -1,4 +1,5 @@ chars . "]+|", $shortCode)) { + if (! preg_match('|[' . $this->chars . ']+|', $shortCode)) { throw InvalidShortCodeException::fromCharset($shortCode, $this->chars); } diff --git a/module/Core/src/Service/UrlShortenerInterface.php b/module/Core/src/Service/UrlShortenerInterface.php index 4ddf4b7b..c677ec4e 100644 --- a/module/Core/src/Service/UrlShortenerInterface.php +++ b/module/Core/src/Service/UrlShortenerInterface.php @@ -1,4 +1,6 @@ [Service\UrlShortener::class, 'translator'], Action\GetVisitsAction::class => [Service\VisitsTracker::class, 'translator', 'Logger_Shlink'], diff --git a/module/Rest/config/entity-manager.config.php b/module/Rest/config/entity-manager.config.php index e9359519..e5008568 100644 --- a/module/Rest/config/entity-manager.config.php +++ b/module/Rest/config/entity-manager.config.php @@ -1,4 +1,6 @@ [ diff --git a/module/Rest/config/error-handler.config.php b/module/Rest/config/error-handler.config.php index cb06f6bf..b3df3f7c 100644 --- a/module/Rest/config/error-handler.config.php +++ b/module/Rest/config/error-handler.config.php @@ -1,4 +1,6 @@ [ diff --git a/module/Rest/src/Action/AbstractRestAction.php b/module/Rest/src/Action/AbstractRestAction.php index 01b8c13d..9ba1e650 100644 --- a/module/Rest/src/Action/AbstractRestAction.php +++ b/module/Rest/src/Action/AbstractRestAction.php @@ -1,4 +1,6 @@ [ 'data' => $visits, - ] + ], ]); } catch (InvalidArgumentException $e) { - $this->logger->warning('Provided nonexistent shortcode'. PHP_EOL . $e); + $this->logger->warning('Provided nonexistent shortcode' . PHP_EOL . $e); return new JsonResponse([ 'error' => RestUtils::getRestErrorCodeFromException($e), 'message' => sprintf( @@ -63,7 +65,7 @@ class GetVisitsAction extends AbstractRestAction ), ], self::STATUS_NOT_FOUND); } catch (\Exception $e) { - $this->logger->error('Unexpected error while parsing short code'. PHP_EOL . $e); + $this->logger->error('Unexpected error while parsing short code' . PHP_EOL . $e); return new JsonResponse([ 'error' => RestUtils::UNKNOWN_ERROR, 'message' => $this->translator->translate('Unexpected error occurred'), diff --git a/module/Rest/src/Action/ListShortcodesAction.php b/module/Rest/src/Action/ListShortcodesAction.php index aa941375..83007e29 100644 --- a/module/Rest/src/Action/ListShortcodesAction.php +++ b/module/Rest/src/Action/ListShortcodesAction.php @@ -1,4 +1,6 @@ process($request); } diff --git a/module/Rest/src/Middleware/CheckAuthenticationMiddleware.php b/module/Rest/src/Middleware/CheckAuthenticationMiddleware.php index 93537d35..b5823f4f 100644 --- a/module/Rest/src/Middleware/CheckAuthenticationMiddleware.php +++ b/module/Rest/src/Middleware/CheckAuthenticationMiddleware.php @@ -1,4 +1,6 @@ Coding standard + @@ -9,11 +10,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + bin From 6208f6f0d5e4b983bfaf4bde65bd0db4bb86edbd Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 12 Oct 2017 11:28:45 +0200 Subject: [PATCH 21/64] Improved Exception management to be more specific --- config/container.php | 2 +- .../Command/Shortcode/ResolveUrlCommand.php | 5 ++++ .../Shortcode/ResolveUrlCommandTest.php | 5 ++-- module/Core/src/Action/PreviewAction.php | 10 +++++--- module/Core/src/Action/QrCodeAction.php | 7 +++--- module/Core/src/Action/RedirectAction.php | 9 ++++--- module/Core/src/Service/UrlShortener.php | 25 ++++++++++--------- .../src/Service/UrlShortenerInterface.php | 6 +++-- module/Core/test/Action/PreviewActionTest.php | 4 ++- module/Core/test/Action/QrCodeActionTest.php | 6 +++-- .../Core/test/Action/RedirectActionTest.php | 19 ++------------ module/Rest/src/Action/ResolveUrlAction.php | 14 +++++------ .../Rest/test/Action/ResolveUrlActionTest.php | 3 ++- 13 files changed, 60 insertions(+), 55 deletions(-) diff --git a/config/container.php b/config/container.php index 85cb0886..126351ae 100644 --- a/config/container.php +++ b/config/container.php @@ -11,7 +11,7 @@ require 'vendor/autoload.php'; // If the Dotenv class exists, load env vars and enable errors if (class_exists(Dotenv::class)) { error_reporting(E_ALL); - ini_set('display_errors', 1); + ini_set('display_errors', '1'); $dotenv = new Dotenv(__DIR__ . '/..'); $dotenv->load(); } diff --git a/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php b/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php index e5b53c23..fe79ee16 100644 --- a/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php +++ b/module/CLI/src/Command/Shortcode/ResolveUrlCommand.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\Shortcode; +use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; use Symfony\Component\Console\Command\Command; @@ -81,6 +82,10 @@ class ResolveUrlCommand extends Command $output->writeln(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)); } } } diff --git a/module/CLI/test/Command/Shortcode/ResolveUrlCommandTest.php b/module/CLI/test/Command/Shortcode/ResolveUrlCommandTest.php index c1fef098..da0eadc3 100644 --- a/module/CLI/test/Command/Shortcode/ResolveUrlCommandTest.php +++ b/module/CLI/test/Command/Shortcode/ResolveUrlCommandTest.php @@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\CLI\Command\Shortcode; use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Shortcode\ResolveUrlCommand; +use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Service\UrlShortener; use Symfony\Component\Console\Application; @@ -57,7 +58,7 @@ class ResolveUrlCommandTest extends TestCase public function incorrectShortCodeOutputsErrorMessage() { $shortCode = 'abc123'; - $this->urlShortener->shortCodeToUrl($shortCode)->willReturn(null) + $this->urlShortener->shortCodeToUrl($shortCode)->willThrow(EntityDoesNotExistException::class) ->shouldBeCalledTimes(1); $this->commandTester->execute([ @@ -65,7 +66,7 @@ class ResolveUrlCommandTest extends TestCase 'shortCode' => $shortCode, ]); $output = $this->commandTester->getDisplay(); - $this->assertEquals('No URL found for short code "' . $shortCode . '"' . PHP_EOL, $output); + $this->assertEquals('Provided short code "' . $shortCode . '" could not be found.' . PHP_EOL, $output); } /** diff --git a/module/Core/src/Action/PreviewAction.php b/module/Core/src/Action/PreviewAction.php index 054da045..788c3c2d 100644 --- a/module/Core/src/Action/PreviewAction.php +++ b/module/Core/src/Action/PreviewAction.php @@ -7,8 +7,10 @@ use Interop\Http\ServerMiddleware\DelegateInterface; use Interop\Http\ServerMiddleware\MiddlewareInterface; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; +use Shlinkio\Shlink\Common\Exception\PreviewGenerationException; use Shlinkio\Shlink\Common\Service\PreviewGeneratorInterface; use Shlinkio\Shlink\Common\Util\ResponseUtilsTrait; +use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; @@ -46,14 +48,14 @@ class PreviewAction implements MiddlewareInterface try { $url = $this->urlShortener->shortCodeToUrl($shortCode); - if (! isset($url)) { - return $delegate->process($request); - } - $imagePath = $this->previewGenerator->generatePreview($url); return $this->generateImageResponse($imagePath); } catch (InvalidShortCodeException $e) { return $delegate->process($request); + } catch (EntityDoesNotExistException $e) { + return $delegate->process($request); + } catch (PreviewGenerationException $e) { + return $delegate->process($request); } } } diff --git a/module/Core/src/Action/QrCodeAction.php b/module/Core/src/Action/QrCodeAction.php index a6e20af8..dfd7eaf2 100644 --- a/module/Core/src/Action/QrCodeAction.php +++ b/module/Core/src/Action/QrCodeAction.php @@ -11,6 +11,7 @@ use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Shlinkio\Shlink\Common\Response\QrCodeResponse; +use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; use Zend\Expressive\Router\RouterInterface; @@ -55,12 +56,12 @@ class QrCodeAction implements MiddlewareInterface $shortCode = $request->getAttribute('shortCode'); try { $shortUrl = $this->urlShortener->shortCodeToUrl($shortCode); - if ($shortUrl === null) { - return $delegate->process($request); - } } catch (InvalidShortCodeException $e) { $this->logger->warning('Tried to create a QR code with an invalid short code' . PHP_EOL . $e); return $delegate->process($request); + } catch (EntityDoesNotExistException $e) { + $this->logger->warning('Tried to create a QR code with a not found short code' . PHP_EOL . $e); + return $delegate->process($request); } $path = $this->router->generateUri('long-url-redirect', ['shortCode' => $shortCode]); diff --git a/module/Core/src/Action/RedirectAction.php b/module/Core/src/Action/RedirectAction.php index ae58da1b..c8d35b4a 100644 --- a/module/Core/src/Action/RedirectAction.php +++ b/module/Core/src/Action/RedirectAction.php @@ -9,8 +9,11 @@ use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; +use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; +use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface; +use Zend\Diactoros\Response\HtmlResponse; use Zend\Diactoros\Response\RedirectResponse; class RedirectAction implements MiddlewareInterface @@ -66,9 +69,9 @@ class RedirectAction implements MiddlewareInterface // Return a redirect response to the long URL. // Use a temporary redirect to make sure browsers always hit the server for analytics purposes return new RedirectResponse($longUrl); - } catch (\Exception $e) { - // In case of error, dispatch 404 error - $this->logger->error('Error redirecting to long URL.' . PHP_EOL . $e); + } catch (InvalidShortCodeException $e) { + return $delegate->process($request); + } catch (EntityDoesNotExistException $e) { return $delegate->process($request); } } diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index df4ee8ed..b30b649a 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -11,6 +11,7 @@ use GuzzleHttp\Exception\GuzzleException; use Psr\Http\Message\UriInterface; use Shlinkio\Shlink\Common\Exception\RuntimeException; use Shlinkio\Shlink\Core\Entity\ShortUrl; +use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Util\TagManagerTrait; @@ -142,10 +143,11 @@ class UrlShortener implements UrlShortenerInterface * Tries to find the mapped URL for provided short code. Returns null if not found * * @param string $shortCode - * @return string|null + * @return string * @throws InvalidShortCodeException + * @throws EntityDoesNotExistException */ - public function shortCodeToUrl($shortCode) + public function shortCodeToUrl($shortCode): string { $cacheKey = sprintf('%s_longUrl', $shortCode); // Check if the short code => URL map is already cached @@ -158,17 +160,16 @@ class UrlShortener implements UrlShortenerInterface throw InvalidShortCodeException::fromCharset($shortCode, $this->chars); } - /** @var ShortUrl $shortUrl */ - $shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([ - 'shortCode' => $shortCode, - ]); - // Cache the shortcode - if (isset($shortUrl)) { - $url = $shortUrl->getOriginalUrl(); - $this->cache->save($cacheKey, $url); - return $url; + $criteria = ['shortCode' => $shortCode]; + /** @var ShortUrl|null $shortUrl */ + $shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy($criteria); + if ($shortUrl === null) { + throw EntityDoesNotExistException::createFromEntityAndConditions(ShortUrl::class, $criteria); } - return null; + // Cache the shortcode + $url = $shortUrl->getOriginalUrl(); + $this->cache->save($cacheKey, $url); + return $url; } } diff --git a/module/Core/src/Service/UrlShortenerInterface.php b/module/Core/src/Service/UrlShortenerInterface.php index c677ec4e..3cd2c35c 100644 --- a/module/Core/src/Service/UrlShortenerInterface.php +++ b/module/Core/src/Service/UrlShortenerInterface.php @@ -5,6 +5,7 @@ namespace Shlinkio\Shlink\Core\Service; use Psr\Http\Message\UriInterface; use Shlinkio\Shlink\Common\Exception\RuntimeException; +use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; @@ -25,8 +26,9 @@ interface UrlShortenerInterface * Tries to find the mapped URL for provided short code. Returns null if not found * * @param string $shortCode - * @return string|null + * @return string * @throws InvalidShortCodeException + * @throws EntityDoesNotExistException */ - public function shortCodeToUrl($shortCode); + public function shortCodeToUrl($shortCode): string; } diff --git a/module/Core/test/Action/PreviewActionTest.php b/module/Core/test/Action/PreviewActionTest.php index 5521e677..d791a100 100644 --- a/module/Core/test/Action/PreviewActionTest.php +++ b/module/Core/test/Action/PreviewActionTest.php @@ -9,6 +9,7 @@ use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Service\PreviewGenerator; use Shlinkio\Shlink\Core\Action\PreviewAction; +use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Service\UrlShortener; use ShlinkioTest\Shlink\Common\Util\TestUtils; @@ -42,7 +43,8 @@ class PreviewActionTest extends TestCase public function invalidShortCodeFallsBackToNextMiddleware() { $shortCode = 'abc123'; - $this->urlShortener->shortCodeToUrl($shortCode)->willReturn(null)->shouldBeCalledTimes(1); + $this->urlShortener->shortCodeToUrl($shortCode)->willThrow(EntityDoesNotExistException::class) + ->shouldBeCalledTimes(1); $delegate = $this->prophesize(DelegateInterface::class); $delegate->process(Argument::cetera())->shouldBeCalledTimes(1); diff --git a/module/Core/test/Action/QrCodeActionTest.php b/module/Core/test/Action/QrCodeActionTest.php index 85f908e2..ba8adc2f 100644 --- a/module/Core/test/Action/QrCodeActionTest.php +++ b/module/Core/test/Action/QrCodeActionTest.php @@ -10,6 +10,7 @@ use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Response\QrCodeResponse; use Shlinkio\Shlink\Core\Action\QrCodeAction; use Shlinkio\Shlink\Core\Entity\ShortUrl; +use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Service\UrlShortener; use Zend\Diactoros\ServerRequestFactory; @@ -42,7 +43,8 @@ class QrCodeActionTest extends TestCase public function aNotFoundShortCodeWillDelegateIntoNextMiddleware() { $shortCode = 'abc123'; - $this->urlShortener->shortCodeToUrl($shortCode)->willReturn(null)->shouldBeCalledTimes(1); + $this->urlShortener->shortCodeToUrl($shortCode)->willThrow(EntityDoesNotExistException::class) + ->shouldBeCalledTimes(1); $delegate = $this->prophesize(DelegateInterface::class); $this->action->process( @@ -77,7 +79,7 @@ class QrCodeActionTest extends TestCase public function aCorrectRequestReturnsTheQrCodeResponse() { $shortCode = 'abc123'; - $this->urlShortener->shortCodeToUrl($shortCode)->willReturn(new ShortUrl())->shouldBeCalledTimes(1); + $this->urlShortener->shortCodeToUrl($shortCode)->willReturn('')->shouldBeCalledTimes(1); $delegate = $this->prophesize(DelegateInterface::class); $resp = $this->action->process( diff --git a/module/Core/test/Action/RedirectActionTest.php b/module/Core/test/Action/RedirectActionTest.php index e2cc700a..49d3baec 100644 --- a/module/Core/test/Action/RedirectActionTest.php +++ b/module/Core/test/Action/RedirectActionTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Action\RedirectAction; +use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Service\UrlShortener; use Shlinkio\Shlink\Core\Service\VisitsTracker; use ShlinkioTest\Shlink\Common\Util\TestUtils; @@ -58,23 +59,7 @@ class RedirectActionTest extends TestCase public function nextMiddlewareIsInvokedIfLongUrlIsNotFound() { $shortCode = 'abc123'; - $this->urlShortener->shortCodeToUrl($shortCode)->willReturn(null) - ->shouldBeCalledTimes(1); - $delegate = $this->prophesize(DelegateInterface::class); - - $request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode); - $this->action->process($request, $delegate->reveal()); - - $delegate->process($request)->shouldHaveBeenCalledTimes(1); - } - - /** - * @test - */ - public function nextMiddlewareIsInvokedIfAnExceptionIsThrown() - { - $shortCode = 'abc123'; - $this->urlShortener->shortCodeToUrl($shortCode)->willThrow(\Exception::class) + $this->urlShortener->shortCodeToUrl($shortCode)->willThrow(EntityDoesNotExistException::class) ->shouldBeCalledTimes(1); $delegate = $this->prophesize(DelegateInterface::class); diff --git a/module/Rest/src/Action/ResolveUrlAction.php b/module/Rest/src/Action/ResolveUrlAction.php index f20d4222..1abf5626 100644 --- a/module/Rest/src/Action/ResolveUrlAction.php +++ b/module/Rest/src/Action/ResolveUrlAction.php @@ -7,6 +7,7 @@ use Interop\Http\ServerMiddleware\DelegateInterface; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Log\LoggerInterface; +use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; use Shlinkio\Shlink\Rest\Util\RestUtils; @@ -46,13 +47,6 @@ class ResolveUrlAction extends AbstractRestAction try { $longUrl = $this->urlShortener->shortCodeToUrl($shortCode); - if ($longUrl === null) { - return new JsonResponse([ - 'error' => RestUtils::INVALID_ARGUMENT_ERROR, - 'message' => sprintf($this->translator->translate('No URL found for short code "%s"'), $shortCode), - ], self::STATUS_NOT_FOUND); - } - return new JsonResponse([ 'longUrl' => $longUrl, ]); @@ -65,6 +59,12 @@ class ResolveUrlAction extends AbstractRestAction $shortCode ), ], self::STATUS_BAD_REQUEST); + } catch (EntityDoesNotExistException $e) { + $this->logger->warning('Provided short code couldn\'t be found.' . PHP_EOL . $e); + return new JsonResponse([ + 'error' => RestUtils::INVALID_ARGUMENT_ERROR, + 'message' => sprintf($this->translator->translate('No URL found for short code "%s"'), $shortCode), + ], self::STATUS_NOT_FOUND); } catch (\Exception $e) { $this->logger->error('Unexpected error while resolving the URL behind a short code.' . PHP_EOL . $e); return new JsonResponse([ diff --git a/module/Rest/test/Action/ResolveUrlActionTest.php b/module/Rest/test/Action/ResolveUrlActionTest.php index 48c17b20..884fae55 100644 --- a/module/Rest/test/Action/ResolveUrlActionTest.php +++ b/module/Rest/test/Action/ResolveUrlActionTest.php @@ -5,6 +5,7 @@ namespace ShlinkioTest\Shlink\Rest\Action; use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\ObjectProphecy; +use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Service\UrlShortener; use Shlinkio\Shlink\Rest\Action\ResolveUrlAction; @@ -36,7 +37,7 @@ class ResolveUrlActionTest extends TestCase public function incorrectShortCodeReturnsError() { $shortCode = 'abc123'; - $this->urlShortener->shortCodeToUrl($shortCode)->willReturn(null) + $this->urlShortener->shortCodeToUrl($shortCode)->willThrow(EntityDoesNotExistException::class) ->shouldBeCalledTimes(1); $request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode); From 70264be8e7e9473b0fd1eb1339f4320edd6a7e32 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 12 Oct 2017 11:29:11 +0200 Subject: [PATCH 22/64] Fixed coding styles --- module/Core/src/Action/RedirectAction.php | 1 - module/Core/test/Action/QrCodeActionTest.php | 1 - 2 files changed, 2 deletions(-) diff --git a/module/Core/src/Action/RedirectAction.php b/module/Core/src/Action/RedirectAction.php index c8d35b4a..99224934 100644 --- a/module/Core/src/Action/RedirectAction.php +++ b/module/Core/src/Action/RedirectAction.php @@ -13,7 +13,6 @@ use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface; -use Zend\Diactoros\Response\HtmlResponse; use Zend\Diactoros\Response\RedirectResponse; class RedirectAction implements MiddlewareInterface diff --git a/module/Core/test/Action/QrCodeActionTest.php b/module/Core/test/Action/QrCodeActionTest.php index ba8adc2f..df452420 100644 --- a/module/Core/test/Action/QrCodeActionTest.php +++ b/module/Core/test/Action/QrCodeActionTest.php @@ -9,7 +9,6 @@ use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Response\QrCodeResponse; use Shlinkio\Shlink\Core\Action\QrCodeAction; -use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Service\UrlShortener; From 391ef5c323a37678c780dfcbfeeb69421726946f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 12 Oct 2017 11:59:22 +0200 Subject: [PATCH 23/64] Added return typehint --- module/Rest/src/ErrorHandler/JsonErrorResponseGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/Rest/src/ErrorHandler/JsonErrorResponseGenerator.php b/module/Rest/src/ErrorHandler/JsonErrorResponseGenerator.php index 6b865e59..664e693a 100644 --- a/module/Rest/src/ErrorHandler/JsonErrorResponseGenerator.php +++ b/module/Rest/src/ErrorHandler/JsonErrorResponseGenerator.php @@ -35,7 +35,7 @@ class JsonErrorResponseGenerator implements ErrorResponseGeneratorInterface, Sta * @param string $responsePhrase * @return string */ - protected function responsePhraseToCode($responsePhrase) + protected function responsePhraseToCode($responsePhrase): string { return strtoupper(str_replace(' ', '_', $responsePhrase)); } From 566940349f8f35f0a44c8052a356dc058f1dcf52 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 13 Oct 2017 11:55:14 +0200 Subject: [PATCH 24/64] Created default delegate that returns a JSON response when accepted type is json --- module/Core/config/dependencies.config.php | 9 ++++ module/Core/src/Response/NotFoundDelegate.php | 54 +++++++++++++++++++ .../JsonErrorResponseGenerator.php | 7 +-- 3 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 module/Core/src/Response/NotFoundDelegate.php diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index a21a92b8..d8009880 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -6,8 +6,10 @@ use Shlinkio\Shlink\Common\Service\PreviewGenerator; use Shlinkio\Shlink\Core\Action; use Shlinkio\Shlink\Core\Middleware; use Shlinkio\Shlink\Core\Options; +use Shlinkio\Shlink\Core\Response\NotFoundDelegate; use Shlinkio\Shlink\Core\Service; use Zend\Expressive\Router\RouterInterface; +use Zend\Expressive\Template\TemplateRendererInterface; use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory; return [ @@ -15,6 +17,7 @@ return [ 'dependencies' => [ 'factories' => [ Options\AppOptions::class => Options\AppOptionsFactory::class, + NotFoundDelegate::class => ConfigAbstractFactory::class, // Services Service\UrlShortener::class => ConfigAbstractFactory::class, @@ -29,9 +32,15 @@ return [ Action\PreviewAction::class => ConfigAbstractFactory::class, Middleware\QrCodeCacheMiddleware::class => ConfigAbstractFactory::class, ], + + 'aliases' => [ + 'Zend\Expressive\Delegate\DefaultDelegate' => NotFoundDelegate::class, + ], ], ConfigAbstractFactory::class => [ + NotFoundDelegate::class => [TemplateRendererInterface::class], + // Services Service\UrlShortener::class => ['httpClient', 'em', Cache::class, 'config.url_shortener.shortcode_chars'], Service\VisitsTracker::class => ['em'], diff --git a/module/Core/src/Response/NotFoundDelegate.php b/module/Core/src/Response/NotFoundDelegate.php new file mode 100644 index 00000000..da0710bb --- /dev/null +++ b/module/Core/src/Response/NotFoundDelegate.php @@ -0,0 +1,54 @@ +renderer = $renderer; + $this->template = $template; + } + + /** + * Dispatch the next available middleware and return the response. + * + * @param ServerRequestInterface $request + * + * @return ResponseInterface + * @throws \InvalidArgumentException + */ + public function process(ServerRequestInterface $request): ResponseInterface + { + $accepts = explode(',', $request->getHeaderLine('Accept')); + $accept = array_shift($accepts); + $status = StatusCodeInterface::STATUS_NOT_FOUND; + + // If the first accepted type is json, return a json response + if (in_array($accept, ['application/json', 'text/json', 'application/x-json'], true)) { + return new Response\JsonResponse([ + 'error' => 'NOT_FOUND', + 'message' => 'Not found', + ], $status); + } + + return new Response\HtmlResponse($this->renderer->render($this->template, ['request' => $request]), $status); + } +} diff --git a/module/Rest/src/ErrorHandler/JsonErrorResponseGenerator.php b/module/Rest/src/ErrorHandler/JsonErrorResponseGenerator.php index 664e693a..7d0b6013 100644 --- a/module/Rest/src/ErrorHandler/JsonErrorResponseGenerator.php +++ b/module/Rest/src/ErrorHandler/JsonErrorResponseGenerator.php @@ -18,6 +18,7 @@ class JsonErrorResponseGenerator implements ErrorResponseGeneratorInterface, Sta * @param Request $request * @param Response $response * @return Response + * @throws \InvalidArgumentException */ public function __invoke($e, Request $request, Response $response) { @@ -31,11 +32,7 @@ class JsonErrorResponseGenerator implements ErrorResponseGeneratorInterface, Sta ], $status); } - /** - * @param string $responsePhrase - * @return string - */ - protected function responsePhraseToCode($responsePhrase): string + protected function responsePhraseToCode(string $responsePhrase): string { return strtoupper(str_replace(' ', '_', $responsePhrase)); } From c12e13dfd7eef1c866a7e315e002b178314ddd11 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 13 Oct 2017 12:02:00 +0200 Subject: [PATCH 25/64] Created NotFoundDelegateTest --- .../test/Response/NotFoundDelegateTest.php | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 module/Core/test/Response/NotFoundDelegateTest.php diff --git a/module/Core/test/Response/NotFoundDelegateTest.php b/module/Core/test/Response/NotFoundDelegateTest.php new file mode 100644 index 00000000..4f80bf56 --- /dev/null +++ b/module/Core/test/Response/NotFoundDelegateTest.php @@ -0,0 +1,61 @@ +renderer = $this->prophesize(TemplateRendererInterface::class); + $this->delegate = new NotFoundDelegate($this->renderer->reveal()); + } + + /** + * @param string $expectedResponse + * @param string $accept + * @param int $renderCalls + * + * @test + * @dataProvider provideResponses + */ + public function properResponseTypeIsReturned(string $expectedResponse, string $accept, int $renderCalls) + { + $request = ServerRequestFactory::fromGlobals()->withHeader('Accept', $accept); + /** @var MethodProphecy $render */ + $render = $this->renderer->render(Argument::cetera())->willReturn(''); + + $resp = $this->delegate->process($request); + + $this->assertInstanceOf($expectedResponse, $resp); + $render->shouldHaveBeenCalledTimes($renderCalls); + } + + public function provideResponses(): array + { + return [ + [Response\JsonResponse::class, 'application/json', 0], + [Response\JsonResponse::class, 'text/json', 0], + [Response\JsonResponse::class, 'application/x-json', 0], + [Response\HtmlResponse::class, 'text/html', 1], + ]; + } +} From ea76092681cf3d9eccadd1f6a8bdd236c3d921d7 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 13 Oct 2017 12:22:19 +0200 Subject: [PATCH 26/64] Ensured a generic template is used to render generic 404 errors, and a more specific one to render 'invalid short url' errors --- module/Common/src/Util/ResponseUtilsTrait.php | 7 ++++--- module/Core/src/Action/PreviewAction.php | 8 +++++--- module/Core/src/Action/QrCodeAction.php | 11 +++++++---- module/Core/src/Action/RedirectAction.php | 7 +++++-- .../Action/Util/ErrorResponseBuilderTrait.php | 18 ++++++++++++++++++ module/Core/src/Response/NotFoundDelegate.php | 11 +++++++---- module/Core/templates/error/404.phtml | 6 +++--- .../Core/templates/invalid-short-code.phtml | 19 +++++++++++++++++++ module/Core/test/Action/PreviewActionTest.php | 9 +++++++-- module/Core/test/Action/QrCodeActionTest.php | 9 +++++++-- .../Core/test/Action/RedirectActionTest.php | 5 ++++- 11 files changed, 86 insertions(+), 24 deletions(-) create mode 100644 module/Core/src/Action/Util/ErrorResponseBuilderTrait.php create mode 100644 module/Core/templates/invalid-short-code.phtml diff --git a/module/Common/src/Util/ResponseUtilsTrait.php b/module/Common/src/Util/ResponseUtilsTrait.php index 7c8fc0b6..90e76c56 100644 --- a/module/Common/src/Util/ResponseUtilsTrait.php +++ b/module/Common/src/Util/ResponseUtilsTrait.php @@ -3,13 +3,14 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Common\Util; +use Psr\Http\Message\ResponseInterface; use Zend\Diactoros\Response; use Zend\Diactoros\Stream; use Zend\Stdlib\ArrayUtils; trait ResponseUtilsTrait { - protected function generateDownloadFileResponse($filePath) + protected function generateDownloadFileResponse(string $filePath): ResponseInterface { return $this->generateBinaryResponse($filePath, [ 'Content-Disposition' => 'attachment; filename=' . basename($filePath), @@ -21,12 +22,12 @@ trait ResponseUtilsTrait ]); } - protected function generateImageResponse($imagePath) + protected function generateImageResponse(string $imagePath): ResponseInterface { return $this->generateBinaryResponse($imagePath); } - protected function generateBinaryResponse($path, $extraHeaders = []) + protected function generateBinaryResponse(string $path, array $extraHeaders = []): ResponseInterface { $body = new Stream($path); return new Response($body, 200, ArrayUtils::merge([ diff --git a/module/Core/src/Action/PreviewAction.php b/module/Core/src/Action/PreviewAction.php index 788c3c2d..0e1043a0 100644 --- a/module/Core/src/Action/PreviewAction.php +++ b/module/Core/src/Action/PreviewAction.php @@ -10,6 +10,7 @@ use Psr\Http\Message\ServerRequestInterface as Request; use Shlinkio\Shlink\Common\Exception\PreviewGenerationException; use Shlinkio\Shlink\Common\Service\PreviewGeneratorInterface; use Shlinkio\Shlink\Common\Util\ResponseUtilsTrait; +use Shlinkio\Shlink\Core\Action\Util\ErrorResponseBuilderTrait; use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; @@ -17,6 +18,7 @@ use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; class PreviewAction implements MiddlewareInterface { use ResponseUtilsTrait; + use ErrorResponseBuilderTrait; /** * @var PreviewGeneratorInterface @@ -51,11 +53,11 @@ class PreviewAction implements MiddlewareInterface $imagePath = $this->previewGenerator->generatePreview($url); return $this->generateImageResponse($imagePath); } catch (InvalidShortCodeException $e) { - return $delegate->process($request); + return $this->buildErrorResponse($request, $delegate); } catch (EntityDoesNotExistException $e) { - return $delegate->process($request); + return $this->buildErrorResponse($request, $delegate); } catch (PreviewGenerationException $e) { - return $delegate->process($request); + return $this->buildErrorResponse($request, $delegate); } } } diff --git a/module/Core/src/Action/QrCodeAction.php b/module/Core/src/Action/QrCodeAction.php index dfd7eaf2..30363619 100644 --- a/module/Core/src/Action/QrCodeAction.php +++ b/module/Core/src/Action/QrCodeAction.php @@ -11,6 +11,7 @@ use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Shlinkio\Shlink\Common\Response\QrCodeResponse; +use Shlinkio\Shlink\Core\Action\Util\ErrorResponseBuilderTrait; use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; @@ -18,6 +19,8 @@ use Zend\Expressive\Router\RouterInterface; class QrCodeAction implements MiddlewareInterface { + use ErrorResponseBuilderTrait; + /** * @var RouterInterface */ @@ -55,19 +58,19 @@ class QrCodeAction implements MiddlewareInterface // Make sure the short URL exists for this short code $shortCode = $request->getAttribute('shortCode'); try { - $shortUrl = $this->urlShortener->shortCodeToUrl($shortCode); + $this->urlShortener->shortCodeToUrl($shortCode); } catch (InvalidShortCodeException $e) { $this->logger->warning('Tried to create a QR code with an invalid short code' . PHP_EOL . $e); - return $delegate->process($request); + return $this->buildErrorResponse($request, $delegate); } catch (EntityDoesNotExistException $e) { $this->logger->warning('Tried to create a QR code with a not found short code' . PHP_EOL . $e); - return $delegate->process($request); + return $this->buildErrorResponse($request, $delegate); } $path = $this->router->generateUri('long-url-redirect', ['shortCode' => $shortCode]); $size = $this->getSizeParam($request); - $qrCode = new QrCode($request->getUri()->withPath($path)->withQuery('')); + $qrCode = new QrCode((string) $request->getUri()->withPath($path)->withQuery('')); $qrCode->setSize($size) ->setPadding(0); return new QrCodeResponse($qrCode); diff --git a/module/Core/src/Action/RedirectAction.php b/module/Core/src/Action/RedirectAction.php index 99224934..42560e5f 100644 --- a/module/Core/src/Action/RedirectAction.php +++ b/module/Core/src/Action/RedirectAction.php @@ -9,6 +9,7 @@ use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; +use Shlinkio\Shlink\Core\Action\Util\ErrorResponseBuilderTrait; use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; @@ -17,6 +18,8 @@ use Zend\Diactoros\Response\RedirectResponse; class RedirectAction implements MiddlewareInterface { + use ErrorResponseBuilderTrait; + /** * @var UrlShortenerInterface */ @@ -69,9 +72,9 @@ class RedirectAction implements MiddlewareInterface // Use a temporary redirect to make sure browsers always hit the server for analytics purposes return new RedirectResponse($longUrl); } catch (InvalidShortCodeException $e) { - return $delegate->process($request); + return $this->buildErrorResponse($request, $delegate); } catch (EntityDoesNotExistException $e) { - return $delegate->process($request); + return $this->buildErrorResponse($request, $delegate); } } } diff --git a/module/Core/src/Action/Util/ErrorResponseBuilderTrait.php b/module/Core/src/Action/Util/ErrorResponseBuilderTrait.php new file mode 100644 index 00000000..57380b8b --- /dev/null +++ b/module/Core/src/Action/Util/ErrorResponseBuilderTrait.php @@ -0,0 +1,18 @@ +withAttribute(NotFoundDelegate::NOT_FOUND_TEMPLATE, 'ShlinkCore::invalid-short-code'); + return $delegate->process($request); + } +} diff --git a/module/Core/src/Response/NotFoundDelegate.php b/module/Core/src/Response/NotFoundDelegate.php index da0710bb..abe5f06a 100644 --- a/module/Core/src/Response/NotFoundDelegate.php +++ b/module/Core/src/Response/NotFoundDelegate.php @@ -12,6 +12,8 @@ use Zend\Expressive\Template\TemplateRendererInterface; class NotFoundDelegate implements DelegateInterface { + const NOT_FOUND_TEMPLATE = 'notFoundTemplate'; + /** * @var TemplateRendererInterface */ @@ -19,12 +21,12 @@ class NotFoundDelegate implements DelegateInterface /** * @var string */ - private $template; + private $defaultTemplate; - public function __construct(TemplateRendererInterface $renderer, string $template = 'ShlinkCore::error/404') + public function __construct(TemplateRendererInterface $renderer, string $defaultTemplate = 'ShlinkCore::error/404') { $this->renderer = $renderer; - $this->template = $template; + $this->defaultTemplate = $defaultTemplate; } /** @@ -49,6 +51,7 @@ class NotFoundDelegate implements DelegateInterface ], $status); } - return new Response\HtmlResponse($this->renderer->render($this->template, ['request' => $request]), $status); + $notFoundTemplate = $request->getAttribute(self::NOT_FOUND_TEMPLATE, $this->defaultTemplate); + return new Response\HtmlResponse($this->renderer->render($notFoundTemplate, ['request' => $request]), $status); } } diff --git a/module/Core/templates/error/404.phtml b/module/Core/templates/error/404.phtml index 369a168b..94f42c05 100644 --- a/module/Core/templates/error/404.phtml +++ b/module/Core/templates/error/404.phtml @@ -12,8 +12,8 @@ end() ?> start('main') ?> -

translate('Oops!') ?>

+

404


-

translate('This short URL doesn\'t seem to be valid.') ?>

-

translate('Make sure you included all the characters, with no extra punctuation.') ?>

+

translate('Page not found.') ?>

+

translate('The page you requested could not be found.') ?>

end() ?> diff --git a/module/Core/templates/invalid-short-code.phtml b/module/Core/templates/invalid-short-code.phtml new file mode 100644 index 00000000..369a168b --- /dev/null +++ b/module/Core/templates/invalid-short-code.phtml @@ -0,0 +1,19 @@ +layout('ShlinkCore::layout/default') ?> + +start('title') ?> + translate('URL Not Found') ?> +end() ?> + +start('stylesheets') ?> + +end() ?> + +start('main') ?> +

translate('Oops!') ?>

+
+

translate('This short URL doesn\'t seem to be valid.') ?>

+

translate('Make sure you included all the characters, with no extra punctuation.') ?>

+end() ?> diff --git a/module/Core/test/Action/PreviewActionTest.php b/module/Core/test/Action/PreviewActionTest.php index d791a100..8817eaa4 100644 --- a/module/Core/test/Action/PreviewActionTest.php +++ b/module/Core/test/Action/PreviewActionTest.php @@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\Core\Action; use Interop\Http\ServerMiddleware\DelegateInterface; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\Prophecy\MethodProphecy; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Service\PreviewGenerator; use Shlinkio\Shlink\Core\Action\PreviewAction; @@ -13,6 +14,7 @@ use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Service\UrlShortener; use ShlinkioTest\Shlink\Common\Util\TestUtils; +use Zend\Diactoros\Response; use Zend\Diactoros\ServerRequestFactory; class PreviewActionTest extends TestCase @@ -46,7 +48,8 @@ class PreviewActionTest extends TestCase $this->urlShortener->shortCodeToUrl($shortCode)->willThrow(EntityDoesNotExistException::class) ->shouldBeCalledTimes(1); $delegate = $this->prophesize(DelegateInterface::class); - $delegate->process(Argument::cetera())->shouldBeCalledTimes(1); + $delegate->process(Argument::cetera())->shouldBeCalledTimes(1) + ->willReturn(new Response()); $this->action->process( ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode), @@ -83,12 +86,14 @@ class PreviewActionTest extends TestCase $this->urlShortener->shortCodeToUrl($shortCode)->willThrow(InvalidShortCodeException::class) ->shouldBeCalledTimes(1); $delegate = $this->prophesize(DelegateInterface::class); + /** @var MethodProphecy $process */ + $process = $delegate->process(Argument::any())->willReturn(new Response()); $this->action->process( ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode), $delegate->reveal() ); - $delegate->process(Argument::any())->shouldHaveBeenCalledTimes(1); + $process->shouldHaveBeenCalledTimes(1); } } diff --git a/module/Core/test/Action/QrCodeActionTest.php b/module/Core/test/Action/QrCodeActionTest.php index df452420..c071ef7e 100644 --- a/module/Core/test/Action/QrCodeActionTest.php +++ b/module/Core/test/Action/QrCodeActionTest.php @@ -6,12 +6,14 @@ namespace ShlinkioTest\Shlink\Core\Action; use Interop\Http\ServerMiddleware\DelegateInterface; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\Prophecy\MethodProphecy; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Response\QrCodeResponse; use Shlinkio\Shlink\Core\Action\QrCodeAction; use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Service\UrlShortener; +use Zend\Diactoros\Response; use Zend\Diactoros\ServerRequestFactory; use Zend\Expressive\Router\RouterInterface; @@ -45,13 +47,14 @@ class QrCodeActionTest extends TestCase $this->urlShortener->shortCodeToUrl($shortCode)->willThrow(EntityDoesNotExistException::class) ->shouldBeCalledTimes(1); $delegate = $this->prophesize(DelegateInterface::class); + $process = $delegate->process(Argument::any())->willReturn(new Response()); $this->action->process( ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode), $delegate->reveal() ); - $delegate->process(Argument::any())->shouldHaveBeenCalledTimes(1); + $process->shouldHaveBeenCalledTimes(1); } /** @@ -63,13 +66,15 @@ class QrCodeActionTest extends TestCase $this->urlShortener->shortCodeToUrl($shortCode)->willThrow(InvalidShortCodeException::class) ->shouldBeCalledTimes(1); $delegate = $this->prophesize(DelegateInterface::class); + /** @var MethodProphecy $process */ + $process = $delegate->process(Argument::any())->willReturn(new Response()); $this->action->process( ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode), $delegate->reveal() ); - $delegate->process(Argument::any())->shouldHaveBeenCalledTimes(1); + $process->shouldHaveBeenCalledTimes(1); } /** diff --git a/module/Core/test/Action/RedirectActionTest.php b/module/Core/test/Action/RedirectActionTest.php index 49d3baec..a429549f 100644 --- a/module/Core/test/Action/RedirectActionTest.php +++ b/module/Core/test/Action/RedirectActionTest.php @@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\Core\Action; use Interop\Http\ServerMiddleware\DelegateInterface; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\Prophecy\MethodProphecy; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Action\RedirectAction; use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; @@ -62,10 +63,12 @@ class RedirectActionTest extends TestCase $this->urlShortener->shortCodeToUrl($shortCode)->willThrow(EntityDoesNotExistException::class) ->shouldBeCalledTimes(1); $delegate = $this->prophesize(DelegateInterface::class); + /** @var MethodProphecy $process */ + $process = $delegate->process(Argument::any())->willReturn(new Response()); $request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode); $this->action->process($request, $delegate->reveal()); - $delegate->process($request)->shouldHaveBeenCalledTimes(1); + $process->shouldHaveBeenCalledTimes(1); } } From 29645e77cf8b666bf7ab08a59be38c3a30884399 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 13 Oct 2017 12:27:20 +0200 Subject: [PATCH 27/64] Created DottedAccessConfigAbstractFactory --- .../DottedAccessConfigAbstractFactoryTest.php | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 module/Common/test/Factory/DottedAccessConfigAbstractFactoryTest.php diff --git a/module/Common/test/Factory/DottedAccessConfigAbstractFactoryTest.php b/module/Common/test/Factory/DottedAccessConfigAbstractFactoryTest.php new file mode 100644 index 00000000..59bc08e3 --- /dev/null +++ b/module/Common/test/Factory/DottedAccessConfigAbstractFactoryTest.php @@ -0,0 +1,91 @@ +factory = new DottedAccessConfigAbstractFactory(); + } + + /** + * @param string $serviceName + * @param bool $canCreate + * + * @test + * @dataProvider provideDotNames + */ + public function canCreateOnlyServicesWithDot(string $serviceName, bool $canCreate) + { + $this->assertEquals($canCreate, $this->factory->canCreate(new ServiceManager(), $serviceName)); + } + + public function provideDotNames(): array + { + return [ + ['foo.bar', true], + ['config.something', true], + ['config_something', false], + ['foo', false], + ]; + } + + /** + * @test + */ + public function throwsExceptionWhenFirstPartOfTheServiceIsNotRegistered() + { + $this->expectException(ServiceNotCreatedException::class); + $this->expectExceptionMessage( + 'Defined service "foo" could not be found in container after resolving dotted expression "foo.bar"' + ); + + $this->factory->__invoke(new ServiceManager(), 'foo.bar'); + } + + /** + * @test + */ + public function dottedNotationIsRecursivelyResolvedUntilLastValueIsFoundAndReturned() + { + $expected = 'this is the result'; + + $result = $this->factory->__invoke(new ServiceManager(['services' => [ + 'foo' => [ + 'bar' => ['baz' => $expected], + ], + ]]), 'foo.bar.baz'); + + $this->assertEquals($expected, $result); + } + + /** + * @test + */ + public function exceptionIsThrownIfAnyStepCannotBeResolved() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage( + 'The key "baz" provided in the dotted notation could not be found in the array service' + ); + + $this->factory->__invoke(new ServiceManager(['services' => [ + 'foo' => [ + 'bar' => ['something' => 123], + ], + ]]), 'foo.bar.baz'); + } +} From 0df8f17e7b1632d4b5086c3e15697a5ba4e2fabd Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 13 Oct 2017 12:30:54 +0200 Subject: [PATCH 28/64] Added new translations --- module/CLI/lang/es.mo | Bin 7812 -> 7957 bytes module/CLI/lang/es.po | 14 +++++++++----- module/Core/lang/es.mo | Bin 1084 -> 1247 bytes module/Core/lang/es.po | 28 +++++++++++++++++----------- module/Rest/lang/es.mo | Bin 2645 -> 2645 bytes module/Rest/lang/es.po | 4 ++-- 6 files changed, 28 insertions(+), 18 deletions(-) diff --git a/module/CLI/lang/es.mo b/module/CLI/lang/es.mo index 93bf65593ba92f90696353bc8fe4caab31896c14..742fbbe6d0b6949aa17586c6b731b88f683d640f 100644 GIT binary patch delta 1833 zcmZA1S!_&E9LMqhP@}6($6l(pwH0l3TI;l`D5HwT8WAsLN=40Bst=Y-2%(5bq|$_l zK8Si`h~+_Si9{rpNNgz&jfe*!5s`?5@9%cRa+3di&Ryo7bN>Hx@BSHWm9aPeq&A~1 zCWaG7dzo#*A4&YsI=yCt@B$9TTUdfGa3-eoHCu@_I1kTaHNMAuEKKfsZe7AA)N@*K zmRZa?=*;0nH}=D^DQ140g6UX;LvbYz!7Zq{wI;3~OLz)(UndU0JJ<)GVNfgz{j`%KOr-uV=tAWDQn{8mHDr{fB6K}>`JdYFbKIY&Ttivp3y$tIIl7G(G6;5b#J-{sNMosK1>W@Cspv_c_%)ut1 z{ue@BUz2br>b`c=eP>Xqzld7kE98-uM0%)-Wyk2W(J8<&coUc7J6woCW~GT8pmD#g!GEB=Y9Z7%84_3;?Q5NhCks1mdz)wI|-I!$ygqf(g1vNf|t)Cya$ z3Xddwh}u*cyh~nOgv!`*bZ|W`!GjpUm#Bf#n2oz)RP76pwmyIPs%? zu*B{0K}sm*6Jr#uO zh$`a$Hj)!c@zlRA=*y@5^1IETqjIqS+@@5k>ARv=mrubzt%9RoN|bhEIiby@I?W~~ z6Kd5xxSvt&YP|v3@dMs#UjMr2j;62^i8ecH8=Upg9g%Q6KVw(#v_MHwpv(!B21}=% z==9~J{yC06@}2j1oOp|WgU1s*(ba#RCm0_&V2^iLBxQQHxp7hdGLvpAv zFKvkRv~pT%lrh~noW}>z?=YqWkKq(NkA57$W%voVpm&ThEqEB4@G;Ip=h*CXYi-w| zo)gFA#w5)VIxG2N5GUf8amEy42|Ce>Q!#|4*ovB4+`isxdjNG`KNjHy9F4bd65hiy ze1=-+Cv-8sapY$QF2#N>%)<)2j%)BaGE*~ce0G2i_TsgFL*#oMp2B7v z!XSD`(+u2-^Kl>mry^vkJ>~-$k^sR>W3rt zbth@keO0LYqNw{epi^+fox|K2~Ty%%iECV;SE{Yb`3Y z+f@#Q>Z;l)>S$W6PK8#hW2@CElul(VOj|%(OH)L%So?wJ* zn%1a}9ZF-3-KV@(s1%BN8hgun18O7wi=}kfN>(ZKa?;-6xU1II5e2i5)}W3m)tv2E zui6Ox%FHFlfFpB!!j9aaKkN&H-2TOpx`p%oLBBu0HRfyUO2qr(dwP9MoqM|??w~&q g_Wi#vQ|`K)lX+O!opZXns3p@^bjXnjl)caW3&)3^XaE2J diff --git a/module/CLI/lang/es.po b/module/CLI/lang/es.po index 0a456205..eabeaf1e 100644 --- a/module/CLI/lang/es.po +++ b/module/CLI/lang/es.po @@ -1,8 +1,8 @@ msgid "" msgstr "" "Project-Id-Version: Shlink 1.0\n" -"POT-Creation-Date: 2017-07-16 09:35+0200\n" -"PO-Revision-Date: 2017-07-16 09:39+0200\n" +"POT-Creation-Date: 2017-10-13 12:28+0200\n" +"PO-Revision-Date: 2017-10-13 12:29+0200\n" "Last-Translator: Alejandro Celaya \n" "Language-Team: \n" "Language: es_ES\n" @@ -92,7 +92,7 @@ msgid "Processing URL %s..." msgstr "Procesando URL %s..." msgid " Success!" -msgstr "¡Correcto!" +msgstr " ¡Correcto!" msgid "Error" msgstr "Error" @@ -141,7 +141,7 @@ 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?" +msgstr "No se prporcionó un código corto. ¿Qué código corto deseas usar?:" msgid "Referer" msgstr "Origen" @@ -210,7 +210,7 @@ msgstr "El código corto a convertir" 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?" +"No se proporcionó un código corto. ¿Qué código corto quieres convertir?:" #, php-format msgid "No URL found for short code \"%s\"" @@ -223,6 +223,10 @@ msgstr "URL larga:" msgid "Provided short code \"%s\" has an invalid format." msgstr "El código corto proporcionado \"%s\" tiene un formato inválido." +#, php-format +msgid "Provided short code \"%s\" could not be found." +msgstr "El código corto proporcionado \"%s\" no ha podido ser encontrado." + msgid "Creates one or more tags." msgstr "Crea una o más etiquetas." diff --git a/module/Core/lang/es.mo b/module/Core/lang/es.mo index efc80eca6eb74a8dedc805d72356ebe012fa890b..d1a628d4640df875d0a4df8bb1c6e4bfe17c7b12 100644 GIT binary patch delta 391 zcmYk0y-EW?6h`l6qpnC0h1jStjUYmn-Ay4$Wuu^2tYE3dxHBdTVJ7ZRk;=lt!hnRu z&c-60ot9!_qc31-I-kG?@S7z?J~*72JNI7Z&6zA-|EyLY2yws$I0H-I5tw{}eeeb5 zz_JD4I=lmK!P{^M*WdxX3E#qd6{0bGg!`{ev;g~6qBXb$2Sf)H;$Rlsz$@@G%;0wz zvjmkf!zShv^8mZcU@tkNao7%dM>3@|cBykETsrMa);G0*&RHU_a+zjQut?`UQTAQP zY3u0kuD09oy@uape$WnD#n4)xtu(#j)%vyr*L%6IoLGn9$5SU(99x)Fkycs41$XI~ ev;OoY)x9{1Gn7qJ7Y|y*LbFsRvm?dCKK}>vS5JHZ delta 251 zcmcc5xrd|vo)F7a1|VPqVi_Rz0b*_-t^r~YSOLVYK)e!&S%LUH5NiPOS0LsEVmU?z z23a7j1Eigp7#O^Ov=@-y%gn&Q4y11YX^{E{Kw1z;bFwfnC;(|`APrQi2DTW;2OAEe zK>#Sn05l5>fch91CLRr#9KhIRYNl&op=)HQU}#}wXs&HwWMIG*;IA8$T9#RynV+ZY jl30>zrC?-Wh@x&XKhrf\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 1.8.7.1\n" +"X-Generator: Poedit 2.0.1\n" "X-Poedit-Basepath: ..\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-SourceCharset: UTF-8\n" @@ -18,18 +18,24 @@ msgstr "" "X-Poedit-SearchPath-1: config\n" "X-Poedit-SearchPath-2: src\n" -msgid "Make sure you included all the characters, with no extra punctuation." -msgstr "Asegúrate de haber incluído todos los caracteres, sin puntuación extra." +msgid "URL Not Found" +msgstr "URL no encontrada" + +msgid "Page not found." +msgstr "Página no encontrada." + +msgid "The page you requested could not be found." +msgstr "La página solicitada no ha podido ser encontrada." msgid "Oops!" msgstr "¡Vaya!" -msgid "This short URL doesn't seem to be valid." -msgstr "Esta URL acortada no parece ser válida." - -msgid "URL Not Found" -msgstr "URL no encontrada" - #, php-format msgid "We encountered a %s %s error." msgstr "Hemos encontrado un error %s %s." + +msgid "This short URL doesn't seem to be valid." +msgstr "Esta URL acortada no parece ser válida." + +msgid "Make sure you included all the characters, with no extra punctuation." +msgstr "Asegúrate de haber incluído todos los caracteres, sin puntuación extra." diff --git a/module/Rest/lang/es.mo b/module/Rest/lang/es.mo index a7b89c77959117bbacb4d479354ffb959de3734a..666e94007e6931625f846733f3b7652e2c1da539 100644 GIT binary patch delta 39 mcmcaAa#du*Vis;g16@O71w$h%V}r@-SY){n{LNQc8dw40APS}c delta 39 ncmcaAa#du*Vis-#b6rC-1p`YfW6R0wSY)~2e3Q*rSsGXY\n" "Language-Team: \n" "Language: es_ES\n" From 2d85a207d19eb86a692ec1b674f6f92db182e8ab Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 13 Oct 2017 12:31:44 +0200 Subject: [PATCH 29/64] Removed comented translations --- module/Rest/lang/es.po | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/module/Rest/lang/es.po b/module/Rest/lang/es.po index 97860b72..16de915c 100644 --- a/module/Rest/lang/es.po +++ b/module/Rest/lang/es.po @@ -79,15 +79,3 @@ msgstr "" "No se ha proporcionado token de autenticación o este es inválido. Realiza " "una nueva petición de autenticación y envía el token proporcionado en cada " "nueva petición en la cabecera \"%s\"" - -#~ msgid "You have to provide both \"username\" and \"password\"" -#~ msgstr "Debes proporcionar tanto \"username\" como \"password\"" - -#~ msgid "Invalid username and/or password" -#~ msgstr "Usuario y/o contraseña no válidos" - -#~ msgid "Requested route does not exist." -#~ msgstr "La ruta solicitada no existe." - -#~ msgid "RestToken not found for token \"%s\"" -#~ msgstr "No se ha encontrado un RestToken para el token \"%s\"" From c8346bc5f8a2b5090e61e92b34cf178b26c27de1 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 16 Oct 2017 18:23:13 +0200 Subject: [PATCH 30/64] Added target php platform in composer.json to prevent building versions that cannot be executed in older versions --- composer.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d58a88aa..7dd1bef4 100644 --- a/composer.json +++ b/composer.json @@ -84,6 +84,9 @@ }, "config": { "process-timeout": 0, - "sort-packages": true + "sort-packages": true, + "platform": { + "php": "7.0" + } } } From 97a54aef06c5b617cf2336a83c45676fa9193cb7 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 21 Oct 2017 11:28:44 +0200 Subject: [PATCH 31/64] Imported function in config file --- config/autoload/url-shortener.global.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/autoload/url-shortener.global.php b/config/autoload/url-shortener.global.php index 919f4d3f..3fe6714a 100644 --- a/config/autoload/url-shortener.global.php +++ b/config/autoload/url-shortener.global.php @@ -1,17 +1,17 @@ [ 'domain' => [ - 'schema' => Common\env('SHORTENED_URL_SCHEMA', 'http'), - 'hostname' => Common\env('SHORTENED_URL_HOSTNAME'), + 'schema' => env('SHORTENED_URL_SCHEMA', 'http'), + 'hostname' => env('SHORTENED_URL_HOSTNAME'), ], - 'shortcode_chars' => Common\env('SHORTCODE_CHARS', UrlShortener::DEFAULT_CHARS), + 'shortcode_chars' => env('SHORTCODE_CHARS', UrlShortener::DEFAULT_CHARS), ], ]; From 68b4cfbae024573a80604d4973d9661ff32d3ef8 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 21 Oct 2017 11:39:27 +0200 Subject: [PATCH 32/64] Added valid_since and valid_until columns to shoirt_urls table --- data/migrations/Version20171021093246.php | 41 +++++++++++++++++++ module/Core/config/dependencies.config.php | 2 +- module/Core/src/Action/RedirectAction.php | 14 +------ module/Core/src/Service/UrlShortener.php | 6 +-- .../src/Service/UrlShortenerInterface.php | 4 +- 5 files changed, 49 insertions(+), 18 deletions(-) create mode 100644 data/migrations/Version20171021093246.php diff --git a/data/migrations/Version20171021093246.php b/data/migrations/Version20171021093246.php new file mode 100644 index 00000000..a9c39ebb --- /dev/null +++ b/data/migrations/Version20171021093246.php @@ -0,0 +1,41 @@ +getTable('short_urls'); + $shortUrls->addColumn('valid_since', Type::DATETIME, [ + 'notnull' => false, + ]); + $shortUrls->addColumn('valid_until', Type::DATETIME, [ + 'notnull' => false, + ]); + } + + /** + * @param Schema $schema + * @throws SchemaException + */ + public function down(Schema $schema) + { + $shortUrls = $schema->getTable('short_urls'); + $shortUrls->dropColumn('valid_since'); + $shortUrls->dropColumn('valid_until'); + } +} diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index d8009880..9fbd2766 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -49,7 +49,7 @@ return [ Service\Tag\TagService::class => ['em'], // Middleware - Action\RedirectAction::class => [Service\UrlShortener::class, Service\VisitsTracker::class, 'Logger_Shlink'], + Action\RedirectAction::class => [Service\UrlShortener::class, Service\VisitsTracker::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 42560e5f..e22639c7 100644 --- a/module/Core/src/Action/RedirectAction.php +++ b/module/Core/src/Action/RedirectAction.php @@ -7,8 +7,6 @@ use Interop\Http\ServerMiddleware\DelegateInterface; use Interop\Http\ServerMiddleware\MiddlewareInterface; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; -use Psr\Log\LoggerInterface; -use Psr\Log\NullLogger; use Shlinkio\Shlink\Core\Action\Util\ErrorResponseBuilderTrait; use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; @@ -28,19 +26,11 @@ class RedirectAction implements MiddlewareInterface * @var VisitsTrackerInterface */ private $visitTracker; - /** - * @var null|LoggerInterface - */ - private $logger; - public function __construct( - UrlShortenerInterface $urlShortener, - VisitsTrackerInterface $visitTracker, - LoggerInterface $logger = null - ) { + public function __construct(UrlShortenerInterface $urlShortener, VisitsTrackerInterface $visitTracker) + { $this->urlShortener = $urlShortener; $this->visitTracker = $visitTracker; - $this->logger = $logger ?: new NullLogger(); } /** diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index b30b649a..148279e8 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -60,13 +60,13 @@ class UrlShortener implements UrlShortenerInterface * @throws InvalidUrlException * @throws RuntimeException */ - public function urlToShortCode(UriInterface $url, array $tags = []) + public function urlToShortCode(UriInterface $url, array $tags = []): string { // If the url already exists in the database, just return its short code $shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([ 'originalUrl' => $url, ]); - if (isset($shortUrl)) { + if ($shortUrl !== null) { return $shortUrl->getShortCode(); } @@ -147,7 +147,7 @@ class UrlShortener implements UrlShortenerInterface * @throws InvalidShortCodeException * @throws EntityDoesNotExistException */ - public function shortCodeToUrl($shortCode): string + public function shortCodeToUrl(string $shortCode): string { $cacheKey = sprintf('%s_longUrl', $shortCode); // Check if the short code => URL map is already cached diff --git a/module/Core/src/Service/UrlShortenerInterface.php b/module/Core/src/Service/UrlShortenerInterface.php index 3cd2c35c..6fc0c24a 100644 --- a/module/Core/src/Service/UrlShortenerInterface.php +++ b/module/Core/src/Service/UrlShortenerInterface.php @@ -20,7 +20,7 @@ interface UrlShortenerInterface * @throws InvalidUrlException * @throws RuntimeException */ - public function urlToShortCode(UriInterface $url, array $tags = []); + public function urlToShortCode(UriInterface $url, array $tags = []): string; /** * Tries to find the mapped URL for provided short code. Returns null if not found @@ -30,5 +30,5 @@ interface UrlShortenerInterface * @throws InvalidShortCodeException * @throws EntityDoesNotExistException */ - public function shortCodeToUrl($shortCode): string; + public function shortCodeToUrl(string $shortCode): string; } From a3bbd06fe3a6a9bd0dd5b26f1fa0be03a5dc3910 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 21 Oct 2017 11:58:20 +0200 Subject: [PATCH 33/64] Updated UrlShortener so that it does not match a short code which is out of the validity dat erange --- .../PaginableRepositoryInterface.php | 12 ++- module/Core/src/Entity/ShortUrl.php | 86 ++++++++++++------- .../src/Repository/ShortUrlRepository.php | 38 +++++++- .../ShortUrlRepositoryInterface.php | 6 ++ module/Core/src/Service/UrlShortener.php | 11 ++- module/Core/test/Service/UrlShortenerTest.php | 5 +- 6 files changed, 116 insertions(+), 42 deletions(-) diff --git a/module/Common/src/Repository/PaginableRepositoryInterface.php b/module/Common/src/Repository/PaginableRepositoryInterface.php index c02e2045..31808ee1 100644 --- a/module/Common/src/Repository/PaginableRepositoryInterface.php +++ b/module/Common/src/Repository/PaginableRepositoryInterface.php @@ -15,14 +15,20 @@ interface PaginableRepositoryInterface * @param string|array|null $orderBy * @return array */ - public function findList($limit = null, $offset = null, $searchTerm = null, array $tags = [], $orderBy = null); + public function findList( + int $limit = null, + int $offset = null, + string $searchTerm = null, + array $tags = [], + $orderBy = null + ): array; /** * Counts the number of elements in a list using provided filtering data * - * @param null $searchTerm + * @param string|null $searchTerm * @param array $tags * @return int */ - public function countList($searchTerm = null, array $tags = []); + public function countList(string $searchTerm = null, array $tags = []): int; } diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index 06cb02ce..38fc5911 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -54,22 +54,32 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable * }) */ protected $tags; + /** + * @var \DateTime + * @ORM\Column(name="valid_since", type="datetime", nullable=true) + */ + protected $validSince; + /** + * @var \DateTime + * @ORM\Column(name="valid_until", type="datetime", nullable=true) + */ + protected $validUntil; /** * ShortUrl constructor. */ public function __construct() { - $this->setDateCreated(new \DateTime()); - $this->setVisits(new ArrayCollection()); - $this->setShortCode(''); + $this->dateCreated = new \DateTime(); + $this->visits = new ArrayCollection(); + $this->shortCode = ''; $this->tags = new ArrayCollection(); } /** * @return string */ - public function getOriginalUrl() + public function getOriginalUrl(): string { return $this->originalUrl; } @@ -78,7 +88,7 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable * @param string $originalUrl * @return $this */ - public function setOriginalUrl($originalUrl) + public function setOriginalUrl(string $originalUrl) { $this->originalUrl = (string) $originalUrl; return $this; @@ -87,7 +97,7 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable /** * @return string */ - public function getShortCode() + public function getShortCode(): string { return $this->shortCode; } @@ -96,7 +106,7 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable * @param string $shortCode * @return $this */ - public function setShortCode($shortCode) + public function setShortCode(string $shortCode) { $this->shortCode = $shortCode; return $this; @@ -105,7 +115,7 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable /** * @return \DateTime */ - public function getDateCreated() + public function getDateCreated(): \DateTime { return $this->dateCreated; } @@ -114,34 +124,16 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable * @param \DateTime $dateCreated * @return $this */ - public function setDateCreated($dateCreated) + public function setDateCreated(\DateTime $dateCreated) { $this->dateCreated = $dateCreated; return $this; } - /** - * @return Visit[]|Collection - */ - public function getVisits() - { - return $this->visits; - } - - /** - * @param Visit[]|Collection $visits - * @return $this - */ - public function setVisits($visits) - { - $this->visits = $visits; - return $this; - } - /** * @return Collection|Tag[] */ - public function getTags() + public function getTags(): Collection { return $this->tags; } @@ -150,7 +142,7 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable * @param Collection|Tag[] $tags * @return $this */ - public function setTags($tags) + public function setTags(Collection $tags) { $this->tags = $tags; return $this; @@ -166,6 +158,42 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable return $this; } + /** + * @return \DateTime|null + */ + public function getValidSince() + { + return $this->validSince; + } + + /** + * @param \DateTime|null $validSince + * @return $this|self + */ + public function setValidSince($validSince): self + { + $this->validSince = $validSince; + return $this; + } + + /** + * @return \DateTime|null + */ + public function getValidUntil() + { + return $this->validUntil; + } + + /** + * @param \DateTime|null $validUntil + * @return $this|self + */ + public function setValidUntil($validUntil): self + { + $this->validUntil = $validUntil; + return $this; + } + /** * Specify data which should be serialized to JSON * @link http://php.net/manual/en/jsonserializable.jsonserialize.php diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index a888c07c..84d07511 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -17,8 +17,13 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI * @param string|array|null $orderBy * @return \Shlinkio\Shlink\Core\Entity\ShortUrl[] */ - public function findList($limit = null, $offset = null, $searchTerm = null, array $tags = [], $orderBy = null) - { + public function findList( + int $limit = null, + int $offset = null, + string $searchTerm = null, + array $tags = [], + $orderBy = null + ): array { $qb = $this->createListQueryBuilder($searchTerm, $tags); $qb->select('s'); @@ -74,7 +79,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI * @param array $tags * @return int */ - public function countList($searchTerm = null, array $tags = []) + public function countList(string $searchTerm = null, array $tags = []): int { $qb = $this->createListQueryBuilder($searchTerm, $tags); $qb->select('COUNT(s)'); @@ -87,7 +92,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI * @param array $tags * @return QueryBuilder */ - protected function createListQueryBuilder($searchTerm = null, array $tags = []) + protected function createListQueryBuilder(string $searchTerm = null, array $tags = []): QueryBuilder { $qb = $this->getEntityManager()->createQueryBuilder(); $qb->from(ShortUrl::class, 's'); @@ -117,4 +122,29 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI return $qb; } + + /** + * @param string $shortCode + * @return ShortUrl|null + */ + public function findOneByShortCode(string $shortCode) + { + $now = new \DateTimeImmutable(); + + $qb = $this->createQueryBuilder('s'); + $qb->where($qb->expr()->eq('s.shortCode', ':shortCode')) + ->setParameter('shortCode', $shortCode) + ->andWhere($qb->expr()->orX( + $qb->expr()->lte('s.validSince', ':now'), + $qb->expr()->isNull('s.validSince') + )) + ->andWhere($qb->expr()->orX( + $qb->expr()->gte('s.validUntil', ':now'), + $qb->expr()->isNull('s.validUntil') + )) + ->setParameter('now', $now) + ->setMaxResults(1); + + return $qb->getQuery()->getOneOrNullResult(); + } } diff --git a/module/Core/src/Repository/ShortUrlRepositoryInterface.php b/module/Core/src/Repository/ShortUrlRepositoryInterface.php index 3ddae2d6..bff0d723 100644 --- a/module/Core/src/Repository/ShortUrlRepositoryInterface.php +++ b/module/Core/src/Repository/ShortUrlRepositoryInterface.php @@ -5,7 +5,13 @@ namespace Shlinkio\Shlink\Core\Repository; use Doctrine\Common\Persistence\ObjectRepository; use Shlinkio\Shlink\Common\Repository\PaginableRepositoryInterface; +use Shlinkio\Shlink\Core\Entity\ShortUrl; interface ShortUrlRepositoryInterface extends ObjectRepository, PaginableRepositoryInterface { + /** + * @param string $shortCode + * @return ShortUrl|null + */ + public function findOneByShortCode(string $shortCode); } diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index 148279e8..4aecade9 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -14,6 +14,7 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; +use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Util\TagManagerTrait; class UrlShortener implements UrlShortenerInterface @@ -160,11 +161,13 @@ class UrlShortener implements UrlShortenerInterface throw InvalidShortCodeException::fromCharset($shortCode, $this->chars); } - $criteria = ['shortCode' => $shortCode]; - /** @var ShortUrl|null $shortUrl */ - $shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy($criteria); + /** @var ShortUrlRepository $shortUrlRepo */ + $shortUrlRepo = $this->em->getRepository(ShortUrl::class); + $shortUrl = $shortUrlRepo->findOneByShortCode($shortCode); if ($shortUrl === null) { - throw EntityDoesNotExistException::createFromEntityAndConditions(ShortUrl::class, $criteria); + throw EntityDoesNotExistException::createFromEntityAndConditions(ShortUrl::class, [ + 'shortCode' => $shortCode, + ]); } // Cache the shortcode diff --git a/module/Core/test/Service/UrlShortenerTest.php b/module/Core/test/Service/UrlShortenerTest.php index 6b7ed22c..89aeb44e 100644 --- a/module/Core/test/Service/UrlShortenerTest.php +++ b/module/Core/test/Service/UrlShortenerTest.php @@ -16,6 +16,7 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\ShortUrl; +use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\Service\UrlShortener; use Zend\Diactoros\Uri; @@ -127,8 +128,8 @@ class UrlShortenerTest extends TestCase $shortUrl->setShortCode($shortCode) ->setOriginalUrl('expected_url'); - $repo = $this->prophesize(ObjectRepository::class); - $repo->findOneBy(['shortCode' => $shortCode])->willReturn($shortUrl); + $repo = $this->prophesize(ShortUrlRepositoryInterface::class); + $repo->findOneByShortCode($shortCode)->willReturn($shortUrl); $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); $this->assertFalse($this->cache->contains($shortCode . '_longUrl')); From 070055a8b96dfe7987638355956c71eee895ae67 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 21 Oct 2017 11:59:31 +0200 Subject: [PATCH 34/64] Fixed type hints --- module/Core/src/Entity/ShortUrl.php | 2 +- module/Core/src/Service/UrlShortener.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index 38fc5911..0f7ea621 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -90,7 +90,7 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable */ public function setOriginalUrl(string $originalUrl) { - $this->originalUrl = (string) $originalUrl; + $this->originalUrl = $originalUrl; return $this; } diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index 4aecade9..a14665e4 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -80,7 +80,7 @@ class UrlShortener implements UrlShortenerInterface // First, create the short URL with an empty short code $shortUrl = new ShortUrl(); - $shortUrl->setOriginalUrl($url); + $shortUrl->setOriginalUrl((string) $url); $this->em->persist($shortUrl); $this->em->flush(); From 0232f68b9170987d68222a9c5093a0995f5e31b9 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 21 Oct 2017 12:24:53 +0200 Subject: [PATCH 35/64] Updated action and command to create short urls so that it accepts validity dates --- .../Shortcode/GenerateShortcodeCommand.php | 25 ++++++++++++++++--- module/Core/src/Service/UrlShortener.php | 14 ++++++++--- .../src/Service/UrlShortenerInterface.php | 9 ++++++- .../Rest/src/Action/CreateShortcodeAction.php | 11 ++++++-- .../test/Action/CreateShortcodeActionTest.php | 6 ++--- 5 files changed, 53 insertions(+), 12 deletions(-) diff --git a/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php b/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php index 9677c466..e84fee6c 100644 --- a/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php +++ b/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php @@ -51,9 +51,17 @@ class GenerateShortcodeCommand extends Command ->addOption( 'tags', 't', - InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, + InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, $this->translator->translate('Tags to apply to the new short URL') - ); + ) + ->addOption('validSince', 's', InputOption::VALUE_REQUIRED, $this->translator->translate( + 'The date from which this short URL will be valid. ' + . 'If someone tries to access it before this date, it will not be found.' + )) + ->addOption('validUntil', 'u', InputOption::VALUE_REQUIRED, $this->translator->translate( + 'The date until which this short URL will be valid. ' + . 'If someone tries to access it after this date, it will not be found.' + )); } public function interact(InputInterface $input, OutputInterface $output) @@ -93,7 +101,12 @@ class GenerateShortcodeCommand extends Command return; } - $shortCode = $this->urlShortener->urlToShortCode(new Uri($longUrl), $tags); + $shortCode = $this->urlShortener->urlToShortCode( + new Uri($longUrl), + $tags, + $this->getOptionalDate($input, 'validSince'), + $this->getOptionalDate($input, 'validUntil') + ); $shortUrl = (new Uri())->withPath($shortCode) ->withScheme($this->domainConfig['schema']) ->withHost($this->domainConfig['hostname']); @@ -111,4 +124,10 @@ class GenerateShortcodeCommand extends Command )); } } + + private function getOptionalDate(InputInterface $input, string $fieldName) + { + $since = $input->getOption($fieldName); + return $since !== null ? new \DateTime($since) : null; + } } diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index a14665e4..eb5d585a 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -57,12 +57,18 @@ class UrlShortener implements UrlShortenerInterface * * @param UriInterface $url * @param string[] $tags + * @param \DateTime|null $validSince + * @param \DateTime|null $validUntil * @return string * @throws InvalidUrlException * @throws RuntimeException */ - public function urlToShortCode(UriInterface $url, array $tags = []): string - { + public function urlToShortCode( + UriInterface $url, + array $tags = [], + \DateTime $validSince = null, + \DateTime $validUntil = null + ): string { // If the url already exists in the database, just return its short code $shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([ 'originalUrl' => $url, @@ -80,7 +86,9 @@ class UrlShortener implements UrlShortenerInterface // First, create the short URL with an empty short code $shortUrl = new ShortUrl(); - $shortUrl->setOriginalUrl((string) $url); + $shortUrl->setOriginalUrl((string) $url) + ->setValidSince($validSince) + ->setValidUntil($validUntil); $this->em->persist($shortUrl); $this->em->flush(); diff --git a/module/Core/src/Service/UrlShortenerInterface.php b/module/Core/src/Service/UrlShortenerInterface.php index 6fc0c24a..27b0574a 100644 --- a/module/Core/src/Service/UrlShortenerInterface.php +++ b/module/Core/src/Service/UrlShortenerInterface.php @@ -16,11 +16,18 @@ interface UrlShortenerInterface * * @param UriInterface $url * @param string[] $tags + * @param \DateTime|null $validSince + * @param \DateTime|null $validUntil * @return string * @throws InvalidUrlException * @throws RuntimeException */ - public function urlToShortCode(UriInterface $url, array $tags = []): string; + public function urlToShortCode( + UriInterface $url, + array $tags = [], + \DateTime $validSince = null, + \DateTime $validUntil = null + ): string; /** * Tries to find the mapped URL for provided short code. Returns null if not found diff --git a/module/Rest/src/Action/CreateShortcodeAction.php b/module/Rest/src/Action/CreateShortcodeAction.php index 9915a5ec..8969ee75 100644 --- a/module/Rest/src/Action/CreateShortcodeAction.php +++ b/module/Rest/src/Action/CreateShortcodeAction.php @@ -57,10 +57,12 @@ class CreateShortcodeAction extends AbstractRestAction ], self::STATUS_BAD_REQUEST); } $longUrl = $postData['longUrl']; - $tags = isset($postData['tags']) && is_array($postData['tags']) ? $postData['tags'] : []; + $tags = (array) ($postData['tags'] ?? []); + $validSince = $this->getOptionalDate($postData, 'validSince'); + $validUntil = $this->getOptionalDate($postData, 'validUntil'); try { - $shortCode = $this->urlShortener->urlToShortCode(new Uri($longUrl), $tags); + $shortCode = $this->urlShortener->urlToShortCode(new Uri($longUrl), $tags, $validSince, $validUntil); $shortUrl = (new Uri())->withPath($shortCode) ->withScheme($this->domainConfig['schema']) ->withHost($this->domainConfig['hostname']); @@ -87,4 +89,9 @@ class CreateShortcodeAction extends AbstractRestAction ], self::STATUS_INTERNAL_SERVER_ERROR); } } + + private function getOptionalDate(array $postData, string $fieldName) + { + return isset($postData[$fieldName]) ? new \DateTime($postData[$fieldName]) : null; + } } diff --git a/module/Rest/test/Action/CreateShortcodeActionTest.php b/module/Rest/test/Action/CreateShortcodeActionTest.php index a5590785..adb1ada4 100644 --- a/module/Rest/test/Action/CreateShortcodeActionTest.php +++ b/module/Rest/test/Action/CreateShortcodeActionTest.php @@ -52,7 +52,7 @@ class CreateShortcodeActionTest extends TestCase */ public function properShortcodeConversionReturnsData() { - $this->urlShortener->urlToShortCode(Argument::type(Uri::class), Argument::type('array')) + $this->urlShortener->urlToShortCode(Argument::type(Uri::class), Argument::type('array'), null, null) ->willReturn('abc123') ->shouldBeCalledTimes(1); @@ -69,7 +69,7 @@ class CreateShortcodeActionTest extends TestCase */ public function anInvalidUrlReturnsError() { - $this->urlShortener->urlToShortCode(Argument::type(Uri::class), Argument::type('array')) + $this->urlShortener->urlToShortCode(Argument::type(Uri::class), Argument::type('array'), null, null) ->willThrow(InvalidUrlException::class) ->shouldBeCalledTimes(1); @@ -86,7 +86,7 @@ class CreateShortcodeActionTest extends TestCase */ public function aGenericExceptionWillReturnError() { - $this->urlShortener->urlToShortCode(Argument::type(Uri::class), Argument::type('array')) + $this->urlShortener->urlToShortCode(Argument::type(Uri::class), Argument::type('array'), null, null) ->willThrow(\Exception::class) ->shouldBeCalledTimes(1); From 1f7a94794df38c8cbc018e3ccc61a022ad8a968a Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 21 Oct 2017 17:18:57 +0200 Subject: [PATCH 36/64] Added option to provide custom slug when creating a short url --- composer.json | 1 + .../src/Exception/NonUniqueSlugException.php | 14 ++++ module/Core/src/Service/UrlShortener.php | 40 ++++++++++-- .../src/Service/UrlShortenerInterface.php | 6 +- module/Core/test/Service/UrlShortenerTest.php | 64 ++++++++++++++++++- 5 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 module/Core/src/Exception/NonUniqueSlugException.php diff --git a/composer.json b/composer.json index 7dd1bef4..31854436 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "require": { "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", diff --git a/module/Core/src/Exception/NonUniqueSlugException.php b/module/Core/src/Exception/NonUniqueSlugException.php new file mode 100644 index 00000000..4f76b725 --- /dev/null +++ b/module/Core/src/Exception/NonUniqueSlugException.php @@ -0,0 +1,14 @@ +httpClient = $httpClient; $this->em = $em; $this->chars = empty($chars) ? self::DEFAULT_CHARS : $chars; $this->cache = $cache; + $this->slugger = $slugger ?: new Slugify(); } /** @@ -59,7 +68,9 @@ class UrlShortener implements UrlShortenerInterface * @param string[] $tags * @param \DateTime|null $validSince * @param \DateTime|null $validUntil + * @param string|null $customSlug * @return string + * @throws NonUniqueSlugException * @throws InvalidUrlException * @throws RuntimeException */ @@ -67,7 +78,8 @@ class UrlShortener implements UrlShortenerInterface UriInterface $url, array $tags = [], \DateTime $validSince = null, - \DateTime $validUntil = null + \DateTime $validUntil = null, + string $customSlug = null ): string { // If the url already exists in the database, just return its short code $shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([ @@ -79,6 +91,8 @@ class UrlShortener implements UrlShortenerInterface // Check that the URL exists $this->checkUrlExists($url); + $customSlug = $this->processCustomSlug($customSlug); + // Transactionally insert the short url, then generate the short code and finally update the short code try { @@ -93,7 +107,7 @@ class UrlShortener implements UrlShortenerInterface $this->em->flush(); // Generate the short code and persist it - $shortCode = $this->convertAutoincrementIdToShortCode($shortUrl->getId()); + $shortCode = $customSlug ?? $this->convertAutoincrementIdToShortCode($shortUrl->getId()); $shortUrl->setShortCode($shortCode) ->setTags($this->tagNamesToEntities($this->em, $tags)); $this->em->flush(); @@ -116,7 +130,7 @@ class UrlShortener implements UrlShortenerInterface * @param UriInterface $url * @return void */ - protected function checkUrlExists(UriInterface $url) + private function checkUrlExists(UriInterface $url) { try { $this->httpClient->request('GET', $url, ['allow_redirects' => [ @@ -133,7 +147,7 @@ class UrlShortener implements UrlShortenerInterface * @param int $id * @return string */ - protected function convertAutoincrementIdToShortCode($id) + private function convertAutoincrementIdToShortCode($id) { $id = ((int) $id) + 200000; // Increment the Id so that the generated shortcode is not too short $length = strlen($this->chars); @@ -148,6 +162,22 @@ class UrlShortener implements UrlShortenerInterface return $this->chars[(int) $id] . $code; } + private function processCustomSlug($customSlug) + { + if ($customSlug === null) { + return null; + } + + // If a custom slug was provided, check it is unique + $customSlug = $this->slugger->slugify($customSlug); + $shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy(['shortCode' => $customSlug]); + if ($shortUrl !== null) { + throw NonUniqueSlugException::fromSlug($customSlug); + } + + return $customSlug; + } + /** * Tries to find the mapped URL for provided short code. Returns null if not found * diff --git a/module/Core/src/Service/UrlShortenerInterface.php b/module/Core/src/Service/UrlShortenerInterface.php index 27b0574a..aa346756 100644 --- a/module/Core/src/Service/UrlShortenerInterface.php +++ b/module/Core/src/Service/UrlShortenerInterface.php @@ -8,6 +8,7 @@ use Shlinkio\Shlink\Common\Exception\RuntimeException; use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; +use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; interface UrlShortenerInterface { @@ -18,7 +19,9 @@ interface UrlShortenerInterface * @param string[] $tags * @param \DateTime|null $validSince * @param \DateTime|null $validUntil + * @param string|null $customSlug * @return string + * @throws NonUniqueSlugException * @throws InvalidUrlException * @throws RuntimeException */ @@ -26,7 +29,8 @@ interface UrlShortenerInterface UriInterface $url, array $tags = [], \DateTime $validSince = null, - \DateTime $validUntil = null + \DateTime $validUntil = null, + string $customSlug = null ): string; /** diff --git a/module/Core/test/Service/UrlShortenerTest.php b/module/Core/test/Service/UrlShortenerTest.php index 89aeb44e..7a1c93c6 100644 --- a/module/Core/test/Service/UrlShortenerTest.php +++ b/module/Core/test/Service/UrlShortenerTest.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Service; +use Cocur\Slugify\SlugifyInterface; use Doctrine\Common\Cache\ArrayCache; use Doctrine\Common\Cache\Cache; use Doctrine\Common\Persistence\ObjectRepository; @@ -14,8 +15,10 @@ use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Psr7\Request; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\Prophecy\MethodProphecy; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\ShortUrl; +use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\Service\UrlShortener; use Zend\Diactoros\Uri; @@ -38,6 +41,10 @@ class UrlShortenerTest extends TestCase * @var Cache */ protected $cache; + /** + * @var ObjectProphecy + */ + protected $slugger; public function setUp() { @@ -60,8 +67,15 @@ class UrlShortenerTest extends TestCase $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); $this->cache = new ArrayCache(); + $this->slugger = $this->prophesize(SlugifyInterface::class); - $this->urlShortener = new UrlShortener($this->httpClient->reveal(), $this->em->reveal(), $this->cache); + $this->urlShortener = new UrlShortener( + $this->httpClient->reveal(), + $this->em->reveal(), + $this->cache, + UrlShortener::DEFAULT_CHARS, + $this->slugger->reveal() + ); } /** @@ -117,6 +131,54 @@ class UrlShortenerTest extends TestCase $this->assertEquals($shortUrl->getShortCode(), $shortCode); } + /** + * @test + */ + public function whenCustomSlugIsProvidedItIsUsed() + { + /** @var MethodProphecy $slugify */ + $slugify = $this->slugger->slugify('custom-slug')->willReturnArgument(); + + $this->urlShortener->urlToShortCode( + new Uri('http://foobar.com/12345/hello?foo=bar'), + [], + null, + null, + 'custom-slug' + ); + + $slugify->shouldHaveBeenCalledTimes(1); + } + + /** + * @test + */ + public function exceptionIsThrownWhenNonUniqueSlugIsProvided() + { + /** @var MethodProphecy $slugify */ + $slugify = $this->slugger->slugify('custom-slug')->willReturnArgument(); + + $repo = $this->prophesize(ShortUrlRepositoryInterface::class); + /** @var MethodProphecy $findBySlug */ + $findBySlug = $repo->findOneBy(['shortCode' => 'custom-slug'])->willReturn(new ShortUrl()); + $repo->findOneBy(Argument::cetera())->willReturn(null); + /** @var MethodProphecy $getRepo */ + $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); + + $slugify->shouldBeCalledTimes(1); + $findBySlug->shouldBeCalledTimes(1); + $getRepo->shouldBeCalled(); + $this->expectException(NonUniqueSlugException::class); + + $this->urlShortener->urlToShortCode( + new Uri('http://foobar.com/12345/hello?foo=bar'), + [], + null, + null, + 'custom-slug' + ); + } + /** * @test */ From fd468cd4e978fe42020743f2e47344bce539990c Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 21 Oct 2017 17:32:05 +0200 Subject: [PATCH 37/64] Added support for custom slug in shortcode command --- .../Shortcode/GenerateShortcodeCommand.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php b/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php index e84fee6c..9df42f20 100644 --- a/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php +++ b/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\Shortcode; 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; @@ -61,6 +62,9 @@ class GenerateShortcodeCommand extends Command ->addOption('validUntil', 'u', InputOption::VALUE_REQUIRED, $this->translator->translate( 'The date until which this short URL will be valid. ' . 'If someone tries to access it after this date, it will not be found.' + )) + ->addOption('customSlug', 'c', InputOption::VALUE_REQUIRED, $this->translator->translate( + 'If provided, this slug will be used instead of generating a short code' )); } @@ -94,6 +98,7 @@ class GenerateShortcodeCommand extends Command $processedTags = array_merge($processedTags, $explodedTags); } $tags = $processedTags; + $customSlug = $input->getOption('customSlug'); try { if (! isset($longUrl)) { @@ -105,7 +110,8 @@ class GenerateShortcodeCommand extends Command new Uri($longUrl), $tags, $this->getOptionalDate($input, 'validSince'), - $this->getOptionalDate($input, 'validUntil') + $this->getOptionalDate($input, 'validUntil'), + $customSlug ); $shortUrl = (new Uri())->withPath($shortCode) ->withScheme($this->domainConfig['schema']) @@ -122,6 +128,13 @@ class GenerateShortcodeCommand extends Command ) . '', $longUrl )); + } catch (NonUniqueSlugException $e) { + $output->writeln(sprintf( + '' . $this->translator->translate( + 'Provided slug "%s" is already in use by another URL. Try with a different one.' + ) . '', + $customSlug + )); } } From 5f0d2812559af119fc5c71e049a1fa5c0cbbea09 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 21 Oct 2017 20:09:30 +0200 Subject: [PATCH 38/64] Updated create shortcode action to accept the custom slug --- module/Core/src/Service/UrlShortener.php | 1 - .../Rest/src/Action/CreateShortcodeAction.php | 24 +++++++++++++++---- module/Rest/src/Util/RestUtils.php | 3 +++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index e85062b5..e6cdf947 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -93,7 +93,6 @@ class UrlShortener implements UrlShortenerInterface $this->checkUrlExists($url); $customSlug = $this->processCustomSlug($customSlug); - // Transactionally insert the short url, then generate the short code and finally update the short code try { $this->em->beginTransaction(); diff --git a/module/Rest/src/Action/CreateShortcodeAction.php b/module/Rest/src/Action/CreateShortcodeAction.php index 8969ee75..a4f7919b 100644 --- a/module/Rest/src/Action/CreateShortcodeAction.php +++ b/module/Rest/src/Action/CreateShortcodeAction.php @@ -8,6 +8,7 @@ use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; +use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; use Shlinkio\Shlink\Rest\Util\RestUtils; use Zend\Diactoros\Response\JsonResponse; @@ -57,12 +58,16 @@ class CreateShortcodeAction extends AbstractRestAction ], self::STATUS_BAD_REQUEST); } $longUrl = $postData['longUrl']; - $tags = (array) ($postData['tags'] ?? []); - $validSince = $this->getOptionalDate($postData, 'validSince'); - $validUntil = $this->getOptionalDate($postData, 'validUntil'); + $customSlug = $postData['customSlug'] ?? null; try { - $shortCode = $this->urlShortener->urlToShortCode(new Uri($longUrl), $tags, $validSince, $validUntil); + $shortCode = $this->urlShortener->urlToShortCode( + new Uri($longUrl), + (array) ($postData['tags'] ?? []), + $this->getOptionalDate($postData, 'validSince'), + $this->getOptionalDate($postData, 'validUntil'), + $customSlug + ); $shortUrl = (new Uri())->withPath($shortCode) ->withScheme($this->domainConfig['schema']) ->withHost($this->domainConfig['hostname']); @@ -81,7 +86,16 @@ class CreateShortcodeAction extends AbstractRestAction $longUrl ), ], self::STATUS_BAD_REQUEST); - } catch (\Exception $e) { + } catch (NonUniqueSlugException $e) { + $this->logger->warning('Provided non-unique slug.' . PHP_EOL . $e); + return new JsonResponse([ + 'error' => RestUtils::getRestErrorCodeFromException($e), + 'message' => sprintf( + $this->translator->translate('Provided slug %s is already in use. Try with a different one.'), + $customSlug + ), + ], self::STATUS_BAD_REQUEST); + } catch (\Throwable $e) { $this->logger->error('Unexpected error creating shortcode.' . PHP_EOL . $e); return new JsonResponse([ 'error' => RestUtils::UNKNOWN_ERROR, diff --git a/module/Rest/src/Util/RestUtils.php b/module/Rest/src/Util/RestUtils.php index f18ae72e..dcd6632e 100644 --- a/module/Rest/src/Util/RestUtils.php +++ b/module/Rest/src/Util/RestUtils.php @@ -12,6 +12,7 @@ class RestUtils const INVALID_SHORTCODE_ERROR = 'INVALID_SHORTCODE'; const INVALID_URL_ERROR = 'INVALID_URL'; const INVALID_ARGUMENT_ERROR = 'INVALID_ARGUMENT'; + const INVALID_SLUG_ERROR = 'INVALID_SLUG'; const INVALID_CREDENTIALS_ERROR = 'INVALID_CREDENTIALS'; const INVALID_AUTH_TOKEN_ERROR = 'INVALID_AUTH_TOKEN'; const INVALID_AUTHORIZATION_ERROR = 'INVALID_AUTHORIZATION'; @@ -26,6 +27,8 @@ class RestUtils return self::INVALID_SHORTCODE_ERROR; case $e instanceof Core\InvalidUrlException: return self::INVALID_URL_ERROR; + case $e instanceof Core\NonUniqueSlugException: + return self::INVALID_SLUG_ERROR; case $e instanceof Common\InvalidArgumentException: return self::INVALID_ARGUMENT_ERROR; case $e instanceof Rest\AuthenticationException: From 6bbe66e8f1d84da82003d454a7525f460ee1c884 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 21 Oct 2017 20:16:39 +0200 Subject: [PATCH 39/64] Improved CreateShortcodeActiontest --- .../Rest/src/Action/CreateShortcodeAction.php | 2 +- .../test/Action/CreateShortcodeActionTest.php | 25 ++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/module/Rest/src/Action/CreateShortcodeAction.php b/module/Rest/src/Action/CreateShortcodeAction.php index a4f7919b..790c361b 100644 --- a/module/Rest/src/Action/CreateShortcodeAction.php +++ b/module/Rest/src/Action/CreateShortcodeAction.php @@ -74,7 +74,7 @@ class CreateShortcodeAction extends AbstractRestAction return new JsonResponse([ 'longUrl' => $longUrl, - 'shortUrl' => $shortUrl->__toString(), + 'shortUrl' => (string) $shortUrl, 'shortCode' => $shortCode, ]); } catch (InvalidUrlException $e) { diff --git a/module/Rest/test/Action/CreateShortcodeActionTest.php b/module/Rest/test/Action/CreateShortcodeActionTest.php index adb1ada4..ce297105 100644 --- a/module/Rest/test/Action/CreateShortcodeActionTest.php +++ b/module/Rest/test/Action/CreateShortcodeActionTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; +use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\Service\UrlShortener; use Shlinkio\Shlink\Rest\Action\CreateShortcodeAction; use Shlinkio\Shlink\Rest\Util\RestUtils; @@ -52,7 +53,7 @@ class CreateShortcodeActionTest extends TestCase */ public function properShortcodeConversionReturnsData() { - $this->urlShortener->urlToShortCode(Argument::type(Uri::class), Argument::type('array'), null, null) + $this->urlShortener->urlToShortCode(Argument::type(Uri::class), Argument::type('array'), Argument::cetera()) ->willReturn('abc123') ->shouldBeCalledTimes(1); @@ -69,7 +70,7 @@ class CreateShortcodeActionTest extends TestCase */ public function anInvalidUrlReturnsError() { - $this->urlShortener->urlToShortCode(Argument::type(Uri::class), Argument::type('array'), null, null) + $this->urlShortener->urlToShortCode(Argument::type(Uri::class), Argument::type('array'), Argument::cetera()) ->willThrow(InvalidUrlException::class) ->shouldBeCalledTimes(1); @@ -81,12 +82,30 @@ class CreateShortcodeActionTest extends TestCase $this->assertTrue(strpos($response->getBody()->getContents(), RestUtils::INVALID_URL_ERROR) > 0); } + /** + * @test + */ + public function nonUniqueSlugReturnsError() + { + $this->urlShortener->urlToShortCode(Argument::type(Uri::class), Argument::type('array'), null, null, 'foo') + ->willThrow(NonUniqueSlugException::class) + ->shouldBeCalledTimes(1); + + $request = ServerRequestFactory::fromGlobals()->withParsedBody([ + 'longUrl' => 'http://www.domain.com/foo/bar', + 'customSlug' => 'foo', + ]); + $response = $this->action->process($request, TestUtils::createDelegateMock()->reveal()); + $this->assertEquals(400, $response->getStatusCode()); + $this->assertContains(RestUtils::INVALID_SLUG_ERROR, (string) $response->getBody()); + } + /** * @test */ public function aGenericExceptionWillReturnError() { - $this->urlShortener->urlToShortCode(Argument::type(Uri::class), Argument::type('array'), null, null) + $this->urlShortener->urlToShortCode(Argument::type(Uri::class), Argument::type('array'), Argument::cetera()) ->willThrow(\Exception::class) ->shouldBeCalledTimes(1); From a1c8c51f702097390aaf46ac357d154b8a670b53 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 21 Oct 2017 20:21:08 +0200 Subject: [PATCH 40/64] Updated translations --- module/CLI/lang/es.mo | Bin 7957 -> 8853 bytes module/CLI/lang/es.po | 30 ++++++++++++++++++++++++++++-- module/Rest/lang/es.mo | Bin 2645 -> 2793 bytes module/Rest/lang/es.po | 8 ++++++-- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/module/CLI/lang/es.mo b/module/CLI/lang/es.mo index 742fbbe6d0b6949aa17586c6b731b88f683d640f..472ff44716283ef20e0f7be572b93c5cc6c6daa4 100644 GIT binary patch delta 2676 zcmb`{TWl0n9LMp~inLO0ML{Y>j@1G}ZA$?Q1-Vp;U;&|kfN#UjYHN0pv@np|_=6cTm z{QqZnXw^_x@#3^`Lx$EvucyCPY0Q3Hc{5+ME8~rsfxqMJSY2gID_UHQ+wf65j_dI< zuEUmUV;V3*jr&UC$Eb16;#y;h=4TEb$6pq8Q$^G+* zKcb$yg16y>3C3s*b8rea;w)T>3TPYFGQY`jpotISbNC|Oi8E`Ac^KP~NX;SC3O`3p zR6}$;Y8vq-Y{gq~3EqVpP=V}2Vlu_#{1{r!Pht)8n-LE9m@|ASknhmJOW2GXCmF-X zJi(U+Jc8}`8aClMY(c%e1zM3Rm?H8qNBL5~FQZEK0nW!OSY$D#j{L+c!Reg0qEfsb zRl{y|NYWaKsAL0BrT!5=<%j4}sW%OX;>!>~Onewj(&vQWmR8V*p zZ6=~7uERTUCEkm>a0(mrS=5S-lMbc&UDO`>8rNVgt9T51a5KJ-3iLX*VJEAYgM}I7 ze<24$T$qigQ7iZXw_^)oD&>b!o9|U5R`Ve$uu;@|7g3w-408XGv@e#65%m`-j8&oP+@R9;Ngj(rKxCY-${0X($ z=CS-e*o))wG%AB%qGf*bJqO#ka2*%nW_F4uETSfO0ks!SpngUpsFHn!op=@bm}M;M zFz&+Lcma>%5?kH_pW^+TPhi7PC9@VA^!wk#K_eHQMuIkPpdK7SMS2ZYt7@W931*?i zPE^1ttU$fHmEKHOfMZP?Rhd*))l}|Pbd|4z`QuftL>x5QQCjK+p$g!eV z3C5Z>>^);AsQo}P<7{b9JV2jESJNICYb3}NSM$XJ%|mppR;`ELMPIBFn(9m0voVlo=1Hn|k1zljt5 z#?|!iX6gEq+Dsp7+WBkgTj=^LZly1ySJJilmeQ-~>*yQk#+Ke0e{Nh?+V+J(zwdf( zlZ`Td-sZD~UOV7tvv#j%3wh7Ael8z*j%$Oo?e%hA=tO?5*E%+z3Bt&x0@o`&RWrA8 z@z!LZ@(>M;`3B2)PB!G_1B{~4?A`;`$puly3+>LHO@s3%)t81RbygJ9VX)s0Wc*Yn z2_qhyNB)YX-^u!Jv)!1s`Cz{n8@b)^4NNIC{b^Wv#db!9eb`41E~`!hNLtp zVtXJws|MvU+zTe;f-%;05k$9Vx++nmP zVk~he$!r7uO6G@lF2!sFp2v}R3&Z#lD=|IQY$ev?eC)(p{D}Ejl-Bp$X3veN=d|NI zvxIfgso_8m4#mmoW?48B{aBBqaV3tzt*E)Rd*=^(oZ#j z>(T;!1C}CdunN>TjREqXLuWGwreHf}umK&^3huHIl+rh-J@Ol?u!elE#=W=*-=QWL zB2Ak34$Q@~I1L}*IQ)jqn8~b{VM{jo=a^mQfHv17%)}nl#J;0$43GwGreb6cHXU_e z9Cdz;=T6jh$5GdvLZ$u!YJsniM_MxJp(>V>pwmI85DV}oF2@hJ5M#_r6WhlRNwH4U z02ffZ_zEh;&rvJ>jjC-f>C^eC7{fSf;611k97n2Yi8FLI(z%36VIIrY%+{k;*oIYj z(DN~BQw`x=O2I{_j4j6?uEQm`4Yb(N@zwV_I}AC=(~7{ogm(fi*+$HxImXuF8yR%jX)KtT; z+v9`eDHaf=L_N_&sFo^&N>ofxJvU2WX+%gJS{>0qC?m58ZALX^rie(ec(*Q%IFYC# z{%;dFpcI$=b3$J}?U%o8E*+JF{pU8NT20>-y}En~`n7WYB`HzbjdKWXCe^8$m_ex3 z_Thd;wX5|8\n" "Language-Team: \n" "Language: es_ES\n" @@ -107,6 +107,24 @@ msgstr "La URL larga a procesar" msgid "Tags to apply to the new short URL" msgstr "Etiquetas a aplicar a la nueva URL acortada" +msgid "" +"The date from which this short URL will be valid. If someone tries to access " +"it before this date, it will not be found." +msgstr "" +"La fecha desde la cual será válida esta URL acortada. Si alguien intenta " +"acceder a ella antes de esta fecha, no será encontrada." + +msgid "" +"The date until which this short URL will be valid. If someone tries to " +"access it after this date, it will not be found." +msgstr "" +"La fecha hasta la cual será válida está URL acortada. Si alguien intenta " +"acceder a ella después de esta fecha, no será encontrada." + +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?:" msgstr "No se ha proporcionado una URL larga. ¿Qué URL deseas acortar?" @@ -123,6 +141,14 @@ msgstr "URL generada:" msgid "Provided URL \"%s\" is invalid. Try with a different one." msgstr "La URL proporcionada \"%s\" e inválida. Prueba con una diferente." +#, php-format +msgid "" +"Provided slug \"%s\" is already in use by another URL. Try with a different " +"one." +msgstr "" +"El slug proporcionado \"%s\" ya está siendo usado para otra URL. Prueba con " +"uno diferente." + msgid "Returns the detailed visits information for provided short code" msgstr "" "Devuelve la información detallada de visitas para el código corto " diff --git a/module/Rest/lang/es.mo b/module/Rest/lang/es.mo index 666e94007e6931625f846733f3b7652e2c1da539..f81f6971c73fee650fb517556d5e9daaf42f605d 100644 GIT binary patch delta 481 zcmXxgJxjw-6b9gvHrUpWR;0BE#U6)(6dJWCmO}9ZJGcdLbBgA+24Vt9qXvW`IQj*} z&BY%e2z3&iy6N0iba3dZf510wz3}Ea_rN`un{Kj~YJN<~cZ4;GWRQC#9n=y&Sf_9r z`Y;P`;R1Yum+%uZJPHvh@EWebSGWa#hy7BRD2II=R^Sz!A!^b)icK`W;U1iq2Lmo( z0*7DVEc$OFLTj>}z~rV{h5P;Qm|;U5}-tKd84-(f|Me delta 348 zcmXxgziPrz6vy$C7^|si3-uobv5_LQLo1qAMQ~7j0C90Bbyf)7y0z*9bhF~%;tM#G z%!RI{iw-UwT%3G{EPgN61HXLkxo|G`W*c9{2J3TUEYulEkq?q2md1zbVgh@3i9^ic z4Bv5qHa_Vhci6`VoMQtw|NGYgkt}^1FYpcPA_JMQcx2-b)nL)!G7Pzxrhh#Wxy31# z@dv+94~bOp8%yY84W01*ycbmc64iXv+&{2}E&2{VtEYUjxM#yY7U3~+LR6_gqKb1R kLQaY0Rkfz>{RVEdP~Nfgg^Dxsjm%-~T?JjkTbVI!2d@4mNB{r; diff --git a/module/Rest/lang/es.po b/module/Rest/lang/es.po index 16de915c..fa02fd1f 100644 --- a/module/Rest/lang/es.po +++ b/module/Rest/lang/es.po @@ -1,8 +1,8 @@ msgid "" msgstr "" "Project-Id-Version: Shlink 1.0\n" -"POT-Creation-Date: 2017-10-13 12:30+0200\n" -"PO-Revision-Date: 2017-10-13 12:30+0200\n" +"POT-Creation-Date: 2017-10-21 20:20+0200\n" +"PO-Revision-Date: 2017-10-21 20:20+0200\n" "Last-Translator: Alejandro Celaya \n" "Language-Team: \n" "Language: es_ES\n" @@ -32,6 +32,10 @@ msgstr "No se ha proporcionado una URL" msgid "Provided URL %s is invalid. Try with a different one." msgstr "La URL proporcionada \"%s\" es inválida. Prueba con una diferente." +#, php-format +msgid "Provided slug %s is already in use. Try with a different one." +msgstr "El slug proporcionado \"%s\" ya está en uso. Prueba con uno diferente." + msgid "Unexpected error occurred" msgstr "Ocurrió un error inesperado" From 7f4678261ef559dd5d0fefbcd2132443dc9c71dc Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 21 Oct 2017 20:27:19 +0200 Subject: [PATCH 41/64] Added first tasks to the CHANGELOG --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7aa00a38..7755706c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ ## CHANGELOG +### 1.6.0 + +**Features** + +* [44: Consider allowing to set custom slugs instead of generating a short code](https://github.com/shlinkio/shlink/issues/44) +* [47: Allow to limit short codes availability by date range](https://github.com/shlinkio/shlink/issues/47) + +**Enhancements:** + +* [86: Drop support for PHP 5](https://github.com/shlinkio/shlink/issues/86) +* [101: Make actions just capture very specific exceptions, and let the ErrorHandler catch any other exception](https://github.com/shlinkio/shlink/issues/101) +* [104: Use different templates for requested-short-code-does-not-exist and route-could-not-be-match](https://github.com/shlinkio/shlink/issues/104) + +**Tasks** + +* [99: Replace AnnotatedFactory by ConfigAbstractFactory](https://github.com/shlinkio/shlink/issues/99) +* [100: Replace twig by plates](https://github.com/shlinkio/shlink/issues/100) +* [102: Improve coding standards strictness](https://github.com/shlinkio/shlink/issues/102) + +**Bugs** + +* [103: Make NotFoundDelegate return proper content types based on accepted content](https://github.com/shlinkio/shlink/issues/103) + ### 1.5.0 **Enhancements:** From af7c11665c64cd06686c157132f9a253747ccf70 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 22 Oct 2017 09:00:32 +0200 Subject: [PATCH 42/64] Added max_visits field to short_urls --- data/migrations/Version20171022064541.php | 38 +++++++++++++++++++ module/Core/src/Entity/ShortUrl.php | 37 +++++++++++++++++- module/Core/src/Service/VisitsTracker.php | 19 ++++++---- .../src/Service/VisitsTrackerInterface.php | 5 ++- .../Core/test/Service/VisitsTrackerTest.php | 4 +- 5 files changed, 89 insertions(+), 14 deletions(-) create mode 100644 data/migrations/Version20171022064541.php diff --git a/data/migrations/Version20171022064541.php b/data/migrations/Version20171022064541.php new file mode 100644 index 00000000..bac05c61 --- /dev/null +++ b/data/migrations/Version20171022064541.php @@ -0,0 +1,38 @@ +getTable('short_urls'); + $shortUrls->addColumn('max_visits', Type::INTEGER, [ + 'unsigned' => true, + 'notnull' => false, + ]); + } + + /** + * @param Schema $schema + * @throws SchemaException + */ + public function down(Schema $schema) + { + $shortUrls = $schema->getTable('short_urls'); + $shortUrls->dropColumn('max_visits'); + } +} diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index 0f7ea621..6c9b5189 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -64,6 +64,11 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable * @ORM\Column(name="valid_until", type="datetime", nullable=true) */ protected $validUntil; + /** + * @var integer + * @ORM\Column(name="max_visits", type="integer", nullable=true) + */ + protected $maxVisits; /** * ShortUrl constructor. @@ -194,6 +199,34 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable return $this; } + public function getVisitsCount(): int + { + return count($this->visits); + } + + /** + * @return int + */ + public function getMaxVisits(): int + { + return $this->maxVisits; + } + + /** + * @param int $maxVisits + * @return $this|self + */ + public function setMaxVisits(int $maxVisits): self + { + $this->maxVisits = $maxVisits; + return $this; + } + + public function maxVisitsReached(): bool + { + return $this->maxVisits !== null && $this->maxVisits >= $this->getVisitsCount(); + } + /** * Specify data which should be serialized to JSON * @link http://php.net/manual/en/jsonserializable.jsonserialize.php @@ -206,8 +239,8 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable return [ 'shortCode' => $this->shortCode, 'originalUrl' => $this->originalUrl, - 'dateCreated' => isset($this->dateCreated) ? $this->dateCreated->format(\DateTime::ATOM) : null, - 'visitsCount' => count($this->visits), + 'dateCreated' => $this->dateCreated !== null ? $this->dateCreated->format(\DateTime::ATOM) : null, + 'visitsCount' => $this->getVisitsCount(), 'tags' => $this->tags->toArray(), ]; } diff --git a/module/Core/src/Service/VisitsTracker.php b/module/Core/src/Service/VisitsTracker.php index 360b0d2b..38e44fea 100644 --- a/module/Core/src/Service/VisitsTracker.php +++ b/module/Core/src/Service/VisitsTracker.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Service; +use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; use Psr\Http\Message\ServerRequestInterface; use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; @@ -14,7 +15,7 @@ use Shlinkio\Shlink\Core\Repository\VisitRepository; class VisitsTracker implements VisitsTrackerInterface { /** - * @var EntityManagerInterface + * @var EntityManagerInterface|EntityManager */ private $em; @@ -41,24 +42,25 @@ class VisitsTracker implements VisitsTrackerInterface ->setUserAgent($request->getHeaderLine('User-Agent')) ->setReferer($request->getHeaderLine('Referer')) ->setRemoteAddr($this->findOutRemoteAddr($request)); + $this->em->persist($visit); - $this->em->flush(); + $this->em->flush($visit); } /** * @param ServerRequestInterface $request - * @return string + * @return string|null */ - protected function findOutRemoteAddr(ServerRequestInterface $request) + private function findOutRemoteAddr(ServerRequestInterface $request) { $forwardedFor = $request->getHeaderLine('X-Forwarded-For'); if (empty($forwardedFor)) { $serverParams = $request->getServerParams(); - return isset($serverParams['REMOTE_ADDR']) ? $serverParams['REMOTE_ADDR'] : null; + return $serverParams['REMOTE_ADDR'] ?? null; } $ips = explode(',', $forwardedFor); - return $ips[0]; + return $ips[0] ?? null; } /** @@ -67,14 +69,15 @@ class VisitsTracker implements VisitsTrackerInterface * @param $shortCode * @param DateRange $dateRange * @return Visit[] + * @throws InvalidArgumentException */ - public function info($shortCode, DateRange $dateRange = null) + public function info($shortCode, DateRange $dateRange = null): array { /** @var ShortUrl $shortUrl */ $shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([ 'shortCode' => $shortCode, ]); - if (! isset($shortUrl)) { + if ($shortUrl === null) { throw new InvalidArgumentException(sprintf('Short code "%s" not found', $shortCode)); } diff --git a/module/Core/src/Service/VisitsTrackerInterface.php b/module/Core/src/Service/VisitsTrackerInterface.php index 8dfa6145..d3295f37 100644 --- a/module/Core/src/Service/VisitsTrackerInterface.php +++ b/module/Core/src/Service/VisitsTrackerInterface.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Service; use Psr\Http\Message\ServerRequestInterface; +use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Entity\Visit; @@ -14,7 +15,6 @@ interface VisitsTrackerInterface * * @param string $shortCode * @param ServerRequestInterface $request - * @return */ public function track($shortCode, ServerRequestInterface $request); @@ -24,6 +24,7 @@ interface VisitsTrackerInterface * @param $shortCode * @param DateRange $dateRange * @return Visit[] + * @throws InvalidArgumentException */ - public function info($shortCode, DateRange $dateRange = null); + public function info($shortCode, DateRange $dateRange = null): array; } diff --git a/module/Core/test/Service/VisitsTrackerTest.php b/module/Core/test/Service/VisitsTrackerTest.php index a3d475c4..f931c4e4 100644 --- a/module/Core/test/Service/VisitsTrackerTest.php +++ b/module/Core/test/Service/VisitsTrackerTest.php @@ -42,7 +42,7 @@ class VisitsTrackerTest extends TestCase $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal())->shouldBeCalledTimes(1); $this->em->persist(Argument::any())->shouldBeCalledTimes(1); - $this->em->flush()->shouldBeCalledTimes(1); + $this->em->flush(Argument::type(Visit::class))->shouldBeCalledTimes(1); $this->visitsTracker->track($shortCode, ServerRequestFactory::fromGlobals()); } @@ -63,7 +63,7 @@ class VisitsTrackerTest extends TestCase $visit = $args[0]; $test->assertEquals('4.3.2.1', $visit->getRemoteAddr()); })->shouldBeCalledTimes(1); - $this->em->flush()->shouldBeCalledTimes(1); + $this->em->flush(Argument::type(Visit::class))->shouldBeCalledTimes(1); $this->visitsTracker->track($shortCode, ServerRequestFactory::fromGlobals( ['REMOTE_ADDR' => '1.2.3.4'] From cb23d38b38031584685a5e11705de36e8e7d6bab Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 22 Oct 2017 09:15:37 +0200 Subject: [PATCH 43/64] Used maxVisits field when creating or fetching a ShortUrl --- .../src/Command/Shortcode/GenerateShortcodeCommand.php | 7 ++++++- module/Core/src/Entity/ShortUrl.php | 2 +- module/Core/src/Repository/ShortUrlRepository.php | 4 +++- module/Core/src/Service/UrlShortener.php | 9 ++++++--- module/Core/src/Service/UrlShortenerInterface.php | 4 +++- module/Rest/src/Action/CreateShortcodeAction.php | 3 ++- 6 files changed, 21 insertions(+), 8 deletions(-) diff --git a/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php b/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php index 9df42f20..e685e876 100644 --- a/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php +++ b/module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php @@ -65,6 +65,9 @@ class GenerateShortcodeCommand extends Command )) ->addOption('customSlug', 'c', InputOption::VALUE_REQUIRED, $this->translator->translate( 'If provided, this slug will be used instead of generating a short code' + )) + ->addOption('maxVisits', 'm', InputOption::VALUE_REQUIRED, $this->translator->translate( + 'This will limit the number of visits for this short URL.' )); } @@ -99,6 +102,7 @@ class GenerateShortcodeCommand extends Command } $tags = $processedTags; $customSlug = $input->getOption('customSlug'); + $maxVisits = $input->getOption('maxVisits'); try { if (! isset($longUrl)) { @@ -111,7 +115,8 @@ class GenerateShortcodeCommand extends Command $tags, $this->getOptionalDate($input, 'validSince'), $this->getOptionalDate($input, 'validUntil'), - $customSlug + $customSlug, + $maxVisits !== null ? (int) $maxVisits : null ); $shortUrl = (new Uri())->withPath($shortCode) ->withScheme($this->domainConfig['schema']) diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index 6c9b5189..1431f465 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -224,7 +224,7 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable public function maxVisitsReached(): bool { - return $this->maxVisits !== null && $this->maxVisits >= $this->getVisitsCount(); + return $this->maxVisits !== null && $this->getVisitsCount() >= $this->maxVisits; } /** diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index 84d07511..7223b188 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -145,6 +145,8 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI ->setParameter('now', $now) ->setMaxResults(1); - return $qb->getQuery()->getOneOrNullResult(); + /** @var ShortUrl|null $result */ + $result = $qb->getQuery()->getOneOrNullResult(); + return $result === null || $result->maxVisitsReached() ? null : $result; } } diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index e6cdf947..e294fe70 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -69,6 +69,7 @@ class UrlShortener implements UrlShortenerInterface * @param \DateTime|null $validSince * @param \DateTime|null $validUntil * @param string|null $customSlug + * @param int|null $maxVisits * @return string * @throws NonUniqueSlugException * @throws InvalidUrlException @@ -79,7 +80,8 @@ class UrlShortener implements UrlShortenerInterface array $tags = [], \DateTime $validSince = null, \DateTime $validUntil = null, - string $customSlug = null + string $customSlug = null, + int $maxVisits = null ): string { // If the url already exists in the database, just return its short code $shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([ @@ -101,7 +103,8 @@ class UrlShortener implements UrlShortenerInterface $shortUrl = new ShortUrl(); $shortUrl->setOriginalUrl((string) $url) ->setValidSince($validSince) - ->setValidUntil($validUntil); + ->setValidUntil($validUntil) + ->setMaxVisits($maxVisits); $this->em->persist($shortUrl); $this->em->flush(); @@ -146,7 +149,7 @@ class UrlShortener implements UrlShortenerInterface * @param int $id * @return string */ - private function convertAutoincrementIdToShortCode($id) + private function convertAutoincrementIdToShortCode($id): string { $id = ((int) $id) + 200000; // Increment the Id so that the generated shortcode is not too short $length = strlen($this->chars); diff --git a/module/Core/src/Service/UrlShortenerInterface.php b/module/Core/src/Service/UrlShortenerInterface.php index aa346756..17859a81 100644 --- a/module/Core/src/Service/UrlShortenerInterface.php +++ b/module/Core/src/Service/UrlShortenerInterface.php @@ -20,6 +20,7 @@ interface UrlShortenerInterface * @param \DateTime|null $validSince * @param \DateTime|null $validUntil * @param string|null $customSlug + * @param int|null $maxVisits * @return string * @throws NonUniqueSlugException * @throws InvalidUrlException @@ -30,7 +31,8 @@ interface UrlShortenerInterface array $tags = [], \DateTime $validSince = null, \DateTime $validUntil = null, - string $customSlug = null + string $customSlug = null, + int $maxVisits = null ): string; /** diff --git a/module/Rest/src/Action/CreateShortcodeAction.php b/module/Rest/src/Action/CreateShortcodeAction.php index 790c361b..8edcd499 100644 --- a/module/Rest/src/Action/CreateShortcodeAction.php +++ b/module/Rest/src/Action/CreateShortcodeAction.php @@ -66,7 +66,8 @@ class CreateShortcodeAction extends AbstractRestAction (array) ($postData['tags'] ?? []), $this->getOptionalDate($postData, 'validSince'), $this->getOptionalDate($postData, 'validUntil'), - $customSlug + $customSlug, + isset($postData['maxVisits']) ? (int) $postData['maxVisits'] : null ); $shortUrl = (new Uri())->withPath($shortCode) ->withScheme($this->domainConfig['schema']) From 9fb07f40397077f90a2b27cccedeb68b89f6e394 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 22 Oct 2017 09:17:19 +0200 Subject: [PATCH 44/64] Fixed tests --- module/Core/src/Entity/ShortUrl.php | 8 ++++---- module/Rest/test/Action/CreateShortcodeActionTest.php | 11 ++++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index 1431f465..3039bd19 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -205,18 +205,18 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable } /** - * @return int + * @return int|null */ - public function getMaxVisits(): int + public function getMaxVisits() { return $this->maxVisits; } /** - * @param int $maxVisits + * @param int|null $maxVisits * @return $this|self */ - public function setMaxVisits(int $maxVisits): self + public function setMaxVisits($maxVisits): self { $this->maxVisits = $maxVisits; return $this; diff --git a/module/Rest/test/Action/CreateShortcodeActionTest.php b/module/Rest/test/Action/CreateShortcodeActionTest.php index ce297105..2c92f030 100644 --- a/module/Rest/test/Action/CreateShortcodeActionTest.php +++ b/module/Rest/test/Action/CreateShortcodeActionTest.php @@ -87,9 +87,14 @@ class CreateShortcodeActionTest extends TestCase */ public function nonUniqueSlugReturnsError() { - $this->urlShortener->urlToShortCode(Argument::type(Uri::class), Argument::type('array'), null, null, 'foo') - ->willThrow(NonUniqueSlugException::class) - ->shouldBeCalledTimes(1); + $this->urlShortener->urlToShortCode( + Argument::type(Uri::class), + Argument::type('array'), + null, + null, + 'foo', + Argument::cetera() + )->willThrow(NonUniqueSlugException::class)->shouldBeCalledTimes(1); $request = ServerRequestFactory::fromGlobals()->withParsedBody([ 'longUrl' => 'http://www.domain.com/foo/bar', From 5a500a00d7c0be2eed77de5c952b6dfcd26346d6 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 22 Oct 2017 09:18:28 +0200 Subject: [PATCH 45/64] Added another feature to CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7755706c..1cbba834 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * [44: Consider allowing to set custom slugs instead of generating a short code](https://github.com/shlinkio/shlink/issues/44) * [47: Allow to limit short codes availability by date range](https://github.com/shlinkio/shlink/issues/47) +* [48: Allow to limit the number of visits to a short code](https://github.com/shlinkio/shlink/issues/48) **Enhancements:** From d7b7db670f13a20afe24f3249fdafba69ed88463 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 22 Oct 2017 18:03:35 +0200 Subject: [PATCH 46/64] Created first common elements for functional tests --- composer.json | 6 +- func_tests_bootstrap.php | 29 ++++++++++ .../test-func/DbUnit/DatabaseTestCase.php | 58 +++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 func_tests_bootstrap.php create mode 100644 module/Common/test-func/DbUnit/DatabaseTestCase.php diff --git a/composer.json b/composer.json index 31854436..4e0d2e82 100644 --- a/composer.json +++ b/composer.json @@ -46,6 +46,7 @@ }, "require-dev": { "filp/whoops": "^2.0", + "phpunit/dbunit": "^3.0", "phpunit/phpunit": "^6.0", "slevomat/coding-standard": "^4.0", "squizlabs/php_codesniffer": "^3.1", @@ -69,7 +70,10 @@ "ShlinkioTest\\Shlink\\CLI\\": "module/CLI/test", "ShlinkioTest\\Shlink\\Rest\\": "module/Rest/test", "ShlinkioTest\\Shlink\\Core\\": "module/Core/test", - "ShlinkioTest\\Shlink\\Common\\": "module/Common/test" + "ShlinkioTest\\Shlink\\Common\\": [ + "module/Common/test", + "module/Common/test-func" + ] } }, "scripts": { diff --git a/func_tests_bootstrap.php b/func_tests_bootstrap.php new file mode 100644 index 00000000..e3c624da --- /dev/null +++ b/func_tests_bootstrap.php @@ -0,0 +1,29 @@ +setAllowOverride(true); +$config = $sm->get('config'); +$config['entity_manager']['connection'] = [ + 'driver' => 'pdo_sqlite', + 'memory' => true, +]; +$sm->setService('config', $config); + +$process = new Process('vendor/bin/doctrine-migrations migrations:migrate --no-interaction -q', __DIR__); +$process->inheritEnvironmentVariables() + ->setTimeout(60 * 5) // 5 minutes + ->mustRun(); + +DatabaseTestCase::$em = $sm->get(EntityManagerInterface::class); diff --git a/module/Common/test-func/DbUnit/DatabaseTestCase.php b/module/Common/test-func/DbUnit/DatabaseTestCase.php new file mode 100644 index 00000000..58a10bdc --- /dev/null +++ b/module/Common/test-func/DbUnit/DatabaseTestCase.php @@ -0,0 +1,58 @@ +getConnection()->getWrappedConnection(); + return self::$conn = $this->createDefaultDBConnection($pdo, static::$em->getConnection()->getDatabase()); + } + + public function getDataSet(): DataSet + { + return $this->createArrayDataSet([]); + } + + protected function getEntityManager(): EntityManagerInterface + { + return static::$em; + } + + public function tearDown() + { + // Empty all entity tables defined by this test after each test + foreach (static::ENTITIES_TO_EMPTY as $entityClass) { + $qb = $this->getEntityManager()->createQueryBuilder(); + $qb->delete($entityClass, 'x'); + $qb->getQuery()->execute(); + } + + // Clear entity manager + $this->getEntityManager()->clear(); + } +} From e282521040508fd8f4d63686dbf414e24d21a86f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 23 Oct 2017 11:11:26 +0200 Subject: [PATCH 47/64] Updated migrations to prevent duplication when running after orm schema tool --- data/migrations/Version20171021093246.php | 8 ++++++++ data/migrations/Version20171022064541.php | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/data/migrations/Version20171021093246.php b/data/migrations/Version20171021093246.php index a9c39ebb..d0aff69b 100644 --- a/data/migrations/Version20171021093246.php +++ b/data/migrations/Version20171021093246.php @@ -20,6 +20,10 @@ class Version20171021093246 extends AbstractMigration public function up(Schema $schema) { $shortUrls = $schema->getTable('short_urls'); + if ($shortUrls->hasColumn('value_since')) { + return; + } + $shortUrls->addColumn('valid_since', Type::DATETIME, [ 'notnull' => false, ]); @@ -35,6 +39,10 @@ class Version20171021093246 extends AbstractMigration public function down(Schema $schema) { $shortUrls = $schema->getTable('short_urls'); + if (! $shortUrls->hasColumn('value_since')) { + return; + } + $shortUrls->dropColumn('valid_since'); $shortUrls->dropColumn('valid_until'); } diff --git a/data/migrations/Version20171022064541.php b/data/migrations/Version20171022064541.php index bac05c61..ef0447aa 100644 --- a/data/migrations/Version20171022064541.php +++ b/data/migrations/Version20171022064541.php @@ -20,6 +20,10 @@ class Version20171022064541 extends AbstractMigration public function up(Schema $schema) { $shortUrls = $schema->getTable('short_urls'); + if ($shortUrls->hasColumn('max_visits')) { + return; + } + $shortUrls->addColumn('max_visits', Type::INTEGER, [ 'unsigned' => true, 'notnull' => false, @@ -33,6 +37,10 @@ class Version20171022064541 extends AbstractMigration public function down(Schema $schema) { $shortUrls = $schema->getTable('short_urls'); + if (! $shortUrls->hasColumn('max_visits')) { + return; + } + $shortUrls->dropColumn('max_visits'); } } From c2feffa50c6ded51134f1fae55d10845843778e3 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 23 Oct 2017 11:20:55 +0200 Subject: [PATCH 48/64] First version of functional tests working --- composer.json | 8 ++- config/cli-config.php | 24 ++++++++- func_tests_bootstrap.php | 14 ++---- .../Repository/TagRepositoryTest.php | 49 +++++++++++++++++++ phpunit-func.xml | 15 ++++++ 5 files changed, 96 insertions(+), 14 deletions(-) create mode 100644 module/Core/test-func/Repository/TagRepositoryTest.php create mode 100644 phpunit-func.xml diff --git a/composer.json b/composer.json index 4e0d2e82..2443e2ff 100644 --- a/composer.json +++ b/composer.json @@ -69,7 +69,10 @@ "psr-4": { "ShlinkioTest\\Shlink\\CLI\\": "module/CLI/test", "ShlinkioTest\\Shlink\\Rest\\": "module/Rest/test", - "ShlinkioTest\\Shlink\\Core\\": "module/Core/test", + "ShlinkioTest\\Shlink\\Core\\": [ + "module/Core/test", + "module/Core/test-func" + ], "ShlinkioTest\\Shlink\\Common\\": [ "module/Common/test", "module/Common/test-func" @@ -85,7 +88,8 @@ "cs-fix": "phpcbf", "serve": "php -S 0.0.0.0:8000 -t public/", "test": "phpunit --coverage-clover build/clover.xml", - "pretty-test": "phpunit --coverage-html build/coverage" + "pretty-test": "phpunit --coverage-html build/coverage", + "func-test": "phpunit -c phpunit-func.xml" }, "config": { "process-timeout": 0, diff --git a/config/cli-config.php b/config/cli-config.php index fefcca28..b90e9ddf 100644 --- a/config/cli-config.php +++ b/config/cli-config.php @@ -4,9 +4,31 @@ declare(strict_types=1); use Doctrine\ORM\EntityManager; use Doctrine\ORM\Tools\Console\ConsoleRunner; use Interop\Container\ContainerInterface; +use Zend\ServiceManager\ServiceManager; -/** @var ContainerInterface $container */ +$isTest = false; +foreach ($_SERVER['argv'] as $i => $arg) { + if ($arg === '--test') { + unset($_SERVER['argv'][$i]); + $isTest = true; + break; + } +} + +/** @var ContainerInterface|ServiceManager $container */ $container = include __DIR__ . '/container.php'; + +// If in testing env, override DB connection to use an in-memory sqlite database +if ($isTest) { + $container->setAllowOverride(true); + $config = $container->get('config'); + $config['entity_manager']['connection'] = [ + 'driver' => 'pdo_sqlite', + 'memory' => true, + ]; + $container->setService('config', $config); +} + /** @var EntityManager $em */ $em = $container->get(EntityManager::class); diff --git a/func_tests_bootstrap.php b/func_tests_bootstrap.php index e3c624da..2f864a82 100644 --- a/func_tests_bootstrap.php +++ b/func_tests_bootstrap.php @@ -1,7 +1,6 @@ setAllowOverride(true); -$config = $sm->get('config'); -$config['entity_manager']['connection'] = [ - 'driver' => 'pdo_sqlite', - 'memory' => true, -]; -$sm->setService('config', $config); -$process = new Process('vendor/bin/doctrine-migrations migrations:migrate --no-interaction -q', __DIR__); +// Create database +$process = new Process('vendor/bin/doctrine orm:schema-tool:create --no-interaction -q --test', __DIR__); $process->inheritEnvironmentVariables() - ->setTimeout(60 * 5) // 5 minutes ->mustRun(); -DatabaseTestCase::$em = $sm->get(EntityManagerInterface::class); +DatabaseTestCase::$em = $sm->get('em'); diff --git a/module/Core/test-func/Repository/TagRepositoryTest.php b/module/Core/test-func/Repository/TagRepositoryTest.php new file mode 100644 index 00000000..b6a96d85 --- /dev/null +++ b/module/Core/test-func/Repository/TagRepositoryTest.php @@ -0,0 +1,49 @@ +repo = $this->getEntityManager()->getRepository(Tag::class); + } + + /** + * @test + */ + public function deleteByNameDoesNothingWhenEmptyListIsProvided() + { + $this->assertEquals(0, $this->repo->deleteByName([])); + } + + /** + * @test + */ + public function allTagsWhichMatchNameAreDeleted() + { + $names = ['foo', 'bar', 'baz']; + $toDelete = ['foo', 'baz']; + + foreach ($names as $name) { + $this->getEntityManager()->persist(new Tag($name)); + } + $this->getEntityManager()->flush(); + + $this->assertEquals(2, $this->repo->deleteByName($toDelete)); + } +} diff --git a/phpunit-func.xml b/phpunit-func.xml new file mode 100644 index 00000000..d125e506 --- /dev/null +++ b/phpunit-func.xml @@ -0,0 +1,15 @@ + + + + ./module/*/test-func + + + + + + ./module/*/src/Repository + ./module/*/src/**/Repository + ./module/*/src/**/**/Repository + + + From c522879c6478d1565b5dfad1cfd1793bb6b6a110 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 23 Oct 2017 11:29:37 +0200 Subject: [PATCH 49/64] Updated composer check to ru functional tests too --- .travis.yml | 2 +- composer.json | 7 ++++--- .../Core/src/Repository/VisitRepository.php | 21 ++++++++++++------- .../Repository/VisitRepositoryInterface.php | 4 ++-- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index f9e19b89..e0490c63 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,6 @@ script: after_script: - wget https://scrutinizer-ci.com/ocular.phar - - php ocular.phar code-coverage:upload --format=php-clover build/clover.xml + - php ocular.phar code-coverage:upload --format=php-clover build/unit-clover.xml sudo: false diff --git a/composer.json b/composer.json index 2443e2ff..f0ee6e27 100644 --- a/composer.json +++ b/composer.json @@ -82,14 +82,15 @@ "scripts": { "check": [ "@cs", - "@test" + "@test", + "@func-test" ], "cs": "phpcs", "cs-fix": "phpcbf", "serve": "php -S 0.0.0.0:8000 -t public/", - "test": "phpunit --coverage-clover build/clover.xml", + "test": "phpunit --coverage-clover build/unit-clover.xml", "pretty-test": "phpunit --coverage-html build/coverage", - "func-test": "phpunit -c phpunit-func.xml" + "func-test": "phpunit -c phpunit-func.xml --coverage-clover build/func-clover.xml" }, "config": { "process-timeout": 0, diff --git a/module/Core/src/Repository/VisitRepository.php b/module/Core/src/Repository/VisitRepository.php index 2e034406..e77d5a43 100644 --- a/module/Core/src/Repository/VisitRepository.php +++ b/module/Core/src/Repository/VisitRepository.php @@ -13,7 +13,7 @@ class VisitRepository extends EntityRepository implements VisitRepositoryInterfa /** * @return Visit[] */ - public function findUnlocatedVisits() + public function findUnlocatedVisits(): array { $qb = $this->createQueryBuilder('v'); $qb->where($qb->expr()->isNull('v.visitLocation')); @@ -22,15 +22,20 @@ class VisitRepository extends EntityRepository implements VisitRepositoryInterfa } /** - * @param ShortUrl|int $shortUrl + * @param ShortUrl|int $shortUrlOrId * @param DateRange|null $dateRange * @return Visit[] */ - public function findVisitsByShortUrl($shortUrl, DateRange $dateRange = null) + public function findVisitsByShortUrl($shortUrlOrId, DateRange $dateRange = null): array { - $shortUrl = $shortUrl instanceof ShortUrl - ? $shortUrl - : $this->getEntityManager()->find(ShortUrl::class, $shortUrl); + /** @var ShortUrl|null $shortUrl */ + $shortUrl = $shortUrlOrId instanceof ShortUrl + ? $shortUrlOrId + : $this->getEntityManager()->find(ShortUrl::class, $shortUrlOrId); + + if ($shortUrl === null) { + return []; + } $qb = $this->createQueryBuilder('v'); $qb->where($qb->expr()->eq('v.shortUrl', ':shortUrl')) @@ -38,11 +43,11 @@ class VisitRepository extends EntityRepository implements VisitRepositoryInterfa ->orderBy('v.date', 'DESC') ; // Apply date range filtering - if (! empty($dateRange->getStartDate())) { + if ($dateRange !== null && $dateRange->getStartDate() !== null) { $qb->andWhere($qb->expr()->gte('v.date', ':startDate')) ->setParameter('startDate', $dateRange->getStartDate()); } - if (! empty($dateRange->getEndDate())) { + if ($dateRange !== null && $dateRange->getEndDate() !== null) { $qb->andWhere($qb->expr()->lte('v.date', ':endDate')) ->setParameter('endDate', $dateRange->getEndDate()); } diff --git a/module/Core/src/Repository/VisitRepositoryInterface.php b/module/Core/src/Repository/VisitRepositoryInterface.php index 4b562d1d..4ee3ebde 100644 --- a/module/Core/src/Repository/VisitRepositoryInterface.php +++ b/module/Core/src/Repository/VisitRepositoryInterface.php @@ -13,12 +13,12 @@ interface VisitRepositoryInterface extends ObjectRepository /** * @return Visit[] */ - public function findUnlocatedVisits(); + public function findUnlocatedVisits(): array; /** * @param ShortUrl|int $shortUrl * @param DateRange|null $dateRange * @return Visit[] */ - public function findVisitsByShortUrl($shortUrl, DateRange $dateRange = null); + public function findVisitsByShortUrl($shortUrl, DateRange $dateRange = null): array; } From a002c6018304208a5f5edfe4550a43fa846d1c8d Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 23 Oct 2017 11:37:16 +0200 Subject: [PATCH 50/64] Updated travis config so that it loads apcu extension --- .travis-php.ini | 1 - .travis.yml | 4 +++- data/infra/travis-php/apcu.ini | 1 + data/infra/travis-php/memcached.ini | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) delete mode 100644 .travis-php.ini create mode 100644 data/infra/travis-php/apcu.ini create mode 100644 data/infra/travis-php/memcached.ini diff --git a/.travis-php.ini b/.travis-php.ini deleted file mode 100644 index c9a2ff0c..00000000 --- a/.travis-php.ini +++ /dev/null @@ -1 +0,0 @@ -extension="memcached.so" \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index e0490c63..e66a102e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,9 @@ php: - 7.1 - 7.2 -before_install: phpenv config-add .travis-php.ini +before_install: + - phpenv config-add data/infra/travis-php/memcached.ini + - phpenv config-add data/infra/travis-php/apcu.ini before_script: - composer self-update diff --git a/data/infra/travis-php/apcu.ini b/data/infra/travis-php/apcu.ini new file mode 100644 index 00000000..4a2dd79a --- /dev/null +++ b/data/infra/travis-php/apcu.ini @@ -0,0 +1 @@ +extension="apcu.so" diff --git a/data/infra/travis-php/memcached.ini b/data/infra/travis-php/memcached.ini new file mode 100644 index 00000000..6b9b24f3 --- /dev/null +++ b/data/infra/travis-php/memcached.ini @@ -0,0 +1 @@ +extension="memcached.so" From c233e807c2b773a81047b5b3f9e901bbae21690f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 23 Oct 2017 11:42:13 +0200 Subject: [PATCH 51/64] Added sqlite extensions to PDO --- .travis.yml | 2 ++ data/infra/travis-php/pdo_sqlite.ini | 1 + data/infra/travis-php/sqlite.ini | 1 + 3 files changed, 4 insertions(+) create mode 100644 data/infra/travis-php/pdo_sqlite.ini create mode 100644 data/infra/travis-php/sqlite.ini diff --git a/.travis.yml b/.travis.yml index e66a102e..b5524c2e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,8 @@ php: before_install: - phpenv config-add data/infra/travis-php/memcached.ini - phpenv config-add data/infra/travis-php/apcu.ini + - phpenv config-add data/infra/travis-php/pdo_sqlite.ini + - phpenv config-add data/infra/travis-php/sqlite.ini before_script: - composer self-update diff --git a/data/infra/travis-php/pdo_sqlite.ini b/data/infra/travis-php/pdo_sqlite.ini new file mode 100644 index 00000000..5a50864a --- /dev/null +++ b/data/infra/travis-php/pdo_sqlite.ini @@ -0,0 +1 @@ +extension="pdo_sqlite.so" diff --git a/data/infra/travis-php/sqlite.ini b/data/infra/travis-php/sqlite.ini new file mode 100644 index 00000000..90fc1598 --- /dev/null +++ b/data/infra/travis-php/sqlite.ini @@ -0,0 +1 @@ +extension="sqlite.so" From 0bd9f1e19fef6d86d47c54ab2f798f5234f1c75b Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 23 Oct 2017 12:09:07 +0200 Subject: [PATCH 52/64] Removed wrong extension for travis --- .travis.yml | 1 - data/infra/travis-php/pdo_sqlite.ini | 1 - 2 files changed, 2 deletions(-) delete mode 100644 data/infra/travis-php/pdo_sqlite.ini diff --git a/.travis.yml b/.travis.yml index b5524c2e..7062a5a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,6 @@ php: before_install: - phpenv config-add data/infra/travis-php/memcached.ini - phpenv config-add data/infra/travis-php/apcu.ini - - phpenv config-add data/infra/travis-php/pdo_sqlite.ini - phpenv config-add data/infra/travis-php/sqlite.ini before_script: diff --git a/data/infra/travis-php/pdo_sqlite.ini b/data/infra/travis-php/pdo_sqlite.ini deleted file mode 100644 index 5a50864a..00000000 --- a/data/infra/travis-php/pdo_sqlite.ini +++ /dev/null @@ -1 +0,0 @@ -extension="pdo_sqlite.so" From 91442a3379a36a153ae014380fce663c72a2ddd2 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 23 Oct 2017 12:19:28 +0200 Subject: [PATCH 53/64] Ensured same testing database is used to generate with entities and to run functional tests --- .travis.yml | 1 - config/cli-config.php | 2 +- data/infra/travis-php/sqlite.ini | 1 - func_tests_bootstrap.php | 12 ++++++++++++ 4 files changed, 13 insertions(+), 3 deletions(-) delete mode 100644 data/infra/travis-php/sqlite.ini diff --git a/.travis.yml b/.travis.yml index 7062a5a4..e66a102e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,6 @@ php: before_install: - phpenv config-add data/infra/travis-php/memcached.ini - phpenv config-add data/infra/travis-php/apcu.ini - - phpenv config-add data/infra/travis-php/sqlite.ini before_script: - composer self-update diff --git a/config/cli-config.php b/config/cli-config.php index b90e9ddf..59373bbd 100644 --- a/config/cli-config.php +++ b/config/cli-config.php @@ -24,7 +24,7 @@ if ($isTest) { $config = $container->get('config'); $config['entity_manager']['connection'] = [ 'driver' => 'pdo_sqlite', - 'memory' => true, + 'path' => realpath(sys_get_temp_dir()) . '/shlink-tests.db', ]; $container->setService('config', $config); } diff --git a/data/infra/travis-php/sqlite.ini b/data/infra/travis-php/sqlite.ini deleted file mode 100644 index 90fc1598..00000000 --- a/data/infra/travis-php/sqlite.ini +++ /dev/null @@ -1 +0,0 @@ -extension="sqlite.so" diff --git a/func_tests_bootstrap.php b/func_tests_bootstrap.php index 2f864a82..6a4300f3 100644 --- a/func_tests_bootstrap.php +++ b/func_tests_bootstrap.php @@ -10,8 +10,20 @@ if (! file_exists('.env')) { touch('.env'); } +$shlinkDbPath = realpath(sys_get_temp_dir()) . '/shlink-tests.db'; +if (file_exists($shlinkDbPath)) { + unlink($shlinkDbPath); +} + /** @var ServiceManager $sm */ $sm = require __DIR__ . '/config/container.php'; +$sm->setAllowOverride(true); +$config = $sm->get('config'); +$config['entity_manager']['connection'] = [ + 'driver' => 'pdo_sqlite', + 'path' => $shlinkDbPath, +]; +$sm->setService('config', $config); // Create database $process = new Process('vendor/bin/doctrine orm:schema-tool:create --no-interaction -q --test', __DIR__); From a24688b92a3c0f3c5e72ef529747baef0b2fcf51 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 23 Oct 2017 12:33:46 +0200 Subject: [PATCH 54/64] Created VisitRepositoryTest --- .../Repository/VisitRepositoryTest.php | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 module/Core/test-func/Repository/VisitRepositoryTest.php diff --git a/module/Core/test-func/Repository/VisitRepositoryTest.php b/module/Core/test-func/Repository/VisitRepositoryTest.php new file mode 100644 index 00000000..b13e4053 --- /dev/null +++ b/module/Core/test-func/Repository/VisitRepositoryTest.php @@ -0,0 +1,80 @@ +repo = $this->getEntityManager()->getRepository(Visit::class); + } + + /** + * @test + */ + public function findUnlocatedVisitsReturnsProperVisits() + { + for ($i = 0; $i < 6; $i++) { + $visit = new Visit(); + + if ($i % 2 === 0) { + $location = new VisitLocation(); + $this->getEntityManager()->persist($location); + $visit->setVisitLocation($location); + } + + $this->getEntityManager()->persist($visit); + } + $this->getEntityManager()->flush(); + + $this->assertCount(3, $this->repo->findUnlocatedVisits()); + } + + /** + * @test + */ + public function findVisitsByShortUrlReturnsProperData() + { + $shortUrl = new ShortUrl(); + $shortUrl->setOriginalUrl(''); + $this->getEntityManager()->persist($shortUrl); + + for ($i = 0; $i < 6; $i++) { + $visit = new Visit(); + $visit->setShortUrl($shortUrl) + ->setDate(new \DateTime('2016-01-0' . ($i + 1))); + + $this->getEntityManager()->persist($visit); + } + $this->getEntityManager()->flush(); + + $this->assertCount(0, $this->repo->findVisitsByShortUrl('invalid')); + $this->assertCount(6, $this->repo->findVisitsByShortUrl($shortUrl->getId())); + $this->assertCount(2, $this->repo->findVisitsByShortUrl($shortUrl->getId(), new DateRange( + new \DateTime('2016-01-02'), + new \DateTime('2016-01-03') + ))); + $this->assertCount(4, $this->repo->findVisitsByShortUrl($shortUrl->getId(), new DateRange( + new \DateTime('2016-01-03') + ))); + } +} From 9577a4da4be68d30a0a37b18cb370deaf89b4f50 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 23 Oct 2017 12:36:03 +0200 Subject: [PATCH 55/64] Refactored ShortUrlRepository to make it more readable --- module/Core/src/Repository/ShortUrlRepository.php | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index 7223b188..6a1d1545 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -50,22 +50,14 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI $fieldName = is_array($orderBy) ? key($orderBy) : $orderBy; $order = is_array($orderBy) ? $orderBy[$fieldName] : 'ASC'; - if (in_array($fieldName, [ - 'visits', - 'visitsCount', - 'visitCount', - ], true)) { + if (in_array($fieldName, ['visits', 'visitsCount', 'visitCount'], true)) { $qb->addSelect('COUNT(v) AS totalVisits') ->leftJoin('s.visits', 'v') ->groupBy('s') ->orderBy('totalVisits', $order); return array_column($qb->getQuery()->getResult(), 0); - } elseif (in_array($fieldName, [ - 'originalUrl', - 'shortCode', - 'dateCreated', - ], true)) { + } elseif (in_array($fieldName, ['originalUrl', 'shortCode', 'dateCreated'], true)) { $qb->orderBy('s.' . $fieldName, $order); } From be3460049421478c1ff75c576eb2825d94f1445c Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 23 Oct 2017 12:51:06 +0200 Subject: [PATCH 56/64] Updated CI process to generate a merged coverage file --- .travis.yml | 3 ++- composer.json | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index e66a102e..4999fdb5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,8 @@ script: - composer check after_script: + - vendor/bin/phpcov merge build --clover build/clover.xml - wget https://scrutinizer-ci.com/ocular.phar - - php ocular.phar code-coverage:upload --format=php-clover build/unit-clover.xml + - php ocular.phar code-coverage:upload --format=php-clover build/clover.xml sudo: false diff --git a/composer.json b/composer.json index f0ee6e27..c98fc6d6 100644 --- a/composer.json +++ b/composer.json @@ -47,6 +47,7 @@ "require-dev": { "filp/whoops": "^2.0", "phpunit/dbunit": "^3.0", + "phpunit/phpcov": "^4.0", "phpunit/phpunit": "^6.0", "slevomat/coding-standard": "^4.0", "squizlabs/php_codesniffer": "^3.1", @@ -88,9 +89,9 @@ "cs": "phpcs", "cs-fix": "phpcbf", "serve": "php -S 0.0.0.0:8000 -t public/", - "test": "phpunit --coverage-clover build/unit-clover.xml", + "test": "phpunit --coverage-php build/coverage-unit.cov", "pretty-test": "phpunit --coverage-html build/coverage", - "func-test": "phpunit -c phpunit-func.xml --coverage-clover build/func-clover.xml" + "func-test": "phpunit -c phpunit-func.xml --coverage-php build/coverage-func.cov" }, "config": { "process-timeout": 0, From 633f3b728f9600701295401d936c9bc83f424b3a Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 23 Oct 2017 12:54:54 +0200 Subject: [PATCH 57/64] Created composer command to generate merge pretty code coverage --- composer.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c98fc6d6..39c59f47 100644 --- a/composer.json +++ b/composer.json @@ -91,7 +91,12 @@ "serve": "php -S 0.0.0.0:8000 -t public/", "test": "phpunit --coverage-php build/coverage-unit.cov", "pretty-test": "phpunit --coverage-html build/coverage", - "func-test": "phpunit -c phpunit-func.xml --coverage-php build/coverage-func.cov" + "func-test": "phpunit -c phpunit-func.xml --coverage-php build/coverage-func.cov", + "complete-pretty-test": [ + "@test", + "@func-test", + "phpcov merge build --html build/html" + ] }, "config": { "process-timeout": 0, From 501a933d2e58cbb6d3631f872e4814c91944434d Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 23 Oct 2017 13:03:23 +0200 Subject: [PATCH 58/64] Created ShortUrlRepositoryTest --- module/Core/src/Entity/ShortUrl.php | 11 ++++ .../Repository/ShortUrlRepositoryTest.php | 65 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 module/Core/test-func/Repository/ShortUrlRepositoryTest.php diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index 3039bd19..b1ffe60f 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -204,6 +204,17 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable return count($this->visits); } + /** + * @param Collection $visits + * @return ShortUrl + * @internal + */ + public function setVisits(Collection $visits): self + { + $this->visits = $visits; + return $this; + } + /** * @return int|null */ diff --git a/module/Core/test-func/Repository/ShortUrlRepositoryTest.php b/module/Core/test-func/Repository/ShortUrlRepositoryTest.php new file mode 100644 index 00000000..3c8688bd --- /dev/null +++ b/module/Core/test-func/Repository/ShortUrlRepositoryTest.php @@ -0,0 +1,65 @@ +repo = $this->getEntityManager()->getRepository(ShortUrl::class); + } + + /** + * @test + */ + public function findOneByShortCodeReturnsProperData() + { + $foo = new ShortUrl(); + $foo->setOriginalUrl('foo') + ->setShortCode('foo'); + $this->getEntityManager()->persist($foo); + + $bar = new ShortUrl(); + $bar->setOriginalUrl('bar') + ->setShortCode('bar') + ->setValidSince((new \DateTime())->add(new \DateInterval('P1M'))); + $this->getEntityManager()->persist($bar); + + $visits = []; + for ($i = 0; $i < 3; $i++) { + $visit = new Visit(); + $this->getEntityManager()->persist($visit); + $visits[] = $visit; + } + $baz = new ShortUrl(); + $baz->setOriginalUrl('baz') + ->setShortCode('baz') + ->setVisits(new ArrayCollection($visits)) + ->setMaxVisits(3); + $this->getEntityManager()->persist($baz); + + $this->getEntityManager()->flush(); + + $this->assertSame($foo, $this->repo->findOneByShortCode($foo->getShortCode())); + $this->assertNull($this->repo->findOneByShortCode('invalid')); + $this->assertNull($this->repo->findOneByShortCode($bar->getShortCode())); + $this->assertNull($this->repo->findOneByShortCode($baz->getShortCode())); + } +} From 433a5a923dcdac304b248695c896b93e67ba3426 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 23 Oct 2017 13:05:11 +0200 Subject: [PATCH 59/64] Improved ShortUrlRepositoryTest --- .../Repository/ShortUrlRepositoryTest.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/module/Core/test-func/Repository/ShortUrlRepositoryTest.php b/module/Core/test-func/Repository/ShortUrlRepositoryTest.php index 3c8688bd..4616f6c8 100644 --- a/module/Core/test-func/Repository/ShortUrlRepositoryTest.php +++ b/module/Core/test-func/Repository/ShortUrlRepositoryTest.php @@ -62,4 +62,21 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $this->assertNull($this->repo->findOneByShortCode($bar->getShortCode())); $this->assertNull($this->repo->findOneByShortCode($baz->getShortCode())); } + + /** + * @test + */ + public function countListReturnsProperNumberOfResults() + { + $count = 5; + for ($i = 0; $i < $count; $i++) { + $this->getEntityManager()->persist( + (new ShortUrl())->setOriginalUrl((string) $i) + ->setShortCode((string) $i) + ); + } + $this->getEntityManager()->flush(); + + $this->assertEquals($count, $this->repo->countList()); + } } From 91f08c9ead6a3347b18d8809d865ecbaa11b447c Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 23 Oct 2017 13:09:10 +0200 Subject: [PATCH 60/64] Updated CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cbba834..a7cc8cd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ **Enhancements:** +* [27: Add repository functional tests with dbunit](https://github.com/shlinkio/shlink/issues/27) * [86: Drop support for PHP 5](https://github.com/shlinkio/shlink/issues/86) * [101: Make actions just capture very specific exceptions, and let the ErrorHandler catch any other exception](https://github.com/shlinkio/shlink/issues/101) * [104: Use different templates for requested-short-code-does-not-exist and route-could-not-be-match](https://github.com/shlinkio/shlink/issues/104) From ef0dd416f9443f6840c5656df505f9b1e25ee1f0 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 23 Oct 2017 13:30:26 +0200 Subject: [PATCH 61/64] Updated CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7cc8cd8..a698b793 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * [44: Consider allowing to set custom slugs instead of generating a short code](https://github.com/shlinkio/shlink/issues/44) * [47: Allow to limit short codes availability by date range](https://github.com/shlinkio/shlink/issues/47) * [48: Allow to limit the number of visits to a short code](https://github.com/shlinkio/shlink/issues/48) +* [105: Added option to enable/disable URL validation by response status code.](https://github.com/shlinkio/shlink/pull/105) **Enhancements:** From a9dff56a92ba6e5f6c08214edd909309ddec1aeb Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 23 Oct 2017 13:33:23 +0200 Subject: [PATCH 62/64] Updated config aggregator version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 39c59f47..8cbe3267 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "symfony/process": "^3.0", "theorchard/monolog-cascade": "^0.4", "zendframework/zend-config": "^3.0", - "zendframework/zend-config-aggregator": "^0.1", + "zendframework/zend-config-aggregator": "^1.0", "zendframework/zend-expressive": "^2.0", "zendframework/zend-expressive-fastroute": "^2.0", "zendframework/zend-expressive-helpers": "^4.2", From 909ecc238759ab0961af7b72320ae8a455cb42b2 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 23 Oct 2017 13:44:50 +0200 Subject: [PATCH 63/64] Ensured build does not fail when trying to delete missing optional files --- build.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sh b/build.sh index b3c65a53..b90d0b2e 100755 --- a/build.sh +++ b/build.sh @@ -20,8 +20,8 @@ cp -R "${projectdir}"/* "${builtcontent}" cd "${builtcontent}" # Install dependencies -rm -r vendor -rm composer.lock +rm -rf vendor +rm -f composer.lock composer self-update composer install --no-dev --optimize-autoloader --no-progress --no-interaction @@ -35,8 +35,8 @@ rm indocker rm docker-compose.yml rm php* rm README.md -rm -r build -rm -f data/database.sqlite +rm -rf build +rm -ff data/database.sqlite rm -rf data/infra rm -rf data/{cache,log,proxies}/{*,.gitignore} rm -rf config/params/{*,.gitignore} From 26c455616b069dbd19d7f6fddea9d8421ab3be77 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 23 Oct 2017 13:56:07 +0200 Subject: [PATCH 64/64] Ensured validate_url does not fail when importing config, and instead, it gets a default BC value --- module/CLI/src/Model/CustomizableAppConfig.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/CLI/src/Model/CustomizableAppConfig.php b/module/CLI/src/Model/CustomizableAppConfig.php index 5ef42d1f..5784f753 100644 --- a/module/CLI/src/Model/CustomizableAppConfig.php +++ b/module/CLI/src/Model/CustomizableAppConfig.php @@ -191,7 +191,7 @@ final class CustomizableAppConfig implements ArraySerializableInterface 'SCHEMA' => $urlShortener['domain']['schema'], 'HOSTNAME' => $urlShortener['domain']['hostname'], 'CHARS' => $urlShortener['shortcode_chars'], - 'VALIDATE_URL' => $urlShortener['validate_url'], + 'VALIDATE_URL' => $urlShortener['validate_url'] ?? true, ]); } }