diff --git a/.gitattributes b/.gitattributes index 80102b6f..53b0a935 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,8 +5,6 @@ /module/CLI/test-resources export-ignore /module/Core/test export-ignore /module/Core/test-db export-ignore -/module/PreviewGenerator/test export-ignore -/module/PreviewGenerator/test-db export-ignore /module/Rest/test export-ignore /module/Rest/test-api export-ignore .gitattributes export-ignore diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php index 9ed9edd8..9e34cb7e 100644 --- a/.phpstorm.meta.php +++ b/.phpstorm.meta.php @@ -2,7 +2,7 @@ namespace PHPSTORM_META; use Psr\Container\ContainerInterface; -use Zend\ServiceManager\ServiceLocatorInterface; +use Laminas\ServiceManager\ServiceLocatorInterface; /** * PhpStorm Container Interop code completion diff --git a/.travis.yml b/.travis.yml index a4b5d1de..5d6176ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,14 +5,8 @@ branches: - /.*/ php: - - '7.2' - - '7.3' - '7.4' -matrix: - allow_failures: - - php: '7.4' - services: - mysql - postgresql @@ -39,7 +33,7 @@ before_script: script: - composer ci - - if [[ ! -z "$DOCKERFILE_CHANGED" && "${TRAVIS_PHP_VERSION}" == "7.2" ]]; then docker build -t shlink-docker-image:temp . ; fi + - if [[ ! -z "$DOCKERFILE_CHANGED" && "${TRAVIS_PHP_VERSION}" == "7.4" ]]; then docker build -t shlink-docker-image:temp . ; fi after_success: - rm -f build/clover.xml @@ -61,4 +55,4 @@ deploy: skip_cleanup: true on: tags: true - php: '7.2' + php: '7.4' diff --git a/CHANGELOG.md b/CHANGELOG.md index d0f69aa0..90f4acd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,39 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org). +## 2.0.0 - 2020-01-08 + +#### Added + +* [#429](https://github.com/shlinkio/shlink/issues/429) Added support for PHP 7.4 +* [#529](https://github.com/shlinkio/shlink/issues/529) Created an UPGRADING.md file explaining how to upgrade from v1.x to v2.x +* [#594](https://github.com/shlinkio/shlink/issues/594) Updated external shlink packages, including installer v4.0, which adds the option to ask for the redis cluster config. + +#### Changed + +* [#592](https://github.com/shlinkio/shlink/issues/592) Updated coding styles to use [shlinkio/php-coding-standard](https://github.com/shlinkio/php-coding-standard) v2.1.0. +* [#530](https://github.com/shlinkio/shlink/issues/530) Migrated project from deprecated `zendframework` components to the new `laminas` and `mezzio` ones. + +#### Deprecated + +* *Nothing* + +#### Removed + +* [#429](https://github.com/shlinkio/shlink/issues/429) Dropped support for PHP 7.2 and 7.3 + +* [#229](https://github.com/shlinkio/shlink/issues/229) Remove everything which was deprecated, including: + + * Preview generation feature completely removed. + * Authentication against REST API using JWT is no longer supported. + + See [UPGRADE](UPGRADE.md) doc in order to get details on how to migrate to this version. + +#### Fixed + +* [#600](https://github.com/shlinkio/shlink/issues/600) Fixed health action so that it works with and without version in the path. + + ## 1.21.1 - 2020-01-02 #### Added diff --git a/Dockerfile b/Dockerfile index f24dd289..01a93c26 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ -FROM php:7.3.11-alpine3.10 +FROM php:7.4.1-alpine3.10 LABEL maintainer="Alejandro Celaya " -ARG SHLINK_VERSION=1.20.2 +ARG SHLINK_VERSION=2.0.0 ENV SHLINK_VERSION ${SHLINK_VERSION} ENV SWOOLE_VERSION 4.4.12 ENV COMPOSER_VERSION 1.9.1 diff --git a/LICENSE b/LICENSE index b3358069..547f267a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016-2019 Alejandro Celaya +Copyright (c) 2016-2020 Alejandro Celaya Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 5f86aeb2..1853110d 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ A PHP-based self-hosted URL shortener that can be used to serve shortened URLs under your own custom domain. +> This document references Shlink 2.x. If you are using an older version and want to upgrade, follow the [UPGRADE](UPGRADE.md) doc. + ## Table of Contents - [Installation](#installation) @@ -29,7 +31,7 @@ A PHP-based self-hosted URL shortener that can be used to serve shortened URLs u First, make sure the host where you are going to run shlink fulfills these requirements: -* PHP 7.2 or greater with JSON, APCu, intl, curl, PDO and gd extensions enabled. +* PHP 7.4 or greater with JSON, APCu, intl, curl, PDO and gd extensions enabled. * MySQL, MariaDB, PostgreSQL or SQLite. * The web server of your choice with PHP integration (Apache or Nginx recommended). @@ -121,7 +123,7 @@ Once Shlink is configured, you need to expose it to the web, either by using a t First you need to install the swoole PHP extension with [pecl](https://pecl.php.net/package/swoole), `pecl install swoole`. - Once installed, it's actually pretty easy to get shlink up and running with swoole. Run `./vendor/bin/zend-expressive-swoole start -d` and you will get shlink running on port 8080. + Once installed, it's actually pretty easy to get shlink up and running with swoole. Run `./vendor/bin/mezzio-swoole start -d` and you will get shlink running on port 8080. However, by doing it this way, you are loosing all the access logs, and the service won't be automatically run if the server has to be restarted. @@ -138,7 +140,7 @@ Once Shlink is configured, you need to expose it to the web, either by using a t # Description: Shlink non-blocking server with swoole ### END INIT INFO - SCRIPT=/path/to/shlink/vendor/bin/zend-expressive-swoole\ start + SCRIPT=/path/to/shlink/vendor/bin/mezzio-swoole\ start RUNAS=root PIDFILE=/var/run/shlink_swoole.pid @@ -197,31 +199,11 @@ Finally access to [https://app.shlink.io](https://app.shlink.io) and configure y ### Bonus -Depending on the shlink version you installed and how you serve it, there are a couple of time-consuming tasks that shlink expects you to do manually, or at least it is recommended, since it will improve runtime performance. +Geo-locating visits to your short links is a time-consuming task. When serving Shlink with swoole, the geo-location task is automatically run asynchronously just after a visit to a short URL happens. -Those tasks can be performed using shlink's CLI tool, so it should be easy to schedule them to be run in the background (for example, using cron jobs): +However, if you are not serving Shlink with swoole, you will have to schedule the geo-location task to be run regularly in the background (for example, using cron jobs): -* **For shlink older than 1.18.0 or not using swoole to serve it**: Resolve IP address locations: `/path/to/shlink/bin/cli visit:locate` - - If you don't run this command regularly, the stats will say all visits come from *unknown* locations. - - > If you serve Shlink with swoole and use v1.18.0 at least, visit location is automatically scheduled by Shlink just after the visit occurs, using swoole's task system. - -* **For shlink older than v1.17.0**: Update IP geolocation database: `/path/to/shlink/bin/cli visit:update-db` - - When shlink is installed it downloads a fresh [GeoLite2](https://dev.maxmind.com/geoip/geoip2/geolite2/) db file. Running this command will update this file. - - The file is updated the first Tuesday of every month, so it should be enough running this command the first Wednesday. - - > You don't need this if you use Shlink v1.17.0 or newer, since now it downloads/updates the geolocation database automatically just before trying to use it. - -* Generate website previews: `/path/to/shlink/bin/cli short-url:process-previews` - - Running this will improve the performance of the `doma.in/abc123/preview` URLs, which return a preview of the site. - - > **Important!** Generating previews is considered deprecated and the feature will be removed in Shlink v2. - -*Any of these commands accept the `-q` flag, which makes it not display any output. This is recommended when configuring the commands as cron jobs.* +The command you need to run is `/path/to/shlink/bin/cli visit:locate`, and you can optionally provide the `-q` flag to remove any output and avoid your cron logs to be polluted. ## Update to new version @@ -274,33 +256,28 @@ Options: -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug Available commands: - help Displays help for a command - list Lists commands + help Displays help for a command + list Lists commands api-key - api-key:disable Disables an API key. - api-key:generate Generates a new valid API key. - api-key:list Lists all the available API keys. - config - config:generate-charset [DEPRECATED] Generates a character set sample just by shuffling the default one, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ". Then it can be set in the SHORTCODE_CHARS environment variable - config:generate-secret [DEPRECATED] Generates a random secret string that can be used for JWT token encryption + api-key:disable Disables an API key. + api-key:generate Generates a new valid API key. + api-key:list Lists all the available API keys. db - db:create Creates the database needed for shlink to work. It will do nothing if the database already exists - db:migrate Runs database migrations, which will ensure the shlink database is up to date. + db:create Creates the database needed for shlink to work. It will do nothing if the database already exists + db:migrate Runs database migrations, which will ensure the shlink database is up to date. short-url - short-url:delete [short-code:delete] Deletes a short URL - short-url:generate [shortcode:generate|short-code:generate] Generates a short URL for provided long URL and returns it - short-url:list [shortcode:list|short-code:list] List all short URLs - short-url:parse [shortcode:parse|short-code:parse] Returns the long URL behind a short code - short-url:process-previews [shortcode:process-previews|short-code:process-previews] [DEPRECATED] Processes and generates the previews for every URL, improving performance for later web requests. - short-url:visits [shortcode:visits|short-code:visits] Returns the detailed visits information for provided short code + short-url:delete Deletes a short URL + short-url:generate Generates a short URL for provided long URL and returns it + short-url:list List all short URLs + short-url:parse Returns the long URL behind a short code + short-url:visits Returns the detailed visits information for provided short code tag - tag:create Creates one or more tags. - tag:delete Deletes one or more tags. - tag:list Lists existing tags. - tag:rename Renames one existing tag. + tag:create Creates one or more tags. + tag:delete Deletes one or more tags. + tag:list Lists existing tags. + tag:rename Renames one existing tag. visit - visit:locate [visit:process] Resolves visits origin locations. - visit:update-db [DEPRECATED] Updates the GeoLite2 database file used to geolocate IP addresses + visit:locate Resolves visits origin locations. ``` > This product includes GeoLite2 data created by MaxMind, available from [https://www.maxmind.com](https://www.maxmind.com) diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 00000000..42b67fe9 --- /dev/null +++ b/UPGRADE.md @@ -0,0 +1,70 @@ +# Upgrading + +## From v1.x to v2.x + +### Preview generation + +The ability to generate website previews has been completely removed and has no replacement. + +The feature never properly worked, and it wasn't really useful. Because of that, the feature is no longer available on Shlink 2.x + +Removing this feature has these implications: + +* The `short-url:process-previews` CLI command no longer exists, and an error will be thrown if executed. +* The `/{shortCode}/preview` path is no longer valid, and will return a 404 status. + +### Removed paths + +These routes have been removed, but have a direct replacement: + +* `/qr/{shortCode}[/{size}]` -> `/{shortCode}/qr-code[/{size}]` +* `PUT /rest/v{version}/short-urls/{shortCode}` -> `PATCH /rest/v{version}/short-urls/{shortCode}` + +When using the old ones, a 404 status will me returned now. + +### Removed command and route aliases + +All the aliases for the CLI commands in the `short-urls` namespace have been removed. If you were using any of those commands with the `shortcode` or `short-code` prefixes, make sure to update them to use the `short-urls` prefix instead. + +The same happens for all REST endpoints starting with `/short-code`. They were previously aliased to `/short-urls` ones, but they will return a 404 now. Make sure to update them accordingly. + +### JWT authentication removed + +Shlink's REST API no longer accepts authentication using a JWT token. The API key has to be passed now in the `x-api-key` header. + +Removing this feature has these implications: + +* Shlink will no longer introspect the `Authorization` header for Bearer tokens. +* The `POST /rest/v{version}/authenticate` endpoint no longer exists and will return a 404. + +### API version is now required + +Endpoints need to provide a version in the path now. Previously, not providing a version used to fall back to v1. Now, it will return a 404 status, as no route will match. + +The only exception is the `/rest/health` endpoint, which will continue working without the version. + +### Changes in models + +The next REST API models have changed: + +* **ShortUrl**: The `originalUrl` property was deprecated and has been removed. Use `longUrl` instead. +* **Visit**: The `remoteAddr` property was deprecated and has been removed. It has no replacement. +* **VisitLocation**: The `latitude` and `longitude` properties are no longer strings, but float. + +### URL validation + +Shlink can verify provided long URLs are valid before trying to shorten them. Starting with v2, it no longer does it by default and needs to be explicitly enabled instead of explicitly disabled. + +### Removed config options + +The `not_found_redirect_to` config option and the `NOT_FOUND_REDIRECT_TO` env var are no longer taken into consideration for the docker image. + +Instead, use `invalid_short_url_redirect_to` and `INVALID_SHORT_URL_REDIRECT_TO` respectively. + +### Migrated to Laminas + +The project has been using Zend Framework components since the beginning. Since it has been re-branded as [Laminas](https://getlaminas.org/), this version updates to the new set of components. + +Updating to Laminas components has these implications: + +* If you were manually serving Shlink with swoole, the entry script has to be changed from `/path/to/shlink/vendor/bin/zend-expressive-swoole` to `/path/to/shlink/vendor/bin/mezzio-swoole` diff --git a/bin/install b/bin/install index 5918cac0..d20db86d 100755 --- a/bin/install +++ b/bin/install @@ -8,5 +8,5 @@ use function chdir; use function dirname; chdir(dirname(__DIR__)); -$run = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php'; -$run(false); +[$install] = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php'; +$install(); diff --git a/bin/test/run-api-tests.sh b/bin/test/run-api-tests.sh index cbc10a1a..2a14b218 100755 --- a/bin/test/run-api-tests.sh +++ b/bin/test/run-api-tests.sh @@ -3,16 +3,16 @@ export APP_ENV=test export DB_DRIVER=mysql # Try to stop server just in case it hanged in last execution -vendor/bin/zend-expressive-swoole stop +vendor/bin/mezzio-swoole stop echo 'Starting server...' -vendor/bin/zend-expressive-swoole start -d +vendor/bin/mezzio-swoole start -d sleep 2 vendor/bin/phpunit --order-by=random -c phpunit-api.xml --testdox --colors=always $* testsExitCode=$? -vendor/bin/zend-expressive-swoole stop +vendor/bin/mezzio-swoole stop # Exit this script with the same code as the tests. If tests failed, this script has to fail exit $testsExitCode diff --git a/bin/update b/bin/update index 5dd32e72..ad20ce46 100755 --- a/bin/update +++ b/bin/update @@ -8,5 +8,5 @@ use function chdir; use function dirname; chdir(dirname(__DIR__)); -$run = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php'; -$run(true); +[, $update] = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php'; +$update(); diff --git a/bin/wkhtmltoimage b/bin/wkhtmltoimage deleted file mode 100755 index 1459957b..00000000 Binary files a/bin/wkhtmltoimage and /dev/null differ diff --git a/composer.json b/composer.json index 7782f61d..a21489ac 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ } ], "require": { - "php": "^7.2", + "php": "^7.4", "ext-json": "*", "ext-pdo": "*", "akrabat/ip-address-middleware": "^1.0", @@ -26,53 +26,52 @@ "firebase/php-jwt": "^4.0", "geoip2/geoip2": "^2.9", "guzzlehttp/guzzle": "^6.5.1", + "laminas/laminas-config": "^3.3", + "laminas/laminas-config-aggregator": "^1.1", + "laminas/laminas-dependency-plugin": "^1.0", + "laminas/laminas-diactoros": "^2.1.3", + "laminas/laminas-inputfilter": "^2.10", + "laminas/laminas-paginator": "^2.8", + "laminas/laminas-servicemanager": "^3.4", + "laminas/laminas-stdlib": "^3.2", "lstrojny/functional-php": "^1.9", - "mikehaertl/phpwkhtmltopdf": "^2.2", + "mezzio/mezzio": "^3.2", + "mezzio/mezzio-fastroute": "^3.0", + "mezzio/mezzio-helpers": "^5.3", + "mezzio/mezzio-platesrenderer": "^2.1", + "mezzio/mezzio-problem-details": "^1.1", + "mezzio/mezzio-swoole": "^2.4", "monolog/monolog": "^2.0", "nikolaposa/monolog-factory": "^3.0", - "ocramius/proxy-manager": "~2.2.2", + "ocramius/proxy-manager": "^2.6.0", "phly/phly-event-dispatcher": "^1.0", "predis/predis": "^1.1", "pugx/shortid-php": "^0.5", - "shlinkio/shlink-common": "^2.4", - "shlinkio/shlink-event-dispatcher": "^1.1", - "shlinkio/shlink-installer": "^3.3", - "shlinkio/shlink-ip-geolocation": "^1.2", + "shlinkio/shlink-common": "^2.5", + "shlinkio/shlink-event-dispatcher": "^1.3", + "shlinkio/shlink-installer": "^4.0", + "shlinkio/shlink-ip-geolocation": "^1.3", "symfony/console": "^5.0", "symfony/filesystem": "^5.0", "symfony/lock": "^5.0", - "symfony/process": "^5.0", - "zendframework/zend-config": "^3.3", - "zendframework/zend-config-aggregator": "^1.1", - "zendframework/zend-diactoros": "^2.1.3", - "zendframework/zend-expressive": "^3.2", - "zendframework/zend-expressive-fastroute": "^3.0", - "zendframework/zend-expressive-helpers": "^5.3", - "zendframework/zend-expressive-platesrenderer": "^2.1", - "zendframework/zend-expressive-swoole": "^2.4", - "zendframework/zend-inputfilter": "^2.10", - "zendframework/zend-paginator": "^2.8", - "zendframework/zend-problem-details": "^1.0", - "zendframework/zend-servicemanager": "^3.4", - "zendframework/zend-stdlib": "^3.2" + "symfony/process": "^5.0" }, "require-dev": { "devster/ubench": "^2.0", "eaglewu/swoole-ide-helper": "dev-master", "infection/infection": "^0.15.0", - "phpstan/phpstan-shim": "^0.11.16", + "phpstan/phpstan": "^0.12.3", "phpunit/phpunit": "^8.3", "roave/security-advisories": "dev-master", - "shlinkio/php-coding-standard": "~2.0.0", - "shlinkio/shlink-test-utils": "^1.2", + "shlinkio/php-coding-standard": "~2.1.0", + "shlinkio/shlink-test-utils": "^1.3", "symfony/var-dumper": "^5.0" }, "autoload": { "psr-4": { "Shlinkio\\Shlink\\CLI\\": "module/CLI/src", "Shlinkio\\Shlink\\Rest\\": "module/Rest/src", - "Shlinkio\\Shlink\\Core\\": "module/Core/src", - "Shlinkio\\Shlink\\PreviewGenerator\\": "module/PreviewGenerator/src/" + "Shlinkio\\Shlink\\Core\\": "module/Core/src" }, "files": [ "module/Core/functions/functions.php" @@ -86,8 +85,7 @@ "ShlinkioTest\\Shlink\\Core\\": [ "module/Core/test", "module/Core/test-db" - ], - "ShlinkioTest\\Shlink\\PreviewGenerator\\": "module/PreviewGenerator/test" + ] } }, "scripts": { diff --git a/config/autoload/app_options.global.php b/config/autoload/app_options.global.php index e10f1b20..f64f9cff 100644 --- a/config/autoload/app_options.global.php +++ b/config/autoload/app_options.global.php @@ -2,14 +2,11 @@ declare(strict_types=1); -use function Shlinkio\Shlink\Common\env; - return [ 'app_options' => [ 'name' => 'Shlink', 'version' => '%SHLINK_VERSION%', - 'secret_key' => env('SECRET_KEY', ''), 'disable_track_param' => null, ], diff --git a/config/autoload/common.global.php b/config/autoload/common.global.php index 3f06b47e..4d8c3520 100644 --- a/config/autoload/common.global.php +++ b/config/autoload/common.global.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use Zend\ConfigAggregator\ConfigAggregator; +use Laminas\ConfigAggregator\ConfigAggregator; return [ diff --git a/config/autoload/common.local.php.dist b/config/autoload/common.local.php.dist index 2b149bb5..9d1276c4 100644 --- a/config/autoload/common.local.php.dist +++ b/config/autoload/common.local.php.dist @@ -1,7 +1,7 @@ [ 'delegators' => [ - Expressive\Application::class => [ + Mezzio\Application::class => [ Container\ApplicationConfigInjectionDelegator::class, ], ], diff --git a/config/autoload/dependencies.local.php.dist b/config/autoload/dependencies.local.php.dist index e7fb274f..d9569029 100644 --- a/config/autoload/dependencies.local.php.dist +++ b/config/autoload/dependencies.local.php.dist @@ -1,4 +1,5 @@ [ - function (ContainerInterface $container, $instance) { + function (ContainerInterface $container, $instance): void { if ($instance instanceof Log\LoggerAwareInterface) { $instance->setLogger($container->get(Log\LoggerInterface::class)); } diff --git a/config/autoload/error-handler.global.php b/config/autoload/error-handler.global.php index 964b90de..b4872bfe 100644 --- a/config/autoload/error-handler.global.php +++ b/config/autoload/error-handler.global.php @@ -2,18 +2,17 @@ declare(strict_types=1); +use Laminas\Stratigility\Middleware\ErrorHandler; +use Mezzio\ProblemDetails\ProblemDetailsMiddleware; use Shlinkio\Shlink\Common\Logger; -use Zend\ProblemDetails\ProblemDetailsMiddleware; -use Zend\Stratigility\Middleware\ErrorHandler; return [ - 'backwards_compatible_problem_details' => [ - 'default_type_fallbacks' => [ + 'problem-details' => [ + 'default_types_map' => [ 404 => 'NOT_FOUND', 500 => 'INTERNAL_SERVER_ERROR', ], - 'json_flags' => JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION, ], 'error_handler' => [ diff --git a/config/autoload/installer.global.php b/config/autoload/installer.global.php index 402e6bb3..296c0635 100644 --- a/config/autoload/installer.global.php +++ b/config/autoload/installer.global.php @@ -2,51 +2,43 @@ declare(strict_types=1); -use Shlinkio\Shlink\Installer\Config\Plugin; +use Shlinkio\Shlink\Installer\Config\Option; return [ - 'installer_plugins_expected_config' => [ - Plugin\UrlShortenerConfigCustomizer::class => [ - Plugin\UrlShortenerConfigCustomizer::SCHEMA, - Plugin\UrlShortenerConfigCustomizer::HOSTNAME, - Plugin\UrlShortenerConfigCustomizer::VALIDATE_URL, - Plugin\UrlShortenerConfigCustomizer::NOTIFY_VISITS_WEBHOOKS, - Plugin\UrlShortenerConfigCustomizer::VISITS_WEBHOOKS, + 'installer' => [ + 'enabled_options' => [ + Option\DatabaseDriverConfigOption::class, + Option\DatabaseNameConfigOption::class, + Option\DatabaseHostConfigOption::class, + Option\DatabasePortConfigOption::class, + Option\DatabaseUserConfigOption::class, + Option\DatabasePasswordConfigOption::class, + Option\DatabaseSqlitePathConfigOption::class, + Option\DatabaseMySqlOptionsConfigOption::class, + Option\ShortDomainHostConfigOption::class, + Option\ShortDomainSchemaConfigOption::class, + Option\ValidateUrlConfigOption::class, + Option\VisitsWebhooksConfigOption::class, + Option\BaseUrlRedirectConfigOption::class, + Option\InvalidShortUrlRedirectConfigOption::class, + Option\Regular404RedirectConfigOption::class, + Option\DisableTrackParamConfigOption::class, + Option\CheckVisitsThresholdConfigOption::class, + Option\VisitsThresholdConfigOption::class, + Option\BasePathConfigOption::class, + Option\TaskWorkerNumConfigOption::class, + Option\WebWorkerNumConfigOption::class, + Option\RedisServersConfigOption::class, ], - Plugin\ApplicationConfigCustomizer::class => [ - Plugin\ApplicationConfigCustomizer::SECRET, - Plugin\ApplicationConfigCustomizer::DISABLE_TRACK_PARAM, - Plugin\ApplicationConfigCustomizer::CHECK_VISITS_THRESHOLD, - Plugin\ApplicationConfigCustomizer::VISITS_THRESHOLD, - Plugin\ApplicationConfigCustomizer::BASE_PATH, - Plugin\ApplicationConfigCustomizer::WEB_WORKER_NUM, - Plugin\ApplicationConfigCustomizer::TASK_WORKER_NUM, - ], - - Plugin\DatabaseConfigCustomizer::class => [ - Plugin\DatabaseConfigCustomizer::DRIVER, - Plugin\DatabaseConfigCustomizer::NAME, - Plugin\DatabaseConfigCustomizer::USER, - Plugin\DatabaseConfigCustomizer::PASSWORD, - Plugin\DatabaseConfigCustomizer::HOST, - Plugin\DatabaseConfigCustomizer::PORT, - ], - - Plugin\RedirectsConfigCustomizer::class => [ - Plugin\RedirectsConfigCustomizer::INVALID_SHORT_URL_REDIRECT_TO, - Plugin\RedirectsConfigCustomizer::REGULAR_404_REDIRECT_TO, - Plugin\RedirectsConfigCustomizer::BASE_URL_REDIRECT_TO, - ], - ], - - 'installation_commands' => [ - 'db_create_schema' => [ - 'command' => 'bin/cli db:create', - ], - 'db_migrate' => [ - 'command' => 'bin/cli db:migrate', + 'installation_commands' => [ + 'db_create_schema' => [ + 'command' => 'bin/cli db:create', + ], + 'db_migrate' => [ + 'command' => 'bin/cli db:migrate', + ], ], ], diff --git a/config/autoload/locks.global.php b/config/autoload/locks.global.php index 0294bf91..22a51e38 100644 --- a/config/autoload/locks.global.php +++ b/config/autoload/locks.global.php @@ -2,11 +2,11 @@ declare(strict_types=1); +use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory; use Shlinkio\Shlink\Common\Cache\RedisFactory; use Shlinkio\Shlink\Common\Lock\RetryLockStoreDelegatorFactory; use Shlinkio\Shlink\Common\Logger\LoggerAwareDelegatorFactory; use Symfony\Component\Lock; -use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory; $localLockFactory = 'Shlinkio\Shlink\LocalLockFactory'; diff --git a/config/autoload/logger.global.php b/config/autoload/logger.global.php index 40aa8a2d..01cf8aab 100644 --- a/config/autoload/logger.global.php +++ b/config/autoload/logger.global.php @@ -75,7 +75,7 @@ return [ ], ], - 'zend-expressive-swoole' => [ + 'mezzio-swoole' => [ 'swoole-http-server' => [ 'logger' => [ 'logger-name' => 'Logger_Access', diff --git a/config/autoload/middleware-pipeline.global.php b/config/autoload/middleware-pipeline.global.php index 35a9a16b..2ace0700 100644 --- a/config/autoload/middleware-pipeline.global.php +++ b/config/autoload/middleware-pipeline.global.php @@ -4,16 +4,16 @@ declare(strict_types=1); namespace Shlinkio\Shlink; -use Zend\Expressive; -use Zend\ProblemDetails; -use Zend\Stratigility\Middleware\ErrorHandler; +use Laminas\Stratigility\Middleware\ErrorHandler; +use Mezzio; +use Mezzio\ProblemDetails; return [ 'middleware_pipeline' => [ 'error-handler' => [ 'middleware' => [ - Expressive\Helper\ContentLengthMiddleware::class, + Mezzio\Helper\ContentLengthMiddleware::class, ErrorHandler::class, ], ], @@ -21,7 +21,6 @@ return [ 'path' => '/rest', 'middleware' => [ Rest\Middleware\CrossDomainMiddleware::class, - Rest\Middleware\BackwardsCompatibleProblemDetailsMiddleware::class, ProblemDetails\ProblemDetailsMiddleware::class, ], ], @@ -31,24 +30,17 @@ return [ Common\Middleware\CloseDbConnectionMiddleware::class, ], ], - 'pre-routing-rest' => [ - 'path' => '/rest', - 'middleware' => [ - Rest\Middleware\PathVersionMiddleware::class, - Rest\Middleware\ShortUrl\ShortCodePathMiddleware::class, - ], - ], 'routing' => [ 'middleware' => [ - Expressive\Router\Middleware\RouteMiddleware::class, + Mezzio\Router\Middleware\RouteMiddleware::class, ], ], 'rest' => [ 'path' => '/rest', 'middleware' => [ - Expressive\Router\Middleware\ImplicitOptionsMiddleware::class, + Mezzio\Router\Middleware\ImplicitOptionsMiddleware::class, Rest\Middleware\BodyParserMiddleware::class, Rest\Middleware\AuthenticationMiddleware::class, ], @@ -56,7 +48,7 @@ return [ 'dispatch' => [ 'middleware' => [ - Expressive\Router\Middleware\DispatchMiddleware::class, + Mezzio\Router\Middleware\DispatchMiddleware::class, ], ], diff --git a/config/autoload/preview-generation.global.php b/config/autoload/preview-generation.global.php deleted file mode 100644 index 93696000..00000000 --- a/config/autoload/preview-generation.global.php +++ /dev/null @@ -1,12 +0,0 @@ - [ - 'files_location' => 'data/cache', - ], - -]; diff --git a/config/autoload/redis.local.php.local b/config/autoload/redis.local.php.local index f89201f1..08dbae32 100644 --- a/config/autoload/redis.local.php.local +++ b/config/autoload/redis.local.php.local @@ -1,13 +1,16 @@ [ - 'servers' => 'tcp://shlink_redis:6379', -// 'servers' => [ -// 'tcp://shlink_redis:6379', -// ], + 'cache' => [ + 'redis' => [ + 'servers' => 'tcp://shlink_redis:6379', +// 'servers' => [ +// 'tcp://shlink_redis:6379', +// ], + ], ], 'dependencies' => [ diff --git a/config/autoload/router.global.php b/config/autoload/router.global.php index 7ab1cc82..d45ee330 100644 --- a/config/autoload/router.global.php +++ b/config/autoload/router.global.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use Zend\Expressive\Router\FastRouteRouter; +use Mezzio\Router\FastRouteRouter; return [ diff --git a/config/autoload/router.local.php.dist b/config/autoload/router.local.php.dist index 3251c8ab..3fe6811a 100644 --- a/config/autoload/router.local.php.dist +++ b/config/autoload/router.local.php.dist @@ -1,5 +1,5 @@ [ + 'mezzio-swoole' => [ 'enable_coroutine' => true, 'swoole-http-server' => [ diff --git a/config/autoload/swoole.local.php.dist b/config/autoload/swoole.local.php.dist index 017d17eb..5e12a5a3 100644 --- a/config/autoload/swoole.local.php.dist +++ b/config/autoload/swoole.local.php.dist @@ -1,12 +1,12 @@ [ + 'mezzio-swoole' => [ 'hot-code-reload' => [ 'enable' => true, ], diff --git a/config/autoload/url-shortener.global.php b/config/autoload/url-shortener.global.php index 58bc3faa..5cf4f86f 100644 --- a/config/autoload/url-shortener.global.php +++ b/config/autoload/url-shortener.global.php @@ -9,7 +9,7 @@ return [ 'schema' => 'https', 'hostname' => '', ], - 'validate_url' => true, + 'validate_url' => false, 'visits_webhooks' => [], ], diff --git a/config/autoload/wkhtmltopdf.global.php b/config/autoload/wkhtmltopdf.global.php deleted file mode 100644 index 0059aae4..00000000 --- a/config/autoload/wkhtmltopdf.global.php +++ /dev/null @@ -1,14 +0,0 @@ - [ - 'images' => [ - 'binary' => __DIR__ . '/../../bin/wkhtmltoimage', - 'type' => 'jpg', - ], - ], - -]; diff --git a/config/cli-config.php b/config/cli-config.php index 1d96d50e..c0e80687 100644 --- a/config/cli-config.php +++ b/config/cli-config.php @@ -4,8 +4,8 @@ declare(strict_types=1); use Doctrine\ORM\EntityManager; use Doctrine\ORM\Tools\Console\ConsoleRunner; +use Laminas\ServiceManager\ServiceManager; use Psr\Container\ContainerInterface; -use Zend\ServiceManager\ServiceManager; return (function () { /** @var ContainerInterface|ServiceManager $container */ diff --git a/config/config.php b/config/config.php index ad18fd20..3fb1a3e4 100644 --- a/config/config.php +++ b/config/config.php @@ -4,18 +4,19 @@ declare(strict_types=1); namespace Shlinkio\Shlink; -use Zend\ConfigAggregator; -use Zend\Expressive; -use Zend\ProblemDetails; +use Laminas\ConfigAggregator; +use Laminas\ZendFrameworkBridge; +use Mezzio; +use Mezzio\ProblemDetails; use function Shlinkio\Shlink\Common\env; return (new ConfigAggregator\ConfigAggregator([ - Expressive\ConfigProvider::class, - Expressive\Router\ConfigProvider::class, - Expressive\Router\FastRouteRouter\ConfigProvider::class, - Expressive\Plates\ConfigProvider::class, - Expressive\Swoole\ConfigProvider::class, + Mezzio\ConfigProvider::class, + Mezzio\Router\ConfigProvider::class, + Mezzio\Router\FastRouteRouter\ConfigProvider::class, + Mezzio\Plates\ConfigProvider::class, + Mezzio\Swoole\ConfigProvider::class, ProblemDetails\ConfigProvider::class, Common\ConfigProvider::class, IpGeolocation\ConfigProvider::class, @@ -23,12 +24,12 @@ return (new ConfigAggregator\ConfigAggregator([ CLI\ConfigProvider::class, Rest\ConfigProvider::class, EventDispatcher\ConfigProvider::class, - PreviewGenerator\ConfigProvider::class, new ConfigAggregator\PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'), env('APP_ENV') === 'test' ? new ConfigAggregator\PhpFileProvider('config/test/*.global.php') - : new ConfigAggregator\ZendConfigProvider('config/params/{generated_config.php,*.config.{php,json}}'), + : new ConfigAggregator\LaminasConfigProvider('config/params/{generated_config.php,*.config.{php,json}}'), ], 'data/cache/app_config.php', [ + ZendFrameworkBridge\ConfigPostProcessor::class, Core\Config\SimplifiedConfigParser::class, Core\Config\BasePathPrefixer::class, Core\Config\DeprecatedConfigParser::class, diff --git a/config/container.php b/config/container.php index 2ea9dc06..3735e14e 100644 --- a/config/container.php +++ b/config/container.php @@ -2,8 +2,8 @@ declare(strict_types=1); +use Laminas\ServiceManager\ServiceManager; use Symfony\Component\Lock; -use Zend\ServiceManager\ServiceManager; chdir(dirname(__DIR__)); diff --git a/config/run.php b/config/run.php index 4ea61775..80116e24 100644 --- a/config/run.php +++ b/config/run.php @@ -2,9 +2,9 @@ declare(strict_types=1); +use Mezzio\Application; use Psr\Container\ContainerInterface; use Symfony\Component\Console\Application as CliApp; -use Zend\Expressive\Application; return function (bool $isCli = false): void { /** @var ContainerInterface $container */ diff --git a/config/test/bootstrap_api_tests.php b/config/test/bootstrap_api_tests.php index 3605427c..4cf01807 100644 --- a/config/test/bootstrap_api_tests.php +++ b/config/test/bootstrap_api_tests.php @@ -15,6 +15,4 @@ $em = $container->get(EntityManager::class); $testHelper->createTestDb(); ApiTest\ApiTestCase::setApiClient($container->get('shlink_test_api_client')); -ApiTest\ApiTestCase::setSeedFixturesCallback(function () use ($testHelper, $em, $config) { - $testHelper->seedFixtures($em, $config['data_fixtures'] ?? []); -}); +ApiTest\ApiTestCase::setSeedFixturesCallback(fn () => $testHelper->seedFixtures($em, $config['data_fixtures'] ?? [])); diff --git a/config/test/test_config.global.php b/config/test/test_config.global.php index a0a9aeb7..c4556b8b 100644 --- a/config/test/test_config.global.php +++ b/config/test/test_config.global.php @@ -5,9 +5,9 @@ declare(strict_types=1); namespace Shlinkio\Shlink; use GuzzleHttp\Client; +use Laminas\ConfigAggregator\ConfigAggregator; +use Laminas\ServiceManager\Factory\InvokableFactory; use PDO; -use Zend\ConfigAggregator\ConfigAggregator; -use Zend\ServiceManager\Factory\InvokableFactory; use function Shlinkio\Shlink\Common\env; use function sprintf; @@ -19,9 +19,7 @@ $swooleTestingPort = 9999; $buildDbConnection = function (): array { $driver = env('DB_DRIVER', 'sqlite'); $isCi = env('TRAVIS', false); - $getMysqlHost = function (string $driver) { - return sprintf('shlink_db%s', $driver === 'mysql' ? '' : '_maria'); - }; + $getMysqlHost = fn (string $driver) => sprintf('shlink_db%s', $driver === 'mysql' ? '' : '_maria'); $driverConfigMap = [ 'sqlite' => [ @@ -63,9 +61,10 @@ return [ 'schema' => 'http', 'hostname' => 'doma.in', ], + 'validate_url' => true, ], - 'zend-expressive-swoole' => [ + 'mezzio-swoole' => [ 'enable_coroutine' => false, 'swoole-http-server' => [ 'host' => $swooleTestingHost, diff --git a/data/infra/examples/nginx-vhost.conf b/data/infra/examples/nginx-vhost.conf index b501a70c..d5bb0a2c 100644 --- a/data/infra/examples/nginx-vhost.conf +++ b/data/infra/examples/nginx-vhost.conf @@ -11,7 +11,7 @@ server { location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass unix:/var/run/php/php7.2-fpm.sock; + fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; fastcgi_index index.php; include fastcgi.conf; } diff --git a/data/infra/examples/shlink-daemon.sh b/data/infra/examples/shlink-daemon.sh index 86b1257d..a18ca65a 100644 --- a/data/infra/examples/shlink-daemon.sh +++ b/data/infra/examples/shlink-daemon.sh @@ -8,7 +8,7 @@ # Description: Shlink non-blocking server with swoole ### END INIT INFO -SCRIPT=/path/to/shlink/vendor/bin/zend-expressive-swoole\ start +SCRIPT=/path/to/shlink/vendor/bin/mezzio-swoole\ start RUNAS=root PIDFILE=/var/run/shlink_swoole.pid diff --git a/data/infra/php.Dockerfile b/data/infra/php.Dockerfile index 0cb74aaa..e92cc815 100644 --- a/data/infra/php.Dockerfile +++ b/data/infra/php.Dockerfile @@ -1,18 +1,20 @@ -FROM php:7.3.11-fpm-alpine3.10 +FROM php:7.4.1-fpm-alpine3.10 MAINTAINER Alejandro Celaya ENV APCU_VERSION 5.1.18 ENV APCU_BC_VERSION 1.0.5 -ENV XDEBUG_VERSION 2.8.0 +ENV XDEBUG_VERSION 2.9.0 RUN apk update # Install common php extensions RUN docker-php-ext-install pdo_mysql RUN docker-php-ext-install iconv -RUN docker-php-ext-install mbstring RUN docker-php-ext-install calendar +RUN apk add --no-cache oniguruma-dev +RUN docker-php-ext-install mbstring + RUN apk add --no-cache sqlite-libs RUN apk add --no-cache sqlite-dev RUN docker-php-ext-install pdo_sqlite diff --git a/data/infra/swoole.Dockerfile b/data/infra/swoole.Dockerfile index ea7dc230..8bc821d9 100644 --- a/data/infra/swoole.Dockerfile +++ b/data/infra/swoole.Dockerfile @@ -1,4 +1,4 @@ -FROM php:7.3.11-alpine3.10 +FROM php:7.4.1-alpine3.10 MAINTAINER Alejandro Celaya ENV APCU_VERSION 5.1.18 @@ -11,9 +11,11 @@ RUN apk update # Install common php extensions RUN docker-php-ext-install pdo_mysql RUN docker-php-ext-install iconv -RUN docker-php-ext-install mbstring RUN docker-php-ext-install calendar +RUN apk add --no-cache oniguruma-dev +RUN docker-php-ext-install mbstring + RUN apk add --no-cache sqlite-libs RUN apk add --no-cache sqlite-dev RUN docker-php-ext-install pdo_sqlite @@ -90,4 +92,4 @@ CMD \ if [[ ! -d "./vendor" ]]; then /usr/local/bin/composer install ; fi && \ # When restarting the container, swoole might think it is already in execution # This forces the app to be started every second until the exit code is 0 - until php ./vendor/bin/zend-expressive-swoole start; do sleep 1 ; done + until php ./vendor/bin/mezzio-swoole start; do sleep 1 ; done diff --git a/data/migrations/Version20160820191203.php b/data/migrations/Version20160820191203.php index 2d8a7c38..d0a4d673 100644 --- a/data/migrations/Version20160820191203.php +++ b/data/migrations/Version20160820191203.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace ShlinkMigrations; use Doctrine\DBAL\Schema\Schema; -use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Types\Types; use Doctrine\Migrations\AbstractMigration; /** @@ -30,12 +30,12 @@ class Version20160820191203 extends AbstractMigration private function createTagsTable(Schema $schema): void { $table = $schema->createTable('tags'); - $table->addColumn('id', Type::BIGINT, [ + $table->addColumn('id', Types::BIGINT, [ 'unsigned' => true, 'autoincrement' => true, 'notnull' => true, ]); - $table->addColumn('name', Type::STRING, [ + $table->addColumn('name', Types::STRING, [ 'length' => 255, 'notnull' => true, ]); @@ -47,11 +47,11 @@ class Version20160820191203 extends AbstractMigration private function createShortUrlsInTagsTable(Schema $schema): void { $table = $schema->createTable('short_urls_in_tags'); - $table->addColumn('short_url_id', Type::BIGINT, [ + $table->addColumn('short_url_id', Types::BIGINT, [ 'unsigned' => true, 'notnull' => true, ]); - $table->addColumn('tag_id', Type::BIGINT, [ + $table->addColumn('tag_id', Types::BIGINT, [ 'unsigned' => true, 'notnull' => true, ]); diff --git a/data/migrations/Version20171021093246.php b/data/migrations/Version20171021093246.php index 6e42d775..b66a2c3f 100644 --- a/data/migrations/Version20171021093246.php +++ b/data/migrations/Version20171021093246.php @@ -6,7 +6,7 @@ namespace ShlinkMigrations; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\SchemaException; -use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Types\Types; use Doctrine\Migrations\AbstractMigration; /** @@ -24,10 +24,10 @@ class Version20171021093246 extends AbstractMigration return; } - $shortUrls->addColumn('valid_since', Type::DATETIME, [ + $shortUrls->addColumn('valid_since', Types::DATETIME, [ 'notnull' => false, ]); - $shortUrls->addColumn('valid_until', Type::DATETIME, [ + $shortUrls->addColumn('valid_until', Types::DATETIME, [ 'notnull' => false, ]); } diff --git a/data/migrations/Version20171022064541.php b/data/migrations/Version20171022064541.php index 36bfdc7b..7ff39666 100644 --- a/data/migrations/Version20171022064541.php +++ b/data/migrations/Version20171022064541.php @@ -6,7 +6,7 @@ namespace ShlinkMigrations; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\SchemaException; -use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Types\Types; use Doctrine\Migrations\AbstractMigration; /** @@ -24,7 +24,7 @@ class Version20171022064541 extends AbstractMigration return; } - $shortUrls->addColumn('max_visits', Type::INTEGER, [ + $shortUrls->addColumn('max_visits', Types::INTEGER, [ 'unsigned' => true, 'notnull' => false, ]); diff --git a/data/migrations/Version20180801183328.php b/data/migrations/Version20180801183328.php index 80ebbfe8..24bcd825 100644 --- a/data/migrations/Version20180801183328.php +++ b/data/migrations/Version20180801183328.php @@ -17,7 +17,6 @@ final class Version20180801183328 extends AbstractMigration private const OLD_SIZE = 10; /** - * @param Schema $schema * @throws SchemaException */ public function up(Schema $schema): void @@ -26,7 +25,6 @@ final class Version20180801183328 extends AbstractMigration } /** - * @param Schema $schema * @throws SchemaException */ public function down(Schema $schema): void @@ -35,8 +33,6 @@ final class Version20180801183328 extends AbstractMigration } /** - * @param Schema $schema - * @param int $size * @throws SchemaException */ private function setSize(Schema $schema, int $size): void diff --git a/data/migrations/Version20180913205455.php b/data/migrations/Version20180913205455.php index 9f81578a..8afa316b 100644 --- a/data/migrations/Version20180913205455.php +++ b/data/migrations/Version20180913205455.php @@ -17,7 +17,6 @@ use Shlinkio\Shlink\Common\Util\IpAddress; final class Version20180913205455 extends AbstractMigration { /** - * @param Schema $schema */ public function up(Schema $schema): void { @@ -25,7 +24,6 @@ final class Version20180913205455 extends AbstractMigration } /** - * @param Schema $schema * @throws DBALException */ public function postUp(Schema $schema): void @@ -67,7 +65,6 @@ final class Version20180913205455 extends AbstractMigration } /** - * @param Schema $schema */ public function down(Schema $schema): void { diff --git a/data/migrations/Version20180915110857.php b/data/migrations/Version20180915110857.php index c08d59eb..73a36597 100644 --- a/data/migrations/Version20180915110857.php +++ b/data/migrations/Version20180915110857.php @@ -19,7 +19,6 @@ final class Version20180915110857 extends AbstractMigration ]; /** - * @param Schema $schema * @throws SchemaException */ public function up(Schema $schema): void @@ -39,7 +38,7 @@ final class Version20180915110857 extends AbstractMigration [ 'onDelete' => self::ON_DELETE_MAP[$foreignTable], 'onUpdate' => 'RESTRICT', - ] + ], ); } } diff --git a/data/migrations/Version20181020060559.php b/data/migrations/Version20181020060559.php index e60b7fc8..5b46b6df 100644 --- a/data/migrations/Version20181020060559.php +++ b/data/migrations/Version20181020060559.php @@ -8,7 +8,7 @@ use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\SchemaException; use Doctrine\DBAL\Schema\Table; -use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Types\Types; use Doctrine\Migrations\AbstractMigration; /** @@ -24,7 +24,6 @@ final class Version20181020060559 extends AbstractMigration ]; /** - * @param Schema $schema * @throws SchemaException */ public function up(Schema $schema): void @@ -36,7 +35,7 @@ final class Version20181020060559 extends AbstractMigration { foreach ($columnNames as $name) { if (! $visitLocations->hasColumn($name)) { - $visitLocations->addColumn($name, Type::STRING, ['notnull' => false]); + $visitLocations->addColumn($name, Types::STRING, ['notnull' => false]); } } } diff --git a/data/migrations/Version20190930165521.php b/data/migrations/Version20190930165521.php index 120f1c16..2e4e8f50 100644 --- a/data/migrations/Version20190930165521.php +++ b/data/migrations/Version20190930165521.php @@ -6,7 +6,7 @@ namespace ShlinkMigrations; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\SchemaException; -use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Types\Types; use Doctrine\Migrations\AbstractMigration; final class Version20190930165521 extends AbstractMigration @@ -22,19 +22,19 @@ final class Version20190930165521 extends AbstractMigration } $domains = $schema->createTable('domains'); - $domains->addColumn('id', Type::BIGINT, [ + $domains->addColumn('id', Types::BIGINT, [ 'unsigned' => true, 'autoincrement' => true, 'notnull' => true, ]); - $domains->addColumn('authority', Type::STRING, [ + $domains->addColumn('authority', Types::STRING, [ 'length' => 512, 'notnull' => true, ]); $domains->addUniqueIndex(['authority']); $domains->setPrimaryKey(['id']); - $shortUrls->addColumn('domain_id', Type::BIGINT, [ + $shortUrls->addColumn('domain_id', Types::BIGINT, [ 'unsigned' => true, 'notnull' => false, ]); diff --git a/data/migrations/Version20200105165647.php b/data/migrations/Version20200105165647.php new file mode 100644 index 00000000..24b9f984 --- /dev/null +++ b/data/migrations/Version20200105165647.php @@ -0,0 +1,71 @@ + 'latitude', 'lon' => 'longitude']; + + public function preUp(Schema $schema): void + { + foreach (self::COLUMNS as $columnName) { + $qb = $this->connection->createQueryBuilder(); + $qb->update('visit_locations') + ->set($columnName, '"0"') + ->where($columnName . '=""') + ->orWhere($columnName . ' IS NULL') + ->execute(); + } + } + + /** + * @throws DBALException + */ + public function up(Schema $schema): void + { + $visitLocations = $schema->getTable('visit_locations'); + + foreach (self::COLUMNS as $newName => $oldName) { + $visitLocations->addColumn($newName, Types::FLOAT); + } + } + + public function postUp(Schema $schema): void + { + foreach (self::COLUMNS as $newName => $oldName) { + $qb = $this->connection->createQueryBuilder(); + $qb->update('visit_locations') + ->set($newName, $oldName) + ->execute(); + } + } + + public function preDown(Schema $schema): void + { + foreach (self::COLUMNS as $newName => $oldName) { + $qb = $this->connection->createQueryBuilder(); + $qb->update('visit_locations') + ->set($oldName, $newName) + ->execute(); + } + } + + /** + * @throws DBALException + */ + public function down(Schema $schema): void + { + $visitLocations = $schema->getTable('visit_locations'); + + foreach (self::COLUMNS as $colName => $oldName) { + $visitLocations->dropColumn($colName); + } + } +} diff --git a/data/migrations/Version20200106215144.php b/data/migrations/Version20200106215144.php new file mode 100644 index 00000000..8969441b --- /dev/null +++ b/data/migrations/Version20200106215144.php @@ -0,0 +1,41 @@ +getTable('visit_locations'); + + foreach (self::COLUMNS as $colName) { + $visitLocations->dropColumn($colName); + } + } + + /** + * @throws DBALException + */ + public function down(Schema $schema): void + { + $visitLocations = $schema->getTable('visit_locations'); + + foreach (self::COLUMNS as $colName) { + $visitLocations->addColumn($colName, Types::STRING, [ + 'notnull' => false, + ]); + } + } +} diff --git a/docker/README.md b/docker/README.md index b600c268..4b1fdea1 100644 --- a/docker/README.md +++ b/docker/README.md @@ -103,7 +103,7 @@ This is the complete list of supported env vars: * **postgres** -> `5432` * `DISABLE_TRACK_PARAM`: The name of a query param that can be used to visit short URLs avoiding the visit to be tracked. This feature won't be available if not value is provided. * `DELETE_SHORT_URL_THRESHOLD`: The amount of visits on short URLs which will not allow them to be deleted. Defaults to `15`. -* `VALIDATE_URLS`: Boolean which tells if shlink should validate a status 20x (after following redirects) is returned when trying to shorten a URL. Defaults to `true`. +* `VALIDATE_URLS`: Boolean which tells if shlink should validate a status 20x is returned (after following redirects) when trying to shorten a URL. Defaults to `false`. * `INVALID_SHORT_URL_REDIRECT_TO`: If a URL is provided here, when a user tries to access an invalid short URL, he/she will be redirected to this value. If this env var is not provided, the user will see a generic `404 - not found` page. * `REGULAR_404_REDIRECT_TO`: If a URL is provided here, when a user tries to access a URL not matching any one supported by the router, he/she will be redirected to this value. If this env var is not provided, the user will see a generic `404 - not found` page. * `BASE_URL_REDIRECT_TO`: If a URL is provided here, when a user tries to access Shlink's base URL, he/she will be redirected to this value. If this env var is not provided, the user will see a generic `404 - not found` page. @@ -119,9 +119,6 @@ This is the complete list of supported env vars: In the future, these redis servers could be used for other caching operations performed by shlink. -* `NOT_FOUND_REDIRECT_TO`: **Deprecated since v1.20 in favor of `INVALID_SHORT_URL_REDIRECT_TO`** If a URL is provided here, when a user tries to access an invalid short URL, he/she will be redirected to this value. If this env var is not provided, the user will see a generic `404 - not found` page. -* `SHORTCODE_CHARS`: **Ignored when using Shlink 1.20 or newer**. A charset to use when building short codes. Only needed when using more than one shlink instance ([Multi instance considerations](#multi-instance-considerations)). - An example using all env vars could look like this: ```bash @@ -138,7 +135,7 @@ docker run \ -e DB_PORT=3306 \ -e DISABLE_TRACK_PARAM="no-track" \ -e DELETE_SHORT_URL_THRESHOLD=30 \ - -e VALIDATE_URLS=false \ + -e VALIDATE_URLS=true \ -e "INVALID_SHORT_URL_REDIRECT_TO=https://my-landing-page.com" \ -e "REGULAR_404_REDIRECT_TO=https://my-landing-page.com" \ -e "BASE_URL_REDIRECT_TO=https://my-landing-page.com" \ @@ -164,7 +161,7 @@ The whole configuration should have this format, but it can be split into multip "delete_short_url_threshold": 30, "short_domain_schema": "https", "short_domain_host": "doma.in", - "validate_url": false, + "validate_url": true, "invalid_short_url_redirect_to": "https://my-landing-page.com", "regular_404_redirect_to": "https://my-landing-page.com", "base_url_redirect_to": "https://my-landing-page.com", @@ -186,15 +183,12 @@ The whole configuration should have this format, but it can be split into multip "password": "123abc", "host": "something.rds.amazonaws.com", "port": "3306" - }, - "not_found_redirect_to": "https://my-landing-page.com" + } } ``` > This is internally parsed to how shlink expects the config. If you are using a version previous to 1.17.0, this parser is not present and you need to provide a config structure like the one [documented previously](https://github.com/shlinkio/shlink-docker-image/tree/v1.16.3#provide-config-via-volumes). -> The `not_found_redirect_to` option has been deprecated in v1.20. Use `invalid_short_url_redirect_to` instead (however, it will still work for backwards compatibility). - Once created just run shlink with the volume: ```bash @@ -211,20 +205,12 @@ These are some considerations to take into account when running multiple instanc You can (and should) make the locks to be shared by all Shlink instances by using a redis server/cluster. Just define the `REDIS_SERVERS` env var with the list of servers. -* **Ignore this if using Shlink 1.20 or newer**. The first time shlink is run, it generates a charset used to generate short codes, which is a shuffled base62 charset. - - If you are using several shlink instances, you will probably want all of them to use the same charset. - - You can get a shuffled base62 charset by going to [https://shlink.io/short-code-chars](https://shlink.io/short-code-chars), and then you just need to pass it to all shlink instances using the `SHORTCODE_CHARS` env var. - - If you don't do this, each shlink instance will use a different charset. However this shouldn't be a problem in practice, since the chances to get a collision will be very low. - ## Versions Versioning on this docker image works as follows: * `X.X.X`: when providing a specific version number, the image version will match the shlink version it contains. For example, installing `shlinkio/shlink:1.15.0`, you will get an image containing shlink v1.15.0. -* `stable`: always holds the latest stable tag. For example, if latest shlink version is 1.20.0, installing `shlinkio/shlink:stable`, you will get an image containing shlink v1.20.0 +* `stable`: always holds the latest stable tag. For example, if latest shlink version is 2.0.0, installing `shlinkio/shlink:stable`, you will get an image containing shlink v2.0.0 * `latest`: always holds the latest contents in master, and it's considered unstable and not suitable for production. > **Important**: The docker image was introduced with shlink v1.15.0, so there are no official images previous to that versions. diff --git a/docker/config/shlink_in_docker.local.php b/docker/config/shlink_in_docker.local.php index 6d3367ac..7eba5560 100644 --- a/docker/config/shlink_in_docker.local.php +++ b/docker/config/shlink_in_docker.local.php @@ -8,19 +8,10 @@ use Monolog\Handler\StreamHandler; use Monolog\Logger; use function explode; -use function file_exists; -use function file_get_contents; -use function file_put_contents; use function Functional\contains; -use function implode; use function Shlinkio\Shlink\Common\env; -use function sprintf; -use function str_shuffle; -use function substr; -use function sys_get_temp_dir; $helper = new class { - private const BASE62 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; private const DB_DRIVERS_MAP = [ 'mysql' => 'pdo_mysql', 'maria' => 'pdo_mysql', @@ -32,40 +23,6 @@ $helper = new class { 'postgres' => '5432', ]; - /** @var string */ - private $secretKey; - - public function __construct() - { - [, $this->secretKey] = $this->initShlinkSecretKey(); - } - - private function initShlinkSecretKey(): array - { - $keysFile = sprintf('%s/shlink.keys', sys_get_temp_dir()); - if (file_exists($keysFile)) { - return explode(',', file_get_contents($keysFile)); - } - - $keys = [ - '', // This was the SHORTCODE_CHARS. Kept as empty string for BC - env('SECRET_KEY', $this->generateSecretKey()), // Deprecated - ]; - - file_put_contents($keysFile, implode(',', $keys)); - return $keys; - } - - private function generateSecretKey(): string - { - return substr(str_shuffle(self::BASE62), 0, 32); - } - - public function getSecretKey(): string - { - return $this->secretKey; - } - public function getDbConfig(): array { $driver = env('DB_DRIVER'); @@ -94,7 +51,7 @@ $helper = new class { public function getNotFoundRedirectsConfig(): array { return [ - 'invalid_short_url' => env('INVALID_SHORT_URL_REDIRECT_TO', env('NOT_FOUND_REDIRECT_TO')), + 'invalid_short_url' => env('INVALID_SHORT_URL_REDIRECT_TO'), 'regular_404' => env('REGULAR_404_REDIRECT_TO'), 'base_url' => env('BASE_URL_REDIRECT_TO'), ]; @@ -105,6 +62,12 @@ $helper = new class { $webhooks = env('VISITS_WEBHOOKS'); return $webhooks === null ? [] : explode(',', $webhooks); } + + public function getRedisConfig(): ?array + { + $redisServers = env('REDIS_SERVERS'); + return $redisServers === null ? null : ['servers' => $redisServers]; + } }; return [ @@ -112,7 +75,6 @@ return [ 'config_cache_enabled' => false, 'app_options' => [ - 'secret_key' => $helper->getSecretKey(), 'disable_track_param' => env('DISABLE_TRACK_PARAM'), ], @@ -130,7 +92,7 @@ return [ 'schema' => env('SHORT_DOMAIN_SCHEMA', 'http'), 'hostname' => env('SHORT_DOMAIN_HOST', ''), ], - 'validate_url' => (bool) env('VALIDATE_URLS', true), + 'validate_url' => (bool) env('VALIDATE_URLS', false), 'visits_webhooks' => $helper->getVisitsWebhooks(), ], @@ -156,15 +118,15 @@ return [ ], ], - 'redis' => [ - 'servers' => env('REDIS_SERVERS'), + 'cache' => [ + 'redis' => $helper->getRedisConfig(), ], 'router' => [ 'base_path' => env('BASE_PATH', ''), ], - 'zend-expressive-swoole' => [ + 'mezzio-swoole' => [ 'swoole-http-server' => [ 'options' => [ 'worker_num' => (int) env('WEB_WORKER_NUM', 16), diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index 4842522d..7f924057 100644 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -14,4 +14,4 @@ php vendor/doctrine/orm/bin/doctrine.php orm:generate-proxies -n -q # When restarting the container, swoole might think it is already in execution # This forces the app to be started every second until the exit code is 0 -until php vendor/zendframework/zend-expressive-swoole/bin/zend-expressive-swoole start; do sleep 1 ; done +until php vendor/mezzio/mezzio-swoole/bin/mezzio-swoole start; do sleep 1 ; done diff --git a/docs/swagger/definitions/Error.json b/docs/swagger/definitions/Error.json index 006fa47a..217f32c5 100644 --- a/docs/swagger/definitions/Error.json +++ b/docs/swagger/definitions/Error.json @@ -17,16 +17,6 @@ "status": { "type": "number", "description": "HTTP response status code" - }, - "code": { - "type": "string", - "description": "**[Deprecated] Use type instead. Not returned for v2 of the REST API** A machine unique code", - "deprecated": true - }, - "message": { - "type": "string", - "description": "**[Deprecated] Use detail instead. Not returned for v2 of the REST API** A human-friendly error message", - "deprecated": true } } } diff --git a/docs/swagger/definitions/ShortUrl.json b/docs/swagger/definitions/ShortUrl.json index 9b259249..8111bd6e 100644 --- a/docs/swagger/definitions/ShortUrl.json +++ b/docs/swagger/definitions/ShortUrl.json @@ -31,11 +31,6 @@ }, "meta": { "$ref": "./ShortUrlMeta.json" - }, - "originalUrl": { - "deprecated": true, - "type": "string", - "description": "The original long URL. [DEPRECATED. Use longUrl instead]" } } } diff --git a/docs/swagger/definitions/Visit.json b/docs/swagger/definitions/Visit.json index 131bc84e..9e1eb5b5 100644 --- a/docs/swagger/definitions/Visit.json +++ b/docs/swagger/definitions/Visit.json @@ -16,11 +16,6 @@ }, "visitLocation": { "$ref": "./VisitLocation.json" - }, - "remoteAddr": { - "type": "string", - "description": "This value is deprecated and will always be null", - "deprecated": true } } } diff --git a/docs/swagger/definitions/VisitLocation.json b/docs/swagger/definitions/VisitLocation.json index e4c9fc90..7e2fa18c 100644 --- a/docs/swagger/definitions/VisitLocation.json +++ b/docs/swagger/definitions/VisitLocation.json @@ -11,10 +11,10 @@ "type": "string" }, "latitude": { - "type": "string" + "type": "number" }, "longitude": { - "type": "string" + "type": "number" }, "regionName": { "type": "string" diff --git a/docs/swagger/paths/v1_authenticate.json b/docs/swagger/paths/v1_authenticate.json deleted file mode 100644 index cffa9e05..00000000 --- a/docs/swagger/paths/v1_authenticate.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "post": { - "deprecated": true, - "operationId": "authenticate", - "tags": [ - "Authentication" - ], - "summary": "[Deprecated] Perform authentication", - "description": "**This endpoint is deprecated, since the authentication can be performed via API key now**. Performs an authentication.", - "requestBody": { - "description": "Request body.", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "apiKey" - ], - "properties": { - "apiKey": { - "description": "The API key to authenticate with", - "type": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "The authentication worked.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "token": { - "type": "string", - "description": "The authentication token that needs to be sent in the Authorization header" - } - } - } - } - }, - "examples": { - "application/json": { - "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" - } - } - }, - "400": { - "description": "An API key was not provided.", - "content": { - "application/json": { - "schema": { - "$ref": "../definitions/Error.json" - } - } - } - }, - "401": { - "description": "The API key is incorrect, is disabled or has expired.", - "content": { - "application/json": { - "schema": { - "$ref": "../definitions/Error.json" - } - } - } - }, - "500": { - "description": "Unexpected error.", - "content": { - "application/json": { - "schema": { - "$ref": "../definitions/Error.json" - } - } - } - } - } - } -} diff --git a/docs/swagger/paths/v1_short-urls.json b/docs/swagger/paths/v1_short-urls.json index 46708e22..0c6d0484 100644 --- a/docs/swagger/paths/v1_short-urls.json +++ b/docs/swagger/paths/v1_short-urls.json @@ -5,7 +5,7 @@ "Short URLs" ], "summary": "List short URLs", - "description": "Returns the list of short URLs.

**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.", + "description": "Returns the list of short URLs.", "parameters": [ { "$ref": "../parameters/version.json" @@ -77,9 +77,6 @@ "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "responses": { @@ -187,13 +184,10 @@ "Short URLs" ], "summary": "Create short URL", - "description": "Creates a new short URL.

**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.

**Param findIfExists:**: Starting with v1.16, this new param allows to force shlink to return existing short URLs when found based on provided params, instead of creating a new one. However, it might add complexity and have unexpected outputs.\n\nThese are the use cases:\n* Only the long URL is provided: It will return the newest match or create a new short URL if none is found.\n* Long url and custom slug are provided: It will return the short URL when both params match, return an error when the slug is in use for another long URL, or create a new short URL otherwise.\n* Any of the above but including other params (tags, validSince, validUntil, maxVisits): It will behave the same as the previous two cases, but it will try to exactly match existing results using all the params. If any of them does not match, it will try to create a new short URL.", + "description": "Creates a new short URL.

**Param findIfExists:**: Starting with v1.16, this new param allows to force shlink to return existing short URLs when found based on provided params, instead of creating a new one. However, it might add complexity and have unexpected outputs.\n\nThese are the use cases:\n* Only the long URL is provided: It will return the newest match or create a new short URL if none is found.\n* Long url and custom slug are provided: It will return the short URL when both params match, return an error when the slug is in use for another long URL, or create a new short URL otherwise.\n* Any of the above but including other params (tags, validSince, validUntil, maxVisits): It will behave the same as the previous two cases, but it will try to exactly match existing results using all the params. If any of them does not match, it will try to create a new short URL.", "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "parameters": [ diff --git a/docs/swagger/paths/v1_short-urls_shorten.json b/docs/swagger/paths/v1_short-urls_shorten.json index 49233c49..c5ad9352 100644 --- a/docs/swagger/paths/v1_short-urls_shorten.json +++ b/docs/swagger/paths/v1_short-urls_shorten.json @@ -5,7 +5,7 @@ "Short URLs" ], "summary": "Create a short URL", - "description": "Creates a short URL in a single API call. Useful for third party integrations.

**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.", + "description": "Creates a short URL in a single API call. Useful for third party integrations.", "parameters": [ { "$ref": "../parameters/version.json" diff --git a/docs/swagger/paths/v1_short-urls_{shortCode}.json b/docs/swagger/paths/v1_short-urls_{shortCode}.json index 95532868..d4537a83 100644 --- a/docs/swagger/paths/v1_short-urls_{shortCode}.json +++ b/docs/swagger/paths/v1_short-urls_{shortCode}.json @@ -5,7 +5,7 @@ "Short URLs" ], "summary": "Parse short code", - "description": "Get the long URL behind a short URL's short code.

**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.", + "description": "Get the long URL behind a short URL's short code.", "parameters": [ { "$ref": "../parameters/version.json" @@ -32,9 +32,6 @@ "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "responses": { @@ -94,7 +91,7 @@ "Short URLs" ], "summary": "Edit short URL", - "description": "Update certain meta arguments from an existing short URL.

**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.", + "description": "Update certain meta arguments from an existing short URL.", "parameters": [ { "$ref": "../parameters/version.json" @@ -137,9 +134,6 @@ "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "responses": { @@ -201,105 +195,13 @@ } }, - "put": { - "deprecated": true, - "operationId": "editShortUrlPut", - "tags": [ - "Short URLs" - ], - "summary": "[DEPRECATED] Edit short URL", - "description": "**[DEPRECATED]** Use [editShortUrl](#/Short_URLs/getShortUrl) instead", - "parameters": [ - { - "$ref": "../parameters/version.json" - }, - { - "name": "shortCode", - "in": "path", - "description": "The short code to edit.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "description": "Request body.", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "validSince": { - "description": "The date (in ISO-8601 format) from which this short code will be valid", - "type": "string" - }, - "validUntil": { - "description": "The date (in ISO-8601 format) until which this short code will be valid", - "type": "string" - }, - "maxVisits": { - "description": "The maximum number of allowed visits for this short code", - "type": "number" - } - } - } - } - } - }, - "security": [ - { - "ApiKey": [] - }, - { - "Bearer": [] - } - ], - "responses": { - "204": { - "description": "The short code has been properly updated." - }, - "400": { - "description": "Provided meta arguments are invalid.", - "content": { - "application/problem+json": { - "schema": { - "$ref": "../definitions/Error.json" - } - } - } - }, - "404": { - "description": "No short URL was found for provided short code.", - "content": { - "application/problem+json": { - "schema": { - "$ref": "../definitions/Error.json" - } - } - } - }, - "500": { - "description": "Unexpected error.", - "content": { - "application/problem+json": { - "schema": { - "$ref": "../definitions/Error.json" - } - } - } - } - } - }, - "delete": { "operationId": "deleteShortUrl", "tags": [ "Short URLs" ], "summary": "Delete short URL", - "description": "Deletes the short URL for provided short code.

**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.", + "description": "Deletes the short URL for provided short code.", "parameters": [ { "$ref": "../parameters/version.json" @@ -317,9 +219,6 @@ "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "responses": { diff --git a/docs/swagger/paths/v1_short-urls_{shortCode}_tags.json b/docs/swagger/paths/v1_short-urls_{shortCode}_tags.json index 45142e62..4065f718 100644 --- a/docs/swagger/paths/v1_short-urls_{shortCode}_tags.json +++ b/docs/swagger/paths/v1_short-urls_{shortCode}_tags.json @@ -5,7 +5,7 @@ "Short URLs" ], "summary": "Edit tags on short URL", - "description": "Edit the tags on URL identified by provided short code.

**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.", + "description": "Edit the tags on URL identified by provided short code.", "parameters": [ { "$ref": "../parameters/version.json" @@ -46,9 +46,6 @@ "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "responses": { diff --git a/docs/swagger/paths/v1_short-urls_{shortCode}_visits.json b/docs/swagger/paths/v1_short-urls_{shortCode}_visits.json index 508449de..2369ba13 100644 --- a/docs/swagger/paths/v1_short-urls_{shortCode}_visits.json +++ b/docs/swagger/paths/v1_short-urls_{shortCode}_visits.json @@ -5,7 +5,7 @@ "Visits" ], "summary": "List visits for short URL", - "description": "Get the list of visits on the short URL behind provided short code.

**Important note**: Before shlink v1.13, this endpoint used to use the `/short-codes` path instead of `/short-urls`. Both of them will keep working, while the first one is considered deprecated.", + "description": "Get the list of visits on the short URL behind provided short code.", "parameters": [ { "$ref": "../parameters/version.json" @@ -59,9 +59,6 @@ "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "responses": { @@ -108,8 +105,8 @@ "cityName": "Cupertino", "countryCode": "US", "countryName": "United States", - "latitude": "37.3042", - "longitude": "-122.0946", + "latitude": 37.3042, + "longitude": -122.0946, "regionName": "California", "timezone": "America/Los_Angeles" } diff --git a/docs/swagger/paths/v1_tags.json b/docs/swagger/paths/v1_tags.json index a0fcc512..5e7fd71c 100644 --- a/docs/swagger/paths/v1_tags.json +++ b/docs/swagger/paths/v1_tags.json @@ -9,9 +9,6 @@ "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "parameters": [ @@ -78,9 +75,6 @@ "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "parameters": [ @@ -170,9 +164,6 @@ "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "parameters": [ @@ -279,9 +270,6 @@ "security": [ { "ApiKey": [] - }, - { - "Bearer": [] } ], "responses": { diff --git a/docs/swagger/paths/{shortCode}_preview.json b/docs/swagger/paths/{shortCode}_preview.json deleted file mode 100644 index 98df559c..00000000 --- a/docs/swagger/paths/{shortCode}_preview.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "get": { - "deprecated": true, - "operationId": "shortUrlPreview", - "tags": [ - "URL Shortener" - ], - "summary": "Short URL preview image", - "description": "Returns the preview of the page behind a short URL", - "parameters": [ - { - "name": "shortCode", - "in": "path", - "description": "The short code to resolve.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Image in PNG format", - "content": { - "image/png": { - "schema": { - "type": "string", - "format": "binary" - } - } - } - } - } - } -} diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index d924b8a0..ddf2b3ad 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -33,12 +33,6 @@ "type": "apiKey", "in": "header", "name": "X-Api-Key" - }, - "Bearer": { - "description": "**[DEPRECATED]** The JWT identifying a previously authenticated API key", - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" } } }, @@ -63,10 +57,6 @@ { "name": "URL Shortener", "description": "Non-rest endpoints, used to be publicly exposed" - }, - { - "name": "Authentication", - "description": "**[DEPRECATED]** Authentication-related endpoints" } ], @@ -104,13 +94,6 @@ }, "/{shortCode}/qr-code": { "$ref": "paths/{shortCode}_qr-code.json" - }, - "/{shortCode}/preview": { - "$ref": "paths/{shortCode}_preview.json" - }, - - "/rest/v1/authenticate": { - "$ref": "paths/v1_authenticate.json" } } } diff --git a/module/CLI/config/cli.config.php b/module/CLI/config/cli.config.php index 0d5d5d5b..fa9efc69 100644 --- a/module/CLI/config/cli.config.php +++ b/module/CLI/config/cli.config.php @@ -12,14 +12,9 @@ return [ Command\ShortUrl\ResolveUrlCommand::NAME => Command\ShortUrl\ResolveUrlCommand::class, Command\ShortUrl\ListShortUrlsCommand::NAME => Command\ShortUrl\ListShortUrlsCommand::class, Command\ShortUrl\GetVisitsCommand::NAME => Command\ShortUrl\GetVisitsCommand::class, - Command\ShortUrl\GeneratePreviewCommand::NAME => Command\ShortUrl\GeneratePreviewCommand::class, Command\ShortUrl\DeleteShortUrlCommand::NAME => Command\ShortUrl\DeleteShortUrlCommand::class, Command\Visit\LocateVisitsCommand::NAME => Command\Visit\LocateVisitsCommand::class, - Command\Visit\UpdateDbCommand::NAME => Command\Visit\UpdateDbCommand::class, - - Command\Config\GenerateCharsetCommand::NAME => Command\Config\GenerateCharsetCommand::class, - Command\Config\GenerateSecretCommand::NAME => Command\Config\GenerateSecretCommand::class, Command\Api\GenerateKeyCommand::NAME => Command\Api\GenerateKeyCommand::class, Command\Api\DisableKeyCommand::NAME => Command\Api\DisableKeyCommand::class, diff --git a/module/CLI/config/dependencies.config.php b/module/CLI/config/dependencies.config.php index c89a0ad7..dfa8452f 100644 --- a/module/CLI/config/dependencies.config.php +++ b/module/CLI/config/dependencies.config.php @@ -6,19 +6,18 @@ namespace Shlinkio\Shlink\CLI; use Doctrine\DBAL\Connection; use GeoIp2\Database\Reader; +use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory; +use Laminas\ServiceManager\Factory\InvokableFactory; use Shlinkio\Shlink\CLI\Util\GeolocationDbUpdater; use Shlinkio\Shlink\Common\Doctrine\NoDbNameConnectionFactory; use Shlinkio\Shlink\Core\Service; use Shlinkio\Shlink\Installer\Factory\ProcessHelperFactory; use Shlinkio\Shlink\IpGeolocation\GeoLite2\DbUpdater; use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface; -use Shlinkio\Shlink\PreviewGenerator\Service\PreviewGenerator; use Shlinkio\Shlink\Rest\Service\ApiKeyService; use Symfony\Component\Console as SymfonyCli; use Symfony\Component\Lock\LockFactory; use Symfony\Component\Process\PhpExecutableFinder; -use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory; -use Zend\ServiceManager\Factory\InvokableFactory; return [ @@ -34,14 +33,9 @@ return [ Command\ShortUrl\ResolveUrlCommand::class => ConfigAbstractFactory::class, Command\ShortUrl\ListShortUrlsCommand::class => ConfigAbstractFactory::class, Command\ShortUrl\GetVisitsCommand::class => ConfigAbstractFactory::class, - Command\ShortUrl\GeneratePreviewCommand::class => ConfigAbstractFactory::class, Command\ShortUrl\DeleteShortUrlCommand::class => ConfigAbstractFactory::class, Command\Visit\LocateVisitsCommand::class => ConfigAbstractFactory::class, - Command\Visit\UpdateDbCommand::class => ConfigAbstractFactory::class, - - Command\Config\GenerateCharsetCommand::class => InvokableFactory::class, - Command\Config\GenerateSecretCommand::class => InvokableFactory::class, Command\Api\GenerateKeyCommand::class => ConfigAbstractFactory::class, Command\Api\DisableKeyCommand::class => ConfigAbstractFactory::class, @@ -64,7 +58,6 @@ return [ Command\ShortUrl\ResolveUrlCommand::class => [Service\UrlShortener::class], Command\ShortUrl\ListShortUrlsCommand::class => [Service\ShortUrlService::class, 'config.url_shortener.domain'], Command\ShortUrl\GetVisitsCommand::class => [Service\VisitsTracker::class], - Command\ShortUrl\GeneratePreviewCommand::class => [Service\ShortUrlService::class, PreviewGenerator::class], Command\ShortUrl\DeleteShortUrlCommand::class => [Service\ShortUrl\DeleteShortUrlService::class], Command\Visit\LocateVisitsCommand::class => [ @@ -73,7 +66,6 @@ return [ LockFactory::class, GeolocationDbUpdater::class, ], - Command\Visit\UpdateDbCommand::class => [DbUpdater::class], Command\Api\GenerateKeyCommand::class => [ApiKeyService::class], Command\Api\DisableKeyCommand::class => [ApiKeyService::class], diff --git a/module/CLI/src/Command/Api/DisableKeyCommand.php b/module/CLI/src/Command/Api/DisableKeyCommand.php index f3eb7f6b..1a8024ec 100644 --- a/module/CLI/src/Command/Api/DisableKeyCommand.php +++ b/module/CLI/src/Command/Api/DisableKeyCommand.php @@ -19,8 +19,7 @@ class DisableKeyCommand extends Command { public const NAME = 'api-key:disable'; - /** @var ApiKeyServiceInterface */ - private $apiKeyService; + private ApiKeyServiceInterface $apiKeyService; public function __construct(ApiKeyServiceInterface $apiKeyService) { diff --git a/module/CLI/src/Command/Api/GenerateKeyCommand.php b/module/CLI/src/Command/Api/GenerateKeyCommand.php index bbe86a51..889556b2 100644 --- a/module/CLI/src/Command/Api/GenerateKeyCommand.php +++ b/module/CLI/src/Command/Api/GenerateKeyCommand.php @@ -19,8 +19,7 @@ class GenerateKeyCommand extends Command { public const NAME = 'api-key:generate'; - /** @var ApiKeyServiceInterface */ - private $apiKeyService; + private ApiKeyServiceInterface $apiKeyService; public function __construct(ApiKeyServiceInterface $apiKeyService) { @@ -37,7 +36,7 @@ class GenerateKeyCommand extends Command 'expirationDate', 'e', InputOption::VALUE_REQUIRED, - 'The date in which the API key should expire. Use any valid PHP format.' + 'The date in which the API key should expire. Use any valid PHP format.', ); } diff --git a/module/CLI/src/Command/Api/ListKeysCommand.php b/module/CLI/src/Command/Api/ListKeysCommand.php index c9df3a07..f54ad8dd 100644 --- a/module/CLI/src/Command/Api/ListKeysCommand.php +++ b/module/CLI/src/Command/Api/ListKeysCommand.php @@ -25,8 +25,7 @@ class ListKeysCommand extends Command public const NAME = 'api-key:list'; - /** @var ApiKeyServiceInterface */ - private $apiKeyService; + private ApiKeyServiceInterface $apiKeyService; public function __construct(ApiKeyServiceInterface $apiKeyService) { @@ -43,7 +42,7 @@ class ListKeysCommand extends Command 'enabledOnly', 'e', InputOption::VALUE_NONE, - 'Tells if only enabled API keys should be returned.' + 'Tells if only enabled API keys should be returned.', ); } @@ -82,8 +81,6 @@ class ListKeysCommand extends Command } /** - * @param ApiKey $apiKey - * @return string */ private function getEnabledSymbol(ApiKey $apiKey): string { diff --git a/module/CLI/src/Command/Config/GenerateCharsetCommand.php b/module/CLI/src/Command/Config/GenerateCharsetCommand.php deleted file mode 100644 index a285c7f0..00000000 --- a/module/CLI/src/Command/Config/GenerateCharsetCommand.php +++ /dev/null @@ -1,40 +0,0 @@ -setName(self::NAME) - ->setDescription(sprintf( - '[DEPRECATED] Generates a character set sample just by shuffling the default one, "%s". ' - . 'Then it can be set in the SHORTCODE_CHARS environment variable', - self::DEFAULT_CHARS - )) - ->setHelp('This command is deprecated. Better leave shlink generate the charset.'); - } - - protected function execute(InputInterface $input, OutputInterface $output): ?int - { - $charSet = str_shuffle(self::DEFAULT_CHARS); - (new SymfonyStyle($input, $output))->success(sprintf('Character set: "%s"', $charSet)); - return ExitCodes::EXIT_SUCCESS; - } -} diff --git a/module/CLI/src/Command/Config/GenerateSecretCommand.php b/module/CLI/src/Command/Config/GenerateSecretCommand.php deleted file mode 100644 index c0d60eea..00000000 --- a/module/CLI/src/Command/Config/GenerateSecretCommand.php +++ /dev/null @@ -1,39 +0,0 @@ -setName(self::NAME) - ->setDescription('[DEPRECATED] Generates a random secret string that can be used for JWT token encryption') - ->setHelp( - 'This command is deprecated. Better leave shlink generate the secret key.' - ); - } - - protected function execute(InputInterface $input, OutputInterface $output): ?int - { - $secret = $this->generateRandomString(32); - (new SymfonyStyle($input, $output))->success(sprintf('Secret key: "%s"', $secret)); - return ExitCodes::EXIT_SUCCESS; - } -} diff --git a/module/CLI/src/Command/Db/AbstractDatabaseCommand.php b/module/CLI/src/Command/Db/AbstractDatabaseCommand.php index bf99de9b..4b45fa56 100644 --- a/module/CLI/src/Command/Db/AbstractDatabaseCommand.php +++ b/module/CLI/src/Command/Db/AbstractDatabaseCommand.php @@ -15,10 +15,8 @@ use function array_unshift; abstract class AbstractDatabaseCommand extends AbstractLockedCommand { - /** @var ProcessHelper */ - private $processHelper; - /** @var string */ - private $phpBinary; + private ProcessHelper $processHelper; + private string $phpBinary; public function __construct(LockFactory $locker, ProcessHelper $processHelper, PhpExecutableFinder $phpFinder) { diff --git a/module/CLI/src/Command/Db/CreateDatabaseCommand.php b/module/CLI/src/Command/Db/CreateDatabaseCommand.php index 54cd27ea..b8e88688 100644 --- a/module/CLI/src/Command/Db/CreateDatabaseCommand.php +++ b/module/CLI/src/Command/Db/CreateDatabaseCommand.php @@ -21,10 +21,8 @@ class CreateDatabaseCommand extends AbstractDatabaseCommand public const DOCTRINE_SCRIPT = 'vendor/doctrine/orm/bin/doctrine.php'; public const DOCTRINE_CREATE_SCHEMA_COMMAND = 'orm:schema-tool:create'; - /** @var Connection */ - private $regularConn; - /** @var Connection */ - private $noDbNameConn; + private Connection $regularConn; + private Connection $noDbNameConn; public function __construct( LockFactory $locker, @@ -43,7 +41,7 @@ class CreateDatabaseCommand extends AbstractDatabaseCommand $this ->setName(self::NAME) ->setDescription( - 'Creates the database needed for shlink to work. It will do nothing if the database already exists' + 'Creates the database needed for shlink to work. It will do nothing if the database already exists', ); } diff --git a/module/CLI/src/Command/ShortUrl/DeleteShortUrlCommand.php b/module/CLI/src/Command/ShortUrl/DeleteShortUrlCommand.php index c2e81d0d..bee66c34 100644 --- a/module/CLI/src/Command/ShortUrl/DeleteShortUrlCommand.php +++ b/module/CLI/src/Command/ShortUrl/DeleteShortUrlCommand.php @@ -19,10 +19,8 @@ use function sprintf; class DeleteShortUrlCommand extends Command { public const NAME = 'short-url:delete'; - private const ALIASES = ['short-code:delete']; - /** @var DeleteShortUrlServiceInterface */ - private $deleteShortUrlService; + private DeleteShortUrlServiceInterface $deleteShortUrlService; public function __construct(DeleteShortUrlServiceInterface $deleteShortUrlService) { @@ -34,7 +32,6 @@ class DeleteShortUrlCommand extends Command { $this ->setName(self::NAME) - ->setAliases(self::ALIASES) ->setDescription('Deletes a short URL') ->addArgument('shortCode', InputArgument::REQUIRED, 'The short code for the short URL to be deleted') ->addOption( @@ -42,7 +39,7 @@ class DeleteShortUrlCommand extends Command 'i', InputOption::VALUE_NONE, 'Ignores the safety visits threshold check, which could make short URLs with many visits to be ' - . 'accidentally deleted' + . 'accidentally deleted', ); } diff --git a/module/CLI/src/Command/ShortUrl/GeneratePreviewCommand.php b/module/CLI/src/Command/ShortUrl/GeneratePreviewCommand.php deleted file mode 100644 index 081fa94f..00000000 --- a/module/CLI/src/Command/ShortUrl/GeneratePreviewCommand.php +++ /dev/null @@ -1,76 +0,0 @@ -shortUrlService = $shortUrlService; - $this->previewGenerator = $previewGenerator; - } - - protected function configure(): void - { - $this - ->setName(self::NAME) - ->setAliases(self::ALIASES) - ->setDescription( - '[DEPRECATED] Processes and generates the previews for every URL, improving performance for later web ' - . 'requests.' - ); - } - - protected function execute(InputInterface $input, OutputInterface $output): ?int - { - $page = 1; - do { - $shortUrls = $this->shortUrlService->listShortUrls($page); - $page += 1; - - foreach ($shortUrls as $shortUrl) { - $this->processUrl($shortUrl->getLongUrl(), $output); - } - } while ($page <= $shortUrls->count()); - - (new SymfonyStyle($input, $output))->success('Finished processing all URLs'); - return ExitCodes::EXIT_SUCCESS; - } - - private function processUrl($url, OutputInterface $output): void - { - try { - $output->write(sprintf('Processing URL %s...', $url)); - $this->previewGenerator->generatePreview($url); - $output->writeln(' Success!'); - } catch (PreviewGenerationException $e) { - $output->writeln(' Error'); - if ($output->isVerbose()) { - $this->getApplication()->renderThrowable($e, $output); - } - } - } -} diff --git a/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php b/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php index faefbf4b..1e9b41ce 100644 --- a/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php +++ b/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\ShortUrl; +use Laminas\Diactoros\Uri; use Shlinkio\Shlink\CLI\Util\ExitCodes; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; @@ -15,7 +16,6 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -use Zend\Diactoros\Uri; use function array_map; use function Functional\curry; @@ -26,12 +26,9 @@ use function sprintf; class GenerateShortUrlCommand extends Command { public const NAME = 'short-url:generate'; - private const ALIASES = ['shortcode:generate', 'short-code:generate']; - /** @var UrlShortenerInterface */ - private $urlShortener; - /** @var array */ - private $domainConfig; + private UrlShortenerInterface $urlShortener; + private array $domainConfig; public function __construct(UrlShortenerInterface $urlShortener, array $domainConfig) { @@ -44,52 +41,51 @@ class GenerateShortUrlCommand extends Command { $this ->setName(self::NAME) - ->setAliases(self::ALIASES) ->setDescription('Generates a short URL for provided long URL and returns it') ->addArgument('longUrl', InputArgument::REQUIRED, 'The long URL to parse') ->addOption( 'tags', 't', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, - 'Tags to apply to the new short URL' + 'Tags to apply to the new short URL', ) ->addOption( 'validSince', 's', InputOption::VALUE_REQUIRED, 'The date from which this short URL will be valid. ' - . 'If someone tries to access it before this date, it will not be found.' + . 'If someone tries to access it before this date, it will not be found.', ) ->addOption( 'validUntil', 'u', InputOption::VALUE_REQUIRED, 'The date until which this short URL will be valid. ' - . 'If someone tries to access it after this date, it will not be found.' + . 'If someone tries to access it after this date, it will not be found.', ) ->addOption( 'customSlug', 'c', InputOption::VALUE_REQUIRED, - 'If provided, this slug will be used instead of generating a short code' + 'If provided, this slug will be used instead of generating a short code', ) ->addOption( 'maxVisits', 'm', InputOption::VALUE_REQUIRED, - 'This will limit the number of visits for this short URL.' + 'This will limit the number of visits for this short URL.', ) ->addOption( 'findIfExists', 'f', InputOption::VALUE_NONE, - 'This will force existing matching URL to be returned if found, instead of creating a new one.' + 'This will force existing matching URL to be returned if found, instead of creating a new one.', ) ->addOption( 'domain', 'd', InputOption::VALUE_REQUIRED, - 'The domain to which this short URL will be attached.' + 'The domain to which this short URL will be attached.', ); } @@ -131,8 +127,8 @@ class GenerateShortUrlCommand extends Command $customSlug, $maxVisits !== null ? (int) $maxVisits : null, $input->getOption('findIfExists'), - $input->getOption('domain') - ) + $input->getOption('domain'), + ), ); $io->writeln([ diff --git a/module/CLI/src/Command/ShortUrl/GetVisitsCommand.php b/module/CLI/src/Command/ShortUrl/GetVisitsCommand.php index 7a51ac0b..363ff3aa 100644 --- a/module/CLI/src/Command/ShortUrl/GetVisitsCommand.php +++ b/module/CLI/src/Command/ShortUrl/GetVisitsCommand.php @@ -22,10 +22,8 @@ use function Functional\select_keys; class GetVisitsCommand extends AbstractWithDateRangeCommand { public const NAME = 'short-url:visits'; - private const ALIASES = ['shortcode:visits', 'short-code:visits']; - /** @var VisitsTrackerInterface */ - private $visitsTracker; + private VisitsTrackerInterface $visitsTracker; public function __construct(VisitsTrackerInterface $visitsTracker) { @@ -37,7 +35,6 @@ class GetVisitsCommand extends AbstractWithDateRangeCommand { $this ->setName(self::NAME) - ->setAliases(self::ALIASES) ->setDescription('Returns the detailed visits information for provided short code') ->addArgument('shortCode', InputArgument::REQUIRED, 'The short code which visits we want to get'); } diff --git a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php index 01080189..c4251ed9 100644 --- a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php +++ b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\ShortUrl; use Cake\Chronos\Chronos; +use Laminas\Paginator\Paginator; use Shlinkio\Shlink\CLI\Command\Util\AbstractWithDateRangeCommand; use Shlinkio\Shlink\CLI\Util\ExitCodes; use Shlinkio\Shlink\CLI\Util\ShlinkTable; @@ -17,7 +18,6 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -use Zend\Paginator\Paginator; use function array_flip; use function array_intersect_key; @@ -32,7 +32,6 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand use PaginatorUtilsTrait; public const NAME = 'short-url:list'; - private const ALIASES = ['shortcode:list', 'short-code:list']; private const COLUMNS_WHITELIST = [ 'shortCode', 'shortUrl', @@ -42,10 +41,8 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand 'tags', ]; - /** @var ShortUrlServiceInterface */ - private $shortUrlService; - /** @var ShortUrlDataTransformer */ - private $transformer; + private ShortUrlServiceInterface $shortUrlService; + private ShortUrlDataTransformer $transformer; public function __construct(ShortUrlServiceInterface $shortUrlService, array $domainConfig) { @@ -58,32 +55,31 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand { $this ->setName(self::NAME) - ->setAliases(self::ALIASES) ->setDescription('List all short URLs') ->addOption( 'page', 'p', InputOption::VALUE_REQUIRED, sprintf('The first page to list (%s items per page)', ShortUrlRepositoryAdapter::ITEMS_PER_PAGE), - '1' + '1', ) ->addOption( 'searchTerm', 'st', InputOption::VALUE_REQUIRED, - 'A query used to filter results by searching for it on the longUrl and shortCode fields' + 'A query used to filter results by searching for it on the longUrl and shortCode fields', ) ->addOption( 'tags', 't', InputOption::VALUE_REQUIRED, - 'A comma-separated list of tags to filter results' + 'A comma-separated list of tags to filter results', ) ->addOption( 'orderBy', 'o', InputOption::VALUE_REQUIRED, - 'The field from which we want to order by. Pass ASC or DESC separated by a comma' + 'The field from which we want to order by. Pass ASC or DESC separated by a comma', ) ->addOption('showTags', null, InputOption::VALUE_NONE, 'Whether to display the tags or not'); } @@ -126,6 +122,9 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand return ExitCodes::EXIT_SUCCESS; } + /** + * @param string|array|null $orderBy + */ private function renderPage( OutputInterface $output, int $page, @@ -141,7 +140,7 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand $searchTerm, $tags, $orderBy, - new DateRange($startDate, $endDate) + new DateRange($startDate, $endDate), ); $headers = ['Short code', 'Short URL', 'Long URL', 'Date created', 'Visits count']; @@ -163,7 +162,7 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand ShlinkTable::fromOutput($output)->render($headers, $rows, $this->formatCurrentPageMessage( $result, - 'Page %s of %s' + 'Page %s of %s', )); return $result; diff --git a/module/CLI/src/Command/ShortUrl/ResolveUrlCommand.php b/module/CLI/src/Command/ShortUrl/ResolveUrlCommand.php index e8db28e2..e4c75410 100644 --- a/module/CLI/src/Command/ShortUrl/ResolveUrlCommand.php +++ b/module/CLI/src/Command/ShortUrl/ResolveUrlCommand.php @@ -19,10 +19,8 @@ use function sprintf; class ResolveUrlCommand extends Command { public const NAME = 'short-url:parse'; - private const ALIASES = ['shortcode:parse', 'short-code:parse']; - /** @var UrlShortenerInterface */ - private $urlShortener; + private UrlShortenerInterface $urlShortener; public function __construct(UrlShortenerInterface $urlShortener) { @@ -34,7 +32,6 @@ class ResolveUrlCommand extends Command { $this ->setName(self::NAME) - ->setAliases(self::ALIASES) ->setDescription('Returns the long URL behind a short code') ->addArgument('shortCode', InputArgument::REQUIRED, 'The short code to parse') ->addOption('domain', 'd', InputOption::VALUE_REQUIRED, 'The domain to which the short URL is attached.'); diff --git a/module/CLI/src/Command/Tag/CreateTagCommand.php b/module/CLI/src/Command/Tag/CreateTagCommand.php index 9971d96a..5fe56d46 100644 --- a/module/CLI/src/Command/Tag/CreateTagCommand.php +++ b/module/CLI/src/Command/Tag/CreateTagCommand.php @@ -16,8 +16,7 @@ class CreateTagCommand extends Command { public const NAME = 'tag:create'; - /** @var TagServiceInterface */ - private $tagService; + private TagServiceInterface $tagService; public function __construct(TagServiceInterface $tagService) { @@ -34,7 +33,7 @@ class CreateTagCommand extends Command 'name', 't', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, - 'The name of the tags to create' + 'The name of the tags to create', ); } diff --git a/module/CLI/src/Command/Tag/DeleteTagsCommand.php b/module/CLI/src/Command/Tag/DeleteTagsCommand.php index 2677f11c..1cebe895 100644 --- a/module/CLI/src/Command/Tag/DeleteTagsCommand.php +++ b/module/CLI/src/Command/Tag/DeleteTagsCommand.php @@ -16,8 +16,7 @@ class DeleteTagsCommand extends Command { public const NAME = 'tag:delete'; - /** @var TagServiceInterface */ - private $tagService; + private TagServiceInterface $tagService; public function __construct(TagServiceInterface $tagService) { @@ -34,7 +33,7 @@ class DeleteTagsCommand extends Command 'name', 't', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, - 'The name of the tags to delete' + 'The name of the tags to delete', ); } diff --git a/module/CLI/src/Command/Tag/ListTagsCommand.php b/module/CLI/src/Command/Tag/ListTagsCommand.php index db3df1c3..0b8f0aa3 100644 --- a/module/CLI/src/Command/Tag/ListTagsCommand.php +++ b/module/CLI/src/Command/Tag/ListTagsCommand.php @@ -18,8 +18,7 @@ class ListTagsCommand extends Command { public const NAME = 'tag:list'; - /** @var TagServiceInterface */ - private $tagService; + private TagServiceInterface $tagService; public function __construct(TagServiceInterface $tagService) { @@ -47,8 +46,6 @@ class ListTagsCommand extends Command return [['No tags yet']]; } - return map($tags, function (Tag $tag) { - return [(string) $tag]; - }); + return map($tags, fn (Tag $tag) => [(string) $tag]); } } diff --git a/module/CLI/src/Command/Tag/RenameTagCommand.php b/module/CLI/src/Command/Tag/RenameTagCommand.php index d1607f34..f30bc757 100644 --- a/module/CLI/src/Command/Tag/RenameTagCommand.php +++ b/module/CLI/src/Command/Tag/RenameTagCommand.php @@ -18,8 +18,7 @@ class RenameTagCommand extends Command { public const NAME = 'tag:rename'; - /** @var TagServiceInterface */ - private $tagService; + private TagServiceInterface $tagService; public function __construct(TagServiceInterface $tagService) { diff --git a/module/CLI/src/Command/Util/AbstractLockedCommand.php b/module/CLI/src/Command/Util/AbstractLockedCommand.php index 59ea74fa..8a43653d 100644 --- a/module/CLI/src/Command/Util/AbstractLockedCommand.php +++ b/module/CLI/src/Command/Util/AbstractLockedCommand.php @@ -14,8 +14,7 @@ use function sprintf; abstract class AbstractLockedCommand extends Command { - /** @var LockFactory */ - private $locker; + private LockFactory $locker; public function __construct(LockFactory $locker) { @@ -30,7 +29,7 @@ abstract class AbstractLockedCommand extends Command if (! $lock->acquire($lockConfig->isBlocking())) { $output->writeln( - sprintf('Command "%s" is already in progress. Skipping.', $lockConfig->lockName()) + sprintf('Command "%s" is already in progress. Skipping.', $lockConfig->lockName()), ); return ExitCodes::EXIT_WARNING; } diff --git a/module/CLI/src/Command/Util/AbstractWithDateRangeCommand.php b/module/CLI/src/Command/Util/AbstractWithDateRangeCommand.php index c6b10be6..bd64701a 100644 --- a/module/CLI/src/Command/Util/AbstractWithDateRangeCommand.php +++ b/module/CLI/src/Command/Util/AbstractWithDateRangeCommand.php @@ -36,7 +36,7 @@ abstract class AbstractWithDateRangeCommand extends Command $output->writeln(sprintf( '> Ignored provided "%s" since its value "%s" is not a valid date. <', $key, - $value + $value, )); if ($output->isVeryVerbose()) { diff --git a/module/CLI/src/Command/Util/LockedCommandConfig.php b/module/CLI/src/Command/Util/LockedCommandConfig.php index be7c2deb..8a217f85 100644 --- a/module/CLI/src/Command/Util/LockedCommandConfig.php +++ b/module/CLI/src/Command/Util/LockedCommandConfig.php @@ -8,12 +8,9 @@ final class LockedCommandConfig { private const DEFAULT_TTL = 90.0; // 1.5 minutes - /** @var string */ - private $lockName; - /** @var bool */ - private $isBlocking; - /** @var float */ - private $ttl; + private string $lockName; + private bool $isBlocking; + private float $ttl; public function __construct(string $lockName, bool $isBlocking = false, float $ttl = self::DEFAULT_TTL) { diff --git a/module/CLI/src/Command/Visit/LocateVisitsCommand.php b/module/CLI/src/Command/Visit/LocateVisitsCommand.php index 710c4a3a..b19e8b19 100644 --- a/module/CLI/src/Command/Visit/LocateVisitsCommand.php +++ b/module/CLI/src/Command/Visit/LocateVisitsCommand.php @@ -30,19 +30,13 @@ use function sprintf; class LocateVisitsCommand extends AbstractLockedCommand { public const NAME = 'visit:locate'; - public const ALIASES = ['visit:process']; - /** @var VisitServiceInterface */ - private $visitService; - /** @var IpLocationResolverInterface */ - private $ipLocationResolver; - /** @var GeolocationDbUpdaterInterface */ - private $dbUpdater; + private VisitServiceInterface $visitService; + private IpLocationResolverInterface $ipLocationResolver; + private GeolocationDbUpdaterInterface $dbUpdater; - /** @var SymfonyStyle */ - private $io; - /** @var ProgressBar */ - private $progressBar; + private SymfonyStyle $io; + private ?ProgressBar $progressBar = null; public function __construct( VisitServiceInterface $visitService, @@ -60,7 +54,6 @@ class LocateVisitsCommand extends AbstractLockedCommand { $this ->setName(self::NAME) - ->setAliases(self::ALIASES) ->setDescription('Resolves visits origin locations.'); } @@ -73,13 +66,13 @@ class LocateVisitsCommand extends AbstractLockedCommand $this->visitService->locateUnlocatedVisits( [$this, 'getGeolocationDataForVisit'], - static function (VisitLocation $location) use ($output) { + static function (VisitLocation $location) use ($output): void { if (!$location->isEmpty()) { $output->writeln( - sprintf(' [Address located at "%s"]', $location->getCountryName()) + sprintf(' [Address located at "%s"]', $location->getCountryName()), ); } - } + }, ); $this->io->success('Finished processing all IPs'); @@ -99,7 +92,7 @@ class LocateVisitsCommand extends AbstractLockedCommand if (! $visit->hasRemoteAddr()) { $this->io->writeln( 'Ignored visit with no IP address', - OutputInterface::VERBOSITY_VERBOSE + OutputInterface::VERBOSITY_VERBOSE, ); throw IpCannotBeLocatedException::forEmptyAddress(); } @@ -126,12 +119,12 @@ class LocateVisitsCommand extends AbstractLockedCommand private function checkDbUpdate(): void { try { - $this->dbUpdater->checkDbUpdate(function (bool $olderDbExists) { + $this->dbUpdater->checkDbUpdate(function (bool $olderDbExists): void { $this->io->writeln( - sprintf('%s GeoLite2 database...', $olderDbExists ? 'Updating' : 'Downloading') + sprintf('%s GeoLite2 database...', $olderDbExists ? 'Updating' : 'Downloading'), ); $this->progressBar = new ProgressBar($this->io); - }, function (int $total, int $downloaded) { + }, function (int $total, int $downloaded): void { $this->progressBar->setMaxSteps($total); $this->progressBar->setProgress($downloaded); }); @@ -148,7 +141,7 @@ class LocateVisitsCommand extends AbstractLockedCommand $this->io->newLine(); $this->io->writeln( - '[Warning] GeoLite2 database update failed. Proceeding with old version.' + '[Warning] GeoLite2 database update failed. Proceeding with old version.', ); } } diff --git a/module/CLI/src/Command/Visit/UpdateDbCommand.php b/module/CLI/src/Command/Visit/UpdateDbCommand.php deleted file mode 100644 index 367423fd..00000000 --- a/module/CLI/src/Command/Visit/UpdateDbCommand.php +++ /dev/null @@ -1,91 +0,0 @@ -geoLiteDbUpdater = $geoLiteDbUpdater; - } - - protected function configure(): void - { - $this - ->setName(self::NAME) - ->setDescription('[DEPRECATED] Updates the GeoLite2 database file used to geolocate IP addresses') - ->setHelp( - 'The GeoLite2 database is updated first Tuesday every month, so this command should be ideally run ' - . 'every first Wednesday' - ) - ->addOption( - 'ignoreErrors', - 'i', - InputOption::VALUE_NONE, - 'Makes the command success even iof the update fails.' - ); - } - - protected function execute(InputInterface $input, OutputInterface $output): ?int - { - $io = new SymfonyStyle($input, $output); - $progressBar = new ProgressBar($output); - $progressBar->start(); - - try { - $this->geoLiteDbUpdater->downloadFreshCopy(function (int $total, int $downloaded) use ($progressBar) { - $progressBar->setMaxSteps($total); - $progressBar->setProgress($downloaded); - }); - - $progressBar->finish(); - $io->newLine(); - - $io->success('GeoLite2 database properly updated'); - return ExitCodes::EXIT_SUCCESS; - } catch (RuntimeException $e) { - $progressBar->finish(); - $io->newLine(); - - return $this->handleError($e, $io, $input); - } - } - - private function handleError(RuntimeException $e, SymfonyStyle $io, InputInterface $input): int - { - $ignoreErrors = $input->getOption('ignoreErrors'); - $baseErrorMsg = 'An error occurred while updating GeoLite2 database'; - - if ($ignoreErrors) { - $io->warning(sprintf('%s, but it was ignored', $baseErrorMsg)); - return ExitCodes::EXIT_SUCCESS; - } - - $io->error($baseErrorMsg); - if ($io->isVerbose()) { - $this->getApplication()->renderThrowable($e, $io); - } - return ExitCodes::EXIT_FAILURE; - } -} diff --git a/module/CLI/src/ConfigProvider.php b/module/CLI/src/ConfigProvider.php index 0bc0a25f..40dcf775 100644 --- a/module/CLI/src/ConfigProvider.php +++ b/module/CLI/src/ConfigProvider.php @@ -8,7 +8,7 @@ use function Shlinkio\Shlink\Common\loadConfigFromGlob; class ConfigProvider { - public function __invoke() + public function __invoke(): array { return loadConfigFromGlob(__DIR__ . '/../config/{,*.}config.php'); } diff --git a/module/CLI/src/Exception/GeolocationDbUpdateFailedException.php b/module/CLI/src/Exception/GeolocationDbUpdateFailedException.php index 38bb4c5f..8ddee216 100644 --- a/module/CLI/src/Exception/GeolocationDbUpdateFailedException.php +++ b/module/CLI/src/Exception/GeolocationDbUpdateFailedException.php @@ -9,15 +9,14 @@ use Throwable; class GeolocationDbUpdateFailedException extends RuntimeException implements ExceptionInterface { - /** @var bool */ - private $olderDbExists; + private bool $olderDbExists; public static function create(bool $olderDbExists, ?Throwable $prev = null): self { $e = new self( 'An error occurred while updating geolocation database, and an older version could not be found', 0, - $prev + $prev, ); $e->olderDbExists = $olderDbExists; diff --git a/module/CLI/src/Util/GeolocationDbUpdater.php b/module/CLI/src/Util/GeolocationDbUpdater.php index 25b49c37..b74ebdac 100644 --- a/module/CLI/src/Util/GeolocationDbUpdater.php +++ b/module/CLI/src/Util/GeolocationDbUpdater.php @@ -15,12 +15,9 @@ class GeolocationDbUpdater implements GeolocationDbUpdaterInterface { private const LOCK_NAME = 'geolocation-db-update'; - /** @var DbUpdaterInterface */ - private $dbUpdater; - /** @var Reader */ - private $geoLiteDbReader; - /** @var LockFactory */ - private $locker; + private DbUpdaterInterface $dbUpdater; + private Reader $geoLiteDbReader; + private LockFactory $locker; public function __construct(DbUpdaterInterface $dbUpdater, Reader $geoLiteDbReader, LockFactory $locker) { diff --git a/module/CLI/src/Util/ShlinkTable.php b/module/CLI/src/Util/ShlinkTable.php index a577ce8d..ac8733ad 100644 --- a/module/CLI/src/Util/ShlinkTable.php +++ b/module/CLI/src/Util/ShlinkTable.php @@ -12,8 +12,7 @@ final class ShlinkTable private const DEFAULT_STYLE_NAME = 'default'; private const TABLE_TITLE_STYLE = ' %s '; - /** @var Table|null */ - private $baseTable; + private ?Table $baseTable; public function __construct(Table $baseTable) { diff --git a/module/CLI/test/Command/Api/DisableKeyCommandTest.php b/module/CLI/test/Command/Api/DisableKeyCommandTest.php index 37629091..8b5ef6c4 100644 --- a/module/CLI/test/Command/Api/DisableKeyCommandTest.php +++ b/module/CLI/test/Command/Api/DisableKeyCommandTest.php @@ -8,20 +8,18 @@ use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Api\DisableKeyCommand; use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; -use Shlinkio\Shlink\Rest\Service\ApiKeyService; +use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; use Symfony\Component\Console\Application; use Symfony\Component\Console\Tester\CommandTester; class DisableKeyCommandTest extends TestCase { - /** @var CommandTester */ - private $commandTester; - /** @var ObjectProphecy */ - private $apiKeyService; + private CommandTester $commandTester; + private ObjectProphecy $apiKeyService; public function setUp(): void { - $this->apiKeyService = $this->prophesize(ApiKeyService::class); + $this->apiKeyService = $this->prophesize(ApiKeyServiceInterface::class); $command = new DisableKeyCommand($this->apiKeyService->reveal()); $app = new Application(); $app->add($command); diff --git a/module/CLI/test/Command/Api/GenerateKeyCommandTest.php b/module/CLI/test/Command/Api/GenerateKeyCommandTest.php index 1556aa85..8ddd9f0b 100644 --- a/module/CLI/test/Command/Api/GenerateKeyCommandTest.php +++ b/module/CLI/test/Command/Api/GenerateKeyCommandTest.php @@ -10,20 +10,18 @@ use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Api\GenerateKeyCommand; use Shlinkio\Shlink\Rest\Entity\ApiKey; -use Shlinkio\Shlink\Rest\Service\ApiKeyService; +use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; use Symfony\Component\Console\Application; use Symfony\Component\Console\Tester\CommandTester; class GenerateKeyCommandTest extends TestCase { - /** @var CommandTester */ - private $commandTester; - /** @var ObjectProphecy */ - private $apiKeyService; + private CommandTester $commandTester; + private ObjectProphecy $apiKeyService; public function setUp(): void { - $this->apiKeyService = $this->prophesize(ApiKeyService::class); + $this->apiKeyService = $this->prophesize(ApiKeyServiceInterface::class); $command = new GenerateKeyCommand($this->apiKeyService->reveal()); $app = new Application(); $app->add($command); @@ -31,7 +29,7 @@ class GenerateKeyCommandTest extends TestCase } /** @test */ - public function noExpirationDateIsDefinedIfNotProvided() + public function noExpirationDateIsDefinedIfNotProvided(): void { $create = $this->apiKeyService->create(null)->willReturn(new ApiKey()); @@ -43,7 +41,7 @@ class GenerateKeyCommandTest extends TestCase } /** @test */ - public function expirationDateIsDefinedIfProvided() + public function expirationDateIsDefinedIfProvided(): void { $this->apiKeyService->create(Argument::type(Chronos::class))->shouldBeCalledOnce() ->willReturn(new ApiKey()); diff --git a/module/CLI/test/Command/Api/ListKeysCommandTest.php b/module/CLI/test/Command/Api/ListKeysCommandTest.php index 027b22c2..9e30605a 100644 --- a/module/CLI/test/Command/Api/ListKeysCommandTest.php +++ b/module/CLI/test/Command/Api/ListKeysCommandTest.php @@ -8,20 +8,18 @@ use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Api\ListKeysCommand; use Shlinkio\Shlink\Rest\Entity\ApiKey; -use Shlinkio\Shlink\Rest\Service\ApiKeyService; +use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; use Symfony\Component\Console\Application; use Symfony\Component\Console\Tester\CommandTester; class ListKeysCommandTest extends TestCase { - /** @var CommandTester */ - private $commandTester; - /** @var ObjectProphecy */ - private $apiKeyService; + private CommandTester $commandTester; + private ObjectProphecy $apiKeyService; public function setUp(): void { - $this->apiKeyService = $this->prophesize(ApiKeyService::class); + $this->apiKeyService = $this->prophesize(ApiKeyServiceInterface::class); $command = new ListKeysCommand($this->apiKeyService->reveal()); $app = new Application(); $app->add($command); diff --git a/module/CLI/test/Command/Config/GenerateCharsetCommandTest.php b/module/CLI/test/Command/Config/GenerateCharsetCommandTest.php deleted file mode 100644 index 8475cecc..00000000 --- a/module/CLI/test/Command/Config/GenerateCharsetCommandTest.php +++ /dev/null @@ -1,48 +0,0 @@ -add($command); - - $this->commandTester = new CommandTester($command); - } - - /** @test */ - public function charactersAreGeneratedFromDefault() - { - $prefix = 'Character set: '; - - $this->commandTester->execute([]); - $output = $this->commandTester->getDisplay(); - - // Both default character set and the new one should have the same length - $this->assertStringContainsString($prefix, $output); - } - - protected function orderStringLetters($string) - { - $letters = str_split($string); - sort($letters); - return implode('', $letters); - } -} diff --git a/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php b/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php index 7945ea05..c88e28fa 100644 --- a/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php +++ b/module/CLI/test/Command/Db/CreateDatabaseCommandTest.php @@ -21,25 +21,19 @@ use Symfony\Component\Process\PhpExecutableFinder; class CreateDatabaseCommandTest extends TestCase { - /** @var CommandTester */ - private $commandTester; - /** @var ObjectProphecy */ - private $processHelper; - /** @var ObjectProphecy */ - private $regularConn; - /** @var ObjectProphecy */ - private $noDbNameConn; - /** @var ObjectProphecy */ - private $schemaManager; - /** @var ObjectProphecy */ - private $databasePlatform; + private CommandTester $commandTester; + private ObjectProphecy $processHelper; + private ObjectProphecy $regularConn; + private ObjectProphecy $noDbNameConn; + private ObjectProphecy $schemaManager; + private ObjectProphecy $databasePlatform; public function setUp(): void { $locker = $this->prophesize(LockFactory::class); $lock = $this->prophesize(LockInterface::class); $lock->acquire(Argument::any())->willReturn(true); - $lock->release()->will(function () { + $lock->release()->will(function (): void { }); $locker->createLock(Argument::cetera())->willReturn($lock->reveal()); @@ -61,7 +55,7 @@ class CreateDatabaseCommandTest extends TestCase $this->processHelper->reveal(), $phpExecutableFinder->reveal(), $this->regularConn->reveal(), - $this->noDbNameConn->reveal() + $this->noDbNameConn->reveal(), ); $app = new Application(); $app->add($command); @@ -75,7 +69,7 @@ class CreateDatabaseCommandTest extends TestCase $shlinkDatabase = 'shlink_database'; $getDatabase = $this->regularConn->getDatabase()->willReturn($shlinkDatabase); $listDatabases = $this->schemaManager->listDatabases()->willReturn(['foo', $shlinkDatabase, 'bar']); - $createDatabase = $this->schemaManager->createDatabase($shlinkDatabase)->will(function () { + $createDatabase = $this->schemaManager->createDatabase($shlinkDatabase)->will(function (): void { }); $listTables = $this->schemaManager->listTableNames()->willReturn(['foo_table', 'bar_table']); @@ -95,7 +89,7 @@ class CreateDatabaseCommandTest extends TestCase $shlinkDatabase = 'shlink_database'; $getDatabase = $this->regularConn->getDatabase()->willReturn($shlinkDatabase); $listDatabases = $this->schemaManager->listDatabases()->willReturn(['foo', 'bar']); - $createDatabase = $this->schemaManager->createDatabase($shlinkDatabase)->will(function () { + $createDatabase = $this->schemaManager->createDatabase($shlinkDatabase)->will(function (): void { }); $listTables = $this->schemaManager->listTableNames()->willReturn(['foo_table', 'bar_table']); @@ -113,7 +107,7 @@ class CreateDatabaseCommandTest extends TestCase $shlinkDatabase = 'shlink_database'; $getDatabase = $this->regularConn->getDatabase()->willReturn($shlinkDatabase); $listDatabases = $this->schemaManager->listDatabases()->willReturn(['foo', $shlinkDatabase, 'bar']); - $createDatabase = $this->schemaManager->createDatabase($shlinkDatabase)->will(function () { + $createDatabase = $this->schemaManager->createDatabase($shlinkDatabase)->will(function (): void { }); $listTables = $this->schemaManager->listTableNames()->willReturn([]); $runCommand = $this->processHelper->mustRun(Argument::type(OutputInterface::class), [ @@ -142,7 +136,7 @@ class CreateDatabaseCommandTest extends TestCase $shlinkDatabase = 'shlink_database'; $getDatabase = $this->regularConn->getDatabase()->willReturn($shlinkDatabase); $listDatabases = $this->schemaManager->listDatabases()->willReturn(['foo', 'bar']); - $createDatabase = $this->schemaManager->createDatabase($shlinkDatabase)->will(function () { + $createDatabase = $this->schemaManager->createDatabase($shlinkDatabase)->will(function (): void { }); $listTables = $this->schemaManager->listTableNames()->willReturn(['foo_table', 'bar_table']); diff --git a/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php b/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php index be36a980..15f756a7 100644 --- a/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php +++ b/module/CLI/test/Command/Db/MigrateDatabaseCommandTest.php @@ -18,17 +18,15 @@ use Symfony\Component\Process\PhpExecutableFinder; class MigrateDatabaseCommandTest extends TestCase { - /** @var CommandTester */ - private $commandTester; - /** @var ObjectProphecy */ - private $processHelper; + private CommandTester $commandTester; + private ObjectProphecy $processHelper; public function setUp(): void { $locker = $this->prophesize(LockFactory::class); $lock = $this->prophesize(LockInterface::class); $lock->acquire(Argument::any())->willReturn(true); - $lock->release()->will(function () { + $lock->release()->will(function (): void { }); $locker->createLock(Argument::cetera())->willReturn($lock->reveal()); @@ -40,7 +38,7 @@ class MigrateDatabaseCommandTest extends TestCase $command = new MigrateDatabaseCommand( $locker->reveal(), $this->processHelper->reveal(), - $phpExecutableFinder->reveal() + $phpExecutableFinder->reveal(), ); $app = new Application(); $app->add($command); diff --git a/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php index 85521835..7fd727ca 100644 --- a/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php @@ -20,10 +20,8 @@ use const PHP_EOL; class DeleteShortUrlCommandTest extends TestCase { - /** @var CommandTester */ - private $commandTester; - /** @var ObjectProphecy */ - private $service; + private CommandTester $commandTester; + private ObjectProphecy $service; public function setUp(): void { @@ -40,7 +38,7 @@ class DeleteShortUrlCommandTest extends TestCase public function successMessageIsPrintedIfUrlIsProperlyDeleted(): void { $shortCode = 'abc123'; - $deleteByShortCode = $this->service->deleteByShortCode($shortCode, false)->will(function () { + $deleteByShortCode = $this->service->deleteByShortCode($shortCode, false)->will(function (): void { }); $this->commandTester->execute(['shortCode' => $shortCode]); @@ -48,7 +46,7 @@ class DeleteShortUrlCommandTest extends TestCase $this->assertStringContainsString( sprintf('Short URL with short code "%s" successfully deleted.', $shortCode), - $output + $output, ); $deleteByShortCode->shouldHaveBeenCalledOnce(); } @@ -58,7 +56,7 @@ class DeleteShortUrlCommandTest extends TestCase { $shortCode = 'abc123'; $deleteByShortCode = $this->service->deleteByShortCode($shortCode, false)->willThrow( - Exception\ShortUrlNotFoundException::fromNotFoundShortCode($shortCode) + Exception\ShortUrlNotFoundException::fromNotFoundShortCode($shortCode), ); $this->commandTester->execute(['shortCode' => $shortCode]); @@ -79,13 +77,13 @@ class DeleteShortUrlCommandTest extends TestCase ): void { $shortCode = 'abc123'; $deleteByShortCode = $this->service->deleteByShortCode($shortCode, Argument::type('bool'))->will( - function (array $args) use ($shortCode) { + function (array $args) use ($shortCode): void { $ignoreThreshold = array_pop($args); if (!$ignoreThreshold) { throw Exception\DeleteShortUrlException::fromVisitsThreshold(10, $shortCode); } - } + }, ); $this->commandTester->setInputs($retryAnswer); @@ -94,7 +92,7 @@ class DeleteShortUrlCommandTest extends TestCase $this->assertStringContainsString(sprintf( 'Impossible to delete short URL with short code "%s" since it has more than "10" visits.', - $shortCode + $shortCode, ), $output); $this->assertStringContainsString($expectedMessage, $output); $deleteByShortCode->shouldHaveBeenCalledTimes($expectedDeleteCalls); @@ -112,7 +110,7 @@ class DeleteShortUrlCommandTest extends TestCase { $shortCode = 'abc123'; $deleteByShortCode = $this->service->deleteByShortCode($shortCode, false)->willThrow( - Exception\DeleteShortUrlException::fromVisitsThreshold(10, $shortCode) + Exception\DeleteShortUrlException::fromVisitsThreshold(10, $shortCode), ); $this->commandTester->setInputs(['no']); @@ -121,7 +119,7 @@ class DeleteShortUrlCommandTest extends TestCase $this->assertStringContainsString(sprintf( 'Impossible to delete short URL with short code "%s" since it has more than "10" visits.', - $shortCode + $shortCode, ), $output); $this->assertStringContainsString('Short URL was not deleted.', $output); $deleteByShortCode->shouldHaveBeenCalledOnce(); diff --git a/module/CLI/test/Command/ShortUrl/GeneratePreviewCommandTest.php b/module/CLI/test/Command/ShortUrl/GeneratePreviewCommandTest.php deleted file mode 100644 index 077e0c60..00000000 --- a/module/CLI/test/Command/ShortUrl/GeneratePreviewCommandTest.php +++ /dev/null @@ -1,95 +0,0 @@ -previewGenerator = $this->prophesize(PreviewGenerator::class); - $this->shortUrlService = $this->prophesize(ShortUrlService::class); - - $command = new GeneratePreviewCommand($this->shortUrlService->reveal(), $this->previewGenerator->reveal()); - $app = new Application(); - $app->add($command); - - $this->commandTester = new CommandTester($command); - } - - /** @test */ - public function previewsForEveryUrlAreGenerated() - { - $paginator = $this->createPaginator([ - new ShortUrl('http://foo.com'), - new ShortUrl('https://bar.com'), - new ShortUrl('http://baz.com/something'), - ]); - $this->shortUrlService->listShortUrls(1)->willReturn($paginator)->shouldBeCalledOnce(); - - $generatePreview1 = $this->previewGenerator->generatePreview('http://foo.com')->willReturn(''); - $generatePreview2 = $this->previewGenerator->generatePreview('https://bar.com')->willReturn(''); - $generatePreview3 = $this->previewGenerator->generatePreview('http://baz.com/something')->willReturn(''); - - $this->commandTester->execute([]); - $output = $this->commandTester->getDisplay(); - - $this->assertStringContainsString('Processing URL http://foo.com', $output); - $this->assertStringContainsString('Processing URL https://bar.com', $output); - $this->assertStringContainsString('Processing URL http://baz.com/something', $output); - $this->assertStringContainsString('Finished processing all URLs', $output); - $generatePreview1->shouldHaveBeenCalledOnce(); - $generatePreview2->shouldHaveBeenCalledOnce(); - $generatePreview3->shouldHaveBeenCalledOnce(); - } - - /** @test */ - public function exceptionWillOutputError() - { - $items = [ - new ShortUrl('http://foo.com'), - new ShortUrl('https://bar.com'), - new ShortUrl('http://baz.com/something'), - ]; - $paginator = $this->createPaginator($items); - $this->shortUrlService->listShortUrls(1)->willReturn($paginator)->shouldBeCalledOnce(); - $this->previewGenerator->generatePreview(Argument::any())->willThrow(PreviewGenerationException::class) - ->shouldBeCalledTimes(count($items)); - - $this->commandTester->execute([]); - $output = $this->commandTester->getDisplay(); - $this->assertEquals(count($items), substr_count($output, 'Error')); - } - - protected function createPaginator(array $items) - { - $paginator = new Paginator(new ArrayAdapter($items)); - $paginator->setItemCountPerPage(count($items)); - - return $paginator; - } -} diff --git a/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php index abae0fe6..df1019b1 100644 --- a/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/GenerateShortUrlCommandTest.php @@ -25,10 +25,8 @@ class GenerateShortUrlCommandTest extends TestCase 'hostname' => 'foo.com', ]; - /** @var CommandTester */ - private $commandTester; - /** @var ObjectProphecy */ - private $urlShortener; + private CommandTester $commandTester; + private ObjectProphecy $urlShortener; public function setUp(): void { @@ -74,7 +72,7 @@ class GenerateShortUrlCommandTest extends TestCase public function providingNonUniqueSlugOutputsError(): void { $urlToShortCode = $this->urlShortener->urlToShortCode(Argument::cetera())->willThrow( - NonUniqueSlugException::fromSlug('my-slug') + NonUniqueSlugException::fromSlug('my-slug'), ); $this->commandTester->execute(['longUrl' => 'http://domain.com/invalid', '--customSlug' => 'my-slug']); @@ -95,7 +93,7 @@ class GenerateShortUrlCommandTest extends TestCase Assert::assertEquals(['foo', 'bar', 'baz', 'boo', 'zar'], $tags); return $tags; }), - Argument::cetera() + Argument::cetera(), )->willReturn($shortUrl); $this->commandTester->execute([ diff --git a/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php b/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php index e2ea29d1..53ba114e 100644 --- a/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php @@ -5,6 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\ShortUrl; use Cake\Chronos\Chronos; +use Laminas\Paginator\Adapter\ArrayAdapter; +use Laminas\Paginator\Paginator; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; @@ -19,17 +21,13 @@ use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface; use Shlinkio\Shlink\IpGeolocation\Model\Location; use Symfony\Component\Console\Application; use Symfony\Component\Console\Tester\CommandTester; -use Zend\Paginator\Adapter\ArrayAdapter; -use Zend\Paginator\Paginator; use function sprintf; class GetVisitsCommandTest extends TestCase { - /** @var CommandTester */ - private $commandTester; - /** @var ObjectProphecy */ - private $visitsTracker; + private CommandTester $commandTester; + private ObjectProphecy $visitsTracker; public function setUp(): void { @@ -45,7 +43,7 @@ class GetVisitsCommandTest extends TestCase { $shortCode = 'abc123'; $this->visitsTracker->info($shortCode, new VisitsParams(new DateRange(null, null)))->willReturn( - new Paginator(new ArrayAdapter([])) + new Paginator(new ArrayAdapter([])), )->shouldBeCalledOnce(); $this->commandTester->execute(['shortCode' => $shortCode]); @@ -59,7 +57,7 @@ class GetVisitsCommandTest extends TestCase $endDate = '2016-02-01'; $this->visitsTracker->info( $shortCode, - new VisitsParams(new DateRange(Chronos::parse($startDate), Chronos::parse($endDate))) + new VisitsParams(new DateRange(Chronos::parse($startDate), Chronos::parse($endDate))), ) ->willReturn(new Paginator(new ArrayAdapter([]))) ->shouldBeCalledOnce(); @@ -88,7 +86,7 @@ class GetVisitsCommandTest extends TestCase $info->shouldHaveBeenCalledOnce(); $this->assertStringContainsString( sprintf('Ignored provided "startDate" since its value "%s" is not a valid date', $startDate), - $output + $output, ); } @@ -99,9 +97,9 @@ class GetVisitsCommandTest extends TestCase $this->visitsTracker->info($shortCode, Argument::any())->willReturn( new Paginator(new ArrayAdapter([ (new Visit(new ShortUrl(''), new Visitor('bar', 'foo', '')))->locate( - new VisitLocation(new Location('', 'Spain', '', '', 0, 0, '')) + new VisitLocation(new Location('', 'Spain', '', '', 0, 0, '')), ), - ])) + ])), )->shouldBeCalledOnce(); $this->commandTester->execute(['shortCode' => $shortCode]); diff --git a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php index 0bf3bfab..d4182e27 100644 --- a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php @@ -5,6 +5,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\ShortUrl; use Cake\Chronos\Chronos; +use Laminas\Paginator\Adapter\ArrayAdapter; +use Laminas\Paginator\Paginator; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; @@ -14,17 +16,13 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface; use Symfony\Component\Console\Application; use Symfony\Component\Console\Tester\CommandTester; -use Zend\Paginator\Adapter\ArrayAdapter; -use Zend\Paginator\Paginator; use function explode; class ListShortUrlsCommandTest extends TestCase { - /** @var CommandTester */ - private $commandTester; - /** @var ObjectProphecy */ - private $shortUrlService; + private CommandTester $commandTester; + private ObjectProphecy $shortUrlService; public function setUp(): void { @@ -44,9 +42,9 @@ class ListShortUrlsCommandTest extends TestCase $data[] = new ShortUrl('url_' . $i); } - $this->shortUrlService->listShortUrls(Argument::cetera())->will(function () use (&$data) { - return new Paginator(new ArrayAdapter($data)); - })->shouldBeCalledTimes(3); + $this->shortUrlService->listShortUrls(Argument::cetera()) + ->will(fn () => new Paginator(new ArrayAdapter($data))) + ->shouldBeCalledTimes(3); $this->commandTester->setInputs(['y', 'y', 'n']); $this->commandTester->execute([]); @@ -164,6 +162,7 @@ class ListShortUrlsCommandTest extends TestCase } /** + * @param string|array|null $expectedOrderBy * @test * @dataProvider provideOrderBy */ diff --git a/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php index 11b549e5..1fe8b238 100644 --- a/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php @@ -19,10 +19,8 @@ use const PHP_EOL; class ResolveUrlCommandTest extends TestCase { - /** @var CommandTester */ - private $commandTester; - /** @var ObjectProphecy */ - private $urlShortener; + private CommandTester $commandTester; + private ObjectProphecy $urlShortener; public function setUp(): void { diff --git a/module/CLI/test/Command/Tag/CreateTagCommandTest.php b/module/CLI/test/Command/Tag/CreateTagCommandTest.php index 402c63a6..bed087a5 100644 --- a/module/CLI/test/Command/Tag/CreateTagCommandTest.php +++ b/module/CLI/test/Command/Tag/CreateTagCommandTest.php @@ -14,10 +14,8 @@ use Symfony\Component\Console\Tester\CommandTester; class CreateTagCommandTest extends TestCase { - /** @var CommandTester */ - private $commandTester; - /** @var ObjectProphecy */ - private $tagService; + private CommandTester $commandTester; + private ObjectProphecy $tagService; public function setUp(): void { @@ -31,7 +29,7 @@ class CreateTagCommandTest extends TestCase } /** @test */ - public function errorIsReturnedWhenNoTagsAreProvided() + public function errorIsReturnedWhenNoTagsAreProvided(): void { $this->commandTester->execute([]); @@ -40,7 +38,7 @@ class CreateTagCommandTest extends TestCase } /** @test */ - public function serviceIsInvokedOnSuccess() + public function serviceIsInvokedOnSuccess(): void { $tagNames = ['foo', 'bar']; $createTags = $this->tagService->createTags($tagNames)->willReturn(new ArrayCollection()); diff --git a/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php b/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php index 79701ddb..060e5aac 100644 --- a/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php +++ b/module/CLI/test/Command/Tag/DeleteTagsCommandTest.php @@ -13,12 +13,8 @@ use Symfony\Component\Console\Tester\CommandTester; class DeleteTagsCommandTest extends TestCase { - /** @var DeleteTagsCommand */ - private $command; - /** @var CommandTester */ - private $commandTester; - /** @var ObjectProphecy */ - private $tagService; + private CommandTester $commandTester; + private ObjectProphecy $tagService; public function setUp(): void { @@ -32,7 +28,7 @@ class DeleteTagsCommandTest extends TestCase } /** @test */ - public function errorIsReturnedWhenNoTagsAreProvided() + public function errorIsReturnedWhenNoTagsAreProvided(): void { $this->commandTester->execute([]); @@ -41,10 +37,10 @@ class DeleteTagsCommandTest extends TestCase } /** @test */ - public function serviceIsInvokedOnSuccess() + public function serviceIsInvokedOnSuccess(): void { $tagNames = ['foo', 'bar']; - $deleteTags = $this->tagService->deleteTags($tagNames)->will(function () { + $deleteTags = $this->tagService->deleteTags($tagNames)->will(function (): void { }); $this->commandTester->execute([ diff --git a/module/CLI/test/Command/Tag/ListTagsCommandTest.php b/module/CLI/test/Command/Tag/ListTagsCommandTest.php index eec94372..f171127c 100644 --- a/module/CLI/test/Command/Tag/ListTagsCommandTest.php +++ b/module/CLI/test/Command/Tag/ListTagsCommandTest.php @@ -14,12 +14,8 @@ use Symfony\Component\Console\Tester\CommandTester; class ListTagsCommandTest extends TestCase { - /** @var ListTagsCommand */ - private $command; - /** @var CommandTester */ - private $commandTester; - /** @var ObjectProphecy */ - private $tagService; + private CommandTester $commandTester; + private ObjectProphecy $tagService; public function setUp(): void { @@ -33,7 +29,7 @@ class ListTagsCommandTest extends TestCase } /** @test */ - public function noTagsPrintsEmptyMessage() + public function noTagsPrintsEmptyMessage(): void { $listTags = $this->tagService->listTags()->willReturn([]); @@ -45,7 +41,7 @@ class ListTagsCommandTest extends TestCase } /** @test */ - public function listOfTagsIsPrinted() + public function listOfTagsIsPrinted(): void { $listTags = $this->tagService->listTags()->willReturn([ new Tag('foo'), diff --git a/module/CLI/test/Command/Tag/RenameTagCommandTest.php b/module/CLI/test/Command/Tag/RenameTagCommandTest.php index c626e0c0..59f8d89c 100644 --- a/module/CLI/test/Command/Tag/RenameTagCommandTest.php +++ b/module/CLI/test/Command/Tag/RenameTagCommandTest.php @@ -15,12 +15,8 @@ use Symfony\Component\Console\Tester\CommandTester; class RenameTagCommandTest extends TestCase { - /** @var RenameTagCommand */ - private $command; - /** @var CommandTester */ - private $commandTester; - /** @var ObjectProphecy */ - private $tagService; + private CommandTester $commandTester; + private ObjectProphecy $tagService; public function setUp(): void { diff --git a/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php b/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php index e01bf85f..90073f10 100644 --- a/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php +++ b/module/CLI/test/Command/Visit/LocateVisitsCommandTest.php @@ -29,18 +29,12 @@ use function sprintf; class LocateVisitsCommandTest extends TestCase { - /** @var CommandTester */ - private $commandTester; - /** @var ObjectProphecy */ - private $visitService; - /** @var ObjectProphecy */ - private $ipResolver; - /** @var ObjectProphecy */ - private $locker; - /** @var ObjectProphecy */ - private $lock; - /** @var ObjectProphecy */ - private $dbUpdater; + private CommandTester $commandTester; + private ObjectProphecy $visitService; + private ObjectProphecy $ipResolver; + private ObjectProphecy $locker; + private ObjectProphecy $lock; + private ObjectProphecy $dbUpdater; public function setUp(): void { @@ -51,7 +45,7 @@ class LocateVisitsCommandTest extends TestCase $this->locker = $this->prophesize(Lock\LockFactory::class); $this->lock = $this->prophesize(Lock\LockInterface::class); $this->lock->acquire(false)->willReturn(true); - $this->lock->release()->will(function () { + $this->lock->release()->will(function (): void { }); $this->locker->createLock(Argument::type('string'), 90.0, false)->willReturn($this->lock->reveal()); @@ -59,7 +53,7 @@ class LocateVisitsCommandTest extends TestCase $this->visitService->reveal(), $this->ipResolver->reveal(), $this->locker->reveal(), - $this->dbUpdater->reveal() + $this->dbUpdater->reveal(), ); $app = new Application(); $app->add($command); @@ -74,16 +68,16 @@ class LocateVisitsCommandTest extends TestCase $location = new VisitLocation(Location::emptyInstance()); $locateVisits = $this->visitService->locateUnlocatedVisits(Argument::cetera())->will( - function (array $args) use ($visit, $location) { + function (array $args) use ($visit, $location): void { $firstCallback = array_shift($args); $firstCallback($visit); $secondCallback = array_shift($args); $secondCallback($location, $visit); - } + }, ); $resolveIpLocation = $this->ipResolver->resolveIpLocation(Argument::any())->willReturn( - Location::emptyInstance() + Location::emptyInstance(), ); $this->commandTester->execute([]); @@ -104,16 +98,16 @@ class LocateVisitsCommandTest extends TestCase $location = new VisitLocation(Location::emptyInstance()); $locateVisits = $this->visitService->locateUnlocatedVisits(Argument::cetera())->will( - function (array $args) use ($visit, $location) { + function (array $args) use ($visit, $location): void { $firstCallback = array_shift($args); $firstCallback($visit); $secondCallback = array_shift($args); $secondCallback($location, $visit); - } + }, ); $resolveIpLocation = $this->ipResolver->resolveIpLocation(Argument::any())->willReturn( - Location::emptyInstance() + Location::emptyInstance(), ); $this->commandTester->execute([], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE]); @@ -143,13 +137,13 @@ class LocateVisitsCommandTest extends TestCase $location = new VisitLocation(Location::emptyInstance()); $locateVisits = $this->visitService->locateUnlocatedVisits(Argument::cetera())->will( - function (array $args) use ($visit, $location) { + function (array $args) use ($visit, $location): void { $firstCallback = array_shift($args); $firstCallback($visit); $secondCallback = array_shift($args); $secondCallback($location, $visit); - } + }, ); $resolveIpLocation = $this->ipResolver->resolveIpLocation(Argument::any())->willThrow(WrongIpException::class); @@ -167,7 +161,7 @@ class LocateVisitsCommandTest extends TestCase { $this->lock->acquire(false)->willReturn(false); - $locateVisits = $this->visitService->locateUnlocatedVisits(Argument::cetera())->will(function () { + $locateVisits = $this->visitService->locateUnlocatedVisits(Argument::cetera())->will(function (): void { }); $resolveIpLocation = $this->ipResolver->resolveIpLocation(Argument::any())->willReturn([]); @@ -176,7 +170,7 @@ class LocateVisitsCommandTest extends TestCase $this->assertStringContainsString( sprintf('Command "%s" is already in progress. Skipping.', LocateVisitsCommand::NAME), - $output + $output, ); $locateVisits->shouldNotHaveBeenCalled(); $resolveIpLocation->shouldNotHaveBeenCalled(); @@ -188,17 +182,17 @@ class LocateVisitsCommandTest extends TestCase */ public function showsProperMessageWhenGeoLiteUpdateFails(bool $olderDbExists, string $expectedMessage): void { - $locateVisits = $this->visitService->locateUnlocatedVisits(Argument::cetera())->will(function () { + $locateVisits = $this->visitService->locateUnlocatedVisits(Argument::cetera())->will(function (): void { }); $checkDbUpdate = $this->dbUpdater->checkDbUpdate(Argument::cetera())->will( - function (array $args) use ($olderDbExists) { + function (array $args) use ($olderDbExists): void { [$mustBeUpdated, $handleProgress] = $args; $mustBeUpdated($olderDbExists); $handleProgress(100, 50); throw GeolocationDbUpdateFailedException::create($olderDbExists); - } + }, ); $this->commandTester->execute([]); @@ -206,7 +200,7 @@ class LocateVisitsCommandTest extends TestCase $this->assertStringContainsString( sprintf('%s GeoLite2 database...', $olderDbExists ? 'Updating' : 'Downloading'), - $output + $output, ); $this->assertStringContainsString($expectedMessage, $output); $locateVisits->shouldHaveBeenCalledTimes((int) $olderDbExists); diff --git a/module/CLI/test/Command/Visit/UpdateDbCommandTest.php b/module/CLI/test/Command/Visit/UpdateDbCommandTest.php deleted file mode 100644 index dc343efd..00000000 --- a/module/CLI/test/Command/Visit/UpdateDbCommandTest.php +++ /dev/null @@ -1,77 +0,0 @@ -dbUpdater = $this->prophesize(DbUpdaterInterface::class); - - $command = new UpdateDbCommand($this->dbUpdater->reveal()); - $app = new Application(); - $app->add($command); - - $this->commandTester = new CommandTester($command); - } - - /** @test */ - public function successMessageIsPrintedIfEverythingWorks(): void - { - $download = $this->dbUpdater->downloadFreshCopy(Argument::type('callable'))->will(function () { - }); - - $this->commandTester->execute([]); - $output = $this->commandTester->getDisplay(); - $exitCode = $this->commandTester->getStatusCode(); - - $this->assertStringContainsString('GeoLite2 database properly updated', $output); - $this->assertEquals(ExitCodes::EXIT_SUCCESS, $exitCode); - $download->shouldHaveBeenCalledOnce(); - } - - /** @test */ - public function errorMessageIsPrintedIfAnExceptionIsThrown(): void - { - $download = $this->dbUpdater->downloadFreshCopy(Argument::type('callable'))->willThrow(RuntimeException::class); - - $this->commandTester->execute([]); - $output = $this->commandTester->getDisplay(); - $exitCode = $this->commandTester->getStatusCode(); - - $this->assertStringContainsString('An error occurred while updating GeoLite2 database', $output); - $this->assertEquals(ExitCodes::EXIT_FAILURE, $exitCode); - $download->shouldHaveBeenCalledOnce(); - } - - /** @test */ - public function warningMessageIsPrintedIfAnExceptionIsThrownAndErrorsAreIgnored(): void - { - $download = $this->dbUpdater->downloadFreshCopy(Argument::type('callable'))->willThrow(RuntimeException::class); - - $this->commandTester->execute(['--ignoreErrors' => true]); - $output = $this->commandTester->getDisplay(); - $exitCode = $this->commandTester->getStatusCode(); - - $this->assertStringContainsString('ignored', $output); - $this->assertEquals(ExitCodes::EXIT_SUCCESS, $exitCode); - $download->shouldHaveBeenCalledOnce(); - } -} diff --git a/module/CLI/test/ConfigProviderTest.php b/module/CLI/test/ConfigProviderTest.php index 48931551..baa4f311 100644 --- a/module/CLI/test/ConfigProviderTest.php +++ b/module/CLI/test/ConfigProviderTest.php @@ -9,8 +9,7 @@ use Shlinkio\Shlink\CLI\ConfigProvider; class ConfigProviderTest extends TestCase { - /** @var ConfigProvider */ - private $configProvider; + private ConfigProvider $configProvider; public function setUp(): void { @@ -18,7 +17,7 @@ class ConfigProviderTest extends TestCase } /** @test */ - public function confiIsProperlyReturned() + public function confiIsProperlyReturned(): void { $config = ($this->configProvider)(); diff --git a/module/CLI/test/Exception/GeolocationDbUpdateFailedExceptionTest.php b/module/CLI/test/Exception/GeolocationDbUpdateFailedExceptionTest.php index 70a8cc6f..21a1e006 100644 --- a/module/CLI/test/Exception/GeolocationDbUpdateFailedExceptionTest.php +++ b/module/CLI/test/Exception/GeolocationDbUpdateFailedExceptionTest.php @@ -23,7 +23,7 @@ class GeolocationDbUpdateFailedExceptionTest extends TestCase $this->assertEquals($olderDbExists, $e->olderDbExists()); $this->assertEquals( 'An error occurred while updating geolocation database, and an older version could not be found', - $e->getMessage() + $e->getMessage(), ); $this->assertEquals(0, $e->getCode()); $this->assertEquals($prev, $e->getPrevious()); diff --git a/module/CLI/test/Factory/ApplicationFactoryTest.php b/module/CLI/test/Factory/ApplicationFactoryTest.php index 7f8ca6ea..043349ab 100644 --- a/module/CLI/test/Factory/ApplicationFactoryTest.php +++ b/module/CLI/test/Factory/ApplicationFactoryTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Factory; +use Laminas\ServiceManager\ServiceManager; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; @@ -11,12 +12,10 @@ use Shlinkio\Shlink\CLI\Factory\ApplicationFactory; use Shlinkio\Shlink\Core\Options\AppOptions; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; -use Zend\ServiceManager\ServiceManager; class ApplicationFactoryTest extends TestCase { - /** @var ApplicationFactory */ - private $factory; + private ApplicationFactory $factory; public function setUp(): void { @@ -36,7 +35,6 @@ class ApplicationFactoryTest extends TestCase $sm->setService('foo', $this->createCommandMock('foo')->reveal()); $sm->setService('bar', $this->createCommandMock('bar')->reveal()); - /** @var Application $instance */ $instance = ($this->factory)($sm); $this->assertTrue($instance->has('foo')); @@ -61,7 +59,7 @@ class ApplicationFactoryTest extends TestCase $command->getDefinition()->willReturn($name); $command->isEnabled()->willReturn(true); $command->getAliases()->willReturn([]); - $command->setApplication(Argument::type(Application::class))->willReturn(function () { + $command->setApplication(Argument::type(Application::class))->willReturn(function (): void { }); return $command; diff --git a/module/CLI/test/Util/GeolocationDbUpdaterTest.php b/module/CLI/test/Util/GeolocationDbUpdaterTest.php index f2b0f98c..b5346629 100644 --- a/module/CLI/test/Util/GeolocationDbUpdaterTest.php +++ b/module/CLI/test/Util/GeolocationDbUpdaterTest.php @@ -22,16 +22,11 @@ use function range; class GeolocationDbUpdaterTest extends TestCase { - /** @var GeolocationDbUpdater */ - private $geolocationDbUpdater; - /** @var ObjectProphecy */ - private $dbUpdater; - /** @var ObjectProphecy */ - private $geoLiteDbReader; - /** @var ObjectProphecy */ - private $locker; - /** @var ObjectProphecy */ - private $lock; + private GeolocationDbUpdater $geolocationDbUpdater; + private ObjectProphecy $dbUpdater; + private ObjectProphecy $geoLiteDbReader; + private ObjectProphecy $locker; + private ObjectProphecy $lock; public function setUp(): void { @@ -41,23 +36,21 @@ class GeolocationDbUpdaterTest extends TestCase $this->locker = $this->prophesize(Lock\LockFactory::class); $this->lock = $this->prophesize(Lock\LockInterface::class); $this->lock->acquire(true)->willReturn(true); - $this->lock->release()->will(function () { + $this->lock->release()->will(function (): void { }); $this->locker->createLock(Argument::type('string'))->willReturn($this->lock->reveal()); $this->geolocationDbUpdater = new GeolocationDbUpdater( $this->dbUpdater->reveal(), $this->geoLiteDbReader->reveal(), - $this->locker->reveal() + $this->locker->reveal(), ); } /** @test */ public function exceptionIsThrownWhenOlderDbDoesNotExistAndDownloadFails(): void { - $mustBeUpdated = function () { - $this->assertTrue(true); - }; + $mustBeUpdated = fn () => $this->assertTrue(true); $prev = new RuntimeException(''); $fileExists = $this->dbUpdater->databaseFileExists()->willReturn(false); @@ -141,7 +134,7 @@ class GeolocationDbUpdaterTest extends TestCase 'node_count' => 1, 'record_size' => 4, ])); - $download = $this->dbUpdater->downloadFreshCopy(null)->will(function () { + $download = $this->dbUpdater->downloadFreshCopy(null)->will(function (): void { }); $this->geolocationDbUpdater->checkDbUpdate(); @@ -153,8 +146,6 @@ class GeolocationDbUpdaterTest extends TestCase public function provideSmallDays(): iterable { - return map(range(0, 34), function (int $days) { - return [$days]; - }); + return map(range(0, 34), fn (int $days) => [$days]); } } diff --git a/module/CLI/test/Util/ShlinkTableTest.php b/module/CLI/test/Util/ShlinkTableTest.php index c67ebe47..23c4eb32 100644 --- a/module/CLI/test/Util/ShlinkTableTest.php +++ b/module/CLI/test/Util/ShlinkTableTest.php @@ -15,10 +15,8 @@ use Symfony\Component\Console\Output\OutputInterface; class ShlinkTableTest extends TestCase { - /** @var ShlinkTable */ - private $shlinkTable; - /** @var ObjectProphecy */ - private $baseTable; + private ShlinkTable $shlinkTable; + private ObjectProphecy $baseTable; public function setUp(): void { @@ -35,7 +33,7 @@ class ShlinkTableTest extends TestCase $footerTitle = 'Footer'; $setStyle = $this->baseTable->setStyle(Argument::type(TableStyle::class))->willReturn( - $this->baseTable->reveal() + $this->baseTable->reveal(), ); $setHeaders = $this->baseTable->setHeaders($headers)->willReturn($this->baseTable->reveal()); $setRows = $this->baseTable->setRows($rows)->willReturn($this->baseTable->reveal()); diff --git a/module/Core/config/app_options.config.php b/module/Core/config/app_options.config.php deleted file mode 100644 index ccab5752..00000000 --- a/module/Core/config/app_options.config.php +++ /dev/null @@ -1,9 +0,0 @@ - [], - -]; diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index 0ebdae7d..8d053c4f 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -5,13 +5,12 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core; use Doctrine\Common\Cache\Cache; +use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory; +use Mezzio\Router\RouterInterface; +use Mezzio\Template\TemplateRendererInterface; use Psr\EventDispatcher\EventDispatcherInterface; use Shlinkio\Shlink\Core\ErrorHandler; use Shlinkio\Shlink\Core\Options\NotFoundRedirectOptions; -use Shlinkio\Shlink\PreviewGenerator\Service\PreviewGenerator; -use Zend\Expressive\Router\RouterInterface; -use Zend\Expressive\Template\TemplateRendererInterface; -use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory; return [ @@ -37,7 +36,6 @@ return [ Action\RedirectAction::class => ConfigAbstractFactory::class, Action\PixelAction::class => ConfigAbstractFactory::class, Action\QrCodeAction::class => ConfigAbstractFactory::class, - Action\PreviewAction::class => ConfigAbstractFactory::class, Middleware\QrCodeCacheMiddleware::class => ConfigAbstractFactory::class, ], @@ -74,7 +72,6 @@ return [ 'Logger_Shlink', ], Action\QrCodeAction::class => [RouterInterface::class, Service\UrlShortener::class, 'Logger_Shlink'], - Action\PreviewAction::class => [PreviewGenerator::class, Service\UrlShortener::class, 'Logger_Shlink'], Middleware\QrCodeCacheMiddleware::class => [Cache::class], ], diff --git a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Domain.php b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Domain.php index b3afe195..de7252ee 100644 --- a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Domain.php +++ b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Domain.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core; -use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder; use Doctrine\ORM\Mapping\ClassMetadata; // @codingStandardsIgnoreLine @@ -13,13 +13,13 @@ $builder = new ClassMetadataBuilder($metadata); $builder->setTable('domains'); -$builder->createField('id', Type::BIGINT) +$builder->createField('id', Types::BIGINT) ->columnName('id') ->makePrimaryKey() ->generatedValue('IDENTITY') ->option('unsigned', true) ->build(); -$builder->createField('authority', Type::STRING) +$builder->createField('authority', Types::STRING) ->unique() ->build(); diff --git a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.ShortUrl.php b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.ShortUrl.php index 76e5ae7b..0d24d555 100644 --- a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.ShortUrl.php +++ b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.ShortUrl.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core; -use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder; use Doctrine\ORM\Mapping\ClassMetadata; // @codingStandardsIgnoreLine use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType; @@ -15,19 +15,19 @@ $builder = new ClassMetadataBuilder($metadata); $builder->setTable('short_urls') ->setCustomRepositoryClass(Repository\ShortUrlRepository::class); -$builder->createField('id', Type::BIGINT) +$builder->createField('id', Types::BIGINT) ->columnName('id') ->makePrimaryKey() ->generatedValue('IDENTITY') ->option('unsigned', true) ->build(); -$builder->createField('longUrl', Type::STRING) +$builder->createField('longUrl', Types::STRING) ->columnName('original_url') ->length(2048) ->build(); -$builder->createField('shortCode', Type::STRING) +$builder->createField('shortCode', Types::STRING) ->columnName('short_code') ->length(255) ->build(); @@ -46,7 +46,7 @@ $builder->createField('validUntil', ChronosDateTimeType::CHRONOS_DATETIME) ->nullable() ->build(); -$builder->createField('maxVisits', Type::INTEGER) +$builder->createField('maxVisits', Types::INTEGER) ->columnName('max_visits') ->nullable() ->build(); diff --git a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Tag.php b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Tag.php index 8ecaa2c6..09a98151 100644 --- a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Tag.php +++ b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Tag.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core; -use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder; use Doctrine\ORM\Mapping\ClassMetadata; // @codingStandardsIgnoreLine @@ -14,13 +14,13 @@ $builder = new ClassMetadataBuilder($metadata); $builder->setTable('tags') ->setCustomRepositoryClass(Repository\TagRepository::class); -$builder->createField('id', Type::BIGINT) +$builder->createField('id', Types::BIGINT) ->columnName('id') ->makePrimaryKey() ->generatedValue('IDENTITY') ->option('unsigned', true) ->build(); -$builder->createField('name', Type::STRING) +$builder->createField('name', Types::STRING) ->unique() ->build(); diff --git a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Visit.php b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Visit.php index c30b83c9..6770f9d3 100644 --- a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Visit.php +++ b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Visit.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core; -use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder; use Doctrine\ORM\Mapping\ClassMetadata; // @codingStandardsIgnoreLine use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType; @@ -16,14 +16,14 @@ $builder = new ClassMetadataBuilder($metadata); $builder->setTable('visits') ->setCustomRepositoryClass(Repository\VisitRepository::class); -$builder->createField('id', Type::BIGINT) +$builder->createField('id', Types::BIGINT) ->columnName('id') ->makePrimaryKey() ->generatedValue('IDENTITY') ->option('unsigned', true) ->build(); -$builder->createField('referer', Type::STRING) +$builder->createField('referer', Types::STRING) ->nullable() ->length(Visitor::REFERER_MAX_LENGTH) ->build(); @@ -32,13 +32,13 @@ $builder->createField('date', ChronosDateTimeType::CHRONOS_DATETIME) ->columnName('`date`') ->build(); -$builder->createField('remoteAddr', Type::STRING) +$builder->createField('remoteAddr', Types::STRING) ->columnName('remote_addr') ->length(Visitor::REMOTE_ADDRESS_MAX_LENGTH) ->nullable() ->build(); -$builder->createField('userAgent', Type::STRING) +$builder->createField('userAgent', Types::STRING) ->columnName('user_agent') ->length(Visitor::USER_AGENT_MAX_LENGTH) ->nullable() diff --git a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.VisitLocation.php b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.VisitLocation.php index 073214d3..117c2acc 100644 --- a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.VisitLocation.php +++ b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.VisitLocation.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core; -use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder; use Doctrine\ORM\Mapping\ClassMetadata; // @codingStandardsIgnoreLine @@ -13,7 +13,7 @@ $builder = new ClassMetadataBuilder($metadata); $builder->setTable('visit_locations'); -$builder->createField('id', Type::BIGINT) +$builder->createField('id', Types::BIGINT) ->columnName('id') ->makePrimaryKey() ->generatedValue('IDENTITY') @@ -25,14 +25,22 @@ $columns = [ 'country_name' => 'countryName', 'region_name' => 'regionName', 'city_name' => 'cityName', - 'latitude' => 'latitude', - 'longitude' => 'longitude', 'timezone' => 'timezone', ]; foreach ($columns as $columnName => $fieldName) { - $builder->createField($fieldName, Type::STRING) - ->columnName($columnName) - ->nullable() - ->build(); + $builder->createField($fieldName, Types::STRING) + ->columnName($columnName) + ->nullable() + ->build(); } + +$builder->createField('latitude', Types::FLOAT) + ->columnName('lat') + ->nullable(false) + ->build(); + +$builder->createField('longitude', Types::FLOAT) + ->columnName('lon') + ->nullable(false) + ->build(); diff --git a/module/Core/config/event_dispatcher.config.php b/module/Core/config/event_dispatcher.config.php index aead1447..29dbbd11 100644 --- a/module/Core/config/event_dispatcher.config.php +++ b/module/Core/config/event_dispatcher.config.php @@ -4,10 +4,10 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core; +use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory; use Psr\EventDispatcher\EventDispatcherInterface; use Shlinkio\Shlink\CLI\Util\GeolocationDbUpdater; use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface; -use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory; return [ diff --git a/module/Core/config/zend-expressive.config.php b/module/Core/config/mezzio.config.php similarity index 88% rename from module/Core/config/zend-expressive.config.php rename to module/Core/config/mezzio.config.php index c0fa05c9..5e4acb22 100644 --- a/module/Core/config/zend-expressive.config.php +++ b/module/Core/config/mezzio.config.php @@ -4,7 +4,7 @@ declare(strict_types=1); return [ - 'zend-expressive' => [ + 'mezzio' => [ 'error_handler' => [ 'template_404' => 'ShlinkCore::error/404', 'template_error' => 'ShlinkCore::error/error', diff --git a/module/Core/config/routes.config.php b/module/Core/config/routes.config.php index b38222df..d636ed23 100644 --- a/module/Core/config/routes.config.php +++ b/module/Core/config/routes.config.php @@ -37,23 +37,6 @@ return [ ], 'allowed_methods' => [RequestMethod::METHOD_GET], ], - - // Deprecated routes - [ - 'name' => 'short-url-preview', - 'path' => '/{shortCode}/preview', - 'middleware' => Action\PreviewAction::class, - 'allowed_methods' => [RequestMethod::METHOD_GET], - ], - [ - 'name' => 'short-url-qr-code-old', - 'path' => '/qr/{shortCode}[/{size:[0-9]+}]', - 'middleware' => [ - Middleware\QrCodeCacheMiddleware::class, - Action\QrCodeAction::class, - ], - 'allowed_methods' => [RequestMethod::METHOD_GET], - ], ], ]; diff --git a/module/Core/src/Action/AbstractTrackingAction.php b/module/Core/src/Action/AbstractTrackingAction.php index ff8d91f2..384b7a3e 100644 --- a/module/Core/src/Action/AbstractTrackingAction.php +++ b/module/Core/src/Action/AbstractTrackingAction.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Action; +use Laminas\Diactoros\Uri; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; @@ -16,7 +17,6 @@ use Shlinkio\Shlink\Core\Model\Visitor; use Shlinkio\Shlink\Core\Options\AppOptions; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface; -use Zend\Diactoros\Uri; use function array_key_exists; use function array_merge; @@ -25,14 +25,10 @@ use function http_build_query; abstract class AbstractTrackingAction implements MiddlewareInterface { - /** @var UrlShortenerInterface */ - private $urlShortener; - /** @var VisitsTrackerInterface */ - private $visitTracker; - /** @var AppOptions */ - private $appOptions; - /** @var LoggerInterface */ - private $logger; + private UrlShortenerInterface $urlShortener; + private VisitsTrackerInterface $visitTracker; + private AppOptions $appOptions; + private LoggerInterface $logger; public function __construct( UrlShortenerInterface $urlShortener, @@ -50,10 +46,7 @@ abstract class AbstractTrackingAction implements MiddlewareInterface * Process an incoming server request and return a response, optionally delegating * to the next middleware component to create the response. * - * @param ServerRequestInterface $request - * @param RequestHandlerInterface $handler * - * @return ResponseInterface */ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { diff --git a/module/Core/src/Action/PreviewAction.php b/module/Core/src/Action/PreviewAction.php deleted file mode 100644 index d243f12c..00000000 --- a/module/Core/src/Action/PreviewAction.php +++ /dev/null @@ -1,63 +0,0 @@ -previewGenerator = $previewGenerator; - $this->urlShortener = $urlShortener; - $this->logger = $logger ?: new NullLogger(); - } - - /** - * Process an incoming server request and return a response, optionally delegating - * to the next middleware component to create the response. - * - * @param Request $request - * @param RequestHandlerInterface $handler - * - * @return Response - */ - public function process(Request $request, RequestHandlerInterface $handler): Response - { - $shortCode = $request->getAttribute('shortCode'); - - try { - $url = $this->urlShortener->shortCodeToUrl($shortCode); - $imagePath = $this->previewGenerator->generatePreview($url->getLongUrl()); - return $this->generateImageResponse($imagePath); - } catch (ShortUrlNotFoundException | PreviewGenerationException $e) { - $this->logger->warning('An error occurred while generating preview image. {e}', ['e' => $e]); - return $handler->handle($request); - } - } -} diff --git a/module/Core/src/Action/QrCodeAction.php b/module/Core/src/Action/QrCodeAction.php index 3cdee70e..a1a58ae7 100644 --- a/module/Core/src/Action/QrCodeAction.php +++ b/module/Core/src/Action/QrCodeAction.php @@ -5,6 +5,8 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Action; use Endroid\QrCode\QrCode; +use Mezzio\Router\Exception\RuntimeException; +use Mezzio\Router\RouterInterface; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Server\MiddlewareInterface; @@ -14,8 +16,6 @@ use Psr\Log\NullLogger; use Shlinkio\Shlink\Common\Response\QrCodeResponse; use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; -use Zend\Expressive\Router\Exception\RuntimeException; -use Zend\Expressive\Router\RouterInterface; class QrCodeAction implements MiddlewareInterface { @@ -23,12 +23,9 @@ class QrCodeAction implements MiddlewareInterface private const MIN_SIZE = 50; private const MAX_SIZE = 1000; - /** @var RouterInterface */ - private $router; - /** @var UrlShortenerInterface */ - private $urlShortener; - /** @var LoggerInterface */ - private $logger; + private RouterInterface $router; + private UrlShortenerInterface $urlShortener; + private LoggerInterface $logger; public function __construct( RouterInterface $router, @@ -44,10 +41,7 @@ class QrCodeAction implements MiddlewareInterface * Process an incoming server request and return a response, optionally delegating * to the next middleware component to create the response. * - * @param Request $request - * @param RequestHandlerInterface $handler * - * @return Response * @throws \InvalidArgumentException * @throws RuntimeException */ @@ -74,8 +68,6 @@ class QrCodeAction implements MiddlewareInterface } /** - * @param Request $request - * @return int */ private function getSizeParam(Request $request): int { diff --git a/module/Core/src/Action/RedirectAction.php b/module/Core/src/Action/RedirectAction.php index 31fdb1a3..e6e1ec4c 100644 --- a/module/Core/src/Action/RedirectAction.php +++ b/module/Core/src/Action/RedirectAction.php @@ -4,10 +4,10 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Action; +use Laminas\Diactoros\Response\RedirectResponse; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; -use Zend\Diactoros\Response\RedirectResponse; class RedirectAction extends AbstractTrackingAction { diff --git a/module/Core/src/Config/DeprecatedConfigParser.php b/module/Core/src/Config/DeprecatedConfigParser.php index 059c81a9..92074bfc 100644 --- a/module/Core/src/Config/DeprecatedConfigParser.php +++ b/module/Core/src/Config/DeprecatedConfigParser.php @@ -10,7 +10,7 @@ class DeprecatedConfigParser { public function __invoke(array $config): array { - return compose([$this, 'parseNotFoundRedirect'])($config); + return compose([$this, 'parseNotFoundRedirect'], [$this, 'removeSecretKey'])($config); } public function parseNotFoundRedirect(array $config): array @@ -30,4 +30,11 @@ class DeprecatedConfigParser return $config; } + + public function removeSecretKey(array $config): array + { + // Removing secret_key from any generated config will prevent the AppOptions object from crashing + unset($config['app_options']['secret_key']); + return $config; + } } diff --git a/module/Core/src/Config/SimplifiedConfigParser.php b/module/Core/src/Config/SimplifiedConfigParser.php index 5ee912b0..fa7a4acb 100644 --- a/module/Core/src/Config/SimplifiedConfigParser.php +++ b/module/Core/src/Config/SimplifiedConfigParser.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Config; +use Laminas\Stdlib\ArrayUtils; use Shlinkio\Shlink\Installer\Util\PathCollection; -use Zend\Stdlib\ArrayUtils; use function array_flip; use function array_intersect_key; @@ -22,16 +22,15 @@ class SimplifiedConfigParser 'short_domain_schema' => ['url_shortener', 'domain', 'schema'], 'short_domain_host' => ['url_shortener', 'domain', 'hostname'], 'validate_url' => ['url_shortener', 'validate_url'], - 'not_found_redirect_to' => ['not_found_redirects', 'invalid_short_url'], // Deprecated 'invalid_short_url_redirect_to' => ['not_found_redirects', 'invalid_short_url'], 'regular_404_redirect_to' => ['not_found_redirects', 'regular_404'], 'base_url_redirect_to' => ['not_found_redirects', 'base_path'], 'db_config' => ['entity_manager', 'connection'], 'delete_short_url_threshold' => ['delete_short_urls', 'visits_threshold'], - 'redis_servers' => ['redis', 'servers'], + 'redis_servers' => ['cache', 'redis', 'servers'], 'base_path' => ['router', 'base_path'], - 'web_worker_num' => ['zend-expressive-swoole', 'swoole-http-server', 'options', 'worker_num'], - 'task_worker_num' => ['zend-expressive-swoole', 'swoole-http-server', 'options', 'task_worker_num'], + 'web_worker_num' => ['mezzio-swoole', 'swoole-http-server', 'options', 'worker_num'], + 'task_worker_num' => ['mezzio-swoole', 'swoole-http-server', 'options', 'task_worker_num'], 'visits_webhooks' => ['url_shortener', 'visits_webhooks'], ]; private const SIMPLIFIED_CONFIG_SIDE_EFFECTS = [ @@ -75,9 +74,10 @@ class SimplifiedConfigParser // This mainly allows deprecating keys and defining new ones that will replace the older and always take // preference, while the old one keeps working for backwards compatibility if the new one is not provided. $simplifiedConfigOrder = array_flip(array_keys(self::SIMPLIFIED_CONFIG_MAPPING)); - uksort($configForExistingKeys, function (string $a, string $b) use ($simplifiedConfigOrder): int { - return $simplifiedConfigOrder[$a] - $simplifiedConfigOrder[$b]; - }); + uksort( + $configForExistingKeys, + fn (string $a, string $b): int => $simplifiedConfigOrder[$a] - $simplifiedConfigOrder[$b], + ); return $configForExistingKeys; } diff --git a/module/Core/src/ConfigProvider.php b/module/Core/src/ConfigProvider.php index c5ee793a..086d093d 100644 --- a/module/Core/src/ConfigProvider.php +++ b/module/Core/src/ConfigProvider.php @@ -8,7 +8,7 @@ use function Shlinkio\Shlink\Common\loadConfigFromGlob; class ConfigProvider { - public function __invoke() + public function __invoke(): array { return loadConfigFromGlob(__DIR__ . '/../config/{,*.}config.php'); } diff --git a/module/Core/src/Domain/Resolver/PersistenceDomainResolver.php b/module/Core/src/Domain/Resolver/PersistenceDomainResolver.php index 6532a1c3..ca679d96 100644 --- a/module/Core/src/Domain/Resolver/PersistenceDomainResolver.php +++ b/module/Core/src/Domain/Resolver/PersistenceDomainResolver.php @@ -9,8 +9,7 @@ use Shlinkio\Shlink\Core\Entity\Domain; class PersistenceDomainResolver implements DomainResolverInterface { - /** @var EntityManagerInterface */ - private $em; + private EntityManagerInterface $em; public function __construct(EntityManagerInterface $em) { diff --git a/module/Core/src/Entity/Domain.php b/module/Core/src/Entity/Domain.php index 8be40c00..924b50e5 100644 --- a/module/Core/src/Entity/Domain.php +++ b/module/Core/src/Entity/Domain.php @@ -8,8 +8,7 @@ use Shlinkio\Shlink\Common\Entity\AbstractEntity; class Domain extends AbstractEntity { - /** @var string */ - private $authority; + private string $authority; public function __construct(string $authority) { diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index 6cf6378a..a48a93fa 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -7,12 +7,12 @@ namespace Shlinkio\Shlink\Core\Entity; use Cake\Chronos\Chronos; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; +use Laminas\Diactoros\Uri; use Shlinkio\Shlink\Common\Entity\AbstractEntity; use Shlinkio\Shlink\Core\Domain\Resolver\DomainResolverInterface; use Shlinkio\Shlink\Core\Domain\Resolver\SimpleDomainResolver; use Shlinkio\Shlink\Core\Exception\ShortCodeCannotBeRegeneratedException; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; -use Zend\Diactoros\Uri; use function array_reduce; use function count; @@ -22,26 +22,18 @@ use function Shlinkio\Shlink\Core\generateRandomShortCode; class ShortUrl extends AbstractEntity { - /** @var string */ - private $longUrl; - /** @var string */ - private $shortCode; - /** @var Chronos */ - private $dateCreated; + private string $longUrl; + private string $shortCode; + private Chronos $dateCreated; /** @var Collection|Visit[] */ - private $visits; + private Collection $visits; /** @var Collection|Tag[] */ - private $tags; - /** @var Chronos|null */ - private $validSince; - /** @var Chronos|null */ - private $validUntil; - /** @var integer|null */ - private $maxVisits; - /** @var Domain|null */ - private $domain; - /** @var bool */ - private $customSlugWasProvided; + private Collection $tags; + private ?Chronos $validSince; + private ?Chronos $validUntil; + private ?int $maxVisits; + private ?Domain $domain; + private bool $customSlugWasProvided; public function __construct( string $longUrl, @@ -196,10 +188,8 @@ class ShortUrl extends AbstractEntity $shortUrlTags = invoke($this->getTags(), '__toString'); $hasAllTags = count($shortUrlTags) === count($tags) && array_reduce( $tags, - function (bool $hasAllTags, string $tag) use ($shortUrlTags) { - return $hasAllTags && contains($shortUrlTags, $tag); - }, - true + fn (bool $hasAllTags, string $tag) => $hasAllTags && contains($shortUrlTags, $tag), + true, ); return $hasAllTags; diff --git a/module/Core/src/Entity/Tag.php b/module/Core/src/Entity/Tag.php index a703ebab..7530b70a 100644 --- a/module/Core/src/Entity/Tag.php +++ b/module/Core/src/Entity/Tag.php @@ -9,8 +9,7 @@ use Shlinkio\Shlink\Common\Entity\AbstractEntity; class Tag extends AbstractEntity implements JsonSerializable { - /** @var string */ - private $name; + private string $name; public function __construct(string $name) { diff --git a/module/Core/src/Entity/Visit.php b/module/Core/src/Entity/Visit.php index b2ce3640..8ada8176 100644 --- a/module/Core/src/Entity/Visit.php +++ b/module/Core/src/Entity/Visit.php @@ -15,18 +15,12 @@ use Shlinkio\Shlink\Core\Visit\Model\VisitLocationInterface; class Visit extends AbstractEntity implements JsonSerializable { - /** @var string */ - private $referer; - /** @var Chronos */ - private $date; - /** @var string|null */ - private $remoteAddr; - /** @var string */ - private $userAgent; - /** @var ShortUrl */ - private $shortUrl; - /** @var VisitLocation */ - private $visitLocation; + private string $referer = ''; + private Chronos $date; + private ?string $remoteAddr = null; + private string $userAgent = ''; + private ShortUrl $shortUrl; + private ?VisitLocation $visitLocation = null; public function __construct(ShortUrl $shortUrl, Visitor $visitor, ?Chronos $date = null) { @@ -93,12 +87,9 @@ class Visit extends AbstractEntity implements JsonSerializable { return [ 'referer' => $this->referer, - 'date' => $this->date !== null ? $this->date->toAtomString() : null, + 'date' => $this->date->toAtomString(), 'userAgent' => $this->userAgent, 'visitLocation' => $this->visitLocation, - - // Deprecated - 'remoteAddr' => null, ]; } diff --git a/module/Core/src/Entity/VisitLocation.php b/module/Core/src/Entity/VisitLocation.php index 9108a719..641b078e 100644 --- a/module/Core/src/Entity/VisitLocation.php +++ b/module/Core/src/Entity/VisitLocation.php @@ -10,20 +10,13 @@ use Shlinkio\Shlink\IpGeolocation\Model\Location; class VisitLocation extends AbstractEntity implements VisitLocationInterface { - /** @var string */ - private $countryCode; - /** @var string */ - private $countryName; - /** @var string */ - private $regionName; - /** @var string */ - private $cityName; - /** @var string */ - private $latitude; - /** @var string */ - private $longitude; - /** @var string */ - private $timezone; + private string $countryCode; + private string $countryName; + private string $regionName; + private string $cityName; + private float $latitude; + private float $longitude; + private string $timezone; public function __construct(Location $location) { @@ -32,22 +25,22 @@ class VisitLocation extends AbstractEntity implements VisitLocationInterface public function getCountryName(): string { - return $this->countryName ?? ''; + return $this->countryName; } - public function getLatitude(): string + public function getLatitude(): float { - return $this->latitude ?? ''; + return $this->latitude; } - public function getLongitude(): string + public function getLongitude(): float { - return $this->longitude ?? ''; + return $this->longitude; } public function getCityName(): string { - return $this->cityName ?? ''; + return $this->cityName; } private function exchangeLocationInfo(Location $info): void @@ -56,8 +49,8 @@ class VisitLocation extends AbstractEntity implements VisitLocationInterface $this->countryName = $info->countryName(); $this->regionName = $info->regionName(); $this->cityName = $info->city(); - $this->latitude = (string) $info->latitude(); - $this->longitude = (string) $info->longitude(); + $this->latitude = $info->latitude(); + $this->longitude = $info->longitude(); $this->timezone = $info->timeZone(); } @@ -81,8 +74,8 @@ class VisitLocation extends AbstractEntity implements VisitLocationInterface $this->countryName === '' && $this->regionName === '' && $this->cityName === '' && - ((float) $this->latitude) === 0.0 && - ((float) $this->longitude) === 0.0 && + $this->latitude === 0.0 && + $this->longitude === 0.0 && $this->timezone === ''; } } diff --git a/module/Core/src/ErrorHandler/NotFoundRedirectHandler.php b/module/Core/src/ErrorHandler/NotFoundRedirectHandler.php index f6a03395..13615b7d 100644 --- a/module/Core/src/ErrorHandler/NotFoundRedirectHandler.php +++ b/module/Core/src/ErrorHandler/NotFoundRedirectHandler.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\ErrorHandler; +use Laminas\Diactoros\Response; +use Mezzio\Router\RouteResult; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UriInterface; @@ -11,17 +13,13 @@ use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Shlinkio\Shlink\Core\Action\RedirectAction; use Shlinkio\Shlink\Core\Options\NotFoundRedirectOptions; -use Zend\Diactoros\Response; -use Zend\Expressive\Router\RouteResult; use function rtrim; class NotFoundRedirectHandler implements MiddlewareInterface { - /** @var NotFoundRedirectOptions */ - private $redirectOptions; - /** @var string */ - private $shlinkBasePath; + private NotFoundRedirectOptions $redirectOptions; + private string $shlinkBasePath; public function __construct(NotFoundRedirectOptions $redirectOptions, string $shlinkBasePath) { diff --git a/module/Core/src/ErrorHandler/NotFoundTemplateHandler.php b/module/Core/src/ErrorHandler/NotFoundTemplateHandler.php index 7b84043d..e5968a68 100644 --- a/module/Core/src/ErrorHandler/NotFoundTemplateHandler.php +++ b/module/Core/src/ErrorHandler/NotFoundTemplateHandler.php @@ -6,20 +6,19 @@ namespace Shlinkio\Shlink\Core\ErrorHandler; use Fig\Http\Message\StatusCodeInterface; use InvalidArgumentException; +use Laminas\Diactoros\Response; +use Mezzio\Router\RouteResult; +use Mezzio\Template\TemplateRendererInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; -use Zend\Diactoros\Response; -use Zend\Expressive\Router\RouteResult; -use Zend\Expressive\Template\TemplateRendererInterface; class NotFoundTemplateHandler implements RequestHandlerInterface { public const NOT_FOUND_TEMPLATE = 'ShlinkCore::error/404'; public const INVALID_SHORT_CODE_TEMPLATE = 'ShlinkCore::invalid-short-code'; - /** @var TemplateRendererInterface */ - private $renderer; + private TemplateRendererInterface $renderer; public function __construct(TemplateRendererInterface $renderer) { @@ -29,9 +28,7 @@ class NotFoundTemplateHandler implements RequestHandlerInterface /** * Dispatch the next available middleware and return the response. * - * @param ServerRequestInterface $request * - * @return ResponseInterface * @throws InvalidArgumentException */ public function handle(ServerRequestInterface $request): ResponseInterface diff --git a/module/Core/src/EventDispatcher/LocateShortUrlVisit.php b/module/Core/src/EventDispatcher/LocateShortUrlVisit.php index 4d767272..a0f8d033 100644 --- a/module/Core/src/EventDispatcher/LocateShortUrlVisit.php +++ b/module/Core/src/EventDispatcher/LocateShortUrlVisit.php @@ -19,16 +19,11 @@ use function sprintf; class LocateShortUrlVisit { - /** @var IpLocationResolverInterface */ - private $ipLocationResolver; - /** @var EntityManagerInterface */ - private $em; - /** @var LoggerInterface */ - private $logger; - /** @var GeolocationDbUpdaterInterface */ - private $dbUpdater; - /** @var EventDispatcherInterface */ - private $eventDispatcher; + private IpLocationResolverInterface $ipLocationResolver; + private EntityManagerInterface $em; + private LoggerInterface $logger; + private GeolocationDbUpdaterInterface $dbUpdater; + private EventDispatcherInterface $eventDispatcher; public function __construct( IpLocationResolverInterface $ipLocationResolver, @@ -67,14 +62,14 @@ class LocateShortUrlVisit private function downloadOrUpdateGeoLiteDb(string $visitId): bool { try { - $this->dbUpdater->checkDbUpdate(function (bool $olderDbExists) { + $this->dbUpdater->checkDbUpdate(function (bool $olderDbExists): void { $this->logger->notice(sprintf('%s GeoLite2 database...', $olderDbExists ? 'Updating' : 'Downloading')); }); } catch (GeolocationDbUpdateFailedException $e) { if (! $e->olderDbExists()) { $this->logger->error( 'GeoLite2 database download failed. It is not possible to locate visit with id {visitId}. {e}', - ['e' => $e, 'visitId' => $visitId] + ['e' => $e, 'visitId' => $visitId], ); return false; } @@ -97,7 +92,7 @@ class LocateShortUrlVisit } catch (WrongIpException $e) { $this->logger->warning( 'Tried to locate visit with id "{visitId}", but its address seems to be wrong. {e}', - ['e' => $e, 'visitId' => $visitId] + ['e' => $e, 'visitId' => $visitId], ); } } diff --git a/module/Core/src/EventDispatcher/NotifyVisitToWebHooks.php b/module/Core/src/EventDispatcher/NotifyVisitToWebHooks.php index d99defb5..de07d5ef 100644 --- a/module/Core/src/EventDispatcher/NotifyVisitToWebHooks.php +++ b/module/Core/src/EventDispatcher/NotifyVisitToWebHooks.php @@ -22,18 +22,13 @@ use function GuzzleHttp\Promise\settle; class NotifyVisitToWebHooks { - /** @var ClientInterface */ - private $httpClient; - /** @var EntityManagerInterface */ - private $em; - /** @var LoggerInterface */ - private $logger; - /** @var array */ - private $webhooks; - /** @var ShortUrlDataTransformer */ - private $transformer; - /** @var AppOptions */ - private $appOptions; + private ClientInterface $httpClient; + private EntityManagerInterface $em; + private LoggerInterface $logger; + /** @var string[] */ + private array $webhooks; + private ShortUrlDataTransformer $transformer; + private AppOptions $appOptions; public function __construct( ClientInterface $httpClient, @@ -83,7 +78,7 @@ class NotifyVisitToWebHooks 'User-Agent' => (string) $this->appOptions, ], RequestOptions::JSON => [ - 'shortUrl' => $this->transformer->transform($visit->getShortUrl(), false), + 'shortUrl' => $this->transformer->transform($visit->getShortUrl()), 'visit' => $visit->jsonSerialize(), ], ]; @@ -97,7 +92,7 @@ class NotifyVisitToWebHooks return map($this->webhooks, function (string $webhook) use ($requestOptions, $visitId) { $promise = $this->httpClient->requestAsync(RequestMethodInterface::METHOD_POST, $webhook, $requestOptions); return $promise->otherwise( - partial_left(Closure::fromCallable([$this, 'logWebhookFailure']), $webhook, $visitId) + partial_left(Closure::fromCallable([$this, 'logWebhookFailure']), $webhook, $visitId), ); }); } diff --git a/module/Core/src/EventDispatcher/ShortUrlVisited.php b/module/Core/src/EventDispatcher/ShortUrlVisited.php index d12cdaf7..1f0b5b5c 100644 --- a/module/Core/src/EventDispatcher/ShortUrlVisited.php +++ b/module/Core/src/EventDispatcher/ShortUrlVisited.php @@ -8,8 +8,7 @@ use JsonSerializable; final class ShortUrlVisited implements JsonSerializable { - /** @var string */ - private $visitId; + private string $visitId; public function __construct(string $visitId) { diff --git a/module/Core/src/EventDispatcher/VisitLocated.php b/module/Core/src/EventDispatcher/VisitLocated.php index 4873ffa7..0e1c1176 100644 --- a/module/Core/src/EventDispatcher/VisitLocated.php +++ b/module/Core/src/EventDispatcher/VisitLocated.php @@ -8,8 +8,7 @@ use JsonSerializable; final class VisitLocated implements JsonSerializable { - /** @var string */ - private $visitId; + private string $visitId; public function __construct(string $visitId) { diff --git a/module/Core/src/Exception/DeleteShortUrlException.php b/module/Core/src/Exception/DeleteShortUrlException.php index ddd77418..f98b7e14 100644 --- a/module/Core/src/Exception/DeleteShortUrlException.php +++ b/module/Core/src/Exception/DeleteShortUrlException.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Exception; use Fig\Http\Message\StatusCodeInterface; -use Zend\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait; -use Zend\ProblemDetails\Exception\ProblemDetailsExceptionInterface; +use Mezzio\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait; +use Mezzio\ProblemDetails\Exception\ProblemDetailsExceptionInterface; use function sprintf; @@ -22,7 +22,7 @@ class DeleteShortUrlException extends DomainException implements ProblemDetailsE $e = new self(sprintf( 'Impossible to delete short URL with short code "%s" since it has more than "%s" visits.', $shortCode, - $threshold + $threshold, )); $e->detail = $e->getMessage(); diff --git a/module/Core/src/Exception/InvalidUrlException.php b/module/Core/src/Exception/InvalidUrlException.php index ffec94df..ee4caaf6 100644 --- a/module/Core/src/Exception/InvalidUrlException.php +++ b/module/Core/src/Exception/InvalidUrlException.php @@ -5,9 +5,9 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Exception; use Fig\Http\Message\StatusCodeInterface; +use Mezzio\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait; +use Mezzio\ProblemDetails\Exception\ProblemDetailsExceptionInterface; use Throwable; -use Zend\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait; -use Zend\ProblemDetails\Exception\ProblemDetailsExceptionInterface; use function sprintf; diff --git a/module/Core/src/Exception/IpCannotBeLocatedException.php b/module/Core/src/Exception/IpCannotBeLocatedException.php index 8c0b220f..b1ba731c 100644 --- a/module/Core/src/Exception/IpCannotBeLocatedException.php +++ b/module/Core/src/Exception/IpCannotBeLocatedException.php @@ -8,32 +8,24 @@ use Throwable; class IpCannotBeLocatedException extends RuntimeException { - /** @var bool */ - private $isNonLocatableAddress; - - public function __construct( - bool $isNonLocatableAddress, - string $message, - int $code = 0, - ?Throwable $previous = null - ) { - $this->isNonLocatableAddress = $isNonLocatableAddress; - parent::__construct($message, $code, $previous); - } + private bool $isNonLocatableAddress = true; public static function forEmptyAddress(): self { - return new self(true, 'Ignored visit with no IP address'); + return new self('Ignored visit with no IP address'); } public static function forLocalhost(): self { - return new self(true, 'Ignored localhost address'); + return new self('Ignored localhost address'); } public static function forError(Throwable $e): self { - return new self(false, 'An error occurred while locating IP', $e->getCode(), $e); + $e = new self('An error occurred while locating IP', $e->getCode(), $e); + $e->isNonLocatableAddress = false; + + return $e; } /** diff --git a/module/Core/src/Exception/NonUniqueSlugException.php b/module/Core/src/Exception/NonUniqueSlugException.php index 51beff82..8887f961 100644 --- a/module/Core/src/Exception/NonUniqueSlugException.php +++ b/module/Core/src/Exception/NonUniqueSlugException.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Exception; use Fig\Http\Message\StatusCodeInterface; -use Zend\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait; -use Zend\ProblemDetails\Exception\ProblemDetailsExceptionInterface; +use Mezzio\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait; +use Mezzio\ProblemDetails\Exception\ProblemDetailsExceptionInterface; use function sprintf; diff --git a/module/Core/src/Exception/ShortUrlNotFoundException.php b/module/Core/src/Exception/ShortUrlNotFoundException.php index aca1bce9..e68e55ed 100644 --- a/module/Core/src/Exception/ShortUrlNotFoundException.php +++ b/module/Core/src/Exception/ShortUrlNotFoundException.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Exception; use Fig\Http\Message\StatusCodeInterface; -use Zend\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait; -use Zend\ProblemDetails\Exception\ProblemDetailsExceptionInterface; +use Mezzio\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait; +use Mezzio\ProblemDetails\Exception\ProblemDetailsExceptionInterface; use function sprintf; diff --git a/module/Core/src/Exception/TagConflictException.php b/module/Core/src/Exception/TagConflictException.php index 5a624f8f..7362f76b 100644 --- a/module/Core/src/Exception/TagConflictException.php +++ b/module/Core/src/Exception/TagConflictException.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Exception; use Fig\Http\Message\StatusCodeInterface; -use Zend\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait; -use Zend\ProblemDetails\Exception\ProblemDetailsExceptionInterface; +use Mezzio\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait; +use Mezzio\ProblemDetails\Exception\ProblemDetailsExceptionInterface; use function sprintf; diff --git a/module/Core/src/Exception/TagNotFoundException.php b/module/Core/src/Exception/TagNotFoundException.php index 1924e89a..18c1554c 100644 --- a/module/Core/src/Exception/TagNotFoundException.php +++ b/module/Core/src/Exception/TagNotFoundException.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Exception; use Fig\Http\Message\StatusCodeInterface; -use Zend\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait; -use Zend\ProblemDetails\Exception\ProblemDetailsExceptionInterface; +use Mezzio\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait; +use Mezzio\ProblemDetails\Exception\ProblemDetailsExceptionInterface; use function sprintf; diff --git a/module/Core/src/Exception/ValidationException.php b/module/Core/src/Exception/ValidationException.php index abceec91..fc090279 100644 --- a/module/Core/src/Exception/ValidationException.php +++ b/module/Core/src/Exception/ValidationException.php @@ -5,10 +5,10 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Exception; use Fig\Http\Message\StatusCodeInterface; +use Laminas\InputFilter\InputFilterInterface; +use Mezzio\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait; +use Mezzio\ProblemDetails\Exception\ProblemDetailsExceptionInterface; use Throwable; -use Zend\InputFilter\InputFilterInterface; -use Zend\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait; -use Zend\ProblemDetails\Exception\ProblemDetailsExceptionInterface; use function array_keys; use function Functional\reduce_left; @@ -25,8 +25,7 @@ class ValidationException extends InvalidArgumentException implements ProblemDet private const TITLE = 'Invalid data'; private const TYPE = 'INVALID_ARGUMENT'; - /** @var array */ - private $invalidElements; + private array $invalidElements; public static function fromInputFilter(InputFilterInterface $inputFilter, ?Throwable $prev = null): self { @@ -64,18 +63,16 @@ class ValidationException extends InvalidArgumentException implements ProblemDet $this->invalidElementsToString(), PHP_EOL, PHP_EOL, - $this->getTraceAsString() + $this->getTraceAsString(), ); } private function invalidElementsToString(): string { - return reduce_left($this->getInvalidElements(), function ($messageSet, string $name, $_, string $acc) { - return $acc . sprintf( - "\n '%s' => %s", - $name, - is_array($messageSet) ? print_r($messageSet, true) : $messageSet - ); - }, ''); + return reduce_left($this->getInvalidElements(), fn ($messages, string $name, $_, string $acc) => $acc . sprintf( + "\n '%s' => %s", + $name, + is_array($messages) ? print_r($messages, true) : $messages, + ), ''); } } diff --git a/module/Core/src/Middleware/QrCodeCacheMiddleware.php b/module/Core/src/Middleware/QrCodeCacheMiddleware.php index 2036d687..79101ae5 100644 --- a/module/Core/src/Middleware/QrCodeCacheMiddleware.php +++ b/module/Core/src/Middleware/QrCodeCacheMiddleware.php @@ -5,16 +5,15 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Middleware; use Doctrine\Common\Cache\Cache; +use Laminas\Diactoros\Response as DiactResp; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Zend\Diactoros\Response as DiactResp; class QrCodeCacheMiddleware implements MiddlewareInterface { - /** @var Cache */ - private $cache; + private Cache $cache; public function __construct(Cache $cache) { @@ -25,10 +24,7 @@ class QrCodeCacheMiddleware implements MiddlewareInterface * Process an incoming server request and return a response, optionally delegating * to the next middleware component to create the response. * - * @param Request $request - * @param RequestHandlerInterface $handler * - * @return Response */ public function process(Request $request, RequestHandlerInterface $handler): Response { diff --git a/module/Core/src/Model/CreateShortUrlData.php b/module/Core/src/Model/CreateShortUrlData.php index 125f379a..24ed90a6 100644 --- a/module/Core/src/Model/CreateShortUrlData.php +++ b/module/Core/src/Model/CreateShortUrlData.php @@ -8,12 +8,9 @@ use Psr\Http\Message\UriInterface; final class CreateShortUrlData { - /** @var UriInterface */ - private $longUrl; - /** @var array */ - private $tags; - /** @var ShortUrlMeta */ - private $meta; + private UriInterface $longUrl; + private array $tags; + private ShortUrlMeta $meta; public function __construct( UriInterface $longUrl, @@ -26,7 +23,6 @@ final class CreateShortUrlData } /** - * @return UriInterface */ public function getLongUrl(): UriInterface { @@ -42,7 +38,6 @@ final class CreateShortUrlData } /** - * @return ShortUrlMeta */ public function getMeta(): ShortUrlMeta { diff --git a/module/Core/src/Model/ShortUrlMeta.php b/module/Core/src/Model/ShortUrlMeta.php index 24e770c6..f9491d02 100644 --- a/module/Core/src/Model/ShortUrlMeta.php +++ b/module/Core/src/Model/ShortUrlMeta.php @@ -11,18 +11,12 @@ use Shlinkio\Shlink\Core\Validation\ShortUrlMetaInputFilter; final class ShortUrlMeta { - /** @var Chronos|null */ - private $validSince; - /** @var Chronos|null */ - private $validUntil; - /** @var string|null */ - private $customSlug; - /** @var int|null */ - private $maxVisits; - /** @var bool|null */ - private $findIfExists; - /** @var string|null */ - private $domain; + private ?Chronos $validSince = null; + private ?Chronos $validUntil = null; + private ?string $customSlug = null; + private ?int $maxVisits = null; + private ?bool $findIfExists = null; + private ?string $domain = null; // Force named constructors private function __construct() @@ -54,7 +48,7 @@ final class ShortUrlMeta * @param string|null $domain * @throws ValidationException */ - public static function createFromParams( + public static function createFromParams( // phpcs:ignore $validSince = null, $validUntil = null, $customSlug = null, @@ -90,8 +84,8 @@ final class ShortUrlMeta $this->validSince = $this->parseDateField($inputFilter->getValue(ShortUrlMetaInputFilter::VALID_SINCE)); $this->validUntil = $this->parseDateField($inputFilter->getValue(ShortUrlMetaInputFilter::VALID_UNTIL)); $this->customSlug = $inputFilter->getValue(ShortUrlMetaInputFilter::CUSTOM_SLUG); - $this->maxVisits = $inputFilter->getValue(ShortUrlMetaInputFilter::MAX_VISITS); - $this->maxVisits = $this->maxVisits !== null ? (int) $this->maxVisits : null; + $maxVisits = $inputFilter->getValue(ShortUrlMetaInputFilter::MAX_VISITS); + $this->maxVisits = $maxVisits !== null ? (int) $maxVisits : null; $this->findIfExists = $inputFilter->getValue(ShortUrlMetaInputFilter::FIND_IF_EXISTS); $this->domain = $inputFilter->getValue(ShortUrlMetaInputFilter::DOMAIN); } diff --git a/module/Core/src/Model/Visitor.php b/module/Core/src/Model/Visitor.php index 584e8956..8c24ab26 100644 --- a/module/Core/src/Model/Visitor.php +++ b/module/Core/src/Model/Visitor.php @@ -15,12 +15,9 @@ final class Visitor public const REFERER_MAX_LENGTH = 1024; public const REMOTE_ADDRESS_MAX_LENGTH = 256; - /** @var string */ - private $userAgent; - /** @var string */ - private $referer; - /** @var string|null */ - private $remoteAddress; + private string $userAgent; + private string $referer; + private ?string $remoteAddress; public function __construct(string $userAgent, string $referer, ?string $remoteAddress) { @@ -39,7 +36,7 @@ final class Visitor return new self( $request->getHeaderLine('User-Agent'), $request->getHeaderLine('Referer'), - $request->getAttribute(IpAddressMiddlewareFactory::REQUEST_ATTR) + $request->getAttribute(IpAddressMiddlewareFactory::REQUEST_ATTR), ); } diff --git a/module/Core/src/Model/VisitsParams.php b/module/Core/src/Model/VisitsParams.php index f5af1c67..98fcbe82 100644 --- a/module/Core/src/Model/VisitsParams.php +++ b/module/Core/src/Model/VisitsParams.php @@ -12,12 +12,9 @@ final class VisitsParams private const FIRST_PAGE = 1; private const ALL_ITEMS = -1; - /** @var null|DateRange */ - private $dateRange; - /** @var int */ - private $page; - /** @var int */ - private $itemsPerPage; + private ?DateRange $dateRange; + private int $page; + private int $itemsPerPage; public function __construct(?DateRange $dateRange = null, int $page = self::FIRST_PAGE, ?int $itemsPerPage = null) { @@ -43,7 +40,7 @@ final class VisitsParams return new self( new DateRange($startDate, $endDate), (int) ($query['page'] ?? 1), - isset($query['itemsPerPage']) ? (int) $query['itemsPerPage'] : null + isset($query['itemsPerPage']) ? (int) $query['itemsPerPage'] : null, ); } diff --git a/module/Core/src/Options/AppOptions.php b/module/Core/src/Options/AppOptions.php index a26998d4..aa51b871 100644 --- a/module/Core/src/Options/AppOptions.php +++ b/module/Core/src/Options/AppOptions.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Options; +use Laminas\Stdlib\AbstractOptions; use Shlinkio\Shlink\Common\Util\StringUtilsTrait; -use Zend\Stdlib\AbstractOptions; use function sprintf; @@ -13,17 +13,9 @@ class AppOptions extends AbstractOptions { use StringUtilsTrait; - /** @var string */ - private $name = ''; - /** @var string */ - private $version = '1.0'; - /** - * @var string - * @deprecated - */ - private $secretKey = ''; - /** @var string|null */ - private $disableTrackParam; + private string $name = ''; + private string $version = '1.0'; + private ?string $disableTrackParam = null; public function getName(): string { @@ -48,24 +40,6 @@ class AppOptions extends AbstractOptions } /** - * @deprecated - */ - public function getSecretKey(): string - { - return $this->secretKey; - } - - /** - * @deprecated - */ - protected function setSecretKey(string $secretKey): self - { - $this->secretKey = $secretKey; - return $this; - } - - /** - * @return string|null */ public function getDisableTrackParam(): ?string { diff --git a/module/Core/src/Options/DeleteShortUrlsOptions.php b/module/Core/src/Options/DeleteShortUrlsOptions.php index e2923e19..ee7d9dee 100644 --- a/module/Core/src/Options/DeleteShortUrlsOptions.php +++ b/module/Core/src/Options/DeleteShortUrlsOptions.php @@ -4,12 +4,12 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Options; -use Zend\Stdlib\AbstractOptions; +use Laminas\Stdlib\AbstractOptions; class DeleteShortUrlsOptions extends AbstractOptions { - private $visitsThreshold = 15; - private $checkVisitsThreshold = true; + private int $visitsThreshold = 15; + private bool $checkVisitsThreshold = true; public function getVisitsThreshold(): int { diff --git a/module/Core/src/Options/NotFoundRedirectOptions.php b/module/Core/src/Options/NotFoundRedirectOptions.php index 0165f8fc..1bb3b828 100644 --- a/module/Core/src/Options/NotFoundRedirectOptions.php +++ b/module/Core/src/Options/NotFoundRedirectOptions.php @@ -4,16 +4,13 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Options; -use Zend\Stdlib\AbstractOptions; +use Laminas\Stdlib\AbstractOptions; class NotFoundRedirectOptions extends AbstractOptions { - /** @var string|null */ - private $invalidShortUrl; - /** @var string|null */ - private $regular404; - /** @var string|null */ - private $baseUrl; + private ?string $invalidShortUrl = null; + private ?string $regular404 = null; + private ?string $baseUrl = null; public function getInvalidShortUrlRedirect(): ?string { diff --git a/module/Core/src/Options/UrlShortenerOptions.php b/module/Core/src/Options/UrlShortenerOptions.php index 6816ba6e..19267ea1 100644 --- a/module/Core/src/Options/UrlShortenerOptions.php +++ b/module/Core/src/Options/UrlShortenerOptions.php @@ -4,24 +4,22 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Options; -use Zend\Stdlib\AbstractOptions; +use Laminas\Stdlib\AbstractOptions; class UrlShortenerOptions extends AbstractOptions { - // phpcs:disable - protected $__strictMode__ = false; - // phpcs:enable + protected $__strictMode__ = false; // phpcs:ignore - private $validateUrl = true; + private bool $validateUrl = true; public function isUrlValidationEnabled(): bool { return $this->validateUrl; } - protected function setValidateUrl($validateUrl): self + protected function setValidateUrl(bool $validateUrl): self { - $this->validateUrl = (bool) $validateUrl; + $this->validateUrl = $validateUrl; return $this; } } diff --git a/module/Core/src/Paginator/Adapter/ShortUrlRepositoryAdapter.php b/module/Core/src/Paginator/Adapter/ShortUrlRepositoryAdapter.php index de382d17..d15d925c 100644 --- a/module/Core/src/Paginator/Adapter/ShortUrlRepositoryAdapter.php +++ b/module/Core/src/Paginator/Adapter/ShortUrlRepositoryAdapter.php @@ -4,9 +4,9 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Paginator\Adapter; +use Laminas\Paginator\Adapter\AdapterInterface; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; -use Zend\Paginator\Adapter\AdapterInterface; use function strip_tags; use function trim; @@ -15,20 +15,19 @@ class ShortUrlRepositoryAdapter implements AdapterInterface { public const ITEMS_PER_PAGE = 10; - /** @var ShortUrlRepositoryInterface */ - private $repository; - /** @var null|string */ - private $searchTerm; + private ShortUrlRepositoryInterface $repository; + private ?string $searchTerm; /** @var null|array|string */ private $orderBy; - /** @var array */ - private $tags; - /** @var DateRange|null */ - private $dateRange; + private array $tags; + private ?DateRange $dateRange; + /** + * @param string|array|null $orderBy + */ public function __construct( ShortUrlRepositoryInterface $repository, - $searchTerm = null, + ?string $searchTerm = null, array $tags = [], $orderBy = null, ?DateRange $dateRange = null @@ -45,9 +44,8 @@ class ShortUrlRepositoryAdapter implements AdapterInterface * * @param int $offset Page offset * @param int $itemCountPerPage Number of items per page - * @return array */ - public function getItems($offset, $itemCountPerPage): array + public function getItems($offset, $itemCountPerPage): array // phpcs:ignore { return $this->repository->findList( $itemCountPerPage, @@ -55,7 +53,7 @@ class ShortUrlRepositoryAdapter implements AdapterInterface $this->searchTerm, $this->tags, $this->orderBy, - $this->dateRange + $this->dateRange, ); } diff --git a/module/Core/src/Paginator/Adapter/VisitsPaginatorAdapter.php b/module/Core/src/Paginator/Adapter/VisitsPaginatorAdapter.php index cffa2f3d..19baff73 100644 --- a/module/Core/src/Paginator/Adapter/VisitsPaginatorAdapter.php +++ b/module/Core/src/Paginator/Adapter/VisitsPaginatorAdapter.php @@ -4,18 +4,15 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Paginator\Adapter; +use Laminas\Paginator\Adapter\AdapterInterface; use Shlinkio\Shlink\Core\Model\VisitsParams; use Shlinkio\Shlink\Core\Repository\VisitRepositoryInterface; -use Zend\Paginator\Adapter\AdapterInterface; class VisitsPaginatorAdapter implements AdapterInterface { - /** @var VisitRepositoryInterface */ - private $visitRepository; - /** @var string */ - private $shortCode; - /** @var VisitsParams */ - private $params; + private VisitRepositoryInterface $visitRepository; + private string $shortCode; + private VisitsParams $params; public function __construct(VisitRepositoryInterface $visitRepository, string $shortCode, VisitsParams $params) { @@ -24,13 +21,13 @@ class VisitsPaginatorAdapter implements AdapterInterface $this->params = $params; } - public function getItems($offset, $itemCountPerPage): array + public function getItems($offset, $itemCountPerPage): array // phpcs:ignore { return $this->visitRepository->findVisitsByShortCode( $this->shortCode, $this->params->getDateRange(), $itemCountPerPage, - $offset + $offset, ); } diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index ac7b5f50..dafc841f 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -52,6 +52,9 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI return $qb->getQuery()->getResult(); } + /** + * @param string|array|null $orderBy + */ private function processOrderByForList(QueryBuilder $qb, $orderBy): array { $isArray = is_array($orderBy); @@ -117,7 +120,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI $qb->andWhere($qb->expr()->orX( $qb->expr()->like('s.longUrl', ':searchPattern'), $qb->expr()->like('s.shortCode', ':searchPattern'), - $qb->expr()->like('t.name', ':searchPattern') + $qb->expr()->like('t.name', ':searchPattern'), )); $qb->setParameter('searchPattern', '%' . $searchTerm . '%'); } diff --git a/module/Core/src/Service/ShortUrl/DeleteShortUrlService.php b/module/Core/src/Service/ShortUrl/DeleteShortUrlService.php index c7cfb9c4..c9624bf3 100644 --- a/module/Core/src/Service/ShortUrl/DeleteShortUrlService.php +++ b/module/Core/src/Service/ShortUrl/DeleteShortUrlService.php @@ -13,10 +13,8 @@ class DeleteShortUrlService implements DeleteShortUrlServiceInterface { use FindShortCodeTrait; - /** @var EntityManagerInterface */ - private $em; - /** @var DeleteShortUrlsOptions */ - private $deleteShortUrlsOptions; + private EntityManagerInterface $em; + private DeleteShortUrlsOptions $deleteShortUrlsOptions; public function __construct(EntityManagerInterface $em, DeleteShortUrlsOptions $deleteShortUrlsOptions) { @@ -34,7 +32,7 @@ class DeleteShortUrlService implements DeleteShortUrlServiceInterface if (! $ignoreThreshold && $this->isThresholdReached($shortUrl)) { throw Exception\DeleteShortUrlException::fromVisitsThreshold( $this->deleteShortUrlsOptions->getVisitsThreshold(), - $shortUrl->getShortCode() + $shortUrl->getShortCode(), ); } diff --git a/module/Core/src/Service/ShortUrl/FindShortCodeTrait.php b/module/Core/src/Service/ShortUrl/FindShortCodeTrait.php index 81cc0e84..95009704 100644 --- a/module/Core/src/Service/ShortUrl/FindShortCodeTrait.php +++ b/module/Core/src/Service/ShortUrl/FindShortCodeTrait.php @@ -11,8 +11,6 @@ use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; trait FindShortCodeTrait { /** - * @param string $shortCode - * @return ShortUrl * @throws ShortUrlNotFoundException */ private function findByShortCode(EntityManagerInterface $em, string $shortCode): ShortUrl diff --git a/module/Core/src/Service/ShortUrlService.php b/module/Core/src/Service/ShortUrlService.php index 15a3b432..8733cddb 100644 --- a/module/Core/src/Service/ShortUrlService.php +++ b/module/Core/src/Service/ShortUrlService.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Service; use Doctrine\ORM; +use Laminas\Paginator\Paginator; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; @@ -13,15 +14,13 @@ use Shlinkio\Shlink\Core\Paginator\Adapter\ShortUrlRepositoryAdapter; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Service\ShortUrl\FindShortCodeTrait; use Shlinkio\Shlink\Core\Util\TagManagerTrait; -use Zend\Paginator\Paginator; class ShortUrlService implements ShortUrlServiceInterface { use FindShortCodeTrait; use TagManagerTrait; - /** @var ORM\EntityManagerInterface */ - private $em; + private ORM\EntityManagerInterface $em; public function __construct(ORM\EntityManagerInterface $em) { diff --git a/module/Core/src/Service/ShortUrlServiceInterface.php b/module/Core/src/Service/ShortUrlServiceInterface.php index 6e3fe199..310ba6b3 100644 --- a/module/Core/src/Service/ShortUrlServiceInterface.php +++ b/module/Core/src/Service/ShortUrlServiceInterface.php @@ -4,11 +4,11 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Service; +use Laminas\Paginator\Paginator; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; -use Zend\Paginator\Paginator; interface ShortUrlServiceInterface { diff --git a/module/Core/src/Service/Tag/TagService.php b/module/Core/src/Service/Tag/TagService.php index 672f0b05..b95ddf82 100644 --- a/module/Core/src/Service/Tag/TagService.php +++ b/module/Core/src/Service/Tag/TagService.php @@ -16,8 +16,7 @@ class TagService implements TagServiceInterface { use TagManagerTrait; - /** @var ORM\EntityManagerInterface */ - private $em; + private ORM\EntityManagerInterface $em; public function __construct(ORM\EntityManagerInterface $em) { diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index 70112bd7..be61d15b 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -24,12 +24,9 @@ class UrlShortener implements UrlShortenerInterface { use TagManagerTrait; - /** @var EntityManagerInterface */ - private $em; - /** @var UrlShortenerOptions */ - private $options; - /** @var UrlValidatorInterface */ - private $urlValidator; + private EntityManagerInterface $em; + private UrlShortenerOptions $options; + private UrlValidatorInterface $urlValidator; public function __construct( UrlValidatorInterface $urlValidator, diff --git a/module/Core/src/Service/VisitService.php b/module/Core/src/Service/VisitService.php index 582a10fe..20a30f4c 100644 --- a/module/Core/src/Service/VisitService.php +++ b/module/Core/src/Service/VisitService.php @@ -13,8 +13,7 @@ use Shlinkio\Shlink\IpGeolocation\Model\Location; class VisitService implements VisitServiceInterface { - /** @var EntityManagerInterface */ - private $em; + private EntityManagerInterface $em; public function __construct(EntityManagerInterface $em) { diff --git a/module/Core/src/Service/VisitsTracker.php b/module/Core/src/Service/VisitsTracker.php index 612ad4ee..91c83a03 100644 --- a/module/Core/src/Service/VisitsTracker.php +++ b/module/Core/src/Service/VisitsTracker.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Service; use Doctrine\ORM; +use Laminas\Paginator\Paginator; use Psr\EventDispatcher\EventDispatcherInterface; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\Visit; @@ -14,14 +15,11 @@ use Shlinkio\Shlink\Core\Model\Visitor; use Shlinkio\Shlink\Core\Model\VisitsParams; use Shlinkio\Shlink\Core\Paginator\Adapter\VisitsPaginatorAdapter; use Shlinkio\Shlink\Core\Repository\VisitRepository; -use Zend\Paginator\Paginator; class VisitsTracker implements VisitsTrackerInterface { - /** @var ORM\EntityManagerInterface */ - private $em; - /** @var EventDispatcherInterface */ - private $eventDispatcher; + private ORM\EntityManagerInterface $em; + private EventDispatcherInterface $eventDispatcher; public function __construct(ORM\EntityManagerInterface $em, EventDispatcherInterface $eventDispatcher) { diff --git a/module/Core/src/Service/VisitsTrackerInterface.php b/module/Core/src/Service/VisitsTrackerInterface.php index 2786d23b..5eddd96d 100644 --- a/module/Core/src/Service/VisitsTrackerInterface.php +++ b/module/Core/src/Service/VisitsTrackerInterface.php @@ -4,11 +4,11 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Service; +use Laminas\Paginator\Paginator; use Shlinkio\Shlink\Core\Entity\Visit; use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; use Shlinkio\Shlink\Core\Model\Visitor; use Shlinkio\Shlink\Core\Model\VisitsParams; -use Zend\Paginator\Paginator; interface VisitsTrackerInterface { diff --git a/module/Core/src/Transformer/ShortUrlDataTransformer.php b/module/Core/src/Transformer/ShortUrlDataTransformer.php index 348ff0d5..532fa122 100644 --- a/module/Core/src/Transformer/ShortUrlDataTransformer.php +++ b/module/Core/src/Transformer/ShortUrlDataTransformer.php @@ -12,8 +12,7 @@ use function Functional\invoke_if; class ShortUrlDataTransformer implements DataTransformerInterface { - /** @var array */ - private $domainConfig; + private array $domainConfig; public function __construct(array $domainConfig) { @@ -23,11 +22,11 @@ class ShortUrlDataTransformer implements DataTransformerInterface /** * @param ShortUrl $shortUrl */ - public function transform($shortUrl, bool $includeDeprecated = true): array + public function transform($shortUrl): array // phpcs:ignore { $longUrl = $shortUrl->getLongUrl(); - $rawData = [ + return [ 'shortCode' => $shortUrl->getShortCode(), 'shortUrl' => $shortUrl->toString($this->domainConfig), 'longUrl' => $longUrl, @@ -36,12 +35,6 @@ class ShortUrlDataTransformer implements DataTransformerInterface 'tags' => invoke($shortUrl->getTags(), '__toString'), 'meta' => $this->buildMeta($shortUrl), ]; - - if ($includeDeprecated) { - $rawData['originalUrl'] = $longUrl; - } - - return $rawData; } private function buildMeta(ShortUrl $shortUrl): array diff --git a/module/Core/src/Util/TagManagerTrait.php b/module/Core/src/Util/TagManagerTrait.php index f227afef..27fb22b5 100644 --- a/module/Core/src/Util/TagManagerTrait.php +++ b/module/Core/src/Util/TagManagerTrait.php @@ -16,7 +16,6 @@ use function trim; trait TagManagerTrait { /** - * @param EntityManagerInterface $em * @param string[] $tags * @return Collections\Collection|Tag[] */ diff --git a/module/Core/src/Util/UrlValidator.php b/module/Core/src/Util/UrlValidator.php index db7c6c2a..dca037cd 100644 --- a/module/Core/src/Util/UrlValidator.php +++ b/module/Core/src/Util/UrlValidator.php @@ -14,8 +14,7 @@ class UrlValidator implements UrlValidatorInterface, RequestMethodInterface { private const MAX_REDIRECTS = 15; - /** @var ClientInterface */ - private $httpClient; + private ClientInterface $httpClient; public function __construct(ClientInterface $httpClient) { diff --git a/module/Core/src/Validation/ShortUrlMetaInputFilter.php b/module/Core/src/Validation/ShortUrlMetaInputFilter.php index 068cf831..eca97a13 100644 --- a/module/Core/src/Validation/ShortUrlMetaInputFilter.php +++ b/module/Core/src/Validation/ShortUrlMetaInputFilter.php @@ -5,9 +5,9 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Validation; use DateTime; +use Laminas\InputFilter\InputFilter; +use Laminas\Validator; use Shlinkio\Shlink\Common\Validation; -use Zend\InputFilter\InputFilter; -use Zend\Validator; class ShortUrlMetaInputFilter extends InputFilter { diff --git a/module/Core/src/Visit/Model/UnknownVisitLocation.php b/module/Core/src/Visit/Model/UnknownVisitLocation.php index baf056b4..b8926bd5 100644 --- a/module/Core/src/Visit/Model/UnknownVisitLocation.php +++ b/module/Core/src/Visit/Model/UnknownVisitLocation.php @@ -11,14 +11,14 @@ final class UnknownVisitLocation implements VisitLocationInterface return 'Unknown'; } - public function getLatitude(): string + public function getLatitude(): float { - return '0.0'; + return 0.0; } - public function getLongitude(): string + public function getLongitude(): float { - return '0.0'; + return 0.0; } public function getCityName(): string @@ -33,8 +33,8 @@ final class UnknownVisitLocation implements VisitLocationInterface 'countryName' => 'Unknown', 'regionName' => 'Unknown', 'cityName' => 'Unknown', - 'latitude' => '0.0', - 'longitude' => '0.0', + 'latitude' => 0.0, + 'longitude' => 0.0, 'timezone' => 'Unknown', ]; } diff --git a/module/Core/src/Visit/Model/VisitLocationInterface.php b/module/Core/src/Visit/Model/VisitLocationInterface.php index 992e138f..9a296a28 100644 --- a/module/Core/src/Visit/Model/VisitLocationInterface.php +++ b/module/Core/src/Visit/Model/VisitLocationInterface.php @@ -10,9 +10,9 @@ interface VisitLocationInterface extends JsonSerializable { public function getCountryName(): string; - public function getLatitude(): string; + public function getLatitude(): float; - public function getLongitude(): string; + public function getLongitude(): float; public function getCityName(): string; } diff --git a/module/Core/test-db/Repository/ShortUrlRepositoryTest.php b/module/Core/test-db/Repository/ShortUrlRepositoryTest.php index 2006623a..81f32bd8 100644 --- a/module/Core/test-db/Repository/ShortUrlRepositoryTest.php +++ b/module/Core/test-db/Repository/ShortUrlRepositoryTest.php @@ -28,8 +28,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase Domain::class, ]; - /** @var ShortUrlRepository */ - private $repo; + private ShortUrlRepository $repo; public function setUp(): void { @@ -44,7 +43,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $notYetValid = new ShortUrl( 'bar', - ShortUrlMeta::createFromParams(Chronos::now()->addMonth(), null, 'bar_very_long_text') + ShortUrlMeta::createFromParams(Chronos::now()->addMonth(), null, 'bar_very_long_text'), ); $this->getEntityManager()->persist($notYetValid); @@ -83,11 +82,11 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $this->assertSame($withDomain, $this->repo->findOneByShortCode($withDomain->getShortCode(), 'example.com')); $this->assertSame( $withDomainDuplicatingRegular, - $this->repo->findOneByShortCode($withDomainDuplicatingRegular->getShortCode(), 'doma.in') + $this->repo->findOneByShortCode($withDomainDuplicatingRegular->getShortCode(), 'doma.in'), ); $this->assertSame( $regularOne, - $this->repo->findOneByShortCode($withDomainDuplicatingRegular->getShortCode(), 'other-domain.com') + $this->repo->findOneByShortCode($withDomainDuplicatingRegular->getShortCode(), 'other-domain.com'), ); $this->assertNull($this->repo->findOneByShortCode('invalid')); $this->assertNull($this->repo->findOneByShortCode($withDomain->getShortCode())); @@ -161,7 +160,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $this->assertCount( 2, - $this->repo->findList(null, null, null, [], null, new DateRange(Chronos::now()->subDays(2))) + $this->repo->findList(null, null, null, [], null, new DateRange(Chronos::now()->subDays(2))), ); $this->assertEquals(2, $this->repo->countList(null, [], new DateRange(Chronos::now()->subDays(2)))); } @@ -193,7 +192,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $shortUrlWithDomain = new ShortUrl( 'foo', - ShortUrlMeta::createFromRawData(['domain' => 'doma.in', 'customSlug' => 'another-slug']) + ShortUrlMeta::createFromRawData(['domain' => 'doma.in', 'customSlug' => 'another-slug']), ); $this->getEntityManager()->persist($shortUrlWithDomain); diff --git a/module/Core/test-db/Repository/TagRepositoryTest.php b/module/Core/test-db/Repository/TagRepositoryTest.php index 6e4ae404..94e38f53 100644 --- a/module/Core/test-db/Repository/TagRepositoryTest.php +++ b/module/Core/test-db/Repository/TagRepositoryTest.php @@ -14,8 +14,7 @@ class TagRepositoryTest extends DatabaseTestCase Tag::class, ]; - /** @var TagRepository */ - private $repo; + private TagRepository $repo; protected function setUp(): void { @@ -23,13 +22,13 @@ class TagRepositoryTest extends DatabaseTestCase } /** @test */ - public function deleteByNameDoesNothingWhenEmptyListIsProvided() + public function deleteByNameDoesNothingWhenEmptyListIsProvided(): void { $this->assertEquals(0, $this->repo->deleteByName([])); } /** @test */ - public function allTagsWhichMatchNameAreDeleted() + public function allTagsWhichMatchNameAreDeleted(): void { $names = ['foo', 'bar', 'baz']; $toDelete = ['foo', 'baz']; diff --git a/module/Core/test-db/Repository/VisitRepositoryTest.php b/module/Core/test-db/Repository/VisitRepositoryTest.php index 23fa4c7a..80207d5a 100644 --- a/module/Core/test-db/Repository/VisitRepositoryTest.php +++ b/module/Core/test-db/Repository/VisitRepositoryTest.php @@ -26,8 +26,7 @@ class VisitRepositoryTest extends DatabaseTestCase ShortUrl::class, ]; - /** @var VisitRepository */ - private $repo; + private VisitRepository $repo; protected function setUp(): void { @@ -67,9 +66,7 @@ class VisitRepositoryTest extends DatabaseTestCase public function provideBlockSize(): iterable { - return map(range(1, 5), function (int $value) { - return [$value]; - }); + return map(range(1, 5), fn (int $value) => [$value]); } /** @test */ @@ -88,10 +85,10 @@ class VisitRepositoryTest extends DatabaseTestCase $this->assertCount(6, $this->repo->findVisitsByShortCode($shortUrl->getShortCode())); $this->assertCount(2, $this->repo->findVisitsByShortCode($shortUrl->getShortCode(), new DateRange( Chronos::parse('2016-01-02'), - Chronos::parse('2016-01-03') + Chronos::parse('2016-01-03'), ))); $this->assertCount(4, $this->repo->findVisitsByShortCode($shortUrl->getShortCode(), new DateRange( - Chronos::parse('2016-01-03') + Chronos::parse('2016-01-03'), ))); $this->assertCount(3, $this->repo->findVisitsByShortCode($shortUrl->getShortCode(), null, 3, 2)); $this->assertCount(2, $this->repo->findVisitsByShortCode($shortUrl->getShortCode(), null, 5, 4)); @@ -113,10 +110,10 @@ class VisitRepositoryTest extends DatabaseTestCase $this->assertEquals(6, $this->repo->countVisitsByShortCode($shortUrl->getShortCode())); $this->assertEquals(2, $this->repo->countVisitsByShortCode($shortUrl->getShortCode(), new DateRange( Chronos::parse('2016-01-02'), - Chronos::parse('2016-01-03') + Chronos::parse('2016-01-03'), ))); $this->assertEquals(4, $this->repo->countVisitsByShortCode($shortUrl->getShortCode(), new DateRange( - Chronos::parse('2016-01-03') + Chronos::parse('2016-01-03'), ))); } } diff --git a/module/Core/test/Action/PixelActionTest.php b/module/Core/test/Action/PixelActionTest.php index df39f1df..fc9b85a1 100644 --- a/module/Core/test/Action/PixelActionTest.php +++ b/module/Core/test/Action/PixelActionTest.php @@ -4,27 +4,23 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Action; +use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Server\RequestHandlerInterface; use Shlinkio\Shlink\Common\Response\PixelResponse; use Shlinkio\Shlink\Core\Action\PixelAction; -use Shlinkio\Shlink\Core\Action\RedirectAction; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Options\AppOptions; use Shlinkio\Shlink\Core\Service\UrlShortener; use Shlinkio\Shlink\Core\Service\VisitsTracker; -use Zend\Diactoros\ServerRequest; class PixelActionTest extends TestCase { - /** @var RedirectAction */ - private $action; - /** @var ObjectProphecy */ - private $urlShortener; - /** @var ObjectProphecy */ - private $visitTracker; + private PixelAction $action; + private ObjectProphecy $urlShortener; + private ObjectProphecy $visitTracker; public function setUp(): void { @@ -34,7 +30,7 @@ class PixelActionTest extends TestCase $this->action = new PixelAction( $this->urlShortener->reveal(), $this->visitTracker->reveal(), - new AppOptions() + new AppOptions(), ); } @@ -43,7 +39,7 @@ class PixelActionTest extends TestCase { $shortCode = 'abc123'; $this->urlShortener->shortCodeToUrl($shortCode, '')->willReturn( - new ShortUrl('http://domain.com/foo/bar') + new ShortUrl('http://domain.com/foo/bar'), )->shouldBeCalledOnce(); $this->visitTracker->track(Argument::cetera())->shouldBeCalledOnce(); diff --git a/module/Core/test/Action/PreviewActionTest.php b/module/Core/test/Action/PreviewActionTest.php deleted file mode 100644 index e2cb089c..00000000 --- a/module/Core/test/Action/PreviewActionTest.php +++ /dev/null @@ -1,75 +0,0 @@ -previewGenerator = $this->prophesize(PreviewGenerator::class); - $this->urlShortener = $this->prophesize(UrlShortener::class); - $this->action = new PreviewAction($this->previewGenerator->reveal(), $this->urlShortener->reveal()); - } - - /** @test */ - public function correctShortCodeReturnsImageResponse(): void - { - $shortCode = 'abc123'; - $url = 'foobar.com'; - $shortUrl = new ShortUrl($url); - $path = __FILE__; - $this->urlShortener->shortCodeToUrl($shortCode)->willReturn($shortUrl)->shouldBeCalledOnce(); - $this->previewGenerator->generatePreview($url)->willReturn($path)->shouldBeCalledOnce(); - - $resp = $this->action->process( - (new ServerRequest())->withAttribute('shortCode', $shortCode), - $this->prophesize(RequestHandlerInterface::class)->reveal() - ); - - $this->assertEquals(filesize($path), $resp->getHeaderLine('Content-length')); - $this->assertEquals((new finfo(FILEINFO_MIME))->file($path), $resp->getHeaderLine('Content-type')); - } - - /** @test */ - public function invalidShortCodeExceptionFallsBackToNextMiddleware(): void - { - $shortCode = 'abc123'; - $this->urlShortener->shortCodeToUrl($shortCode)->willThrow(ShortUrlNotFoundException::class) - ->shouldBeCalledOnce(); - $delegate = $this->prophesize(RequestHandlerInterface::class); - $process = $delegate->handle(Argument::any())->willReturn(new Response()); - - $this->action->process( - (new ServerRequest())->withAttribute('shortCode', $shortCode), - $delegate->reveal() - ); - - $process->shouldHaveBeenCalledOnce(); - } -} diff --git a/module/Core/test/Action/QrCodeActionTest.php b/module/Core/test/Action/QrCodeActionTest.php index 6327ad69..e98f0393 100644 --- a/module/Core/test/Action/QrCodeActionTest.php +++ b/module/Core/test/Action/QrCodeActionTest.php @@ -4,6 +4,9 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Action; +use Laminas\Diactoros\Response; +use Laminas\Diactoros\ServerRequest; +use Mezzio\Router\RouterInterface; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; @@ -13,16 +16,11 @@ use Shlinkio\Shlink\Core\Action\QrCodeAction; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; use Shlinkio\Shlink\Core\Service\UrlShortener; -use Zend\Diactoros\Response; -use Zend\Diactoros\ServerRequest; -use Zend\Expressive\Router\RouterInterface; class QrCodeActionTest extends TestCase { - /** @var QrCodeAction */ - private $action; - /** @var ObjectProphecy */ - private $urlShortener; + private QrCodeAction $action; + private ObjectProphecy $urlShortener; public function setUp(): void { @@ -72,7 +70,7 @@ class QrCodeActionTest extends TestCase $resp = $this->action->process( (new ServerRequest())->withAttribute('shortCode', $shortCode), - $delegate->reveal() + $delegate->reveal(), ); $this->assertInstanceOf(QrCodeResponse::class, $resp); diff --git a/module/Core/test/Action/RedirectActionTest.php b/module/Core/test/Action/RedirectActionTest.php index 55342cb5..04705251 100644 --- a/module/Core/test/Action/RedirectActionTest.php +++ b/module/Core/test/Action/RedirectActionTest.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Action; +use Laminas\Diactoros\Response; +use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; @@ -14,19 +16,14 @@ use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; use Shlinkio\Shlink\Core\Options; use Shlinkio\Shlink\Core\Service\UrlShortener; use Shlinkio\Shlink\Core\Service\VisitsTracker; -use Zend\Diactoros\Response; -use Zend\Diactoros\ServerRequest; use function array_key_exists; class RedirectActionTest extends TestCase { - /** @var RedirectAction */ - private $action; - /** @var ObjectProphecy */ - private $urlShortener; - /** @var ObjectProphecy */ - private $visitTracker; + private RedirectAction $action; + private ObjectProphecy $urlShortener; + private ObjectProphecy $visitTracker; public function setUp(): void { @@ -36,7 +33,7 @@ class RedirectActionTest extends TestCase $this->action = new RedirectAction( $this->urlShortener->reveal(), $this->visitTracker->reveal(), - new Options\AppOptions(['disableTrackParam' => 'foobar']) + new Options\AppOptions(['disableTrackParam' => 'foobar']), ); } @@ -49,7 +46,7 @@ class RedirectActionTest extends TestCase $shortCode = 'abc123'; $shortUrl = new ShortUrl('http://domain.com/foo/bar?some=thing'); $shortCodeToUrl = $this->urlShortener->shortCodeToUrl($shortCode, '')->willReturn($shortUrl); - $track = $this->visitTracker->track(Argument::cetera())->will(function () { + $track = $this->visitTracker->track(Argument::cetera())->will(function (): void { }); $request = (new ServerRequest())->withAttribute('shortCode', $shortCode)->withQueryParams($query); diff --git a/module/Core/test/Config/BasePathPrefixerTest.php b/module/Core/test/Config/BasePathPrefixerTest.php index fe4c7940..0e08fa89 100644 --- a/module/Core/test/Config/BasePathPrefixerTest.php +++ b/module/Core/test/Config/BasePathPrefixerTest.php @@ -9,8 +9,7 @@ use Shlinkio\Shlink\Core\Config\BasePathPrefixer; class BasePathPrefixerTest extends TestCase { - /** @var BasePathPrefixer */ - private $prefixer; + private BasePathPrefixer $prefixer; public function setUp(): void { diff --git a/module/Core/test/Config/DeprecatedConfigParserTest.php b/module/Core/test/Config/DeprecatedConfigParserTest.php index be76ba80..0da1d314 100644 --- a/module/Core/test/Config/DeprecatedConfigParserTest.php +++ b/module/Core/test/Config/DeprecatedConfigParserTest.php @@ -11,8 +11,7 @@ use function array_merge; class DeprecatedConfigParserTest extends TestCase { - /** @var DeprecatedConfigParser */ - private $postProcessor; + private DeprecatedConfigParser $postProcessor; public function setUp(): void { @@ -92,4 +91,21 @@ class DeprecatedConfigParserTest extends TestCase $this->assertEquals($expected, $result); } + + /** @test */ + public function removesTheOldSecretKey(): void + { + $config = [ + 'app_options' => [ + 'secret_key' => 'foobar', + ], + ]; + $expected = [ + 'app_options' => [], + ]; + + $result = ($this->postProcessor)($config); + + $this->assertEquals($expected, $result); + } } diff --git a/module/Core/test/Config/SimplifiedConfigParserTest.php b/module/Core/test/Config/SimplifiedConfigParserTest.php index 76ba9b1b..1d4f3b8d 100644 --- a/module/Core/test/Config/SimplifiedConfigParserTest.php +++ b/module/Core/test/Config/SimplifiedConfigParserTest.php @@ -11,7 +11,7 @@ use function array_merge; class SimplifiedConfigParserTest extends TestCase { - private $postProcessor; + private SimplifiedConfigParser $postProcessor; public function setUp(): void { @@ -38,9 +38,9 @@ class SimplifiedConfigParserTest extends TestCase 'disable_track_param' => 'bar', 'short_domain_schema' => 'https', 'short_domain_host' => 'doma.in', - 'validate_url' => false, + 'validate_url' => true, 'delete_short_url_threshold' => 50, - 'not_found_redirect_to' => 'foobar.com', + 'invalid_short_url_redirect_to' => 'foobar.com', 'redis_servers' => [ 'tcp://1.1.1.1:1111', 'tcp://1.2.2.2:2222', @@ -79,7 +79,7 @@ class SimplifiedConfigParserTest extends TestCase 'schema' => 'https', 'hostname' => 'doma.in', ], - 'validate_url' => false, + 'validate_url' => true, 'visits_webhooks' => [ 'http://my-api.com/api/v2.3/notify', 'https://third-party.io/foo', @@ -97,10 +97,12 @@ class SimplifiedConfigParserTest extends TestCase ], ], - 'redis' => [ - 'servers' => [ - 'tcp://1.1.1.1:1111', - 'tcp://1.2.2.2:2222', + 'cache' => [ + 'redis' => [ + 'servers' => [ + 'tcp://1.1.1.1:1111', + 'tcp://1.2.2.2:2222', + ], ], ], @@ -112,7 +114,7 @@ class SimplifiedConfigParserTest extends TestCase 'invalid_short_url' => 'foobar.com', ], - 'zend-expressive-swoole' => [ + 'mezzio-swoole' => [ 'swoole-http-server' => [ 'options' => [ 'task_worker_num' => 50, @@ -125,28 +127,4 @@ class SimplifiedConfigParserTest extends TestCase $this->assertEquals(array_merge($expected, $simplified), $result); } - - /** - * @test - * @dataProvider provideConfigWithDeprecates - */ - public function properlyMapsDeprecatedConfigs(array $config, string $expected): void - { - $result = ($this->postProcessor)($config); - $this->assertEquals($expected, $result['not_found_redirects']['invalid_short_url']); - } - - public function provideConfigWithDeprecates(): iterable - { - yield 'only deprecated config' => [['not_found_redirect_to' => 'old_value'], 'old_value']; - yield 'only new config' => [['invalid_short_url_redirect_to' => 'new_value'], 'new_value']; - yield 'both configs, new first' => [ - ['invalid_short_url_redirect_to' => 'new_value', 'not_found_redirect_to' => 'old_value'], - 'new_value', - ]; - yield 'both configs, deprecated first' => [ - ['not_found_redirect_to' => 'old_value', 'invalid_short_url_redirect_to' => 'new_value'], - 'new_value', - ]; - } } diff --git a/module/Core/test/ConfigProviderTest.php b/module/Core/test/ConfigProviderTest.php index f38fe6bd..71cd90d1 100644 --- a/module/Core/test/ConfigProviderTest.php +++ b/module/Core/test/ConfigProviderTest.php @@ -9,8 +9,7 @@ use Shlinkio\Shlink\Core\ConfigProvider; class ConfigProviderTest extends TestCase { - /** @var ConfigProvider */ - private $configProvider; + private ConfigProvider $configProvider; public function setUp(): void { @@ -18,13 +17,13 @@ class ConfigProviderTest extends TestCase } /** @test */ - public function properConfigIsReturned() + public function properConfigIsReturned(): void { $config = $this->configProvider->__invoke(); $this->assertArrayHasKey('routes', $config); $this->assertArrayHasKey('dependencies', $config); $this->assertArrayHasKey('templates', $config); - $this->assertArrayHasKey('zend-expressive', $config); + $this->assertArrayHasKey('mezzio', $config); } } diff --git a/module/Core/test/Domain/Resolver/PersistenceDomainResolverTest.php b/module/Core/test/Domain/Resolver/PersistenceDomainResolverTest.php index 4ba796ab..d3769af9 100644 --- a/module/Core/test/Domain/Resolver/PersistenceDomainResolverTest.php +++ b/module/Core/test/Domain/Resolver/PersistenceDomainResolverTest.php @@ -13,10 +13,8 @@ use Shlinkio\Shlink\Core\Entity\Domain; class PersistenceDomainResolverTest extends TestCase { - /** @var PersistenceDomainResolver */ - private $domainResolver; - /** @var ObjectProphecy */ - private $em; + private PersistenceDomainResolver $domainResolver; + private ObjectProphecy $em; public function setUp(): void { diff --git a/module/Core/test/Domain/Resolver/SimpleDomainResolverTest.php b/module/Core/test/Domain/Resolver/SimpleDomainResolverTest.php index 4fe4f74f..a0fa4bf1 100644 --- a/module/Core/test/Domain/Resolver/SimpleDomainResolverTest.php +++ b/module/Core/test/Domain/Resolver/SimpleDomainResolverTest.php @@ -10,8 +10,7 @@ use Shlinkio\Shlink\Core\Entity\Domain; class SimpleDomainResolverTest extends TestCase { - /** @var SimpleDomainResolver */ - private $domainResolver; + private SimpleDomainResolver $domainResolver; public function setUp(): void { diff --git a/module/Core/test/Entity/TagTest.php b/module/Core/test/Entity/TagTest.php index 06a921e7..01b2f6ea 100644 --- a/module/Core/test/Entity/TagTest.php +++ b/module/Core/test/Entity/TagTest.php @@ -10,7 +10,7 @@ use Shlinkio\Shlink\Core\Entity\Tag; class TagTest extends TestCase { /** @test */ - public function jsonSerializationOfTagsReturnsItsStringRepresentation() + public function jsonSerializationOfTagsReturnsItsStringRepresentation(): void { $tag = new Tag('This is my name'); $this->assertEquals((string) $tag, $tag->jsonSerialize()); diff --git a/module/Core/test/Entity/VisitLocationTest.php b/module/Core/test/Entity/VisitLocationTest.php index 2ea4befa..f662645d 100644 --- a/module/Core/test/Entity/VisitLocationTest.php +++ b/module/Core/test/Entity/VisitLocationTest.php @@ -10,16 +10,6 @@ use Shlinkio\Shlink\IpGeolocation\Model\Location; class VisitLocationTest extends TestCase { - /** @test */ - public function valuesFoundWhenExchangingArrayAreCastToString(): void - { - $payload = new Location('', '', '', '', 1000.7, -2000.4, ''); - $location = new VisitLocation($payload); - - $this->assertSame('1000.7', $location->getLatitude()); - $this->assertSame('-2000.4', $location->getLongitude()); - } - /** * @test * @dataProvider provideArgs diff --git a/module/Core/test/Entity/VisitTest.php b/module/Core/test/Entity/VisitTest.php index ff2c4cec..9af71a09 100644 --- a/module/Core/test/Entity/VisitTest.php +++ b/module/Core/test/Entity/VisitTest.php @@ -25,9 +25,6 @@ class VisitTest extends TestCase 'date' => ($date ?? $visit->getDate())->toAtomString(), 'userAgent' => 'Chrome', 'visitLocation' => null, - - // Deprecated - 'remoteAddr' => null, ], $visit->jsonSerialize()); } diff --git a/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php b/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php index b7796e61..61288e02 100644 --- a/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php +++ b/module/Core/test/ErrorHandler/NotFoundRedirectHandlerTest.php @@ -4,6 +4,11 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\ErrorHandler; +use Laminas\Diactoros\Response; +use Laminas\Diactoros\ServerRequestFactory; +use Laminas\Diactoros\Uri; +use Mezzio\Router\Route; +use Mezzio\Router\RouteResult; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; @@ -11,18 +16,11 @@ use Psr\Http\Server\RequestHandlerInterface; use Shlinkio\Shlink\Core\Action\RedirectAction; use Shlinkio\Shlink\Core\ErrorHandler\NotFoundRedirectHandler; use Shlinkio\Shlink\Core\Options\NotFoundRedirectOptions; -use Zend\Diactoros\Response; -use Zend\Diactoros\ServerRequestFactory; -use Zend\Diactoros\Uri; -use Zend\Expressive\Router\Route; -use Zend\Expressive\Router\RouteResult; class NotFoundRedirectHandlerTest extends TestCase { - /** @var NotFoundRedirectHandler */ - private $middleware; - /** @var NotFoundRedirectOptions */ - private $redirectOptions; + private NotFoundRedirectHandler $middleware; + private NotFoundRedirectOptions $redirectOptions; public function setUp(): void { @@ -75,9 +73,9 @@ class NotFoundRedirectHandlerTest extends TestCase '', $this->prophesize(MiddlewareInterface::class)->reveal(), ['GET'], - RedirectAction::class - ) - ) + RedirectAction::class, + ), + ), ) ->withUri(new Uri('/abc123')), 'invalidShortUrl', diff --git a/module/Core/test/ErrorHandler/NotFoundTemplateHandlerTest.php b/module/Core/test/ErrorHandler/NotFoundTemplateHandlerTest.php index 7d763448..e43b3aab 100644 --- a/module/Core/test/ErrorHandler/NotFoundTemplateHandlerTest.php +++ b/module/Core/test/ErrorHandler/NotFoundTemplateHandlerTest.php @@ -4,23 +4,21 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\ErrorHandler; +use Laminas\Diactoros\Response; +use Laminas\Diactoros\ServerRequestFactory; +use Mezzio\Router\Route; +use Mezzio\Router\RouteResult; +use Mezzio\Template\TemplateRendererInterface; use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Shlinkio\Shlink\Core\ErrorHandler\NotFoundTemplateHandler; -use Zend\Diactoros\Response; -use Zend\Diactoros\ServerRequestFactory; -use Zend\Expressive\Router\Route; -use Zend\Expressive\Router\RouteResult; -use Zend\Expressive\Template\TemplateRendererInterface; class NotFoundTemplateHandlerTest extends TestCase { - /** @var NotFoundTemplateHandler */ - private $handler; - /** @var ObjectProphecy */ - private $renderer; + private NotFoundTemplateHandler $handler; + private ObjectProphecy $renderer; public function setUp(): void { @@ -51,7 +49,7 @@ class NotFoundTemplateHandlerTest extends TestCase yield [ $request->withAttribute( RouteResult::class, - RouteResult::fromRoute(new Route('', $this->prophesize(MiddlewareInterface::class)->reveal())) + RouteResult::fromRoute(new Route('', $this->prophesize(MiddlewareInterface::class)->reveal())), ), NotFoundTemplateHandler::INVALID_SHORT_CODE_TEMPLATE, ]; diff --git a/module/Core/test/EventDispatcher/LocateShortUrlVisitTest.php b/module/Core/test/EventDispatcher/LocateShortUrlVisitTest.php index a451ddea..5f40bb7b 100644 --- a/module/Core/test/EventDispatcher/LocateShortUrlVisitTest.php +++ b/module/Core/test/EventDispatcher/LocateShortUrlVisitTest.php @@ -27,18 +27,12 @@ use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface; class LocateShortUrlVisitTest extends TestCase { - /** @var LocateShortUrlVisit */ - private $locateVisit; - /** @var ObjectProphecy */ - private $ipLocationResolver; - /** @var ObjectProphecy */ - private $em; - /** @var ObjectProphecy */ - private $logger; - /** @var ObjectProphecy */ - private $dbUpdater; - /** @var ObjectProphecy */ - private $eventDispatcher; + private LocateShortUrlVisit $locateVisit; + private ObjectProphecy $ipLocationResolver; + private ObjectProphecy $em; + private ObjectProphecy $logger; + private ObjectProphecy $dbUpdater; + private ObjectProphecy $eventDispatcher; public function setUp(): void { @@ -53,7 +47,7 @@ class LocateShortUrlVisitTest extends TestCase $this->em->reveal(), $this->logger->reveal(), $this->dbUpdater->reveal(), - $this->eventDispatcher->reveal() + $this->eventDispatcher->reveal(), ); } @@ -65,7 +59,7 @@ class LocateShortUrlVisitTest extends TestCase $logWarning = $this->logger->warning('Tried to locate visit with id "{visitId}", but it does not exist.', [ 'visitId' => 123, ]); - $dispatch = $this->eventDispatcher->dispatch(new VisitLocated('123'))->will(function () { + $dispatch = $this->eventDispatcher->dispatch(new VisitLocated('123'))->will(function (): void { }); ($this->locateVisit)($event); @@ -82,16 +76,16 @@ class LocateShortUrlVisitTest extends TestCase { $event = new ShortUrlVisited('123'); $findVisit = $this->em->find(Visit::class, '123')->willReturn( - new Visit(new ShortUrl(''), new Visitor('', '', '1.2.3.4')) + new Visit(new ShortUrl(''), new Visitor('', '', '1.2.3.4')), ); $resolveLocation = $this->ipLocationResolver->resolveIpLocation(Argument::cetera())->willThrow( - WrongIpException::class + WrongIpException::class, ); $logWarning = $this->logger->warning( Argument::containingString('Tried to locate visit with id "{visitId}", but its address seems to be wrong.'), - Argument::type('array') + Argument::type('array'), ); - $dispatch = $this->eventDispatcher->dispatch(new VisitLocated('123'))->will(function () { + $dispatch = $this->eventDispatcher->dispatch(new VisitLocated('123'))->will(function (): void { }); ($this->locateVisit)($event); @@ -111,10 +105,10 @@ class LocateShortUrlVisitTest extends TestCase { $event = new ShortUrlVisited('123'); $findVisit = $this->em->find(Visit::class, '123')->willReturn($visit); - $flush = $this->em->flush()->will(function () { + $flush = $this->em->flush()->will(function (): void { }); $resolveIp = $this->ipLocationResolver->resolveIpLocation(Argument::any()); - $dispatch = $this->eventDispatcher->dispatch(new VisitLocated('123'))->will(function () { + $dispatch = $this->eventDispatcher->dispatch(new VisitLocated('123'))->will(function (): void { }); ($this->locateVisit)($event); @@ -145,10 +139,10 @@ class LocateShortUrlVisitTest extends TestCase $event = new ShortUrlVisited('123'); $findVisit = $this->em->find(Visit::class, '123')->willReturn($visit); - $flush = $this->em->flush()->will(function () { + $flush = $this->em->flush()->will(function (): void { }); $resolveIp = $this->ipLocationResolver->resolveIpLocation($ipAddr)->willReturn($location); - $dispatch = $this->eventDispatcher->dispatch(new VisitLocated('123'))->will(function () { + $dispatch = $this->eventDispatcher->dispatch(new VisitLocated('123'))->will(function (): void { }); ($this->locateVisit)($event); @@ -171,11 +165,11 @@ class LocateShortUrlVisitTest extends TestCase $event = new ShortUrlVisited('123'); $findVisit = $this->em->find(Visit::class, '123')->willReturn($visit); - $flush = $this->em->flush()->will(function () { + $flush = $this->em->flush()->will(function (): void { }); $resolveIp = $this->ipLocationResolver->resolveIpLocation($ipAddr)->willReturn($location); $checkUpdateDb = $this->dbUpdater->checkDbUpdate(Argument::cetera())->willThrow($e); - $dispatch = $this->eventDispatcher->dispatch(new VisitLocated('123'))->will(function () { + $dispatch = $this->eventDispatcher->dispatch(new VisitLocated('123'))->will(function (): void { }); ($this->locateVisit)($event); @@ -187,7 +181,7 @@ class LocateShortUrlVisitTest extends TestCase $checkUpdateDb->shouldHaveBeenCalledOnce(); $this->logger->warning( 'GeoLite2 database update failed. Proceeding with old version. {e}', - ['e' => $e] + ['e' => $e], )->shouldHaveBeenCalledOnce(); $dispatch->shouldHaveBeenCalledOnce(); } @@ -202,15 +196,15 @@ class LocateShortUrlVisitTest extends TestCase $event = new ShortUrlVisited('123'); $findVisit = $this->em->find(Visit::class, '123')->willReturn($visit); - $flush = $this->em->flush()->will(function () { + $flush = $this->em->flush()->will(function (): void { }); $resolveIp = $this->ipLocationResolver->resolveIpLocation($ipAddr)->willReturn($location); $checkUpdateDb = $this->dbUpdater->checkDbUpdate(Argument::cetera())->willThrow($e); $logError = $this->logger->error( 'GeoLite2 database download failed. It is not possible to locate visit with id {visitId}. {e}', - ['e' => $e, 'visitId' => 123] + ['e' => $e, 'visitId' => 123], ); - $dispatch = $this->eventDispatcher->dispatch(new VisitLocated('123'))->will(function () { + $dispatch = $this->eventDispatcher->dispatch(new VisitLocated('123'))->will(function (): void { }); ($this->locateVisit)($event); diff --git a/module/Core/test/Exception/DeleteShortUrlExceptionTest.php b/module/Core/test/Exception/DeleteShortUrlExceptionTest.php index f2207c54..6e45521f 100644 --- a/module/Core/test/Exception/DeleteShortUrlExceptionTest.php +++ b/module/Core/test/Exception/DeleteShortUrlExceptionTest.php @@ -43,7 +43,7 @@ class DeleteShortUrlExceptionTest extends TestCase return [$number, $shortCode = generateRandomShortCode(6), sprintf( 'Impossible to delete short URL with short code "%s" since it has more than "%s" visits.', $shortCode, - $number + $number, )]; }); } diff --git a/module/Core/test/Exception/IpCannotBeLocatedExceptionTest.php b/module/Core/test/Exception/IpCannotBeLocatedExceptionTest.php index 47246e99..84ee433c 100644 --- a/module/Core/test/Exception/IpCannotBeLocatedExceptionTest.php +++ b/module/Core/test/Exception/IpCannotBeLocatedExceptionTest.php @@ -11,10 +11,6 @@ use Shlinkio\Shlink\Core\Exception\IpCannotBeLocatedException; use Shlinkio\Shlink\Core\Exception\RuntimeException; use Throwable; -use function count; -use function func_get_args; -use function random_int; - class IpCannotBeLocatedExceptionTest extends TestCase { /** @test */ @@ -59,39 +55,4 @@ class IpCannotBeLocatedExceptionTest extends TestCase yield 'Runtime exception with negative code' => [new RuntimeException('Something went wrong', -50)]; yield 'Logic exception with default code' => [new LogicException('Conditions unmet')]; } - - /** - * @test - * @dataProvider provideConstructorArgs - */ - public function constructorInitializesException(): void - { - $args = func_get_args(); - [$isNonLocatableAddress, $message] = $args; - $code = $args[2] ?? 0; - $prev = $args[3] ?? null; - - switch (count($args)) { - case 2: - $e = new IpCannotBeLocatedException($isNonLocatableAddress, $message); - break; - case 3: - $e = new IpCannotBeLocatedException($isNonLocatableAddress, $message, $code); - break; - default: - $e = new IpCannotBeLocatedException($isNonLocatableAddress, $message, $code, $prev); - } - - $this->assertEquals($isNonLocatableAddress, $e->isNonLocatableAddress()); - $this->assertEquals($message, $e->getMessage()); - $this->assertEquals($code, $e->getCode()); - $this->assertEquals($prev, $e->getPrevious()); - } - - public function provideConstructorArgs(): iterable - { - yield 'without default args' => [true, 'Message']; - yield 'without prev' => [true, 'Message', random_int(1, 100)]; - yield 'without all args' => [false, 'Foo', random_int(1, 100), new RuntimeException('Foo')]; - } } diff --git a/module/Core/test/Exception/ValidationExceptionTest.php b/module/Core/test/Exception/ValidationExceptionTest.php index 11bb8026..780b25e3 100644 --- a/module/Core/test/Exception/ValidationExceptionTest.php +++ b/module/Core/test/Exception/ValidationExceptionTest.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Exception; use Fig\Http\Message\StatusCodeInterface; +use Laminas\InputFilter\InputFilterInterface; use LogicException; use PHPUnit\Framework\TestCase; use RuntimeException; use Shlinkio\Shlink\Core\Exception\ValidationException; use Throwable; -use Zend\InputFilter\InputFilterInterface; use function array_keys; use function print_r; diff --git a/module/Core/test/Middleware/QrCodeCacheMiddlewareTest.php b/module/Core/test/Middleware/QrCodeCacheMiddlewareTest.php index a11f9b0d..e2d73266 100644 --- a/module/Core/test/Middleware/QrCodeCacheMiddlewareTest.php +++ b/module/Core/test/Middleware/QrCodeCacheMiddlewareTest.php @@ -6,20 +6,18 @@ namespace ShlinkioTest\Shlink\Core\Middleware; use Doctrine\Common\Cache\ArrayCache; use Doctrine\Common\Cache\Cache; +use Laminas\Diactoros\Response; +use Laminas\Diactoros\ServerRequest; +use Laminas\Diactoros\Uri; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Psr\Http\Server\RequestHandlerInterface; use Shlinkio\Shlink\Core\Middleware\QrCodeCacheMiddleware; -use Zend\Diactoros\Response; -use Zend\Diactoros\ServerRequest; -use Zend\Diactoros\Uri; class QrCodeCacheMiddlewareTest extends TestCase { - /** @var QrCodeCacheMiddleware */ - private $middleware; - /** @var Cache */ - private $cache; + private QrCodeCacheMiddleware $middleware; + private Cache $cache; public function setUp(): void { @@ -28,7 +26,7 @@ class QrCodeCacheMiddlewareTest extends TestCase } /** @test */ - public function noCachedPathFallsBackToNextMiddleware() + public function noCachedPathFallsBackToNextMiddleware(): void { $delegate = $this->prophesize(RequestHandlerInterface::class); $delegate->handle(Argument::any())->willReturn(new Response())->shouldBeCalledOnce(); @@ -39,7 +37,7 @@ class QrCodeCacheMiddlewareTest extends TestCase } /** @test */ - public function cachedPathReturnsCacheContent() + public function cachedPathReturnsCacheContent(): void { $isCalled = false; $uri = (new Uri())->withPath('/foo'); diff --git a/module/Core/test/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php b/module/Core/test/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php index 8bf69faf..dd2fa24a 100644 --- a/module/Core/test/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php +++ b/module/Core/test/Paginator/Adapter/ShortUrlRepositoryAdapterTest.php @@ -13,8 +13,7 @@ use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; class ShortUrlRepositoryAdapterTest extends TestCase { - /** @var ObjectProphecy */ - private $repo; + private ObjectProphecy $repo; public function setUp(): void { @@ -22,11 +21,12 @@ class ShortUrlRepositoryAdapterTest extends TestCase } /** + * @param string|array|null $orderBy * @test * @dataProvider provideFilteringArgs */ public function getItemsFallsBackToFindList( - $searchTerm = null, + ?string $searchTerm = null, array $tags = [], ?DateRange $dateRange = null, $orderBy = null @@ -41,8 +41,11 @@ class ShortUrlRepositoryAdapterTest extends TestCase * @test * @dataProvider provideFilteringArgs */ - public function countFallsBackToCountList($searchTerm = null, array $tags = [], ?DateRange $dateRange = null): void - { + public function countFallsBackToCountList( + ?string $searchTerm = null, + array $tags = [], + ?DateRange $dateRange = null + ): void { $adapter = new ShortUrlRepositoryAdapter($this->repo->reveal(), $searchTerm, $tags, null, $dateRange); $this->repo->countList($searchTerm, $tags, $dateRange)->shouldBeCalledOnce(); diff --git a/module/Core/test/Service/ShortUrl/DeleteShortUrlServiceTest.php b/module/Core/test/Service/ShortUrl/DeleteShortUrlServiceTest.php index cbb99c57..e7aefd9d 100644 --- a/module/Core/test/Service/ShortUrl/DeleteShortUrlServiceTest.php +++ b/module/Core/test/Service/ShortUrl/DeleteShortUrlServiceTest.php @@ -23,16 +23,14 @@ use function sprintf; class DeleteShortUrlServiceTest extends TestCase { - /** @var ObjectProphecy */ - private $em; - /** @var string */ - private $shortCode; + private ObjectProphecy $em; + private string $shortCode; public function setUp(): void { - $shortUrl = (new ShortUrl(''))->setVisits(new ArrayCollection(map(range(0, 10), function () { - return new Visit(new ShortUrl(''), Visitor::emptyInstance()); - }))); + $shortUrl = (new ShortUrl(''))->setVisits( + new ArrayCollection(map(range(0, 10), fn () => new Visit(new ShortUrl(''), Visitor::emptyInstance()))), + ); $this->shortCode = $shortUrl->getShortCode(); $this->em = $this->prophesize(EntityManagerInterface::class); @@ -50,7 +48,7 @@ class DeleteShortUrlServiceTest extends TestCase $this->expectException(DeleteShortUrlException::class); $this->expectExceptionMessage(sprintf( 'Impossible to delete short URL with short code "%s" since it has more than "5" visits.', - $this->shortCode + $this->shortCode, )); $service->deleteByShortCode($this->shortCode); diff --git a/module/Core/test/Service/ShortUrlServiceTest.php b/module/Core/test/Service/ShortUrlServiceTest.php index 3dae00a7..93cb7ad0 100644 --- a/module/Core/test/Service/ShortUrlServiceTest.php +++ b/module/Core/test/Service/ShortUrlServiceTest.php @@ -21,10 +21,8 @@ use function count; class ShortUrlServiceTest extends TestCase { - /** @var ShortUrlService */ - private $service; - /** @var ObjectProphecy|EntityManagerInterface */ - private $em; + private ShortUrlService $service; + private ObjectProphecy $em; public function setUp(): void { @@ -99,7 +97,7 @@ class ShortUrlServiceTest extends TestCase Chronos::parse('2017-01-01 00:00:00')->toAtomString(), Chronos::parse('2017-01-05 00:00:00')->toAtomString(), null, - 5 + 5, )); $this->assertSame($shortUrl, $result); diff --git a/module/Core/test/Service/Tag/TagServiceTest.php b/module/Core/test/Service/Tag/TagServiceTest.php index d7e5b631..b8c9d59b 100644 --- a/module/Core/test/Service/Tag/TagServiceTest.php +++ b/module/Core/test/Service/Tag/TagServiceTest.php @@ -17,10 +17,8 @@ use Shlinkio\Shlink\Core\Service\Tag\TagService; class TagServiceTest extends TestCase { - /** @var TagService */ - private $service; - /** @var ObjectProphecy */ - private $em; + private TagService $service; + private ObjectProphecy $em; public function setUp(): void { diff --git a/module/Core/test/Service/UrlShortenerTest.php b/module/Core/test/Service/UrlShortenerTest.php index 1995c921..2ec3024d 100644 --- a/module/Core/test/Service/UrlShortenerTest.php +++ b/module/Core/test/Service/UrlShortenerTest.php @@ -9,6 +9,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\DBAL\Connection; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\ORMException; +use Laminas\Diactoros\Uri; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; @@ -21,18 +22,14 @@ use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\Service\UrlShortener; use Shlinkio\Shlink\Core\Util\UrlValidatorInterface; -use Zend\Diactoros\Uri; use function array_map; class UrlShortenerTest extends TestCase { - /** @var UrlShortener */ - private $urlShortener; - /** @var ObjectProphecy */ - private $em; - /** @var ObjectProphecy */ - private $urlValidator; + private UrlShortener $urlShortener; + private ObjectProphecy $em; + private ObjectProphecy $urlValidator; public function setUp(): void { @@ -45,9 +42,9 @@ class UrlShortenerTest extends TestCase $this->em->flush()->willReturn(null); $this->em->commit()->willReturn(null); $this->em->beginTransaction()->willReturn(null); - $this->em->persist(Argument::any())->will(function ($arguments) { + $this->em->persist(Argument::any())->will(function ($arguments): void { /** @var ShortUrl $shortUrl */ - $shortUrl = $arguments[0]; + [$shortUrl] = $arguments; $shortUrl->setId('10'); }); $repo = $this->prophesize(ShortUrlRepository::class); @@ -62,7 +59,7 @@ class UrlShortenerTest extends TestCase $this->urlShortener = new UrlShortener( $this->urlValidator->reveal(), $this->em->reveal(), - new UrlShortenerOptions(['validate_url' => $urlValidationEnabled]) + new UrlShortenerOptions(['validate_url' => $urlValidationEnabled]), ); } @@ -72,7 +69,7 @@ class UrlShortenerTest extends TestCase $shortUrl = $this->urlShortener->urlToShortCode( new Uri('http://foobar.com/12345/hello?foo=bar'), [], - ShortUrlMeta::createEmpty() + ShortUrlMeta::createEmpty(), ); $this->assertEquals('http://foobar.com/12345/hello?foo=bar', $shortUrl->getLongUrl()); @@ -88,7 +85,7 @@ class UrlShortenerTest extends TestCase function () use (&$callIndex, $expectedCalls) { $callIndex++; return $callIndex < $expectedCalls; - } + }, ); $repo->findBy(Argument::cetera())->willReturn([]); $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); @@ -96,7 +93,7 @@ class UrlShortenerTest extends TestCase $shortUrl = $this->urlShortener->urlToShortCode( new Uri('http://foobar.com/12345/hello?foo=bar'), [], - ShortUrlMeta::createEmpty() + ShortUrlMeta::createEmpty(), ); $this->assertEquals('http://foobar.com/12345/hello?foo=bar', $shortUrl->getLongUrl()); @@ -119,7 +116,7 @@ class UrlShortenerTest extends TestCase $this->urlShortener->urlToShortCode( new Uri('http://foobar.com/12345/hello?foo=bar'), [], - ShortUrlMeta::createEmpty() + ShortUrlMeta::createEmpty(), ); } @@ -127,13 +124,15 @@ class UrlShortenerTest extends TestCase public function validatorIsCalledWhenUrlValidationIsEnabled(): void { $this->setUrlShortener(true); - $validateUrl = $this->urlValidator->validateUrl('http://foobar.com/12345/hello?foo=bar')->will(function () { - }); + $validateUrl = $this->urlValidator->validateUrl('http://foobar.com/12345/hello?foo=bar')->will( + function (): void { + }, + ); $this->urlShortener->urlToShortCode( new Uri('http://foobar.com/12345/hello?foo=bar'), [], - ShortUrlMeta::createEmpty() + ShortUrlMeta::createEmpty(), ); $validateUrl->shouldHaveBeenCalledOnce(); @@ -154,7 +153,7 @@ class UrlShortenerTest extends TestCase $this->urlShortener->urlToShortCode( new Uri('http://foobar.com/12345/hello?foo=bar'), [], - ShortUrlMeta::createFromRawData(['customSlug' => 'custom-slug']) + ShortUrlMeta::createFromRawData(['customSlug' => 'custom-slug']), ); } @@ -186,7 +185,7 @@ class UrlShortenerTest extends TestCase yield [$url, [], ShortUrlMeta::createFromRawData(['findIfExists' => true]), new ShortUrl($url)]; yield [$url, [], ShortUrlMeta::createFromRawData( - ['findIfExists' => true, 'customSlug' => 'foo'] + ['findIfExists' => true, 'customSlug' => 'foo'], ), new ShortUrl($url)]; yield [ $url, @@ -243,9 +242,7 @@ class UrlShortenerTest extends TestCase 'validUntil' => Chronos::parse('2017-01-01'), 'maxVisits' => 4, ]); - $tagsCollection = new ArrayCollection(array_map(function (string $tag) { - return new Tag($tag); - }, $tags)); + $tagsCollection = new ArrayCollection(array_map(fn (string $tag) => new Tag($tag), $tags)); $expected = (new ShortUrl($url, $meta))->setTags($tagsCollection); $repo = $this->prophesize(ShortUrlRepository::class); diff --git a/module/Core/test/Service/VisitServiceTest.php b/module/Core/test/Service/VisitServiceTest.php index 3e8d1627..26e95557 100644 --- a/module/Core/test/Service/VisitServiceTest.php +++ b/module/Core/test/Service/VisitServiceTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Service; use Doctrine\ORM\EntityManager; +use Exception; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; @@ -27,10 +28,8 @@ use function sprintf; class VisitServiceTest extends TestCase { - /** @var VisitService */ - private $visitService; - /** @var ObjectProphecy */ - private $em; + private VisitService $visitService; + private ObjectProphecy $em; public function setUp(): void { @@ -41,24 +40,23 @@ class VisitServiceTest extends TestCase /** @test */ public function locateVisitsIteratesAndLocatesUnlocatedVisits(): void { - $unlocatedVisits = map(range(1, 200), function (int $i) { - return new Visit(new ShortUrl(sprintf('short_code_%s', $i)), Visitor::emptyInstance()); - }); + $unlocatedVisits = map( + range(1, 200), + fn (int $i) => new Visit(new ShortUrl(sprintf('short_code_%s', $i)), Visitor::emptyInstance()), + ); $repo = $this->prophesize(VisitRepository::class); $findUnlocatedVisits = $repo->findUnlocatedVisits(false)->willReturn($unlocatedVisits); $getRepo = $this->em->getRepository(Visit::class)->willReturn($repo->reveal()); - $persist = $this->em->persist(Argument::type(Visit::class))->will(function () { + $persist = $this->em->persist(Argument::type(Visit::class))->will(function (): void { }); - $flush = $this->em->flush()->will(function () { + $flush = $this->em->flush()->will(function (): void { }); - $clear = $this->em->clear()->will(function () { + $clear = $this->em->clear()->will(function (): void { }); - $this->visitService->locateUnlocatedVisits(function () { - return Location::emptyInstance(); - }, function () { + $this->visitService->locateUnlocatedVisits(fn () => Location::emptyInstance(), function (): void { $args = func_get_args(); $this->assertInstanceOf(VisitLocation::class, array_shift($args)); @@ -86,15 +84,17 @@ class VisitServiceTest extends TestCase $findUnlocatedVisits = $repo->findUnlocatedVisits(false)->willReturn($unlocatedVisits); $getRepo = $this->em->getRepository(Visit::class)->willReturn($repo->reveal()); - $persist = $this->em->persist(Argument::type(Visit::class))->will(function () { + $persist = $this->em->persist(Argument::type(Visit::class))->will(function (): void { }); - $flush = $this->em->flush()->will(function () { + $flush = $this->em->flush()->will(function (): void { }); - $clear = $this->em->clear()->will(function () { + $clear = $this->em->clear()->will(function (): void { }); - $this->visitService->locateUnlocatedVisits(function () use ($isNonLocatableAddress) { - throw new IpCannotBeLocatedException($isNonLocatableAddress, 'Cannot be located'); + $this->visitService->locateUnlocatedVisits(function () use ($isNonLocatableAddress): void { + throw $isNonLocatableAddress + ? new IpCannotBeLocatedException('Cannot be located') + : IpCannotBeLocatedException::forError(new Exception('')); }); $findUnlocatedVisits->shouldHaveBeenCalledOnce(); diff --git a/module/Core/test/Service/VisitsTrackerTest.php b/module/Core/test/Service/VisitsTrackerTest.php index a59429fb..5cf78b16 100644 --- a/module/Core/test/Service/VisitsTrackerTest.php +++ b/module/Core/test/Service/VisitsTrackerTest.php @@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\Core\Service; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityRepository; +use Laminas\Stdlib\ArrayUtils; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; use Prophecy\Argument; @@ -19,16 +20,12 @@ use Shlinkio\Shlink\Core\Model\Visitor; use Shlinkio\Shlink\Core\Model\VisitsParams; use Shlinkio\Shlink\Core\Repository\VisitRepository; use Shlinkio\Shlink\Core\Service\VisitsTracker; -use Zend\Stdlib\ArrayUtils; class VisitsTrackerTest extends TestCase { - /** @var VisitsTracker */ - private $visitsTracker; - /** @var ObjectProphecy */ - private $em; - /** @var EventDispatcherInterface */ - private $eventDispatcher; + private VisitsTracker $visitsTracker; + private ObjectProphecy $em; + private ObjectProphecy $eventDispatcher; public function setUp(): void { @@ -46,10 +43,7 @@ class VisitsTrackerTest extends TestCase $repo->findOneBy(['shortCode' => $shortCode])->willReturn(new ShortUrl('')); $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal())->shouldBeCalledOnce(); - $this->em->persist(Argument::that(function (Visit $visit) { - $visit->setId('1'); - return $visit; - }))->shouldBeCalledOnce(); + $this->em->persist(Argument::that(fn (Visit $visit) => $visit->setId('1')))->shouldBeCalledOnce(); $this->em->flush()->shouldBeCalledOnce(); $this->visitsTracker->track($shortCode, Visitor::emptyInstance()); diff --git a/module/Core/test/Transformer/ShortUrlDataTransformerTest.php b/module/Core/test/Transformer/ShortUrlDataTransformerTest.php index 92f2c848..094e09d6 100644 --- a/module/Core/test/Transformer/ShortUrlDataTransformerTest.php +++ b/module/Core/test/Transformer/ShortUrlDataTransformerTest.php @@ -14,8 +14,7 @@ use function random_int; class ShortUrlDataTransformerTest extends TestCase { - /** @var ShortUrlDataTransformer */ - private $transformer; + private ShortUrlDataTransformer $transformer; public function setUp(): void { diff --git a/module/Core/test/Util/UrlValidatorTest.php b/module/Core/test/Util/UrlValidatorTest.php index 3a6880ea..5f018a05 100644 --- a/module/Core/test/Util/UrlValidatorTest.php +++ b/module/Core/test/Util/UrlValidatorTest.php @@ -8,19 +8,17 @@ use Fig\Http\Message\RequestMethodInterface; use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\ClientException; use GuzzleHttp\RequestOptions; +use Laminas\Diactoros\Response; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Util\UrlValidator; -use Zend\Diactoros\Response; class UrlValidatorTest extends TestCase { - /** @var UrlValidator */ - private $urlValidator; - /** @var ObjectProphecy */ - private $httpClient; + private UrlValidator $urlValidator; + private ObjectProphecy $httpClient; public function setUp(): void { @@ -47,7 +45,7 @@ class UrlValidatorTest extends TestCase $request = $this->httpClient->request( RequestMethodInterface::METHOD_GET, $expectedUrl, - [RequestOptions::ALLOW_REDIRECTS => ['max' => 15]] + [RequestOptions::ALLOW_REDIRECTS => ['max' => 15]], )->willReturn(new Response()); $this->urlValidator->validateUrl($expectedUrl); diff --git a/module/PreviewGenerator/config/dependencies.config.php b/module/PreviewGenerator/config/dependencies.config.php deleted file mode 100644 index dc7ceb96..00000000 --- a/module/PreviewGenerator/config/dependencies.config.php +++ /dev/null @@ -1,27 +0,0 @@ - [ - 'factories' => [ - Image\ImageBuilder::class => Image\ImageBuilderFactory::class, - Service\PreviewGenerator::class => ConfigAbstractFactory::class, - ], - ], - - ConfigAbstractFactory::class => [ - Service\PreviewGenerator::class => [ - Image\ImageBuilder::class, - Filesystem::class, - 'config.preview_generation.files_location', - ], - ], - -]; diff --git a/module/PreviewGenerator/src/ConfigProvider.php b/module/PreviewGenerator/src/ConfigProvider.php deleted file mode 100644 index 1bc2f7c5..00000000 --- a/module/PreviewGenerator/src/ConfigProvider.php +++ /dev/null @@ -1,16 +0,0 @@ - [ - Image::class => ImageFactory::class, - ]]); - } -} diff --git a/module/PreviewGenerator/src/Image/ImageBuilderInterface.php b/module/PreviewGenerator/src/Image/ImageBuilderInterface.php deleted file mode 100644 index d11daab0..00000000 --- a/module/PreviewGenerator/src/Image/ImageBuilderInterface.php +++ /dev/null @@ -1,12 +0,0 @@ -get('config')['wkhtmltopdf']; - $image = new Image($config['images'] ?? null); - - if ($options['url'] ?? null) { - $image->setPage($options['url']); - } - - return $image; - } -} diff --git a/module/PreviewGenerator/src/Service/PreviewGenerator.php b/module/PreviewGenerator/src/Service/PreviewGenerator.php deleted file mode 100644 index 54e967aa..00000000 --- a/module/PreviewGenerator/src/Service/PreviewGenerator.php +++ /dev/null @@ -1,58 +0,0 @@ -location = $location; - $this->imageBuilder = $imageBuilder; - $this->filesystem = $filesystem; - } - - /** - * Generates and stores preview for provided website and returns the path to the image file - * - * @throws PreviewGenerationException - */ - public function generatePreview(string $url): string - { - $image = $this->imageBuilder->build(Image::class, ['url' => $url]); - - // If the file already exists, return its path - $cacheId = sprintf('preview_%s.%s', urlencode($url), $image->type); - $path = $this->location . '/' . $cacheId; - if ($this->filesystem->exists($path)) { - return $path; - } - - // Save and check if an error occurred - $image->saveAs($path); - $error = $image->getError(); - if (! empty($error)) { - throw PreviewGenerationException::fromImageError($error); - } - - // Cache the path and return it - return $path; - } -} diff --git a/module/PreviewGenerator/src/Service/PreviewGeneratorInterface.php b/module/PreviewGenerator/src/Service/PreviewGeneratorInterface.php deleted file mode 100644 index 26aefda0..00000000 --- a/module/PreviewGenerator/src/Service/PreviewGeneratorInterface.php +++ /dev/null @@ -1,18 +0,0 @@ -configProvider = new ConfigProvider(); - } - - /** @test */ - public function configIsReturned(): void - { - $config = ($this->configProvider)(); - - $this->assertArrayHasKey('dependencies', $config); - } -} diff --git a/module/PreviewGenerator/test/Image/ImageBuilderFactoryTest.php b/module/PreviewGenerator/test/Image/ImageBuilderFactoryTest.php deleted file mode 100644 index 0319dafc..00000000 --- a/module/PreviewGenerator/test/Image/ImageBuilderFactoryTest.php +++ /dev/null @@ -1,28 +0,0 @@ -factory = new ImageBuilderFactory(); - } - - /** @test */ - public function serviceIsCreated() - { - $instance = $this->factory->__invoke(new ServiceManager(), ''); - $this->assertInstanceOf(ImageBuilder::class, $instance); - } -} diff --git a/module/PreviewGenerator/test/Image/ImageFactoryTest.php b/module/PreviewGenerator/test/Image/ImageFactoryTest.php deleted file mode 100644 index e9b9fc8e..00000000 --- a/module/PreviewGenerator/test/Image/ImageFactoryTest.php +++ /dev/null @@ -1,54 +0,0 @@ -factory = new ImageFactory(); - } - - /** @test */ - public function noPageIsSetWhenOptionsAreNotProvided() - { - /** @var Image $image */ - $image = $this->factory->__invoke(new ServiceManager(['services' => [ - 'config' => ['wkhtmltopdf' => []], - ]]), ''); - $this->assertInstanceOf(Image::class, $image); - - $ref = new ReflectionObject($image); - $page = $ref->getProperty('_page'); - $page->setAccessible(true); - $this->assertNull($page->getValue($image)); - } - - /** @test */ - public function aPageIsSetWhenOptionsIncludeTheUrl() - { - $expectedPage = 'foo/bar.html'; - - /** @var Image $image */ - $image = $this->factory->__invoke(new ServiceManager(['services' => [ - 'config' => ['wkhtmltopdf' => []], - ]]), '', ['url' => $expectedPage]); - $this->assertInstanceOf(Image::class, $image); - - $ref = new ReflectionObject($image); - $page = $ref->getProperty('_page'); - $page->setAccessible(true); - $this->assertEquals($expectedPage, $page->getValue($image)); - } -} diff --git a/module/PreviewGenerator/test/Service/PreviewGeneratorTest.php b/module/PreviewGenerator/test/Service/PreviewGeneratorTest.php deleted file mode 100644 index 035a976b..00000000 --- a/module/PreviewGenerator/test/Service/PreviewGeneratorTest.php +++ /dev/null @@ -1,85 +0,0 @@ -image = $this->prophesize(Image::class); - $this->filesystem = $this->prophesize(Filesystem::class); - - $this->generator = new PreviewGenerator(new ImageBuilder(new ServiceManager(), [ - 'factories' => [ - Image::class => function () { - return $this->image->reveal(); - }, - ], - ]), $this->filesystem->reveal(), 'dir'); - } - - /** @test */ - public function alreadyProcessedElementsAreNotProcessed() - { - $url = 'http://foo.com'; - $this->filesystem->exists(sprintf('dir/preview_%s.png', urlencode($url)))->willReturn(true) - ->shouldBeCalledOnce(); - $this->image->saveAs(Argument::cetera())->shouldBeCalledTimes(0); - $this->assertEquals(sprintf('dir/preview_%s.png', urlencode($url)), $this->generator->generatePreview($url)); - } - - /** @test */ - public function nonProcessedElementsAreProcessed() - { - $url = 'http://foo.com'; - $cacheId = sprintf('preview_%s.png', urlencode($url)); - $expectedPath = 'dir/' . $cacheId; - - $this->filesystem->exists(sprintf('dir/preview_%s.png', urlencode($url)))->willReturn(false) - ->shouldBeCalledOnce(); - - $this->image->saveAs($expectedPath)->shouldBeCalledOnce(); - $this->image->getError()->willReturn('')->shouldBeCalledOnce(); - $this->assertEquals($expectedPath, $this->generator->generatePreview($url)); - } - - /** @test */ - public function errorWhileGeneratingPreviewThrowsException() - { - $url = 'http://foo.com'; - $cacheId = sprintf('preview_%s.png', urlencode($url)); - $expectedPath = 'dir/' . $cacheId; - - $this->filesystem->exists(sprintf('dir/preview_%s.png', urlencode($url)))->willReturn(false) - ->shouldBeCalledOnce(); - - $this->image->saveAs($expectedPath)->shouldBeCalledOnce(); - $this->image->getError()->willReturn('Error!!')->shouldBeCalledOnce(); - - $this->expectException(PreviewGenerationException::class); - - $this->generator->generatePreview($url); - } -} diff --git a/module/Rest/config/auth.config.php b/module/Rest/config/auth.config.php index 9d0987e0..99141364 100644 --- a/module/Rest/config/auth.config.php +++ b/module/Rest/config/auth.config.php @@ -4,27 +4,24 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest; -use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory; +use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory; return [ 'auth' => [ 'routes_whitelist' => [ - Action\AuthenticateAction::class, Action\HealthAction::class, Action\ShortUrl\SingleStepCreateShortUrlAction::class, + ConfigProvider::UNVERSIONED_HEALTH_ENDPOINT_NAME, ], 'plugins' => [ 'factories' => [ Authentication\Plugin\ApiKeyHeaderPlugin::class => ConfigAbstractFactory::class, - Authentication\Plugin\AuthorizationHeaderPlugin::class => ConfigAbstractFactory::class, ], 'aliases' => [ Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME => Authentication\Plugin\ApiKeyHeaderPlugin::class, - Authentication\Plugin\AuthorizationHeaderPlugin::HEADER_NAME => - Authentication\Plugin\AuthorizationHeaderPlugin::class, ], ], ], @@ -40,7 +37,6 @@ return [ ], ConfigAbstractFactory::class => [ - Authentication\Plugin\AuthorizationHeaderPlugin::class => [Authentication\JWTService::class], Authentication\Plugin\ApiKeyHeaderPlugin::class => [Service\ApiKeyService::class], Authentication\RequestToHttpAuthPlugin::class => [Authentication\AuthenticationPluginManager::class], diff --git a/module/Rest/config/dependencies.config.php b/module/Rest/config/dependencies.config.php index 17c30642..832bf361 100644 --- a/module/Rest/config/dependencies.config.php +++ b/module/Rest/config/dependencies.config.php @@ -5,22 +5,20 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest; use Doctrine\DBAL\Connection; +use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory; +use Laminas\ServiceManager\Factory\InvokableFactory; +use Mezzio\Router\Middleware\ImplicitOptionsMiddleware; use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Core\Options\AppOptions; use Shlinkio\Shlink\Core\Service; use Shlinkio\Shlink\Rest\Service\ApiKeyService; -use Zend\Expressive\Router\Middleware\ImplicitOptionsMiddleware; -use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory; -use Zend\ServiceManager\Factory\InvokableFactory; return [ 'dependencies' => [ 'factories' => [ - Authentication\JWTService::class => ConfigAbstractFactory::class, ApiKeyService::class => ConfigAbstractFactory::class, - Action\AuthenticateAction::class => ConfigAbstractFactory::class, Action\HealthAction::class => ConfigAbstractFactory::class, Action\ShortUrl\CreateShortUrlAction::class => ConfigAbstractFactory::class, Action\ShortUrl\SingleStepCreateShortUrlAction::class => ConfigAbstractFactory::class, @@ -38,18 +36,13 @@ return [ ImplicitOptionsMiddleware::class => Middleware\EmptyResponseImplicitOptionsMiddlewareFactory::class, Middleware\BodyParserMiddleware::class => InvokableFactory::class, Middleware\CrossDomainMiddleware::class => InvokableFactory::class, - Middleware\PathVersionMiddleware::class => InvokableFactory::class, - Middleware\BackwardsCompatibleProblemDetailsMiddleware::class => ConfigAbstractFactory::class, Middleware\ShortUrl\CreateShortUrlContentNegotiationMiddleware::class => InvokableFactory::class, - Middleware\ShortUrl\ShortCodePathMiddleware::class => InvokableFactory::class, ], ], ConfigAbstractFactory::class => [ - Authentication\JWTService::class => [AppOptions::class], ApiKeyService::class => ['em'], - Action\AuthenticateAction::class => [ApiKeyService::class, Authentication\JWTService::class, 'Logger_Shlink'], Action\HealthAction::class => [Connection::class, AppOptions::class, 'Logger_Shlink'], Action\ShortUrl\CreateShortUrlAction::class => [ Service\UrlShortener::class, @@ -76,11 +69,6 @@ return [ 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, LoggerInterface::class], - - Middleware\BackwardsCompatibleProblemDetailsMiddleware::class => [ - 'config.backwards_compatible_problem_details.default_type_fallbacks', - 'config.backwards_compatible_problem_details.json_flags', - ], ], ]; diff --git a/module/Rest/config/entities-mappings/Shlinkio.Shlink.Rest.Entity.ApiKey.php b/module/Rest/config/entities-mappings/Shlinkio.Shlink.Rest.Entity.ApiKey.php index 3b7c2e60..5bc5aec9 100644 --- a/module/Rest/config/entities-mappings/Shlinkio.Shlink.Rest.Entity.ApiKey.php +++ b/module/Rest/config/entities-mappings/Shlinkio.Shlink.Rest.Entity.ApiKey.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest; -use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder; use Doctrine\ORM\Mapping\ClassMetadata; // @codingStandardsIgnoreLine use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType; @@ -14,13 +14,13 @@ $builder = new ClassMetadataBuilder($metadata); $builder->setTable('api_keys'); -$builder->createField('id', Type::BIGINT) +$builder->createField('id', Types::BIGINT) ->makePrimaryKey() ->generatedValue('IDENTITY') ->option('unsigned', true) ->build(); -$builder->createField('key', Type::STRING) +$builder->createField('key', Types::STRING) ->columnName('`key`') ->unique() ->build(); @@ -30,5 +30,5 @@ $builder->createField('expirationDate', ChronosDateTimeType::CHRONOS_DATETIME) ->nullable() ->build(); -$builder->createField('enabled', Type::BOOLEAN) +$builder->createField('enabled', Types::BOOLEAN) ->build(); diff --git a/module/Rest/config/routes.config.php b/module/Rest/config/routes.config.php index 3583a091..d210f13b 100644 --- a/module/Rest/config/routes.config.php +++ b/module/Rest/config/routes.config.php @@ -7,7 +7,6 @@ namespace Shlinkio\Shlink\Rest; return [ 'routes' => [ - Action\AuthenticateAction::getRouteDef(), Action\HealthAction::getRouteDef(), // Short codes diff --git a/module/Rest/src/Action/AbstractRestAction.php b/module/Rest/src/Action/AbstractRestAction.php index 8eb9ceb4..826290b7 100644 --- a/module/Rest/src/Action/AbstractRestAction.php +++ b/module/Rest/src/Action/AbstractRestAction.php @@ -17,8 +17,7 @@ abstract class AbstractRestAction implements RequestHandlerInterface, RequestMet protected const ROUTE_PATH = ''; protected const ROUTE_ALLOWED_METHODS = []; - /** @var LoggerInterface */ - protected $logger; + protected LoggerInterface $logger; public function __construct(?LoggerInterface $logger = null) { diff --git a/module/Rest/src/Action/AuthenticateAction.php b/module/Rest/src/Action/AuthenticateAction.php deleted file mode 100644 index b0919aae..00000000 --- a/module/Rest/src/Action/AuthenticateAction.php +++ /dev/null @@ -1,64 +0,0 @@ -apiKeyService = $apiKeyService; - $this->jwtService = $jwtService; - } - - /** - * @param Request $request - * @return Response - * @throws \InvalidArgumentException - */ - public function handle(Request $request): Response - { - $authData = $request->getParsedBody(); - if (! isset($authData['apiKey'])) { - return new JsonResponse([ - 'error' => 'INVALID_ARGUMENT', - 'message' => 'You have to provide a valid API key under the "apiKey" param name.', - ], self::STATUS_BAD_REQUEST); - } - - // Authenticate using provided API key - $apiKey = $this->apiKeyService->getByKey($authData['apiKey']); - if ($apiKey === null || ! $apiKey->isValid()) { - return new JsonResponse([ - 'error' => 'INVALID_API_KEY', - 'message' => 'Provided API key does not exist or is invalid.', - ], self::STATUS_UNAUTHORIZED); - } - - // Generate a JSON Web Token that will be used for authorization in next requests - $token = $this->jwtService->create($apiKey); - return new JsonResponse(['token' => $token]); - } -} diff --git a/module/Rest/src/Action/HealthAction.php b/module/Rest/src/Action/HealthAction.php index ac548fde..4f3b9c64 100644 --- a/module/Rest/src/Action/HealthAction.php +++ b/module/Rest/src/Action/HealthAction.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Action; use Doctrine\DBAL\Connection; +use Laminas\Diactoros\Response\JsonResponse; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Core\Options\AppOptions; use Throwable; -use Zend\Diactoros\Response\JsonResponse; class HealthAction extends AbstractRestAction { @@ -21,10 +21,8 @@ class HealthAction extends AbstractRestAction protected const ROUTE_PATH = '/health'; protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET]; - /** @var AppOptions */ - private $options; - /** @var Connection */ - private $conn; + private AppOptions $options; + private Connection $conn; public function __construct(Connection $conn, AppOptions $options, ?LoggerInterface $logger = null) { diff --git a/module/Rest/src/Action/ShortUrl/AbstractCreateShortUrlAction.php b/module/Rest/src/Action/ShortUrl/AbstractCreateShortUrlAction.php index af392067..3335e1fa 100644 --- a/module/Rest/src/Action/ShortUrl/AbstractCreateShortUrlAction.php +++ b/module/Rest/src/Action/ShortUrl/AbstractCreateShortUrlAction.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Action\ShortUrl; +use Laminas\Diactoros\Response\JsonResponse; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Log\LoggerInterface; @@ -12,14 +13,11 @@ use Shlinkio\Shlink\Core\Model\CreateShortUrlData; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer; use Shlinkio\Shlink\Rest\Action\AbstractRestAction; -use Zend\Diactoros\Response\JsonResponse; abstract class AbstractCreateShortUrlAction extends AbstractRestAction { - /** @var UrlShortenerInterface */ - private $urlShortener; - /** @var array */ - private $domainConfig; + private UrlShortenerInterface $urlShortener; + private array $domainConfig; public function __construct( UrlShortenerInterface $urlShortener, @@ -45,8 +43,6 @@ abstract class AbstractCreateShortUrlAction extends AbstractRestAction } /** - * @param Request $request - * @return CreateShortUrlData * @throws ValidationException */ abstract protected function buildShortUrlData(Request $request): CreateShortUrlData; diff --git a/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php b/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php index e0b0ddac..e85d2465 100644 --- a/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php +++ b/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php @@ -4,11 +4,11 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Action\ShortUrl; +use Laminas\Diactoros\Uri; use Psr\Http\Message\ServerRequestInterface as Request; use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Model\CreateShortUrlData; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; -use Zend\Diactoros\Uri; class CreateShortUrlAction extends AbstractCreateShortUrlAction { @@ -16,8 +16,6 @@ class CreateShortUrlAction extends AbstractCreateShortUrlAction protected const ROUTE_ALLOWED_METHODS = [self::METHOD_POST]; /** - * @param Request $request - * @return CreateShortUrlData * @throws ValidationException */ protected function buildShortUrlData(Request $request): CreateShortUrlData @@ -35,7 +33,7 @@ class CreateShortUrlAction extends AbstractCreateShortUrlAction $postData['customSlug'] ?? null, $postData['maxVisits'] ?? null, $postData['findIfExists'] ?? null, - $postData['domain'] ?? null + $postData['domain'] ?? null, ); return new CreateShortUrlData(new Uri($postData['longUrl']), (array) ($postData['tags'] ?? []), $meta); diff --git a/module/Rest/src/Action/ShortUrl/DeleteShortUrlAction.php b/module/Rest/src/Action/ShortUrl/DeleteShortUrlAction.php index ba39ec82..e0b269f3 100644 --- a/module/Rest/src/Action/ShortUrl/DeleteShortUrlAction.php +++ b/module/Rest/src/Action/ShortUrl/DeleteShortUrlAction.php @@ -4,20 +4,19 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Action\ShortUrl; +use Laminas\Diactoros\Response\EmptyResponse; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Core\Service\ShortUrl\DeleteShortUrlServiceInterface; use Shlinkio\Shlink\Rest\Action\AbstractRestAction; -use Zend\Diactoros\Response\EmptyResponse; class DeleteShortUrlAction extends AbstractRestAction { protected const ROUTE_PATH = '/short-urls/{shortCode}'; protected const ROUTE_ALLOWED_METHODS = [self::METHOD_DELETE]; - /** @var DeleteShortUrlServiceInterface */ - private $deleteShortUrlService; + private DeleteShortUrlServiceInterface $deleteShortUrlService; public function __construct(DeleteShortUrlServiceInterface $deleteShortUrlService, ?LoggerInterface $logger = null) { diff --git a/module/Rest/src/Action/ShortUrl/EditShortUrlAction.php b/module/Rest/src/Action/ShortUrl/EditShortUrlAction.php index 33796f7d..4270f326 100644 --- a/module/Rest/src/Action/ShortUrl/EditShortUrlAction.php +++ b/module/Rest/src/Action/ShortUrl/EditShortUrlAction.php @@ -4,21 +4,20 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Action\ShortUrl; +use Laminas\Diactoros\Response\EmptyResponse; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface; use Shlinkio\Shlink\Rest\Action\AbstractRestAction; -use Zend\Diactoros\Response\EmptyResponse; class EditShortUrlAction extends AbstractRestAction { protected const ROUTE_PATH = '/short-urls/{shortCode}'; protected const ROUTE_ALLOWED_METHODS = [self::METHOD_PATCH, self::METHOD_PUT]; - /** @var ShortUrlServiceInterface */ - private $shortUrlService; + private ShortUrlServiceInterface $shortUrlService; public function __construct(ShortUrlServiceInterface $shortUrlService, ?LoggerInterface $logger = null) { diff --git a/module/Rest/src/Action/ShortUrl/EditShortUrlTagsAction.php b/module/Rest/src/Action/ShortUrl/EditShortUrlTagsAction.php index 208be169..e30710ed 100644 --- a/module/Rest/src/Action/ShortUrl/EditShortUrlTagsAction.php +++ b/module/Rest/src/Action/ShortUrl/EditShortUrlTagsAction.php @@ -4,21 +4,20 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Action\ShortUrl; +use Laminas\Diactoros\Response\JsonResponse; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface; use Shlinkio\Shlink\Rest\Action\AbstractRestAction; -use Zend\Diactoros\Response\JsonResponse; class EditShortUrlTagsAction extends AbstractRestAction { protected const ROUTE_PATH = '/short-urls/{shortCode}/tags'; protected const ROUTE_ALLOWED_METHODS = [self::METHOD_PUT]; - /** @var ShortUrlServiceInterface */ - private $shortUrlService; + private ShortUrlServiceInterface $shortUrlService; public function __construct(ShortUrlServiceInterface $shortUrlService, ?LoggerInterface $logger = null) { diff --git a/module/Rest/src/Action/ShortUrl/ListShortUrlsAction.php b/module/Rest/src/Action/ShortUrl/ListShortUrlsAction.php index 87e7930b..a50493db 100644 --- a/module/Rest/src/Action/ShortUrl/ListShortUrlsAction.php +++ b/module/Rest/src/Action/ShortUrl/ListShortUrlsAction.php @@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\Rest\Action\ShortUrl; use Cake\Chronos\Chronos; use InvalidArgumentException; +use Laminas\Diactoros\Response\JsonResponse; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Log\LoggerInterface; @@ -14,7 +15,6 @@ use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface; use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer; use Shlinkio\Shlink\Rest\Action\AbstractRestAction; -use Zend\Diactoros\Response\JsonResponse; class ListShortUrlsAction extends AbstractRestAction { @@ -23,10 +23,8 @@ class ListShortUrlsAction extends AbstractRestAction protected const ROUTE_PATH = '/short-urls'; protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET]; - /** @var ShortUrlServiceInterface */ - private $shortUrlService; - /** @var array */ - private $domainConfig; + private ShortUrlServiceInterface $shortUrlService; + private array $domainConfig; public function __construct( ShortUrlServiceInterface $shortUrlService, @@ -39,8 +37,6 @@ class ListShortUrlsAction extends AbstractRestAction } /** - * @param Request $request - * @return Response * @throws InvalidArgumentException */ public function handle(Request $request): Response @@ -48,7 +44,7 @@ class ListShortUrlsAction extends AbstractRestAction $params = $this->queryToListParams($request->getQueryParams()); $shortUrls = $this->shortUrlService->listShortUrls(...$params); return new JsonResponse(['shortUrls' => $this->serializePaginator($shortUrls, new ShortUrlDataTransformer( - $this->domainConfig + $this->domainConfig, ))]); } @@ -71,7 +67,7 @@ class ListShortUrlsAction extends AbstractRestAction { return new DateRange( isset($query['startDate']) ? Chronos::parse($query['startDate']) : null, - isset($query['endDate']) ? Chronos::parse($query['endDate']) : null + isset($query['endDate']) ? Chronos::parse($query['endDate']) : null, ); } } diff --git a/module/Rest/src/Action/ShortUrl/ResolveShortUrlAction.php b/module/Rest/src/Action/ShortUrl/ResolveShortUrlAction.php index e041f4dc..9260f597 100644 --- a/module/Rest/src/Action/ShortUrl/ResolveShortUrlAction.php +++ b/module/Rest/src/Action/ShortUrl/ResolveShortUrlAction.php @@ -5,23 +5,21 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Action\ShortUrl; use InvalidArgumentException; +use Laminas\Diactoros\Response\JsonResponse; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer; use Shlinkio\Shlink\Rest\Action\AbstractRestAction; -use Zend\Diactoros\Response\JsonResponse; class ResolveShortUrlAction extends AbstractRestAction { protected const ROUTE_PATH = '/short-urls/{shortCode}'; protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET]; - /** @var UrlShortenerInterface */ - private $urlShortener; - /** @var array */ - private $domainConfig; + private UrlShortenerInterface $urlShortener; + private array $domainConfig; public function __construct( UrlShortenerInterface $urlShortener, @@ -34,8 +32,6 @@ class ResolveShortUrlAction extends AbstractRestAction } /** - * @param Request $request - * @return Response * @throws InvalidArgumentException */ public function handle(Request $request): Response diff --git a/module/Rest/src/Action/ShortUrl/SingleStepCreateShortUrlAction.php b/module/Rest/src/Action/ShortUrl/SingleStepCreateShortUrlAction.php index 834c6b12..e754e3ad 100644 --- a/module/Rest/src/Action/ShortUrl/SingleStepCreateShortUrlAction.php +++ b/module/Rest/src/Action/ShortUrl/SingleStepCreateShortUrlAction.php @@ -4,21 +4,20 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Action\ShortUrl; +use Laminas\Diactoros\Uri; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Model\CreateShortUrlData; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; -use Zend\Diactoros\Uri; class SingleStepCreateShortUrlAction extends AbstractCreateShortUrlAction { protected const ROUTE_PATH = '/short-urls/shorten'; protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET]; - /** @var ApiKeyServiceInterface */ - private $apiKeyService; + private ApiKeyServiceInterface $apiKeyService; public function __construct( UrlShortenerInterface $urlShortener, @@ -31,8 +30,6 @@ class SingleStepCreateShortUrlAction extends AbstractCreateShortUrlAction } /** - * @param Request $request - * @return CreateShortUrlData * @throws ValidationException */ protected function buildShortUrlData(Request $request): CreateShortUrlData diff --git a/module/Rest/src/Action/Tag/CreateTagsAction.php b/module/Rest/src/Action/Tag/CreateTagsAction.php index c26e274b..eb4a279b 100644 --- a/module/Rest/src/Action/Tag/CreateTagsAction.php +++ b/module/Rest/src/Action/Tag/CreateTagsAction.php @@ -4,20 +4,19 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Action\Tag; +use Laminas\Diactoros\Response\JsonResponse; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Rest\Action\AbstractRestAction; -use Zend\Diactoros\Response\JsonResponse; class CreateTagsAction extends AbstractRestAction { protected const ROUTE_PATH = '/tags'; protected const ROUTE_ALLOWED_METHODS = [self::METHOD_POST]; - /** @var TagServiceInterface */ - private $tagService; + private TagServiceInterface $tagService; public function __construct(TagServiceInterface $tagService, ?LoggerInterface $logger = null) { @@ -29,9 +28,7 @@ class CreateTagsAction extends AbstractRestAction * Process an incoming server request and return a response, optionally delegating * to the next middleware component to create the response. * - * @param ServerRequestInterface $request * - * @return ResponseInterface * @throws \InvalidArgumentException */ public function handle(ServerRequestInterface $request): ResponseInterface diff --git a/module/Rest/src/Action/Tag/DeleteTagsAction.php b/module/Rest/src/Action/Tag/DeleteTagsAction.php index 3d22d8dc..b8bedab9 100644 --- a/module/Rest/src/Action/Tag/DeleteTagsAction.php +++ b/module/Rest/src/Action/Tag/DeleteTagsAction.php @@ -4,20 +4,19 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Action\Tag; +use Laminas\Diactoros\Response\EmptyResponse; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Rest\Action\AbstractRestAction; -use Zend\Diactoros\Response\EmptyResponse; class DeleteTagsAction extends AbstractRestAction { protected const ROUTE_PATH = '/tags'; protected const ROUTE_ALLOWED_METHODS = [self::METHOD_DELETE]; - /** @var TagServiceInterface */ - private $tagService; + private TagServiceInterface $tagService; public function __construct(TagServiceInterface $tagService, ?LoggerInterface $logger = null) { @@ -29,9 +28,7 @@ class DeleteTagsAction extends AbstractRestAction * Process an incoming server request and return a response, optionally delegating * to the next middleware component to create the response. * - * @param ServerRequestInterface $request * - * @return ResponseInterface */ public function handle(ServerRequestInterface $request): ResponseInterface { diff --git a/module/Rest/src/Action/Tag/ListTagsAction.php b/module/Rest/src/Action/Tag/ListTagsAction.php index 22c85fb1..7cc7e063 100644 --- a/module/Rest/src/Action/Tag/ListTagsAction.php +++ b/module/Rest/src/Action/Tag/ListTagsAction.php @@ -4,20 +4,19 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Action\Tag; +use Laminas\Diactoros\Response\JsonResponse; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Rest\Action\AbstractRestAction; -use Zend\Diactoros\Response\JsonResponse; class ListTagsAction extends AbstractRestAction { protected const ROUTE_PATH = '/tags'; protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET]; - /** @var TagServiceInterface */ - private $tagService; + private TagServiceInterface $tagService; public function __construct(TagServiceInterface $tagService, ?LoggerInterface $logger = null) { @@ -29,9 +28,7 @@ class ListTagsAction extends AbstractRestAction * Process an incoming server request and return a response, optionally delegating * to the next middleware component to create the response. * - * @param ServerRequestInterface $request * - * @return ResponseInterface * @throws \InvalidArgumentException */ public function handle(ServerRequestInterface $request): ResponseInterface diff --git a/module/Rest/src/Action/Tag/UpdateTagAction.php b/module/Rest/src/Action/Tag/UpdateTagAction.php index 175ed0a0..6fb72e01 100644 --- a/module/Rest/src/Action/Tag/UpdateTagAction.php +++ b/module/Rest/src/Action/Tag/UpdateTagAction.php @@ -4,21 +4,20 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Action\Tag; +use Laminas\Diactoros\Response\EmptyResponse; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Rest\Action\AbstractRestAction; -use Zend\Diactoros\Response\EmptyResponse; class UpdateTagAction extends AbstractRestAction { protected const ROUTE_PATH = '/tags'; protected const ROUTE_ALLOWED_METHODS = [self::METHOD_PUT]; - /** @var TagServiceInterface */ - private $tagService; + private TagServiceInterface $tagService; public function __construct(TagServiceInterface $tagService, ?LoggerInterface $logger = null) { @@ -30,9 +29,7 @@ class UpdateTagAction extends AbstractRestAction * Process an incoming server request and return a response, optionally delegating * to the next middleware component to create the response. * - * @param ServerRequestInterface $request * - * @return ResponseInterface * @throws \InvalidArgumentException */ public function handle(ServerRequestInterface $request): ResponseInterface diff --git a/module/Rest/src/Action/Visit/GetVisitsAction.php b/module/Rest/src/Action/Visit/GetVisitsAction.php index ca8c2838..c1fa0095 100644 --- a/module/Rest/src/Action/Visit/GetVisitsAction.php +++ b/module/Rest/src/Action/Visit/GetVisitsAction.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Action\Visit; +use Laminas\Diactoros\Response\JsonResponse; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Log\LoggerInterface; @@ -11,7 +12,6 @@ use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait; use Shlinkio\Shlink\Core\Model\VisitsParams; use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface; use Shlinkio\Shlink\Rest\Action\AbstractRestAction; -use Zend\Diactoros\Response\JsonResponse; class GetVisitsAction extends AbstractRestAction { @@ -20,8 +20,7 @@ class GetVisitsAction extends AbstractRestAction protected const ROUTE_PATH = '/short-urls/{shortCode}/visits'; protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET]; - /** @var VisitsTrackerInterface */ - private $visitsTracker; + private VisitsTrackerInterface $visitsTracker; public function __construct(VisitsTrackerInterface $visitsTracker, ?LoggerInterface $logger = null) { diff --git a/module/Rest/src/Authentication/AuthenticationPluginManager.php b/module/Rest/src/Authentication/AuthenticationPluginManager.php index 16b8bbd1..9cd8894e 100644 --- a/module/Rest/src/Authentication/AuthenticationPluginManager.php +++ b/module/Rest/src/Authentication/AuthenticationPluginManager.php @@ -4,9 +4,9 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Authentication; -use Zend\ServiceManager\AbstractPluginManager; +use Laminas\ServiceManager\AbstractPluginManager; class AuthenticationPluginManager extends AbstractPluginManager implements AuthenticationPluginManagerInterface { - protected $instanceOf = Plugin\AuthenticationPluginInterface::class; + protected $instanceOf = Plugin\AuthenticationPluginInterface::class; // phpcs:ignore } diff --git a/module/Rest/src/Authentication/JWTService.php b/module/Rest/src/Authentication/JWTService.php deleted file mode 100644 index 77ec6728..00000000 --- a/module/Rest/src/Authentication/JWTService.php +++ /dev/null @@ -1,111 +0,0 @@ -appOptions = $appOptions; - } - - /** - * Creates a new JSON web token por provided API key - * - * @param ApiKey $apiKey - * @param int $lifetime - * @return string - */ - public function create(ApiKey $apiKey, $lifetime = self::DEFAULT_LIFETIME): string - { - $currentTimestamp = time(); - - return $this->encode([ - 'iss' => (string) $this->appOptions, - 'iat' => $currentTimestamp, - 'exp' => $currentTimestamp + $lifetime, - 'sub' => 'auth', - 'key' => $apiKey->getId(), // The ID is opaque. Returning the key would be insecure - ]); - } - - /** - * Refreshes a token and returns it with the new expiration - * - * @param string $jwt - * @param int $lifetime - * @return string - * @throws AuthenticationException If the token has expired - */ - public function refresh(string $jwt, $lifetime = self::DEFAULT_LIFETIME): string - { - $payload = $this->getPayload($jwt); - $payload['exp'] = time() + $lifetime; - return $this->encode($payload); - } - - /** - * Verifies that certain JWT is valid - * - * @param string $jwt - * @return bool - */ - public function verify(string $jwt): bool - { - try { - // If no exception is thrown while decoding the token, it is considered valid - $this->decode($jwt); - return true; - } catch (UnexpectedValueException $e) { - return false; - } - } - - /** - * Decodes certain token and returns the payload - * - * @param string $jwt - * @return array - * @throws AuthenticationException If the token has expired - */ - public function getPayload(string $jwt): array - { - try { - return $this->decode($jwt); - } catch (UnexpectedValueException $e) { - throw AuthenticationException::expiredJWT($e); - } - } - - /** - * @param array $data - * @return string - */ - private function encode(array $data): string - { - return JWT::encode($data, $this->appOptions->getSecretKey(), self::DEFAULT_ENCRYPTION_ALG); - } - - /** - * @param string $jwt - * @return array - */ - private function decode(string $jwt): array - { - return (array) JWT::decode($jwt, $this->appOptions->getSecretKey(), [self::DEFAULT_ENCRYPTION_ALG]); - } -} diff --git a/module/Rest/src/Authentication/JWTServiceInterface.php b/module/Rest/src/Authentication/JWTServiceInterface.php deleted file mode 100644 index 0ce840f1..00000000 --- a/module/Rest/src/Authentication/JWTServiceInterface.php +++ /dev/null @@ -1,50 +0,0 @@ -jwtService = $jwtService; - } - - /** - * @throws VerifyAuthenticationException - */ - public function verify(ServerRequestInterface $request): void - { - // Get token making sure the an authorization type is provided - $authToken = $request->getHeaderLine(self::HEADER_NAME); - $authTokenParts = explode(' ', $authToken); - if (count($authTokenParts) === 1) { - throw VerifyAuthenticationException::forMissingAuthType(); - } - - // Make sure the authorization type is Bearer - [$authType, $jwt] = $authTokenParts; - if (strtolower($authType) !== 'bearer') { - throw VerifyAuthenticationException::forInvalidAuthType($authType); - } - - try { - if (! $this->jwtService->verify($jwt)) { - throw $this->createInvalidTokenError(); - } - } catch (Throwable $e) { - throw $this->createInvalidTokenError(); - } - } - - private function createInvalidTokenError(): VerifyAuthenticationException - { - return VerifyAuthenticationException::forInvalidAuthToken(); - } - - public function update(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface - { - $authToken = $request->getHeaderLine(self::HEADER_NAME); - [, $jwt] = explode(' ', $authToken); - $jwt = $this->jwtService->refresh($jwt); - - return $response->withHeader(self::HEADER_NAME, sprintf('Bearer %s', $jwt)); - } -} diff --git a/module/Rest/src/Authentication/RequestToHttpAuthPlugin.php b/module/Rest/src/Authentication/RequestToHttpAuthPlugin.php index c10c0321..c45bb9a5 100644 --- a/module/Rest/src/Authentication/RequestToHttpAuthPlugin.php +++ b/module/Rest/src/Authentication/RequestToHttpAuthPlugin.php @@ -17,11 +17,9 @@ class RequestToHttpAuthPlugin implements RequestToHttpAuthPluginInterface // When more than one is matched, the first one to be found will take precedence. public const SUPPORTED_AUTH_HEADERS = [ Plugin\ApiKeyHeaderPlugin::HEADER_NAME, - Plugin\AuthorizationHeaderPlugin::HEADER_NAME, ]; - /** @var AuthenticationPluginManagerInterface */ - private $authPluginManager; + private AuthenticationPluginManagerInterface $authPluginManager; public function __construct(AuthenticationPluginManagerInterface $authPluginManager) { @@ -44,10 +42,8 @@ class RequestToHttpAuthPlugin implements RequestToHttpAuthPluginInterface { return array_reduce( self::SUPPORTED_AUTH_HEADERS, - function (bool $carry, string $header) use ($request) { - return $carry || $request->hasHeader($header); - }, - false + fn (bool $carry, string $header) => $carry || $request->hasHeader($header), + false, ); } diff --git a/module/Rest/src/ConfigProvider.php b/module/Rest/src/ConfigProvider.php index 0c0e99a5..570eab85 100644 --- a/module/Rest/src/ConfigProvider.php +++ b/module/Rest/src/ConfigProvider.php @@ -4,24 +4,27 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest; +use Closure; + +use function Functional\first; +use function Functional\map; use function Shlinkio\Shlink\Common\loadConfigFromGlob; use function sprintf; class ConfigProvider { private const ROUTES_PREFIX = '/rest/v{version:1|2}'; + private const UNVERSIONED_ROUTES_PREFIX = '/rest'; + public const UNVERSIONED_HEALTH_ENDPOINT_NAME = 'unversioned_health'; - /** @var callable */ - private $loadConfig; + private Closure $loadConfig; public function __construct(?callable $loadConfig = null) { - $this->loadConfig = $loadConfig ?? function (string $glob) { - return loadConfigFromGlob($glob); - }; + $this->loadConfig = Closure::fromCallable($loadConfig ?? fn (string $glob) => loadConfigFromGlob($glob)); } - public function __invoke() + public function __invoke(): array { $config = ($this->loadConfig)(__DIR__ . '/../config/{,*.}config.php'); return $this->applyRoutesPrefix($config); @@ -29,14 +32,33 @@ class ConfigProvider private function applyRoutesPrefix(array $config): array { - $routes =& $config['routes'] ?? []; + $routes = $config['routes'] ?? []; + $healthRoute = $this->buildUnversionedHealthRouteFromExistingRoutes($routes); - // Prepend the routes prefix to every path - foreach ($routes as $key => $route) { + $prefixRoute = static function (array $route) { ['path' => $path] = $route; - $routes[$key]['path'] = sprintf('%s%s', self::ROUTES_PREFIX, $path); - } + $route['path'] = sprintf('%s%s', self::ROUTES_PREFIX, $path); + + return $route; + }; + $prefixedRoutes = map($routes, $prefixRoute); + + $config['routes'] = $healthRoute !== null ? [...$prefixedRoutes, $healthRoute] : $prefixedRoutes; return $config; } + + private function buildUnversionedHealthRouteFromExistingRoutes(array $routes): ?array + { + $healthRoute = first($routes, fn (array $route) => $route['path'] === '/health'); + if ($healthRoute === null) { + return null; + } + + $path = $healthRoute['path']; + $healthRoute['path'] = sprintf('%s%s', self::UNVERSIONED_ROUTES_PREFIX, $path); + $healthRoute['name'] = self::UNVERSIONED_HEALTH_ENDPOINT_NAME; + + return $healthRoute; + } } diff --git a/module/Rest/src/Entity/ApiKey.php b/module/Rest/src/Entity/ApiKey.php index 887768d1..8c6d3aeb 100644 --- a/module/Rest/src/Entity/ApiKey.php +++ b/module/Rest/src/Entity/ApiKey.php @@ -12,12 +12,9 @@ class ApiKey extends AbstractEntity { use StringUtilsTrait; - /** @var string */ - private $key; - /** @var Chronos|null */ - private $expirationDate; - /** @var bool */ - private $enabled; + private string $key; + private ?Chronos $expirationDate; + private bool $enabled; public function __construct(?Chronos $expirationDate = null) { diff --git a/module/Rest/src/Exception/AuthenticationException.php b/module/Rest/src/Exception/AuthenticationException.php deleted file mode 100644 index 3e60edb6..00000000 --- a/module/Rest/src/Exception/AuthenticationException.php +++ /dev/null @@ -1,16 +0,0 @@ -detail = $e->getMessage(); diff --git a/module/Rest/src/Exception/VerifyAuthenticationException.php b/module/Rest/src/Exception/VerifyAuthenticationException.php index 3b2a4115..702230ff 100644 --- a/module/Rest/src/Exception/VerifyAuthenticationException.php +++ b/module/Rest/src/Exception/VerifyAuthenticationException.php @@ -5,10 +5,8 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Exception; use Fig\Http\Message\StatusCodeInterface; -use Zend\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait; -use Zend\ProblemDetails\Exception\ProblemDetailsExceptionInterface; - -use function sprintf; +use Mezzio\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait; +use Mezzio\ProblemDetails\Exception\ProblemDetailsExceptionInterface; class VerifyAuthenticationException extends RuntimeException implements ProblemDetailsExceptionInterface { @@ -25,46 +23,4 @@ class VerifyAuthenticationException extends RuntimeException implements ProblemD return $e; } - - /** @deprecated */ - public static function forInvalidAuthToken(): self - { - $e = new self( - 'Missing or invalid auth token provided. Perform a new authentication request and send provided ' - . 'token on every new request on the Authorization header' - ); - - $e->detail = $e->getMessage(); - $e->title = 'Invalid auth token'; - $e->type = 'INVALID_AUTH_TOKEN'; - $e->status = StatusCodeInterface::STATUS_UNAUTHORIZED; - - return $e; - } - - /** @deprecated */ - public static function forMissingAuthType(): self - { - $e = new self('You need to provide the Bearer type in the Authorization header.'); - - $e->detail = $e->getMessage(); - $e->title = 'Invalid authorization'; - $e->type = 'INVALID_AUTHORIZATION'; - $e->status = StatusCodeInterface::STATUS_UNAUTHORIZED; - - return $e; - } - - /** @deprecated */ - public static function forInvalidAuthType(string $providedType): self - { - $e = new self(sprintf('Provided authorization type %s is not supported. Use Bearer instead.', $providedType)); - - $e->detail = $e->getMessage(); - $e->title = 'Invalid authorization'; - $e->type = 'INVALID_AUTHORIZATION'; - $e->status = StatusCodeInterface::STATUS_UNAUTHORIZED; - - return $e; - } } diff --git a/module/Rest/src/Middleware/AuthenticationMiddleware.php b/module/Rest/src/Middleware/AuthenticationMiddleware.php index 4fd44bcf..609ced0a 100644 --- a/module/Rest/src/Middleware/AuthenticationMiddleware.php +++ b/module/Rest/src/Middleware/AuthenticationMiddleware.php @@ -6,21 +6,19 @@ namespace Shlinkio\Shlink\Rest\Middleware; use Fig\Http\Message\RequestMethodInterface; use Fig\Http\Message\StatusCodeInterface; +use Mezzio\Router\RouteResult; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Shlinkio\Shlink\Rest\Authentication\RequestToHttpAuthPluginInterface; -use Zend\Expressive\Router\RouteResult; use function Functional\contains; class AuthenticationMiddleware implements MiddlewareInterface, StatusCodeInterface, RequestMethodInterface { - /** @var array */ - private $routesWhitelist; - /** @var RequestToHttpAuthPluginInterface */ - private $requestToAuthPlugin; + private array $routesWhitelist; + private RequestToHttpAuthPluginInterface $requestToAuthPlugin; public function __construct(RequestToHttpAuthPluginInterface $requestToAuthPlugin, array $routesWhitelist) { diff --git a/module/Rest/src/Middleware/BackwardsCompatibleProblemDetailsMiddleware.php b/module/Rest/src/Middleware/BackwardsCompatibleProblemDetailsMiddleware.php deleted file mode 100644 index 0812c7e0..00000000 --- a/module/Rest/src/Middleware/BackwardsCompatibleProblemDetailsMiddleware.php +++ /dev/null @@ -1,84 +0,0 @@ - 'type', - 'message' => 'detail', - ]; - - /** @var array */ - private $defaultTypeFallbacks; - /** @var int */ - private $jsonFlags; - - public function __construct(array $defaultTypeFallbacks, int $jsonFlags) - { - $this->defaultTypeFallbacks = $defaultTypeFallbacks; - $this->jsonFlags = $jsonFlags; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - $resp = $handler->handle($request); - if ($resp->getHeaderLine('Content-type') !== 'application/problem+json') { - return $resp; - } - - try { - $body = (string) $resp->getBody(); - $payload = json_decode($body); - } catch (Throwable $e) { - return $resp; - } - - $payload = $this->mapStandardErrorTypes($payload, $resp->getStatusCode()); - - if ($this->isVersionOne($request)) { - $payload = $this->makePayloadBackwardsCompatible($payload); - } - - return new JsonResponse($payload, $resp->getStatusCode(), $resp->getHeaders(), $this->jsonFlags); - } - - private function mapStandardErrorTypes(array $payload, int $respStatusCode): array - { - $type = $payload['type'] ?? ''; - if (strpos($type, 'https://httpstatus.es') === 0) { - $payload['type'] = $this->defaultTypeFallbacks[$respStatusCode] ?? $type; - } - - return $payload; - } - - /** @deprecated When Shlink 2 is released, do not chekc the version */ - private function isVersionOne(ServerRequestInterface $request): bool - { - $path = $request->getUri()->getPath(); - return strpos($path, '/v') === false || strpos($path, '/v1') === 0; - } - - /** @deprecated When Shlink v2 is released, do not map old fields */ - private function makePayloadBackwardsCompatible(array $payload): array - { - return reduce_left(self::BACKWARDS_COMPATIBLE_FIELDS, function (string $newKey, string $oldKey, $c, $acc) { - $acc[$oldKey] = $acc[$newKey]; - return $acc; - }, $payload); - } -} diff --git a/module/Rest/src/Middleware/BodyParserMiddleware.php b/module/Rest/src/Middleware/BodyParserMiddleware.php index 5814753e..955041d9 100644 --- a/module/Rest/src/Middleware/BodyParserMiddleware.php +++ b/module/Rest/src/Middleware/BodyParserMiddleware.php @@ -23,10 +23,7 @@ class BodyParserMiddleware implements MiddlewareInterface, RequestMethodInterfac * Process an incoming server request and return a response, optionally delegating * to the next middleware component to create the response. * - * @param Request $request - * @param RequestHandlerInterface $handler * - * @return Response */ public function process(Request $request, RequestHandlerInterface $handler): Response { @@ -55,8 +52,6 @@ class BodyParserMiddleware implements MiddlewareInterface, RequestMethodInterfac } /** - * @param Request $request - * @return string */ private function getRequestContentType(Request $request): string { @@ -66,8 +61,6 @@ class BodyParserMiddleware implements MiddlewareInterface, RequestMethodInterfac } /** - * @param Request $request - * @return Request */ private function parseFromJson(Request $request): Request { @@ -81,8 +74,6 @@ class BodyParserMiddleware implements MiddlewareInterface, RequestMethodInterfac } /** - * @param Request $request - * @return Request */ private function parseFromUrlEncoded(Request $request): Request { diff --git a/module/Rest/src/Middleware/CrossDomainMiddleware.php b/module/Rest/src/Middleware/CrossDomainMiddleware.php index aacda9fc..23cd9ebe 100644 --- a/module/Rest/src/Middleware/CrossDomainMiddleware.php +++ b/module/Rest/src/Middleware/CrossDomainMiddleware.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Middleware; use Fig\Http\Message\RequestMethodInterface; +use Mezzio\Router\RouteResult; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Shlinkio\Shlink\Rest\Authentication; -use Zend\Expressive\Router\RouteResult; use function implode; @@ -27,7 +27,6 @@ class CrossDomainMiddleware implements MiddlewareInterface, RequestMethodInterfa $response = $response->withHeader('Access-Control-Allow-Origin', $request->getHeader('Origin')) ->withHeader('Access-Control-Expose-Headers', implode(', ', [ Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME, - Authentication\Plugin\AuthorizationHeaderPlugin::HEADER_NAME, ])); if ($request->getMethod() !== self::METHOD_OPTIONS) { return $response; diff --git a/module/Rest/src/Middleware/EmptyResponseImplicitOptionsMiddlewareFactory.php b/module/Rest/src/Middleware/EmptyResponseImplicitOptionsMiddlewareFactory.php index 06cdc435..b4bc1952 100644 --- a/module/Rest/src/Middleware/EmptyResponseImplicitOptionsMiddlewareFactory.php +++ b/module/Rest/src/Middleware/EmptyResponseImplicitOptionsMiddlewareFactory.php @@ -4,15 +4,13 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Middleware; -use Zend\Diactoros\Response\EmptyResponse; -use Zend\Expressive\Router\Middleware\ImplicitOptionsMiddleware; +use Laminas\Diactoros\Response\EmptyResponse; +use Mezzio\Router\Middleware\ImplicitOptionsMiddleware; class EmptyResponseImplicitOptionsMiddlewareFactory { - public function __invoke() + public function __invoke(): ImplicitOptionsMiddleware { - return new ImplicitOptionsMiddleware(function () { - return new EmptyResponse(); - }); + return new ImplicitOptionsMiddleware(fn () => new EmptyResponse()); } } diff --git a/module/Rest/src/Middleware/PathVersionMiddleware.php b/module/Rest/src/Middleware/PathVersionMiddleware.php deleted file mode 100644 index 84921710..00000000 --- a/module/Rest/src/Middleware/PathVersionMiddleware.php +++ /dev/null @@ -1,30 +0,0 @@ -getUri(); - $path = $uri->getPath(); - - // If the path does not begin with the version number, prepend v1 by default for BC purposes - if (strpos($path, '/v') !== 0) { - $request = $request->withUri($uri->withPath('/v1' . $uri->getPath())); - } - - return $handler->handle($request); - } -} diff --git a/module/Rest/src/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddleware.php b/module/Rest/src/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddleware.php index 98b64401..a0896976 100644 --- a/module/Rest/src/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddleware.php +++ b/module/Rest/src/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddleware.php @@ -4,12 +4,12 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Middleware\ShortUrl; +use Laminas\Diactoros\Response; +use Laminas\Diactoros\Response\JsonResponse; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use Zend\Diactoros\Response; -use Zend\Diactoros\Response\JsonResponse; use function array_shift; use function explode; diff --git a/module/Rest/src/Middleware/ShortUrl/ShortCodePathMiddleware.php b/module/Rest/src/Middleware/ShortUrl/ShortCodePathMiddleware.php deleted file mode 100644 index 683c3af5..00000000 --- a/module/Rest/src/Middleware/ShortUrl/ShortCodePathMiddleware.php +++ /dev/null @@ -1,34 +0,0 @@ -getUri(); - $path = $uri->getPath(); - - // If the path starts with the old prefix, replace it by the new one - return $handler->handle( - $request->withUri($uri->withPath(str_replace(self::OLD_PATH_PREFIX, self::NEW_PATH_PREFIX, $path))) - ); - } -} diff --git a/module/Rest/src/Service/ApiKeyService.php b/module/Rest/src/Service/ApiKeyService.php index 030957fd..baa545c0 100644 --- a/module/Rest/src/Service/ApiKeyService.php +++ b/module/Rest/src/Service/ApiKeyService.php @@ -13,8 +13,7 @@ use function sprintf; class ApiKeyService implements ApiKeyServiceInterface { - /** @var EntityManagerInterface */ - private $em; + private EntityManagerInterface $em; public function __construct(EntityManagerInterface $em) { diff --git a/module/Rest/test-api/Action/CreateShortUrlActionTest.php b/module/Rest/test-api/Action/CreateShortUrlActionTest.php index 37a2630c..9ec8e8e2 100644 --- a/module/Rest/test-api/Action/CreateShortUrlActionTest.php +++ b/module/Rest/test-api/Action/CreateShortUrlActionTest.php @@ -49,9 +49,7 @@ class CreateShortUrlActionTest extends ApiTestCase $this->assertEquals(self::STATUS_BAD_REQUEST, $statusCode); $this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); $this->assertEquals($detail, $payload['detail']); - $this->assertEquals($detail, $payload['message']); // Deprecated $this->assertEquals('INVALID_SLUG', $payload['type']); - $this->assertEquals('INVALID_SLUG', $payload['error']); // Deprecated $this->assertEquals('Invalid custom slug', $payload['title']); $this->assertEquals($slug, $payload['customSlug']); @@ -91,9 +89,7 @@ class CreateShortUrlActionTest extends ApiTestCase public function provideMaxVisits(): array { - return map(range(10, 15), function (int $i) { - return [$i]; - }); + return map(range(10, 15), fn (int $i) => [$i]); } /** @test */ @@ -217,7 +213,7 @@ class CreateShortUrlActionTest extends ApiTestCase } /** @test */ - public function failsToCreateShortUrlWithInvalidOriginalUrl(): void + public function failsToCreateShortUrlWithInvalidLongUrl(): void { $url = 'https://this-has-to-be-invalid.com'; $expectedDetail = sprintf('Provided URL %s is invalid. Try with a different one.', $url); @@ -227,9 +223,7 @@ class CreateShortUrlActionTest extends ApiTestCase $this->assertEquals(self::STATUS_BAD_REQUEST, $statusCode); $this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); $this->assertEquals('INVALID_URL', $payload['type']); - $this->assertEquals('INVALID_URL', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Invalid URL', $payload['title']); $this->assertEquals($url, $payload['url']); } diff --git a/module/Rest/test-api/Action/DeleteShortUrlActionTest.php b/module/Rest/test-api/Action/DeleteShortUrlActionTest.php index 4680a6f6..7bf01c51 100644 --- a/module/Rest/test-api/Action/DeleteShortUrlActionTest.php +++ b/module/Rest/test-api/Action/DeleteShortUrlActionTest.php @@ -19,9 +19,7 @@ class DeleteShortUrlActionTest extends ApiTestCase $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']); $this->assertEquals('INVALID_SHORTCODE', $payload['type']); - $this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Short URL not found', $payload['title']); $this->assertEquals('invalid', $payload['shortCode']); } @@ -41,9 +39,7 @@ class DeleteShortUrlActionTest extends ApiTestCase $this->assertEquals(self::STATUS_UNPROCESSABLE_ENTITY, $resp->getStatusCode()); $this->assertEquals(self::STATUS_UNPROCESSABLE_ENTITY, $payload['status']); $this->assertEquals('INVALID_SHORTCODE_DELETION', $payload['type']); - $this->assertEquals('INVALID_SHORTCODE_DELETION', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Cannot delete short URL', $payload['title']); } } diff --git a/module/Rest/test-api/Action/EditShortUrlActionTest.php b/module/Rest/test-api/Action/EditShortUrlActionTest.php index bbcb043a..024ffb79 100644 --- a/module/Rest/test-api/Action/EditShortUrlActionTest.php +++ b/module/Rest/test-api/Action/EditShortUrlActionTest.php @@ -20,9 +20,7 @@ class EditShortUrlActionTest extends ApiTestCase $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']); $this->assertEquals('INVALID_SHORTCODE', $payload['type']); - $this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Short URL not found', $payload['title']); $this->assertEquals('invalid', $payload['shortCode']); } @@ -40,9 +38,7 @@ class EditShortUrlActionTest extends ApiTestCase $this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode()); $this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); $this->assertEquals('INVALID_ARGUMENT', $payload['type']); - $this->assertEquals('INVALID_ARGUMENT', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Invalid data', $payload['title']); } } diff --git a/module/Rest/test-api/Action/EditShortUrlTagsActionTest.php b/module/Rest/test-api/Action/EditShortUrlTagsActionTest.php index e2922092..d48ffc5f 100644 --- a/module/Rest/test-api/Action/EditShortUrlTagsActionTest.php +++ b/module/Rest/test-api/Action/EditShortUrlTagsActionTest.php @@ -20,9 +20,7 @@ class EditShortUrlTagsActionTest extends ApiTestCase $this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode()); $this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); $this->assertEquals('INVALID_ARGUMENT', $payload['type']); - $this->assertEquals('INVALID_ARGUMENT', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Invalid data', $payload['title']); } @@ -39,9 +37,7 @@ class EditShortUrlTagsActionTest extends ApiTestCase $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']); $this->assertEquals('INVALID_SHORTCODE', $payload['type']); - $this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Short URL not found', $payload['title']); $this->assertEquals('invalid', $payload['shortCode']); } diff --git a/module/Rest/test-api/Action/GetVisitsActionTest.php b/module/Rest/test-api/Action/GetVisitsActionTest.php index 0db06848..f6167f78 100644 --- a/module/Rest/test-api/Action/GetVisitsActionTest.php +++ b/module/Rest/test-api/Action/GetVisitsActionTest.php @@ -19,9 +19,7 @@ class GetVisitsActionTest extends ApiTestCase $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']); $this->assertEquals('INVALID_SHORTCODE', $payload['type']); - $this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Short URL not found', $payload['title']); $this->assertEquals('invalid', $payload['shortCode']); } diff --git a/module/Rest/test-api/Action/ListShortUrlsTest.php b/module/Rest/test-api/Action/ListShortUrlsTest.php index 0952a627..f8d8c542 100644 --- a/module/Rest/test-api/Action/ListShortUrlsTest.php +++ b/module/Rest/test-api/Action/ListShortUrlsTest.php @@ -24,7 +24,6 @@ class ListShortUrlsTest extends ApiTestCase 'validUntil' => null, 'maxVisits' => null, ], - 'originalUrl' => 'https://shlink.io', ]; private const SHORT_URL_CUSTOM_SLUG_AND_DOMAIN = [ 'shortCode' => 'custom-with-domain', @@ -38,7 +37,6 @@ class ListShortUrlsTest extends ApiTestCase 'validUntil' => null, 'maxVisits' => null, ], - 'originalUrl' => 'https://google.com', ]; private const SHORT_URL_META = [ 'shortCode' => 'def456', @@ -54,9 +52,6 @@ class ListShortUrlsTest extends ApiTestCase 'validUntil' => null, 'maxVisits' => null, ], - 'originalUrl' => - 'https://blog.alejandrocelaya.com/2017/12/09' - . '/acmailer-7-0-the-most-important-release-in-a-long-time/', ]; private const SHORT_URL_CUSTOM_SLUG = [ 'shortCode' => 'custom', @@ -70,7 +65,6 @@ class ListShortUrlsTest extends ApiTestCase 'validUntil' => null, 'maxVisits' => 2, ], - 'originalUrl' => 'https://shlink.io', ]; private const SHORT_URL_CUSTOM_DOMAIN = [ 'shortCode' => 'ghi789', @@ -86,9 +80,6 @@ class ListShortUrlsTest extends ApiTestCase 'validUntil' => null, 'maxVisits' => null, ], - 'originalUrl' => - 'https://blog.alejandrocelaya.com/2019/04/27' - . '/considerations-to-properly-use-open-source-software-projects/', ]; /** diff --git a/module/Rest/test-api/Action/ResolveShortUrlActionTest.php b/module/Rest/test-api/Action/ResolveShortUrlActionTest.php index e4d45f4a..09f48113 100644 --- a/module/Rest/test-api/Action/ResolveShortUrlActionTest.php +++ b/module/Rest/test-api/Action/ResolveShortUrlActionTest.php @@ -19,9 +19,7 @@ class ResolveShortUrlActionTest extends ApiTestCase $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']); $this->assertEquals('INVALID_SHORTCODE', $payload['type']); - $this->assertEquals('INVALID_SHORTCODE', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Short URL not found', $payload['title']); $this->assertEquals('invalid', $payload['shortCode']); } diff --git a/module/Rest/test-api/Action/UpdateTagActionTest.php b/module/Rest/test-api/Action/UpdateTagActionTest.php index fa07fcd1..eb70685a 100644 --- a/module/Rest/test-api/Action/UpdateTagActionTest.php +++ b/module/Rest/test-api/Action/UpdateTagActionTest.php @@ -23,9 +23,7 @@ class UpdateTagActionTest extends ApiTestCase $this->assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode()); $this->assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); $this->assertEquals('INVALID_ARGUMENT', $payload['type']); - $this->assertEquals('INVALID_ARGUMENT', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Invalid data', $payload['title']); } @@ -50,9 +48,7 @@ class UpdateTagActionTest extends ApiTestCase $this->assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); $this->assertEquals(self::STATUS_NOT_FOUND, $payload['status']); $this->assertEquals('TAG_NOT_FOUND', $payload['type']); - $this->assertEquals('TAG_NOT_FOUND', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Tag not found', $payload['title']); } @@ -70,9 +66,7 @@ class UpdateTagActionTest extends ApiTestCase $this->assertEquals(self::STATUS_CONFLICT, $resp->getStatusCode()); $this->assertEquals(self::STATUS_CONFLICT, $payload['status']); $this->assertEquals('TAG_CONFLICT', $payload['type']); - $this->assertEquals('TAG_CONFLICT', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Tag conflict', $payload['title']); } diff --git a/module/Rest/test-api/Fixtures/ShortUrlsFixture.php b/module/Rest/test-api/Fixtures/ShortUrlsFixture.php index cc359047..0b5a841a 100644 --- a/module/Rest/test-api/Fixtures/ShortUrlsFixture.php +++ b/module/Rest/test-api/Fixtures/ShortUrlsFixture.php @@ -16,37 +16,36 @@ class ShortUrlsFixture extends AbstractFixture /** * Load data fixtures with the passed EntityManager * - * @param ObjectManager $manager */ public function load(ObjectManager $manager): void { $abcShortUrl = $this->setShortUrlDate( new ShortUrl('https://shlink.io', ShortUrlMeta::createFromRawData(['customSlug' => 'abc123'])), - '2018-05-01' + '2018-05-01', ); $manager->persist($abcShortUrl); $defShortUrl = $this->setShortUrlDate(new ShortUrl( 'https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/', - ShortUrlMeta::createFromParams(Chronos::parse('2020-05-01'), null, 'def456') + ShortUrlMeta::createFromParams(Chronos::parse('2020-05-01'), null, 'def456'), ), '2019-01-01 00:00:10'); $manager->persist($defShortUrl); $customShortUrl = $this->setShortUrlDate(new ShortUrl( 'https://shlink.io', - ShortUrlMeta::createFromParams(null, null, 'custom', 2) + ShortUrlMeta::createFromParams(null, null, 'custom', 2), ), '2019-01-01 00:00:20'); $manager->persist($customShortUrl); $withDomainShortUrl = $this->setShortUrlDate(new ShortUrl( 'https://blog.alejandrocelaya.com/2019/04/27/considerations-to-properly-use-open-source-software-projects/', - ShortUrlMeta::createFromRawData(['domain' => 'example.com', 'customSlug' => 'ghi789']) + ShortUrlMeta::createFromRawData(['domain' => 'example.com', 'customSlug' => 'ghi789']), ), '2019-01-01 00:00:30'); $manager->persist($withDomainShortUrl); $withDomainAndSlugShortUrl = $this->setShortUrlDate(new ShortUrl( 'https://google.com', - ShortUrlMeta::createFromRawData(['domain' => 'some-domain.com', 'customSlug' => 'custom-with-domain']) + ShortUrlMeta::createFromRawData(['domain' => 'some-domain.com', 'customSlug' => 'custom-with-domain']), ), '2018-10-20'); $manager->persist($withDomainAndSlugShortUrl); diff --git a/module/Rest/test-api/Middleware/AuthenticationTest.php b/module/Rest/test-api/Middleware/AuthenticationTest.php index d92f4a44..aa90451f 100644 --- a/module/Rest/test-api/Middleware/AuthenticationTest.php +++ b/module/Rest/test-api/Middleware/AuthenticationTest.php @@ -18,18 +18,16 @@ class AuthenticationTest extends ApiTestCase { $expectedDetail = sprintf( 'Expected one of the following authentication headers, ["%s"], but none were provided', - implode('", "', RequestToHttpAuthPlugin::SUPPORTED_AUTH_HEADERS) + implode('", "', RequestToHttpAuthPlugin::SUPPORTED_AUTH_HEADERS), ); - $resp = $this->callApi(self::METHOD_GET, '/short-codes'); + $resp = $this->callApi(self::METHOD_GET, '/short-urls'); $payload = $this->getJsonResponsePayload($resp); $this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode()); $this->assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']); $this->assertEquals('INVALID_AUTHORIZATION', $payload['type']); - $this->assertEquals('INVALID_AUTHORIZATION', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Invalid authorization', $payload['title']); } @@ -41,7 +39,7 @@ class AuthenticationTest extends ApiTestCase { $expectedDetail = 'Provided API key does not exist or is invalid.'; - $resp = $this->callApi(self::METHOD_GET, '/short-codes', [ + $resp = $this->callApi(self::METHOD_GET, '/short-urls', [ 'headers' => [ Plugin\ApiKeyHeaderPlugin::HEADER_NAME => $apiKey, ], @@ -51,9 +49,7 @@ class AuthenticationTest extends ApiTestCase $this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode()); $this->assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']); $this->assertEquals('INVALID_API_KEY', $payload['type']); - $this->assertEquals('INVALID_API_KEY', $payload['error']); // Deprecated $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated $this->assertEquals('Invalid API key', $payload['title']); } @@ -63,53 +59,4 @@ class AuthenticationTest extends ApiTestCase yield 'key which is expired' => ['expired_api_key']; yield 'key which is disabled' => ['disabled_api_key']; } - - /** - * @test - * @dataProvider provideInvalidAuthorizations - */ - public function authorizationErrorIsReturnedIfInvalidDataIsProvided( - string $authValue, - string $expectedDetail, - string $expectedType, - string $expectedTitle - ): void { - $resp = $this->callApi(self::METHOD_GET, '/short-codes', [ - 'headers' => [ - Plugin\AuthorizationHeaderPlugin::HEADER_NAME => $authValue, - ], - ]); - $payload = $this->getJsonResponsePayload($resp); - - $this->assertEquals(self::STATUS_UNAUTHORIZED, $resp->getStatusCode()); - $this->assertEquals(self::STATUS_UNAUTHORIZED, $payload['status']); - $this->assertEquals($expectedType, $payload['type']); - $this->assertEquals($expectedType, $payload['error']); // Deprecated - $this->assertEquals($expectedDetail, $payload['detail']); - $this->assertEquals($expectedDetail, $payload['message']); // Deprecated - $this->assertEquals($expectedTitle, $payload['title']); - } - - public function provideInvalidAuthorizations(): iterable - { - yield 'no type' => [ - 'invalid', - 'You need to provide the Bearer type in the Authorization header.', - 'INVALID_AUTHORIZATION', - 'Invalid authorization', - ]; - yield 'invalid type' => [ - 'Basic invalid', - 'Provided authorization type Basic is not supported. Use Bearer instead.', - 'INVALID_AUTHORIZATION', - 'Invalid authorization', - ]; - yield 'invalid JWT' => [ - 'Bearer invalid', - 'Missing or invalid auth token provided. Perform a new authentication request and send provided ' - . 'token on every new request on the Authorization header', - 'INVALID_AUTH_TOKEN', - 'Invalid auth token', - ]; - } } diff --git a/module/Rest/test/Action/AuthenticateActionTest.php b/module/Rest/test/Action/AuthenticateActionTest.php deleted file mode 100644 index eb4ca6dc..00000000 --- a/module/Rest/test/Action/AuthenticateActionTest.php +++ /dev/null @@ -1,71 +0,0 @@ -apiKeyService = $this->prophesize(ApiKeyService::class); - $this->jwtService = $this->prophesize(JWTService::class); - $this->jwtService->create(Argument::cetera())->willReturn(''); - - $this->action = new AuthenticateAction($this->apiKeyService->reveal(), $this->jwtService->reveal()); - } - - /** @test */ - public function notProvidingAuthDataReturnsError() - { - $resp = $this->action->handle(new ServerRequest()); - $this->assertEquals(400, $resp->getStatusCode()); - } - - /** @test */ - public function properApiKeyReturnsTokenInResponse() - { - $this->apiKeyService->getByKey('foo')->willReturn((new ApiKey())->setId('5')) - ->shouldBeCalledOnce(); - - $request = (new ServerRequest())->withParsedBody([ - 'apiKey' => 'foo', - ]); - $response = $this->action->handle($request); - $this->assertEquals(200, $response->getStatusCode()); - - $response->getBody()->rewind(); - $this->assertTrue(strpos($response->getBody()->getContents(), '"token"') > 0); - } - - /** @test */ - public function invalidApiKeyReturnsErrorResponse() - { - $this->apiKeyService->getByKey('foo')->willReturn((new ApiKey())->disable()) - ->shouldBeCalledOnce(); - - $request = (new ServerRequest())->withParsedBody([ - 'apiKey' => 'foo', - ]); - $response = $this->action->handle($request); - $this->assertEquals(401, $response->getStatusCode()); - } -} diff --git a/module/Rest/test/Action/HealthActionTest.php b/module/Rest/test/Action/HealthActionTest.php index a52b7d5e..813fa5cc 100644 --- a/module/Rest/test/Action/HealthActionTest.php +++ b/module/Rest/test/Action/HealthActionTest.php @@ -6,19 +6,17 @@ namespace ShlinkioTest\Shlink\Rest\Action; use Doctrine\DBAL\Connection; use Exception; +use Laminas\Diactoros\Response\JsonResponse; +use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Options\AppOptions; use Shlinkio\Shlink\Rest\Action\HealthAction; -use Zend\Diactoros\Response\JsonResponse; -use Zend\Diactoros\ServerRequest; class HealthActionTest extends TestCase { - /** @var HealthAction */ - private $action; - /** @var ObjectProphecy */ - private $conn; + private HealthAction $action; + private ObjectProphecy $conn; public function setUp(): void { @@ -27,7 +25,7 @@ class HealthActionTest extends TestCase } /** @test */ - public function passResponseIsReturnedWhenConnectionSucceeds() + public function passResponseIsReturnedWhenConnectionSucceeds(): void { $ping = $this->conn->ping()->willReturn(true); @@ -47,7 +45,7 @@ class HealthActionTest extends TestCase } /** @test */ - public function failResponseIsReturnedWhenConnectionFails() + public function failResponseIsReturnedWhenConnectionFails(): void { $ping = $this->conn->ping()->willReturn(false); @@ -67,7 +65,7 @@ class HealthActionTest extends TestCase } /** @test */ - public function failResponseIsReturnedWhenConnectionThrowsException() + public function failResponseIsReturnedWhenConnectionThrowsException(): void { $ping = $this->conn->ping()->willThrow(Exception::class); diff --git a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php index 37f737f9..b459765c 100644 --- a/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/CreateShortUrlActionTest.php @@ -5,6 +5,9 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\ShortUrl; use Cake\Chronos\Chronos; +use Laminas\Diactoros\ServerRequest; +use Laminas\Diactoros\ServerRequestFactory; +use Laminas\Diactoros\Uri; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; @@ -13,9 +16,6 @@ use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Service\UrlShortener; use Shlinkio\Shlink\Rest\Action\ShortUrl\CreateShortUrlAction; -use Zend\Diactoros\ServerRequest; -use Zend\Diactoros\ServerRequestFactory; -use Zend\Diactoros\Uri; use function strpos; @@ -26,10 +26,8 @@ class CreateShortUrlActionTest extends TestCase 'hostname' => 'foo.com', ]; - /** @var CreateShortUrlAction */ - private $action; - /** @var ObjectProphecy */ - private $urlShortener; + private CreateShortUrlAction $action; + private ObjectProphecy $urlShortener; public function setUp(): void { @@ -54,7 +52,7 @@ class CreateShortUrlActionTest extends TestCase $shorten = $this->urlShortener->urlToShortCode( Argument::type(Uri::class), Argument::type('array'), - $expectedMeta + $expectedMeta, )->willReturn($shortUrl); $request = ServerRequestFactory::fromGlobals()->withParsedBody($body); diff --git a/module/Rest/test/Action/ShortUrl/DeleteShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/DeleteShortUrlActionTest.php index 782181ce..cbd8e2bc 100644 --- a/module/Rest/test/Action/ShortUrl/DeleteShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/DeleteShortUrlActionTest.php @@ -4,19 +4,17 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\ShortUrl; +use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Service\ShortUrl\DeleteShortUrlServiceInterface; use Shlinkio\Shlink\Rest\Action\ShortUrl\DeleteShortUrlAction; -use Zend\Diactoros\ServerRequest; class DeleteShortUrlActionTest extends TestCase { - /** @var DeleteShortUrlAction */ - private $action; - /** @var ObjectProphecy */ - private $service; + private DeleteShortUrlAction $action; + private ObjectProphecy $service; public function setUp(): void { @@ -27,7 +25,7 @@ class DeleteShortUrlActionTest extends TestCase /** @test */ public function emptyResponseIsReturnedIfProperlyDeleted(): void { - $deleteByShortCode = $this->service->deleteByShortCode(Argument::any())->will(function () { + $deleteByShortCode = $this->service->deleteByShortCode(Argument::any())->will(function (): void { }); $resp = $this->action->handle(new ServerRequest()); diff --git a/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php index ff86117e..d6247d9f 100644 --- a/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/EditShortUrlActionTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\ShortUrl; +use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; @@ -11,14 +12,11 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface; use Shlinkio\Shlink\Rest\Action\ShortUrl\EditShortUrlAction; -use Zend\Diactoros\ServerRequest; class EditShortUrlActionTest extends TestCase { - /** @var EditShortUrlAction */ - private $action; - /** @var ObjectProphecy */ - private $shortUrlService; + private EditShortUrlAction $action; + private ObjectProphecy $shortUrlService; public function setUp(): void { @@ -46,7 +44,7 @@ class EditShortUrlActionTest extends TestCase 'maxVisits' => 5, ]); $updateMeta = $this->shortUrlService->updateMetadataByShortCode(Argument::cetera())->willReturn( - new ShortUrl('') + new ShortUrl(''), ); $resp = $this->action->handle($request); diff --git a/module/Rest/test/Action/ShortUrl/EditShortUrlTagsActionTest.php b/module/Rest/test/Action/ShortUrl/EditShortUrlTagsActionTest.php index 17293f05..83db484c 100644 --- a/module/Rest/test/Action/ShortUrl/EditShortUrlTagsActionTest.php +++ b/module/Rest/test/Action/ShortUrl/EditShortUrlTagsActionTest.php @@ -4,20 +4,18 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\ShortUrl; +use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Service\ShortUrlService; use Shlinkio\Shlink\Rest\Action\ShortUrl\EditShortUrlTagsAction; -use Zend\Diactoros\ServerRequest; class EditShortUrlTagsActionTest extends TestCase { - /** @var EditShortUrlTagsAction */ - private $action; - /** @var ObjectProphecy */ - private $shortUrlService; + private EditShortUrlTagsAction $action; + private ObjectProphecy $shortUrlService; public function setUp(): void { @@ -41,7 +39,7 @@ class EditShortUrlTagsActionTest extends TestCase $response = $this->action->handle( (new ServerRequest())->withAttribute('shortCode', 'abc123') - ->withParsedBody(['tags' => []]) + ->withParsedBody(['tags' => []]), ); $this->assertEquals(200, $response->getStatusCode()); } diff --git a/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php b/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php index 1dfdc258..52cc62de 100644 --- a/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php +++ b/module/Rest/test/Action/ShortUrl/ListShortUrlsActionTest.php @@ -5,25 +5,22 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\ShortUrl; use Cake\Chronos\Chronos; +use Laminas\Diactoros\Response\JsonResponse; +use Laminas\Diactoros\ServerRequest; +use Laminas\Paginator\Adapter\ArrayAdapter; +use Laminas\Paginator\Paginator; use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\ObjectProphecy; use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Service\ShortUrlService; use Shlinkio\Shlink\Rest\Action\ShortUrl\ListShortUrlsAction; -use Zend\Diactoros\Response\JsonResponse; -use Zend\Diactoros\ServerRequest; -use Zend\Paginator\Adapter\ArrayAdapter; -use Zend\Paginator\Paginator; class ListShortUrlsActionTest extends TestCase { - /** @var ListShortUrlsAction */ - private $action; - /** @var ObjectProphecy */ - private $service; - /** @var ObjectProphecy */ - private $logger; + private ListShortUrlsAction $action; + private ObjectProphecy $service; + private ObjectProphecy $logger; public function setUp(): void { @@ -53,7 +50,7 @@ class ListShortUrlsActionTest extends TestCase $expectedSearchTerm, $expectedTags, $expectedOrderBy, - $expectedDateRange + $expectedDateRange, )->willReturn(new Paginator(new ArrayAdapter())); /** @var JsonResponse $response */ diff --git a/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php index b77a6e45..e2932513 100644 --- a/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/ResolveShortUrlActionTest.php @@ -4,21 +4,19 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\ShortUrl; +use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Service\UrlShortener; use Shlinkio\Shlink\Rest\Action\ShortUrl\ResolveShortUrlAction; -use Zend\Diactoros\ServerRequest; use function strpos; class ResolveShortUrlActionTest extends TestCase { - /** @var ResolveShortUrlAction */ - private $action; - /** @var ObjectProphecy */ - private $urlShortener; + private ResolveShortUrlAction $action; + private ObjectProphecy $urlShortener; public function setUp(): void { @@ -31,7 +29,7 @@ class ResolveShortUrlActionTest extends TestCase { $shortCode = 'abc123'; $this->urlShortener->shortCodeToUrl($shortCode, null)->willReturn( - new ShortUrl('http://domain.com/foo/bar') + new ShortUrl('http://domain.com/foo/bar'), )->shouldBeCalledOnce(); $request = (new ServerRequest())->withAttribute('shortCode', $shortCode); diff --git a/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php b/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php index b0d8c65d..1af4aeba 100644 --- a/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php +++ b/module/Rest/test/Action/ShortUrl/SingleStepCreateShortUrlActionTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\ShortUrl; +use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; use Prophecy\Argument; @@ -15,16 +16,12 @@ use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; use Shlinkio\Shlink\Rest\Action\ShortUrl\SingleStepCreateShortUrlAction; use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; -use Zend\Diactoros\ServerRequest; class SingleStepCreateShortUrlActionTest extends TestCase { - /** @var SingleStepCreateShortUrlAction */ - private $action; - /** @var ObjectProphecy */ - private $urlShortener; - /** @var ObjectProphecy */ - private $apiKeyService; + private SingleStepCreateShortUrlAction $action; + private ObjectProphecy $urlShortener; + private ObjectProphecy $apiKeyService; public function setUp(): void { @@ -37,7 +34,7 @@ class SingleStepCreateShortUrlActionTest extends TestCase [ 'schema' => 'http', 'hostname' => 'foo.com', - ] + ], ); } @@ -79,7 +76,7 @@ class SingleStepCreateShortUrlActionTest extends TestCase return $argument; }), [], - ShortUrlMeta::createEmpty() + ShortUrlMeta::createEmpty(), )->willReturn(new ShortUrl('')); $resp = $this->action->handle($request); diff --git a/module/Rest/test/Action/Tag/CreateTagsActionTest.php b/module/Rest/test/Action/Tag/CreateTagsActionTest.php index c210d97c..357abc5d 100644 --- a/module/Rest/test/Action/Tag/CreateTagsActionTest.php +++ b/module/Rest/test/Action/Tag/CreateTagsActionTest.php @@ -5,18 +5,16 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\Tag; use Doctrine\Common\Collections\ArrayCollection; +use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Rest\Action\Tag\CreateTagsAction; -use Zend\Diactoros\ServerRequest; class CreateTagsActionTest extends TestCase { - /** @var CreateTagsAction */ - private $action; - /** @var ObjectProphecy */ - private $tagService; + private CreateTagsAction $action; + private ObjectProphecy $tagService; public function setUp(): void { diff --git a/module/Rest/test/Action/Tag/DeleteTagsActionTest.php b/module/Rest/test/Action/Tag/DeleteTagsActionTest.php index 69df8938..484bd549 100644 --- a/module/Rest/test/Action/Tag/DeleteTagsActionTest.php +++ b/module/Rest/test/Action/Tag/DeleteTagsActionTest.php @@ -4,18 +4,16 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\Tag; +use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Rest\Action\Tag\DeleteTagsAction; -use Zend\Diactoros\ServerRequest; class DeleteTagsActionTest extends TestCase { - /** @var DeleteTagsAction */ - private $action; - /** @var ObjectProphecy */ - private $tagService; + private DeleteTagsAction $action; + private ObjectProphecy $tagService; public function setUp(): void { diff --git a/module/Rest/test/Action/Tag/ListTagsActionTest.php b/module/Rest/test/Action/Tag/ListTagsActionTest.php index 03546fdb..7e9b061f 100644 --- a/module/Rest/test/Action/Tag/ListTagsActionTest.php +++ b/module/Rest/test/Action/Tag/ListTagsActionTest.php @@ -4,21 +4,19 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\Tag; +use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Rest\Action\Tag\ListTagsAction; -use Zend\Diactoros\ServerRequest; use function Shlinkio\Shlink\Common\json_decode; class ListTagsActionTest extends TestCase { - /** @var ListTagsAction */ - private $action; - /** @var ObjectProphecy */ - private $tagService; + private ListTagsAction $action; + private ObjectProphecy $tagService; public function setUp(): void { @@ -27,7 +25,7 @@ class ListTagsActionTest extends TestCase } /** @test */ - public function returnsDataFromService() + public function returnsDataFromService(): void { $listTags = $this->tagService->listTags()->willReturn([new Tag('foo'), new Tag('bar')]); diff --git a/module/Rest/test/Action/Tag/UpdateTagActionTest.php b/module/Rest/test/Action/Tag/UpdateTagActionTest.php index 3b068add..ab09b4ea 100644 --- a/module/Rest/test/Action/Tag/UpdateTagActionTest.php +++ b/module/Rest/test/Action/Tag/UpdateTagActionTest.php @@ -4,20 +4,18 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\Tag; +use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface; use Shlinkio\Shlink\Rest\Action\Tag\UpdateTagAction; -use Zend\Diactoros\ServerRequest; class UpdateTagActionTest extends TestCase { - /** @var UpdateTagAction */ - private $action; - /** @var ObjectProphecy */ - private $tagService; + private UpdateTagAction $action; + private ObjectProphecy $tagService; public function setUp(): void { diff --git a/module/Rest/test/Action/Visit/GetVisitsActionTest.php b/module/Rest/test/Action/Visit/GetVisitsActionTest.php index 80e06085..a445c3b2 100644 --- a/module/Rest/test/Action/Visit/GetVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/GetVisitsActionTest.php @@ -5,6 +5,9 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\Visit; use Cake\Chronos\Chronos; +use Laminas\Diactoros\ServerRequest; +use Laminas\Paginator\Adapter\ArrayAdapter; +use Laminas\Paginator\Paginator; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; @@ -12,16 +15,11 @@ use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Model\VisitsParams; use Shlinkio\Shlink\Core\Service\VisitsTracker; use Shlinkio\Shlink\Rest\Action\Visit\GetVisitsAction; -use Zend\Diactoros\ServerRequest; -use Zend\Paginator\Adapter\ArrayAdapter; -use Zend\Paginator\Paginator; class GetVisitsActionTest extends TestCase { - /** @var GetVisitsAction */ - private $action; - /** @var ObjectProphecy */ - private $visitsTracker; + private GetVisitsAction $action; + private ObjectProphecy $visitsTracker; public function setUp(): void { @@ -34,7 +32,7 @@ class GetVisitsActionTest extends TestCase { $shortCode = 'abc123'; $this->visitsTracker->info($shortCode, Argument::type(VisitsParams::class))->willReturn( - new Paginator(new ArrayAdapter([])) + new Paginator(new ArrayAdapter([])), )->shouldBeCalledOnce(); $response = $this->action->handle((new ServerRequest())->withAttribute('shortCode', $shortCode)); @@ -48,7 +46,7 @@ class GetVisitsActionTest extends TestCase $this->visitsTracker->info($shortCode, new VisitsParams( new DateRange(null, Chronos::parse('2016-01-01 00:00:00')), 3, - 10 + 10, )) ->willReturn(new Paginator(new ArrayAdapter([]))) ->shouldBeCalledOnce(); @@ -59,7 +57,7 @@ class GetVisitsActionTest extends TestCase 'endDate' => '2016-01-01 00:00:00', 'page' => '3', 'itemsPerPage' => '10', - ]) + ]), ); $this->assertEquals(200, $response->getStatusCode()); } diff --git a/module/Rest/test/Authentication/AuthenticationPluginManagerFactoryTest.php b/module/Rest/test/Authentication/AuthenticationPluginManagerFactoryTest.php index 15beefc0..326054d7 100644 --- a/module/Rest/test/Authentication/AuthenticationPluginManagerFactoryTest.php +++ b/module/Rest/test/Authentication/AuthenticationPluginManagerFactoryTest.php @@ -4,16 +4,15 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Authentication; +use Laminas\ServiceManager\ServiceManager; use PHPUnit\Framework\TestCase; use Shlinkio\Shlink\Rest\Authentication\AuthenticationPluginManager; use Shlinkio\Shlink\Rest\Authentication\AuthenticationPluginManagerFactory; use Shlinkio\Shlink\Rest\Authentication\Plugin\AuthenticationPluginInterface; -use Zend\ServiceManager\ServiceManager; class AuthenticationPluginManagerFactoryTest extends TestCase { - /** @var AuthenticationPluginManagerFactory */ - private $factory; + private AuthenticationPluginManagerFactory $factory; public function setUp(): void { @@ -35,9 +34,7 @@ class AuthenticationPluginManagerFactoryTest extends TestCase private function getPlugins(AuthenticationPluginManager $pluginManager): array { - return (function () { - return $this->services; - })->call($pluginManager); + return (fn () => $this->services)->call($pluginManager); } public function provideConfigs(): iterable diff --git a/module/Rest/test/Authentication/JWTServiceTest.php b/module/Rest/test/Authentication/JWTServiceTest.php deleted file mode 100644 index ab416ca0..00000000 --- a/module/Rest/test/Authentication/JWTServiceTest.php +++ /dev/null @@ -1,85 +0,0 @@ -service = new JWTService(new AppOptions([ - 'name' => 'ShlinkTest', - 'version' => '10000.3.1', - 'secret_key' => 'foo', - ])); - } - - /** @test */ - public function tokenIsProperlyCreated() - { - $id = '34'; - $token = $this->service->create((new ApiKey())->setId($id)); - $payload = (array) JWT::decode($token, 'foo', [JWTService::DEFAULT_ENCRYPTION_ALG]); - $this->assertGreaterThanOrEqual($payload['iat'], time()); - $this->assertGreaterThan(time(), $payload['exp']); - $this->assertEquals($id, $payload['key']); - $this->assertEquals('auth', $payload['sub']); - $this->assertEquals('ShlinkTest:v10000.3.1', $payload['iss']); - } - - /** @test */ - public function refreshIncreasesExpiration() - { - $originalLifetime = 10; - $newLifetime = 30; - $originalPayload = ['exp' => time() + $originalLifetime]; - $token = JWT::encode($originalPayload, 'foo'); - $newToken = $this->service->refresh($token, $newLifetime); - $newPayload = (array) JWT::decode($newToken, 'foo', [JWTService::DEFAULT_ENCRYPTION_ALG]); - - $this->assertGreaterThan($originalPayload['exp'], $newPayload['exp']); - } - - /** @test */ - public function verifyReturnsTrueWhenTheTokenIsCorrect() - { - $this->assertTrue($this->service->verify(JWT::encode([], 'foo'))); - } - - /** @test */ - public function verifyReturnsFalseWhenTheTokenIsCorrect() - { - $this->assertFalse($this->service->verify('invalidToken')); - } - - /** @test */ - public function getPayloadWorksWithCorrectTokens() - { - $originalPayload = [ - 'exp' => time() + 10, - 'sub' => 'testing', - ]; - $token = JWT::encode($originalPayload, 'foo'); - $this->assertEquals($originalPayload, $this->service->getPayload($token)); - } - - /** @test */ - public function getPayloadThrowsExceptionWithIncorrectTokens() - { - $this->expectException(AuthenticationException::class); - $this->service->getPayload('invalidToken'); - } -} diff --git a/module/Rest/test/Authentication/Plugin/ApiKeyHeaderPluginTest.php b/module/Rest/test/Authentication/Plugin/ApiKeyHeaderPluginTest.php index 65cedcfc..e6a9c23d 100644 --- a/module/Rest/test/Authentication/Plugin/ApiKeyHeaderPluginTest.php +++ b/module/Rest/test/Authentication/Plugin/ApiKeyHeaderPluginTest.php @@ -4,21 +4,19 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Authentication\Plugin; +use Laminas\Diactoros\Response; +use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ServerRequestInterface; use Shlinkio\Shlink\Rest\Authentication\Plugin\ApiKeyHeaderPlugin; use Shlinkio\Shlink\Rest\Exception\VerifyAuthenticationException; use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; -use Zend\Diactoros\Response; -use Zend\Diactoros\ServerRequest; class ApiKeyHeaderPluginTest extends TestCase { - /** @var ApiKeyHeaderPlugin */ - private $plugin; - /** @var ObjectProphecy */ - private $apiKeyService; + private ApiKeyHeaderPlugin $plugin; + private ObjectProphecy $apiKeyService; public function setUp(): void { @@ -27,7 +25,7 @@ class ApiKeyHeaderPluginTest extends TestCase } /** @test */ - public function verifyThrowsExceptionWhenApiKeyIsNotValid() + public function verifyThrowsExceptionWhenApiKeyIsNotValid(): void { $apiKey = 'abc-ABC'; $check = $this->apiKeyService->check($apiKey)->willReturn(false); @@ -40,7 +38,7 @@ class ApiKeyHeaderPluginTest extends TestCase } /** @test */ - public function verifyDoesNotThrowExceptionWhenApiKeyIsValid() + public function verifyDoesNotThrowExceptionWhenApiKeyIsValid(): void { $apiKey = 'abc-ABC'; $check = $this->apiKeyService->check($apiKey)->willReturn(true); @@ -51,7 +49,7 @@ class ApiKeyHeaderPluginTest extends TestCase } /** @test */ - public function updateReturnsResponseAsIs() + public function updateReturnsResponseAsIs(): void { $apiKey = 'abc-ABC'; $response = new Response(); diff --git a/module/Rest/test/Authentication/Plugin/AuthorizationHeaderPluginTest.php b/module/Rest/test/Authentication/Plugin/AuthorizationHeaderPluginTest.php deleted file mode 100644 index 0bd03e8a..00000000 --- a/module/Rest/test/Authentication/Plugin/AuthorizationHeaderPluginTest.php +++ /dev/null @@ -1,118 +0,0 @@ -jwtService = $this->prophesize(JWTServiceInterface::class); - $this->plugin = new AuthorizationHeaderPlugin($this->jwtService->reveal()); - } - - /** @test */ - public function verifyAnAuthorizationWithoutBearerTypeThrowsException() - { - $authToken = 'ABC-abc'; - $request = (new ServerRequest())->withHeader( - AuthorizationHeaderPlugin::HEADER_NAME, - $authToken - ); - - $this->expectException(VerifyAuthenticationException::class); - $this->expectExceptionMessage(sprintf( - 'You need to provide the Bearer type in the %s header.', - AuthorizationHeaderPlugin::HEADER_NAME - )); - - $this->plugin->verify($request); - } - - /** @test */ - public function verifyAnAuthorizationWithWrongTypeThrowsException() - { - $authToken = 'Basic ABC-abc'; - $request = (new ServerRequest())->withHeader( - AuthorizationHeaderPlugin::HEADER_NAME, - $authToken - ); - - $this->expectException(VerifyAuthenticationException::class); - $this->expectExceptionMessage( - 'Provided authorization type Basic is not supported. Use Bearer instead.' - ); - - $this->plugin->verify($request); - } - - /** @test */ - public function verifyAnExpiredTokenThrowsException() - { - $authToken = 'Bearer ABC-abc'; - $request = (new ServerRequest())->withHeader( - AuthorizationHeaderPlugin::HEADER_NAME, - $authToken - ); - $jwtVerify = $this->jwtService->verify('ABC-abc')->willReturn(false); - - $this->expectException(VerifyAuthenticationException::class); - $this->expectExceptionMessage(sprintf( - 'Missing or invalid auth token provided. Perform a new authentication request and send provided ' - . 'token on every new request on the %s header', - AuthorizationHeaderPlugin::HEADER_NAME - )); - - $this->plugin->verify($request); - - $jwtVerify->shouldHaveBeenCalledOnce(); - } - - /** @test */ - public function verifyValidTokenDoesNotThrowException() - { - $authToken = 'Bearer ABC-abc'; - $request = (new ServerRequest())->withHeader( - AuthorizationHeaderPlugin::HEADER_NAME, - $authToken - ); - $jwtVerify = $this->jwtService->verify('ABC-abc')->willReturn(true); - - $this->plugin->verify($request); - - $jwtVerify->shouldHaveBeenCalledOnce(); - } - - /** @test */ - public function updateReturnsAnUpdatedResponseWithNewJwt() - { - $authToken = 'Bearer ABC-abc'; - $request = (new ServerRequest())->withHeader( - AuthorizationHeaderPlugin::HEADER_NAME, - $authToken - ); - $jwtRefresh = $this->jwtService->refresh('ABC-abc')->willReturn('DEF-def'); - - $response = $this->plugin->update($request, new Response()); - - $this->assertTrue($response->hasHeader(AuthorizationHeaderPlugin::HEADER_NAME)); - $this->assertEquals('Bearer DEF-def', $response->getHeaderLine(AuthorizationHeaderPlugin::HEADER_NAME)); - $jwtRefresh->shouldHaveBeenCalledOnce(); - } -} diff --git a/module/Rest/test/Authentication/RequestToAuthPluginTest.php b/module/Rest/test/Authentication/RequestToAuthPluginTest.php index a49a4e19..5fac50dc 100644 --- a/module/Rest/test/Authentication/RequestToAuthPluginTest.php +++ b/module/Rest/test/Authentication/RequestToAuthPluginTest.php @@ -4,25 +4,22 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Authentication; +use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Rest\Authentication\AuthenticationPluginManagerInterface; use Shlinkio\Shlink\Rest\Authentication\Plugin\ApiKeyHeaderPlugin; use Shlinkio\Shlink\Rest\Authentication\Plugin\AuthenticationPluginInterface; -use Shlinkio\Shlink\Rest\Authentication\Plugin\AuthorizationHeaderPlugin; use Shlinkio\Shlink\Rest\Authentication\RequestToHttpAuthPlugin; use Shlinkio\Shlink\Rest\Exception\MissingAuthenticationException; -use Zend\Diactoros\ServerRequest; use function implode; use function sprintf; class RequestToAuthPluginTest extends TestCase { - /** @var RequestToHttpAuthPlugin */ - private $requestToPlugin; - /** @var ObjectProphecy */ - private $pluginManager; + private RequestToHttpAuthPlugin $requestToPlugin; + private ObjectProphecy $pluginManager; public function setUp(): void { @@ -38,7 +35,7 @@ class RequestToAuthPluginTest extends TestCase $this->expectException(MissingAuthenticationException::class); $this->expectExceptionMessage(sprintf( 'Expected one of the following authentication headers, ["%s"], but none were provided', - implode('", "', RequestToHttpAuthPlugin::SUPPORTED_AUTH_HEADERS) + implode('", "', RequestToHttpAuthPlugin::SUPPORTED_AUTH_HEADERS), )); $this->requestToPlugin->fromRequest($request); @@ -65,14 +62,7 @@ class RequestToAuthPluginTest extends TestCase public function provideHeaders(): iterable { - yield 'API key header only' => [[ - ApiKeyHeaderPlugin::HEADER_NAME => 'foobar', - ], ApiKeyHeaderPlugin::HEADER_NAME]; - yield 'Authorization header only' => [[ - AuthorizationHeaderPlugin::HEADER_NAME => 'foobar', - ], AuthorizationHeaderPlugin::HEADER_NAME]; - yield 'Both headers' => [[ - AuthorizationHeaderPlugin::HEADER_NAME => 'foobar', + yield 'API key header' => [[ ApiKeyHeaderPlugin::HEADER_NAME => 'foobar', ], ApiKeyHeaderPlugin::HEADER_NAME]; } diff --git a/module/Rest/test/ConfigProviderTest.php b/module/Rest/test/ConfigProviderTest.php index 2922faad..8032a854 100644 --- a/module/Rest/test/ConfigProviderTest.php +++ b/module/Rest/test/ConfigProviderTest.php @@ -9,8 +9,7 @@ use Shlinkio\Shlink\Rest\ConfigProvider; class ConfigProviderTest extends TestCase { - /** @var ConfigProvider */ - private $configProvider; + private ConfigProvider $configProvider; public function setUp(): void { @@ -26,25 +25,47 @@ class ConfigProviderTest extends TestCase $this->assertArrayHasKey('dependencies', $config); } - /** @test */ - public function routesAreProperlyPrefixed(): void + /** + * @test + * @dataProvider provideRoutesConfig + */ + public function routesAreProperlyPrefixed(array $routes, array $expected): void { - $configProvider = new ConfigProvider(function () { - return [ - 'routes' => [ - ['path' => '/foo'], - ['path' => '/bar'], - ['path' => '/baz/foo'], - ], - ]; - }); + $configProvider = new ConfigProvider(fn () => ['routes' => $routes]); $config = $configProvider(); - $this->assertEquals([ - ['path' => '/rest/v{version:1|2}/foo'], - ['path' => '/rest/v{version:1|2}/bar'], - ['path' => '/rest/v{version:1|2}/baz/foo'], - ], $config['routes']); + $this->assertEquals($expected, $config['routes']); + } + + public function provideRoutesConfig(): iterable + { + yield 'health action present' => [ + [ + ['path' => '/foo'], + ['path' => '/bar'], + ['path' => '/baz/foo'], + ['path' => '/health'], + ], + [ + ['path' => '/rest/v{version:1|2}/foo'], + ['path' => '/rest/v{version:1|2}/bar'], + ['path' => '/rest/v{version:1|2}/baz/foo'], + ['path' => '/rest/v{version:1|2}/health'], + ['path' => '/rest/health', 'name' => ConfigProvider::UNVERSIONED_HEALTH_ENDPOINT_NAME], + ], + ]; + yield 'health action not present' => [ + [ + ['path' => '/foo'], + ['path' => '/bar'], + ['path' => '/baz/foo'], + ], + [ + ['path' => '/rest/v{version:1|2}/foo'], + ['path' => '/rest/v{version:1|2}/bar'], + ['path' => '/rest/v{version:1|2}/baz/foo'], + ], + ]; } } diff --git a/module/Rest/test/EventDispatcher/NotifyVisitToWebHooksTest.php b/module/Rest/test/EventDispatcher/NotifyVisitToWebHooksTest.php index 305f2e23..369960e1 100644 --- a/module/Rest/test/EventDispatcher/NotifyVisitToWebHooksTest.php +++ b/module/Rest/test/EventDispatcher/NotifyVisitToWebHooksTest.php @@ -28,12 +28,9 @@ use function Functional\contains; class NotifyVisitToWebHooksTest extends TestCase { - /** @var ObjectProphecy */ - private $httpClient; - /** @var ObjectProphecy */ - private $em; - /** @var ObjectProphecy */ - private $logger; + private ObjectProphecy $httpClient; + private ObjectProphecy $em; + private ObjectProphecy $logger; public function setUp(): void { @@ -59,11 +56,11 @@ class NotifyVisitToWebHooksTest extends TestCase $requestAsync = $this->httpClient->requestAsync( RequestMethodInterface::METHOD_POST, Argument::type('string'), - Argument::type('array') + Argument::type('array'), )->willReturn(new FulfilledPromise('')); $logWarning = $this->logger->warning( 'Tried to notify webhooks for visit with id "{visitId}", but it does not exist.', - ['visitId' => '1'] + ['visitId' => '1'], ); $this->createListener(['foo', 'bar'])(new VisitLocated('1')); @@ -93,7 +90,7 @@ class NotifyVisitToWebHooksTest extends TestCase Assert::assertArrayHasKey('visit', $requestOptions[RequestOptions::JSON]); return $requestOptions; - }) + }), )->will(function (array $args) use ($invalidWebhooks) { [, $webhook] = $args; $e = new Exception(''); @@ -108,7 +105,7 @@ class NotifyVisitToWebHooksTest extends TestCase Assert::assertArrayHasKey('e', $extra); return $extra; - }) + }), ); $this->createListener($webhooks)(new VisitLocated('1')); @@ -126,7 +123,7 @@ class NotifyVisitToWebHooksTest extends TestCase $this->logger->reveal(), $webhooks, [], - new AppOptions(['name' => 'Shlink', 'version' => '1.2.3']) + new AppOptions(['name' => 'Shlink', 'version' => '1.2.3']), ); } } diff --git a/module/Rest/test/Exception/AuthenticationExceptionTest.php b/module/Rest/test/Exception/AuthenticationExceptionTest.php deleted file mode 100644 index da389acb..00000000 --- a/module/Rest/test/Exception/AuthenticationExceptionTest.php +++ /dev/null @@ -1,32 +0,0 @@ -assertEquals($prev, $e->getPrevious()); - $this->assertEquals(-1, $e->getCode()); - $this->assertEquals('The token has expired.', $e->getMessage()); - } - - public function providePrev(): iterable - { - yield 'with previous exception' => [new Exception('Prev')]; - yield 'without previous exception' => [null]; - } -} diff --git a/module/Rest/test/Exception/MissingAuthenticationExceptionTest.php b/module/Rest/test/Exception/MissingAuthenticationExceptionTest.php index 84c72e75..eee6058e 100644 --- a/module/Rest/test/Exception/MissingAuthenticationExceptionTest.php +++ b/module/Rest/test/Exception/MissingAuthenticationExceptionTest.php @@ -20,7 +20,7 @@ class MissingAuthenticationExceptionTest extends TestCase { $expectedMessage = sprintf( 'Expected one of the following authentication headers, ["%s"], but none were provided', - implode('", "', $expectedTypes) + implode('", "', $expectedTypes), ); $e = MissingAuthenticationException::fromExpectedTypes($expectedTypes); diff --git a/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php b/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php index 36dfaeee..cd002f60 100644 --- a/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php +++ b/module/Rest/test/Middleware/AuthenticationMiddlewareTest.php @@ -5,6 +5,10 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Middleware; use Fig\Http\Message\RequestMethodInterface; +use Laminas\Diactoros\Response; +use Laminas\Diactoros\ServerRequest; +use Mezzio\Router\Route; +use Mezzio\Router\RouteResult; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; @@ -13,25 +17,18 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Psr\Log\LoggerInterface; -use Shlinkio\Shlink\Rest\Action\AuthenticateAction; +use Shlinkio\Shlink\Rest\Action\HealthAction; use Shlinkio\Shlink\Rest\Authentication\Plugin\AuthenticationPluginInterface; use Shlinkio\Shlink\Rest\Authentication\RequestToHttpAuthPluginInterface; use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware; -use Zend\Diactoros\Response; -use Zend\Diactoros\ServerRequest; -use Zend\Expressive\Router\Route; -use Zend\Expressive\Router\RouteResult; -use function Zend\Stratigility\middleware; +use function Laminas\Stratigility\middleware; class AuthenticationMiddlewareTest extends TestCase { - /** @var AuthenticationMiddleware */ - private $middleware; - /** @var ObjectProphecy */ - private $requestToPlugin; - /** @var ObjectProphecy */ - private $logger; + private AuthenticationMiddleware $middleware; + private ObjectProphecy $requestToPlugin; + private ObjectProphecy $logger; public function setUp(): void { @@ -40,8 +37,8 @@ class AuthenticationMiddlewareTest extends TestCase $this->middleware = new AuthenticationMiddleware( $this->requestToPlugin->reveal(), - [AuthenticateAction::class], - $this->logger->reveal() + [HealthAction::class], + $this->logger->reveal(), ); } @@ -54,7 +51,7 @@ class AuthenticationMiddlewareTest extends TestCase $handler = $this->prophesize(RequestHandlerInterface::class); $handle = $handler->handle($request)->willReturn(new Response()); $fromRequest = $this->requestToPlugin->fromRequest(Argument::any())->willReturn( - $this->prophesize(AuthenticationPluginInterface::class)->reveal() + $this->prophesize(AuthenticationPluginInterface::class)->reveal(), ); $this->middleware->process($request, $handler->reveal()); @@ -70,17 +67,17 @@ class AuthenticationMiddlewareTest extends TestCase yield 'with no route result' => [new ServerRequest()]; yield 'with failure route result' => [(new ServerRequest())->withAttribute( RouteResult::class, - RouteResult::fromRouteFailure([RequestMethodInterface::METHOD_GET]) + RouteResult::fromRouteFailure([RequestMethodInterface::METHOD_GET]), )]; yield 'with whitelisted route' => [(new ServerRequest())->withAttribute( RouteResult::class, RouteResult::fromRoute( - new Route('foo', $dummyMiddleware, Route::HTTP_METHOD_ANY, AuthenticateAction::class) - ) + new Route('foo', $dummyMiddleware, Route::HTTP_METHOD_ANY, HealthAction::class), + ), )]; yield 'with OPTIONS method' => [(new ServerRequest())->withAttribute( RouteResult::class, - RouteResult::fromRoute(new Route('bar', $dummyMiddleware), []) + RouteResult::fromRoute(new Route('bar', $dummyMiddleware), []), )->withMethod(RequestMethodInterface::METHOD_OPTIONS)]; } @@ -90,11 +87,11 @@ class AuthenticationMiddlewareTest extends TestCase $newResponse = new Response(); $request = (new ServerRequest())->withAttribute( RouteResult::class, - RouteResult::fromRoute(new Route('bar', $this->getDummyMiddleware()), []) + RouteResult::fromRoute(new Route('bar', $this->getDummyMiddleware()), []), ); $plugin = $this->prophesize(AuthenticationPluginInterface::class); - $verify = $plugin->verify($request)->will(function () { + $verify = $plugin->verify($request)->will(function (): void { }); $update = $plugin->update($request, Argument::type(ResponseInterface::class))->willReturn($newResponse); $fromRequest = $this->requestToPlugin->fromRequest(Argument::any())->willReturn($plugin->reveal()); @@ -112,8 +109,6 @@ class AuthenticationMiddlewareTest extends TestCase private function getDummyMiddleware(): MiddlewareInterface { - return middleware(function () { - return new Response\EmptyResponse(); - }); + return middleware(fn () => new Response\EmptyResponse()); } } diff --git a/module/Rest/test/Middleware/BackwardsCompatibleProblemDetailsMiddlewareTest.php b/module/Rest/test/Middleware/BackwardsCompatibleProblemDetailsMiddlewareTest.php deleted file mode 100644 index 4d47c4cb..00000000 --- a/module/Rest/test/Middleware/BackwardsCompatibleProblemDetailsMiddlewareTest.php +++ /dev/null @@ -1,122 +0,0 @@ -handler = $this->prophesize(RequestHandlerInterface::class); - $this->middleware = new BackwardsCompatibleProblemDetailsMiddleware([ - 404 => 'NOT_FOUND', - 500 => 'INTERNAL_SERVER_ERROR', - ], 0); - } - - /** - * @test - * @dataProvider provideNonProcessableResponses - */ - public function nonProblemDetailsOrInvalidResponsesAreReturnedAsTheyAre(Response $response): void - { - $request = ServerRequestFactory::fromGlobals(); - $response = new Response(); - $handle = $this->handler->handle($request)->willReturn($response); - - $result = $this->middleware->process($request, $this->handler->reveal()); - - $this->assertSame($response, $result); - $handle->shouldHaveBeenCalledOnce(); - } - - public function provideNonProcessableResponses(): iterable - { - yield 'no problem details' => [new Response()]; - yield 'invalid JSON' => [(new Response('data://text/plain,{invalid-json'))->withHeader( - 'Content-Type', - 'application/problem+json' - )]; - } - - /** - * @test - * @dataProvider provideStatusAndTypes - */ - public function properlyMapsTypesBasedOnResponseStatus(Response\JsonResponse $response, string $expectedType): void - { - $request = ServerRequestFactory::fromGlobals()->withUri(new Uri('/v2/something')); - $handle = $this->handler->handle($request)->willReturn($response); - - /** @var Response\JsonResponse $result */ - $result = $this->middleware->process($request, $this->handler->reveal()); - $payload = $result->getPayload(); - - $this->assertEquals($expectedType, $payload['type']); - $this->assertArrayNotHasKey('error', $payload); - $this->assertArrayNotHasKey('message', $payload); - $handle->shouldHaveBeenCalledOnce(); - } - - public function provideStatusAndTypes(): iterable - { - yield [$this->jsonResponse(['type' => 'https://httpstatus.es/404'], 404), 'NOT_FOUND']; - yield [$this->jsonResponse(['type' => 'https://httpstatus.es/500'], 500), 'INTERNAL_SERVER_ERROR']; - yield [$this->jsonResponse(['type' => 'https://httpstatus.es/504'], 504), 'https://httpstatus.es/504']; - yield [$this->jsonResponse(['type' => 'something_else'], 404), 'something_else']; - yield [$this->jsonResponse(['type' => 'something_else'], 500), 'something_else']; - yield [$this->jsonResponse(['type' => 'something_else'], 504), 'something_else']; - } - - /** - * @test - * @dataProvider provideRequestPath - */ - public function mapsDeprecatedPropertiesWhenRequestIsPerformedToVersionOne(string $requestPath): void - { - $request = ServerRequestFactory::fromGlobals()->withUri(new Uri($requestPath)); - $response = $this->jsonResponse([ - 'type' => 'the_type', - 'detail' => 'the_detail', - ]); - $handle = $this->handler->handle($request)->willReturn($response); - - /** @var Response\JsonResponse $result */ - $result = $this->middleware->process($request, $this->handler->reveal()); - $payload = $result->getPayload(); - - $this->assertEquals([ - 'type' => 'the_type', - 'detail' => 'the_detail', - 'error' => 'the_type', - 'message' => 'the_detail', - ], $payload); - $handle->shouldHaveBeenCalledOnce(); - } - - public function provideRequestPath(): iterable - { - yield 'no version' => ['/foo']; - yield 'version one' => ['/v1/foo']; - } - - private function jsonResponse(array $payload, int $status = 200): Response\JsonResponse - { - $headers = ['Content-Type' => 'application/problem+json']; - return new Response\JsonResponse($payload, $status, $headers); - } -} diff --git a/module/Rest/test/Middleware/BodyParserMiddlewareTest.php b/module/Rest/test/Middleware/BodyParserMiddlewareTest.php index 829b4b59..5adae27d 100644 --- a/module/Rest/test/Middleware/BodyParserMiddlewareTest.php +++ b/module/Rest/test/Middleware/BodyParserMiddlewareTest.php @@ -4,22 +4,21 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Middleware; +use Laminas\Diactoros\Response; +use Laminas\Diactoros\ServerRequest; +use Laminas\Diactoros\Stream; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ProphecyInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use Shlinkio\Shlink\Rest\Middleware\BodyParserMiddleware; -use Zend\Diactoros\Response; -use Zend\Diactoros\ServerRequest; -use Zend\Diactoros\Stream; use function array_shift; class BodyParserMiddlewareTest extends TestCase { - /** @var BodyParserMiddleware */ - private $middleware; + private BodyParserMiddleware $middleware; public function setUp(): void { @@ -91,7 +90,7 @@ class BodyParserMiddlewareTest extends TestCase ], $req->getParsedBody()); return new Response(); - } + }, ); $this->middleware->process($request, $delegate->reveal()); @@ -119,7 +118,7 @@ class BodyParserMiddlewareTest extends TestCase ], $req->getParsedBody()); return new Response(); - } + }, ); $this->middleware->process($request, $delegate->reveal()); diff --git a/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php b/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php index 1716c19e..5daa8c3d 100644 --- a/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php +++ b/module/Rest/test/Middleware/CrossDomainMiddlewareTest.php @@ -4,26 +4,23 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Middleware; +use Laminas\Diactoros\Response; +use Laminas\Diactoros\ServerRequest; +use Mezzio\Router\Route; +use Mezzio\Router\RouteResult; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Server\RequestHandlerInterface; use Shlinkio\Shlink\Rest\Authentication; use Shlinkio\Shlink\Rest\Middleware\CrossDomainMiddleware; -use Zend\Diactoros\Response; -use Zend\Diactoros\ServerRequest; -use Zend\Expressive\Router\Route; -use Zend\Expressive\Router\RouteResult; -use function implode; -use function Zend\Stratigility\middleware; +use function Laminas\Stratigility\middleware; class CrossDomainMiddlewareTest extends TestCase { - /** @var CrossDomainMiddleware */ - private $middleware; - /** @var ObjectProphecy */ - private $handler; + private CrossDomainMiddleware $middleware; + private ObjectProphecy $handler; public function setUp(): void { @@ -57,17 +54,17 @@ class CrossDomainMiddlewareTest extends TestCase $response = $this->middleware->process( (new ServerRequest())->withHeader('Origin', 'local'), - $this->handler->reveal() + $this->handler->reveal(), ); $this->assertNotSame($originalResponse, $response); $headers = $response->getHeaders(); $this->assertEquals('local', $response->getHeaderLine('Access-Control-Allow-Origin')); - $this->assertEquals(implode(', ', [ + $this->assertEquals( Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME, - Authentication\Plugin\AuthorizationHeaderPlugin::HEADER_NAME, - ]), $response->getHeaderLine('Access-Control-Expose-Headers')); + $response->getHeaderLine('Access-Control-Expose-Headers'), + ); $this->assertArrayNotHasKey('Access-Control-Allow-Methods', $headers); $this->assertArrayNotHasKey('Access-Control-Max-Age', $headers); $this->assertArrayNotHasKey('Access-Control-Allow-Headers', $headers); @@ -89,10 +86,10 @@ class CrossDomainMiddlewareTest extends TestCase $headers = $response->getHeaders(); $this->assertEquals('local', $response->getHeaderLine('Access-Control-Allow-Origin')); - $this->assertEquals(implode(', ', [ + $this->assertEquals( Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME, - Authentication\Plugin\AuthorizationHeaderPlugin::HEADER_NAME, - ]), $response->getHeaderLine('Access-Control-Expose-Headers')); + $response->getHeaderLine('Access-Control-Expose-Headers'), + ); $this->assertArrayHasKey('Access-Control-Allow-Methods', $headers); $this->assertEquals('1000', $response->getHeaderLine('Access-Control-Max-Age')); $this->assertEquals('foo, bar, baz', $response->getHeaderLine('Access-Control-Allow-Headers')); @@ -123,8 +120,8 @@ class CrossDomainMiddlewareTest extends TestCase yield 'with failed route result' => [RouteResult::fromRouteFailure(['POST', 'GET']), 'POST,GET']; yield 'with success route result' => [ RouteResult::fromRoute( - new Route('/', middleware(function () { - }), ['DELETE', 'PATCH', 'PUT']) + new Route('/', middleware(function (): void { + }), ['DELETE', 'PATCH', 'PUT']), ), 'DELETE,PATCH,PUT', ]; diff --git a/module/Rest/test/Middleware/EmptyResponseImplicitOptionsMiddlewareFactoryTest.php b/module/Rest/test/Middleware/EmptyResponseImplicitOptionsMiddlewareFactoryTest.php index 23dc7983..73fdd07e 100644 --- a/module/Rest/test/Middleware/EmptyResponseImplicitOptionsMiddlewareFactoryTest.php +++ b/module/Rest/test/Middleware/EmptyResponseImplicitOptionsMiddlewareFactoryTest.php @@ -4,16 +4,15 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Middleware; +use Laminas\Diactoros\Response\EmptyResponse; +use Mezzio\Router\Middleware\ImplicitOptionsMiddleware; use PHPUnit\Framework\TestCase; use ReflectionObject; use Shlinkio\Shlink\Rest\Middleware\EmptyResponseImplicitOptionsMiddlewareFactory; -use Zend\Diactoros\Response\EmptyResponse; -use Zend\Expressive\Router\Middleware\ImplicitOptionsMiddleware; class EmptyResponseImplicitOptionsMiddlewareFactoryTest extends TestCase { - /** @var EmptyResponseImplicitOptionsMiddlewareFactory */ - private $factory; + private EmptyResponseImplicitOptionsMiddlewareFactory $factory; public function setUp(): void { diff --git a/module/Rest/test/Middleware/PathVersionMiddlewareTest.php b/module/Rest/test/Middleware/PathVersionMiddlewareTest.php deleted file mode 100644 index 887b8fbf..00000000 --- a/module/Rest/test/Middleware/PathVersionMiddlewareTest.php +++ /dev/null @@ -1,59 +0,0 @@ -middleware = new PathVersionMiddleware(); - } - - /** @test */ - public function whenVersionIsProvidedRequestRemainsUnchanged() - { - $request = (new ServerRequest())->withUri(new Uri('/v2/foo')); - - $delegate = $this->prophesize(RequestHandlerInterface::class); - $process = $delegate->handle($request)->willReturn(new Response()); - - $this->middleware->process($request, $delegate->reveal()); - - $process->shouldHaveBeenCalled(); - } - - /** @test */ - public function versionOneIsPrependedWhenNoVersionIsDefined() - { - $request = (new ServerRequest())->withUri(new Uri('/bar/baz')); - - $delegate = $this->prophesize(RequestHandlerInterface::class); - $delegate->handle(Argument::type(Request::class))->will(function (array $args) use ($request) { - $req = array_shift($args); - - Assert::assertNotSame($request, $req); - Assert::assertEquals('/v1/bar/baz', $req->getUri()->getPath()); - return new Response(); - }); - - - $this->middleware->process($request, $delegate->reveal()); - } -} diff --git a/module/Rest/test/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddlewareTest.php b/module/Rest/test/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddlewareTest.php index 83903b82..f70a4d4a 100644 --- a/module/Rest/test/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddlewareTest.php +++ b/module/Rest/test/Middleware/ShortUrl/CreateShortUrlContentNegotiationMiddlewareTest.php @@ -4,21 +4,20 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Middleware\ShortUrl; +use Laminas\Diactoros\Response; +use Laminas\Diactoros\Response\JsonResponse; +use Laminas\Diactoros\ServerRequest; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use Shlinkio\Shlink\Rest\Middleware\ShortUrl\CreateShortUrlContentNegotiationMiddleware; -use Zend\Diactoros\Response; -use Zend\Diactoros\Response\JsonResponse; -use Zend\Diactoros\ServerRequest; class CreateShortUrlContentNegotiationMiddlewareTest extends TestCase { - /** @var CreateShortUrlContentNegotiationMiddleware */ - private $middleware; - /** @var RequestHandlerInterface */ - private $requestHandler; + private CreateShortUrlContentNegotiationMiddleware $middleware; + private ObjectProphecy $requestHandler; public function setUp(): void { @@ -50,7 +49,7 @@ class CreateShortUrlContentNegotiationMiddlewareTest extends TestCase } $handle = $this->requestHandler->handle(Argument::type(ServerRequestInterface::class))->willReturn( - new JsonResponse(['shortUrl' => 'http://doma.in/foo']) + new JsonResponse(['shortUrl' => 'http://doma.in/foo']), ); $response = $this->middleware->process($request, $this->requestHandler->reveal()); @@ -81,7 +80,7 @@ class CreateShortUrlContentNegotiationMiddlewareTest extends TestCase $request = (new ServerRequest())->withQueryParams(['format' => 'txt']); $handle = $this->requestHandler->handle(Argument::type(ServerRequestInterface::class))->willReturn( - new JsonResponse($json) + new JsonResponse($json), ); $response = $this->middleware->process($request, $this->requestHandler->reveal()); diff --git a/module/Rest/test/Middleware/ShortUrl/ShortCodePathMiddlewareTest.php b/module/Rest/test/Middleware/ShortUrl/ShortCodePathMiddlewareTest.php deleted file mode 100644 index fb1ae215..00000000 --- a/module/Rest/test/Middleware/ShortUrl/ShortCodePathMiddlewareTest.php +++ /dev/null @@ -1,49 +0,0 @@ -middleware = new ShortCodePathMiddleware(); - $this->requestHandler = $this->prophesize(RequestHandlerInterface::class); - $this->requestHandler->handle(Argument::type(ServerRequestInterface::class))->willReturn(new Response()); - } - - /** @test */ - public function properlyReplacesTheOldPathByTheNewOne() - { - $uri = new Uri('/short-codes/foo'); - - $request = $this->prophesize(ServerRequestInterface::class); - $request->getUri()->willReturn($uri); - $withUri = $request->withUri(Argument::that(function (UriInterface $uri) { - $path = $uri->getPath(); - - Assert::assertStringContainsString('/short-urls', $path); - Assert::assertStringNotContainsString('/short-codes', $path); - - return $uri; - }))->willReturn($request->reveal()); - - $this->middleware->process($request->reveal(), $this->requestHandler->reveal()); - - $withUri->shouldHaveBeenCalledOnce(); - } -} diff --git a/module/Rest/test/Service/ApiKeyServiceTest.php b/module/Rest/test/Service/ApiKeyServiceTest.php index caa50bf1..c371beab 100644 --- a/module/Rest/test/Service/ApiKeyServiceTest.php +++ b/module/Rest/test/Service/ApiKeyServiceTest.php @@ -16,10 +16,8 @@ use Shlinkio\Shlink\Rest\Service\ApiKeyService; class ApiKeyServiceTest extends TestCase { - /** @var ApiKeyService */ - private $service; - /** @var ObjectProphecy */ - private $em; + private ApiKeyService $service; + private ObjectProphecy $em; public function setUp(): void { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 67d08507..03f73521 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -15,9 +15,6 @@ ./module/CLI/test - - ./module/PreviewGenerator/test -