diff --git a/CHANGELOG.md b/CHANGELOG.md index 63d9c6fe..b1124864 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this This behavior needs to be actively opted in, via installer config options or env vars. ### Changed -* *Nothing* +* [#1118](https://github.com/shlinkio/shlink/issues/1118) Increased phpstan required level to 8. ### Deprecated * *Nothing* diff --git a/bin/cli b/bin/cli index c185efd3..56437c8b 100755 --- a/bin/cli +++ b/bin/cli @@ -3,5 +3,8 @@ declare(strict_types=1); -$run = require __DIR__ . '/../config/run.php'; -$run(true); +use Symfony\Component\Console\Application; + +/** @var Application $app */ +$app = require __DIR__ . '/../config/cli-app.php'; +$app->run(); diff --git a/composer.json b/composer.json index 7d637a60..1dbe2aa8 100644 --- a/composer.json +++ b/composer.json @@ -62,18 +62,20 @@ }, "require-dev": { "devster/ubench": "^2.1", - "dms/phpunit-arraysubset-asserts": "^0.2.1", + "dms/phpunit-arraysubset-asserts": "^0.3.0", "eaglewu/swoole-ide-helper": "dev-master", - "infection/infection": "^0.21.0", + "infection/infection": "^0.23.0", "phpspec/prophecy-phpunit": "^2.0", - "phpstan/phpstan": "^0.12.64", + "phpstan/phpstan": "^0.12.93", + "phpstan/phpstan-doctrine": "^0.12.42", + "phpstan/phpstan-symfony": "^0.12.41", "phpunit/php-code-coverage": "^9.2", "phpunit/phpunit": "^9.5", "roave/security-advisories": "dev-master", "shlinkio/php-coding-standard": "~2.1.1", "shlinkio/shlink-test-utils": "^2.1", "symfony/var-dumper": "^5.2", - "veewee/composer-run-parallel": "^0.1.0" + "veewee/composer-run-parallel": "^1.0" }, "autoload": { "psr-4": { @@ -112,7 +114,7 @@ ], "cs": "phpcs", "cs:fix": "phpcbf", - "stan": "phpstan analyse module/*/src/ module/*/config config docker/config data/migrations --level=6", + "stan": "APP_ENV=test php vendor/bin/phpstan analyse module/*/src module/*/config config docker/config data/migrations --level=8", "test": [ "@test:unit", "@test:db", diff --git a/config/cli-app.php b/config/cli-app.php new file mode 100644 index 00000000..a2272852 --- /dev/null +++ b/config/cli-app.php @@ -0,0 +1,12 @@ +get(CliApp::class); +})(); diff --git a/config/cli-config.php b/config/cli-config.php index 71c7a75e..52659e4e 100644 --- a/config/cli-config.php +++ b/config/cli-config.php @@ -4,12 +4,9 @@ declare(strict_types=1); use Doctrine\ORM\EntityManager; use Doctrine\ORM\Tools\Console\ConsoleRunner; -use Psr\Container\ContainerInterface; - -return (function () { - /** @var ContainerInterface $container */ - $container = include __DIR__ . '/container.php'; - $em = $container->get(EntityManager::class); +return (static function () { + /** @var EntityManager $em */ + $em = include __DIR__ . '/entity-manager.php'; return ConsoleRunner::createHelperSet($em); })(); diff --git a/config/entity-manager.php b/config/entity-manager.php new file mode 100644 index 00000000..2b4794f7 --- /dev/null +++ b/config/entity-manager.php @@ -0,0 +1,12 @@ +get(EntityManager::class); +})(); diff --git a/config/run.php b/config/run.php index 80116e24..5de0df16 100644 --- a/config/run.php +++ b/config/run.php @@ -4,12 +4,11 @@ declare(strict_types=1); use Mezzio\Application; use Psr\Container\ContainerInterface; -use Symfony\Component\Console\Application as CliApp; -return function (bool $isCli = false): void { +return static function (): void { /** @var ContainerInterface $container */ $container = include __DIR__ . '/container.php'; - $app = $container->get($isCli ? CliApp::class : Application::class); + $app = $container->get(Application::class); $app->run(); }; diff --git a/config/test/test_config.global.php b/config/test/test_config.global.php index c2375cba..cf824752 100644 --- a/config/test/test_config.global.php +++ b/config/test/test_config.global.php @@ -120,7 +120,7 @@ return [ 'name' => 'start_collecting_coverage', 'path' => '/api-tests/start-coverage', 'middleware' => middleware(static function () use (&$coverage) { - if ($coverage) { + if ($coverage) { // @phpstan-ignore-line $coverage->start('API tests'); } return new EmptyResponse(); @@ -131,7 +131,7 @@ return [ 'name' => 'dump_coverage', 'path' => '/api-tests/stop-coverage', 'middleware' => middleware(static function () use (&$coverage) { - if ($coverage) { + if ($coverage) { // @phpstan-ignore-line $basePath = __DIR__ . '/../../build/coverage-api'; $coverage->stop(); (new PHP())->process($coverage, $basePath . '.cov'); diff --git a/module/CLI/src/ApiKey/RoleResolver.php b/module/CLI/src/ApiKey/RoleResolver.php index 179fff53..c8cccfc6 100644 --- a/module/CLI/src/ApiKey/RoleResolver.php +++ b/module/CLI/src/ApiKey/RoleResolver.php @@ -8,6 +8,8 @@ use Shlinkio\Shlink\Core\Domain\DomainServiceInterface; use Shlinkio\Shlink\Rest\ApiKey\Model\RoleDefinition; use Symfony\Component\Console\Input\InputInterface; +use function is_string; + class RoleResolver implements RoleResolverInterface { public function __construct(private DomainServiceInterface $domainService) @@ -23,7 +25,7 @@ class RoleResolver implements RoleResolverInterface if ($author) { $roleDefinitions[] = RoleDefinition::forAuthoredShortUrls(); } - if ($domainAuthority !== null) { + if (is_string($domainAuthority)) { $domain = $this->domainService->getOrCreate($domainAuthority); $roleDefinitions[] = RoleDefinition::forDomain($domain); } diff --git a/module/CLI/src/Command/BaseCommand.php b/module/CLI/src/Command/BaseCommand.php index ef6b5435..fbee8681 100644 --- a/module/CLI/src/Command/BaseCommand.php +++ b/module/CLI/src/Command/BaseCommand.php @@ -12,40 +12,36 @@ use function Shlinkio\Shlink\Core\kebabCaseToCamelCase; use function sprintf; use function str_contains; +/** @deprecated */ abstract class BaseCommand extends Command { /** - * @param mixed|null $default + * @param string|string[]|bool|null $default */ protected function addOptionWithDeprecatedFallback( string $name, ?string $shortcut = null, ?int $mode = null, string $description = '', - $default = null, + bool|string|array|null $default = null, ): self { $this->addOption($name, $shortcut, $mode, $description, $default); if (str_contains($name, '-')) { $camelCaseName = kebabCaseToCamelCase($name); - $this->addOption($camelCaseName, null, $mode, sprintf('[DEPRECATED] Same as "%s".', $name), $default); + $this->addOption($camelCaseName, null, $mode, sprintf('[DEPRECATED] Alias for "%s".', $name), $default); } return $this; } - /** - * @return bool|string|string[]|null - */ - protected function getOptionWithDeprecatedFallback(InputInterface $input, string $name) + // @phpstan-ignore-next-line + protected function getOptionWithDeprecatedFallback(InputInterface $input, string $name) // phpcs:ignore { $rawInput = method_exists($input, '__toString') ? $input->__toString() : ''; $camelCaseName = kebabCaseToCamelCase($name); + $resolvedOptionName = str_contains($rawInput, $camelCaseName) ? $camelCaseName : $name; - if (str_contains($rawInput, $camelCaseName)) { - return $input->getOption($camelCaseName); - } - - return $input->getOption($name); + return $input->getOption($resolvedOptionName); } } diff --git a/module/CLI/src/Command/Db/AbstractDatabaseCommand.php b/module/CLI/src/Command/Db/AbstractDatabaseCommand.php index 1b0a4f9b..9cd6e9ea 100644 --- a/module/CLI/src/Command/Db/AbstractDatabaseCommand.php +++ b/module/CLI/src/Command/Db/AbstractDatabaseCommand.php @@ -32,6 +32,6 @@ abstract class AbstractDatabaseCommand extends AbstractLockedCommand protected function getLockConfig(): LockedCommandConfig { - return LockedCommandConfig::blocking($this->getName()); + return LockedCommandConfig::blocking($this->getName() ?? static::class); } } diff --git a/module/CLI/src/Command/Util/AbstractWithDateRangeCommand.php b/module/CLI/src/Command/Util/AbstractWithDateRangeCommand.php index 51731e3a..9d7f5723 100644 --- a/module/CLI/src/Command/Util/AbstractWithDateRangeCommand.php +++ b/module/CLI/src/Command/Util/AbstractWithDateRangeCommand.php @@ -11,6 +11,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Throwable; +use function is_string; use function sprintf; abstract class AbstractWithDateRangeCommand extends BaseCommand @@ -49,7 +50,7 @@ abstract class AbstractWithDateRangeCommand extends BaseCommand private function getDateOption(InputInterface $input, OutputInterface $output, string $key): ?Chronos { $value = $this->getOptionWithDeprecatedFallback($input, $key); - if (empty($value)) { + if (empty($value) || ! is_string($value)) { return null; } diff --git a/module/CLI/src/Command/Visit/DownloadGeoLiteDbCommand.php b/module/CLI/src/Command/Visit/DownloadGeoLiteDbCommand.php index aef30427..41fb5f8d 100644 --- a/module/CLI/src/Command/Visit/DownloadGeoLiteDbCommand.php +++ b/module/CLI/src/Command/Visit/DownloadGeoLiteDbCommand.php @@ -45,8 +45,8 @@ class DownloadGeoLiteDbCommand extends Command $io->text(sprintf('%s GeoLite2 db file...', $olderDbExists ? 'Updating' : 'Downloading')); $this->progressBar = new ProgressBar($io); }, function (int $total, int $downloaded): void { - $this->progressBar->setMaxSteps($total); - $this->progressBar->setProgress($downloaded); + $this->progressBar?->setMaxSteps($total); + $this->progressBar?->setProgress($downloaded); }); if ($this->progressBar === null) { diff --git a/module/CLI/src/Command/Visit/LocateVisitsCommand.php b/module/CLI/src/Command/Visit/LocateVisitsCommand.php index 31b1fb05..7352211e 100644 --- a/module/CLI/src/Command/Visit/LocateVisitsCommand.php +++ b/module/CLI/src/Command/Visit/LocateVisitsCommand.php @@ -139,7 +139,7 @@ class LocateVisitsCommand extends AbstractLockedCommand implements VisitGeolocat throw IpCannotBeLocatedException::forEmptyAddress(); } - $ipAddr = $visit->getRemoteAddr(); + $ipAddr = $visit->getRemoteAddr() ?? ''; $this->io->write(sprintf('Processing IP %s', $ipAddr)); if ($ipAddr === IpAddress::LOCALHOST) { $this->io->writeln(' [Ignored localhost address]'); @@ -168,7 +168,12 @@ class LocateVisitsCommand extends AbstractLockedCommand implements VisitGeolocat private function checkDbUpdate(InputInterface $input): void { - $downloadDbCommand = $this->getApplication()->find(DownloadGeoLiteDbCommand::NAME); + $cliApp = $this->getApplication(); + if ($cliApp === null) { + return; + } + + $downloadDbCommand = $cliApp->find(DownloadGeoLiteDbCommand::NAME); $exitCode = $downloadDbCommand->run($input, $this->io); if ($exitCode === ExitCodes::EXIT_FAILURE) { @@ -178,6 +183,6 @@ class LocateVisitsCommand extends AbstractLockedCommand implements VisitGeolocat protected function getLockConfig(): LockedCommandConfig { - return LockedCommandConfig::nonBlocking($this->getName()); + return LockedCommandConfig::nonBlocking(self::NAME); } } diff --git a/module/CLI/src/Exception/GeolocationDbUpdateFailedException.php b/module/CLI/src/Exception/GeolocationDbUpdateFailedException.php index 07d66855..0c5ef184 100644 --- a/module/CLI/src/Exception/GeolocationDbUpdateFailedException.php +++ b/module/CLI/src/Exception/GeolocationDbUpdateFailedException.php @@ -42,10 +42,7 @@ class GeolocationDbUpdateFailedException extends RuntimeException implements Exc return $e; } - /** - * @param mixed $buildEpoch - */ - public static function withInvalidEpochInOldDb($buildEpoch): self + public static function withInvalidEpochInOldDb(mixed $buildEpoch): self { $e = new self(sprintf( 'Build epoch with value "%s" from existing geolocation database, could not be parsed to integer.', diff --git a/module/CLI/test/ApiKey/RoleResolverTest.php b/module/CLI/test/ApiKey/RoleResolverTest.php index 5e4de2c3..76163348 100644 --- a/module/CLI/test/ApiKey/RoleResolverTest.php +++ b/module/CLI/test/ApiKey/RoleResolverTest.php @@ -68,6 +68,21 @@ class RoleResolverTest extends TestCase [RoleDefinition::forDomain($domain)], 1, ]; + yield 'false domain role' => [ + $buildInput([RoleResolver::DOMAIN_ONLY_PARAM => false]), + [], + 0, + ]; + yield 'true domain role' => [ + $buildInput([RoleResolver::DOMAIN_ONLY_PARAM => true]), + [], + 0, + ]; + yield 'string array domain role' => [ + $buildInput([RoleResolver::DOMAIN_ONLY_PARAM => ['foo', 'bar']]), + [], + 0, + ]; yield 'author role only' => [ $buildInput([RoleResolver::DOMAIN_ONLY_PARAM => null, RoleResolver::AUTHOR_ONLY_PARAM => true]), [RoleDefinition::forAuthoredShortUrls()], diff --git a/module/Core/src/Action/RobotsAction.php b/module/Core/src/Action/RobotsAction.php index b7fa7d42..12baa7b3 100644 --- a/module/Core/src/Action/RobotsAction.php +++ b/module/Core/src/Action/RobotsAction.php @@ -23,6 +23,7 @@ class RobotsAction implements RequestHandlerInterface, StatusCodeInterface public function handle(ServerRequestInterface $request): ResponseInterface { + // @phpstan-ignore-next-line The "Response" phpdoc is wrong return new Response(self::STATUS_OK, ['Content-type' => 'text/plain'], $this->buildRobots()); } diff --git a/module/Core/src/Domain/DomainService.php b/module/Core/src/Domain/DomainService.php index 95ba05c5..1e1ac279 100644 --- a/module/Core/src/Domain/DomainService.php +++ b/module/Core/src/Domain/DomainService.php @@ -57,7 +57,6 @@ class DomainService implements DomainServiceInterface public function getOrCreate(string $authority): Domain { $repo = $this->em->getRepository(Domain::class); - /** @var Domain|null $domain */ $domain = $repo->findOneBy(['authority' => $authority]) ?? new Domain($authority); $this->em->persist($domain); diff --git a/module/Core/src/ErrorHandler/NotFoundRedirectHandler.php b/module/Core/src/ErrorHandler/NotFoundRedirectHandler.php index 27fcf991..93fd4597 100644 --- a/module/Core/src/ErrorHandler/NotFoundRedirectHandler.php +++ b/module/Core/src/ErrorHandler/NotFoundRedirectHandler.php @@ -26,17 +26,20 @@ class NotFoundRedirectHandler implements MiddlewareInterface $notFoundType = $request->getAttribute(NotFoundType::class); if ($notFoundType->isBaseUrl() && $this->redirectOptions->hasBaseUrlRedirect()) { + // @phpstan-ignore-next-line Create custom PHPStan rule return $this->redirectResponseHelper->buildRedirectResponse($this->redirectOptions->getBaseUrlRedirect()); } if ($notFoundType->isRegularNotFound() && $this->redirectOptions->hasRegular404Redirect()) { return $this->redirectResponseHelper->buildRedirectResponse( + // @phpstan-ignore-next-line Create custom PHPStan rule $this->redirectOptions->getRegular404Redirect(), ); } if ($notFoundType->isInvalidShortUrl() && $this->redirectOptions->hasInvalidShortUrlRedirect()) { return $this->redirectResponseHelper->buildRedirectResponse( + // @phpstan-ignore-next-line Create custom PHPStan rule $this->redirectOptions->getInvalidShortUrlRedirect(), ); } diff --git a/module/Core/src/EventDispatcher/LocateVisit.php b/module/Core/src/EventDispatcher/LocateVisit.php index 046430df..bb6ba1d0 100644 --- a/module/Core/src/EventDispatcher/LocateVisit.php +++ b/module/Core/src/EventDispatcher/LocateVisit.php @@ -55,7 +55,7 @@ class LocateVisit } $isLocatable = $originalIpAddress !== null || $visit->isLocatable(); - $addr = $originalIpAddress ?? $visit->getRemoteAddr(); + $addr = $originalIpAddress ?? $visit->getRemoteAddr() ?? ''; try { $location = $isLocatable ? $this->ipLocationResolver->resolveIpLocation($addr) : Location::emptyInstance(); diff --git a/module/Core/src/Importer/ImportedLinksProcessor.php b/module/Core/src/Importer/ImportedLinksProcessor.php index 6073dd26..b153430b 100644 --- a/module/Core/src/Importer/ImportedLinksProcessor.php +++ b/module/Core/src/Importer/ImportedLinksProcessor.php @@ -28,7 +28,7 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface private ShortCodeHelperInterface $shortCodeHelper, private DoctrineBatchHelperInterface $batchHelper ) { - $this->shortUrlRepo = $this->em->getRepository(ShortUrl::class); // @phpstan-ignore-line + $this->shortUrlRepo = $this->em->getRepository(ShortUrl::class); } /** diff --git a/module/Core/src/Mercure/MercureUpdatesGenerator.php b/module/Core/src/Mercure/MercureUpdatesGenerator.php index c6c7a4d6..f2489da3 100644 --- a/module/Core/src/Mercure/MercureUpdatesGenerator.php +++ b/module/Core/src/Mercure/MercureUpdatesGenerator.php @@ -42,7 +42,7 @@ final class MercureUpdatesGenerator implements MercureUpdatesGeneratorInterface public function newShortUrlVisitUpdate(Visit $visit): Update { $shortUrl = $visit->getShortUrl(); - $topic = sprintf('%s/%s', self::NEW_VISIT_TOPIC, $shortUrl->getShortCode()); + $topic = sprintf('%s/%s', self::NEW_VISIT_TOPIC, $shortUrl?->getShortCode()); return new Update($topic, $this->serialize([ 'shortUrl' => $this->shortUrlTransformer->transform($shortUrl), diff --git a/module/Core/src/Model/ShortUrlsOrdering.php b/module/Core/src/Model/ShortUrlsOrdering.php index b59435ca..4184fcc6 100644 --- a/module/Core/src/Model/ShortUrlsOrdering.php +++ b/module/Core/src/Model/ShortUrlsOrdering.php @@ -49,7 +49,6 @@ final class ShortUrlsOrdering ]); } - /** @var string|array $orderBy */ if (! $isArray) { [$field, $dir] = array_pad(explode('-', $orderBy), 2, null); $this->orderField = $field; diff --git a/module/Core/src/Model/ShortUrlsParams.php b/module/Core/src/Model/ShortUrlsParams.php index b27a6187..a916704b 100644 --- a/module/Core/src/Model/ShortUrlsParams.php +++ b/module/Core/src/Model/ShortUrlsParams.php @@ -19,7 +19,7 @@ final class ShortUrlsParams private array $tags; private ShortUrlsOrdering $orderBy; private ?DateRange $dateRange; - private ?int $itemsPerPage = null; + private int $itemsPerPage; private function __construct() { diff --git a/module/Core/src/Model/Visitor.php b/module/Core/src/Model/Visitor.php index b73ed68a..9436e900 100644 --- a/module/Core/src/Model/Visitor.php +++ b/module/Core/src/Model/Visitor.php @@ -29,13 +29,16 @@ final class Visitor $this->userAgent = $this->cropToLength($userAgent, self::USER_AGENT_MAX_LENGTH); $this->referer = $this->cropToLength($referer, self::REFERER_MAX_LENGTH); $this->visitedUrl = $this->cropToLength($visitedUrl, self::VISITED_URL_MAX_LENGTH); - $this->remoteAddress = $this->cropToLength($remoteAddress, self::REMOTE_ADDRESS_MAX_LENGTH); + $this->remoteAddress = $remoteAddress === null ? null : $this->cropToLength( + $remoteAddress, + self::REMOTE_ADDRESS_MAX_LENGTH, + ); $this->potentialBot = isCrawler($userAgent); } - private function cropToLength(?string $value, int $length): ?string + private function cropToLength(string $value, int $length): string { - return $value === null ? null : substr($value, 0, $length); + return substr($value, 0, $length); } public static function fromRequest(ServerRequestInterface $request): self diff --git a/module/Core/src/Model/VisitsParams.php b/module/Core/src/Model/VisitsParams.php index 659eb5dc..1f78de00 100644 --- a/module/Core/src/Model/VisitsParams.php +++ b/module/Core/src/Model/VisitsParams.php @@ -13,7 +13,7 @@ final class VisitsParams private const FIRST_PAGE = 1; private const ALL_ITEMS = -1; - private ?DateRange $dateRange; + private DateRange $dateRange; private int $itemsPerPage; public function __construct( diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index c4ccedbf..4c3a4e9c 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -18,7 +18,6 @@ use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use function array_column; -use function array_key_exists; use function count; use function Functional\contains; @@ -59,6 +58,7 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU // visitsCount and visitCount are deprecated. Only visits should work if (contains(['visits', 'visitsCount', 'visitCount'], $fieldName)) { + // FIXME This query is inefficient. Debug it. $qb->addSelect('COUNT(DISTINCT v) AS totalVisits') ->leftJoin('s.visits', 'v') ->groupBy('s') @@ -75,9 +75,11 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU 'dateCreated' => 'dateCreated', 'title' => 'title', ]; - if (array_key_exists($fieldName, $fieldNameMap)) { - $qb->orderBy('s.' . $fieldNameMap[$fieldName], $order); + $resolvedFieldName = $fieldNameMap[$fieldName] ?? null; + if ($resolvedFieldName !== null) { + $qb->orderBy('s.' . $resolvedFieldName, $order); } + return $qb->getQuery()->getResult(); } @@ -194,10 +196,12 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU private function doShortCodeIsInUse(ShortUrlIdentifier $identifier, ?Specification $spec, ?int $lockMode): bool { - $qb = $this->createFindOneQueryBuilder($identifier, $spec); - $qb->select('s.id'); + $qb = $this->createFindOneQueryBuilder($identifier, $spec)->select('s.id'); + $query = $qb->getQuery(); - $query = $qb->getQuery()->setLockMode($lockMode); + if ($lockMode !== null) { + $query = $query->setLockMode($lockMode); + } return $query->getOneOrNullResult() !== null; } diff --git a/module/Core/src/Util/CocurSymfonySluggerBridge.php b/module/Core/src/Util/CocurSymfonySluggerBridge.php index a612068c..da60836e 100644 --- a/module/Core/src/Util/CocurSymfonySluggerBridge.php +++ b/module/Core/src/Util/CocurSymfonySluggerBridge.php @@ -7,8 +7,7 @@ namespace Shlinkio\Shlink\Core\Util; use Cocur\Slugify\SlugifyInterface; use Symfony\Component\String\AbstractUnicodeString; use Symfony\Component\String\Slugger\SluggerInterface; - -use function Symfony\Component\String\s; +use Symfony\Component\String\UnicodeString; class CocurSymfonySluggerBridge implements SluggerInterface { @@ -18,6 +17,6 @@ class CocurSymfonySluggerBridge implements SluggerInterface public function slug(string $string, string $separator = '-', ?string $locale = null): AbstractUnicodeString { - return s($this->slugger->slugify($string, $separator)); + return new UnicodeString($this->slugger->slugify($string, $separator)); } } diff --git a/module/Rest/src/Action/ShortUrl/EditShortUrlTagsAction.php b/module/Rest/src/Action/ShortUrl/EditShortUrlTagsAction.php index d3211d1c..feda3a62 100644 --- a/module/Rest/src/Action/ShortUrl/EditShortUrlTagsAction.php +++ b/module/Rest/src/Action/ShortUrl/EditShortUrlTagsAction.php @@ -27,6 +27,7 @@ class EditShortUrlTagsAction extends AbstractRestAction public function handle(Request $request): Response { + /** @var array $bodyParams */ $bodyParams = $request->getParsedBody(); if (! isset($bodyParams['tags'])) { diff --git a/module/Rest/src/Action/Tag/CreateTagsAction.php b/module/Rest/src/Action/Tag/CreateTagsAction.php index e3d0bb9f..09c860f5 100644 --- a/module/Rest/src/Action/Tag/CreateTagsAction.php +++ b/module/Rest/src/Action/Tag/CreateTagsAction.php @@ -20,15 +20,9 @@ class CreateTagsAction extends AbstractRestAction { } - /** - * Process an incoming server request and return a response, optionally delegating - * to the next middleware component to create the response. - * - * - * @throws \InvalidArgumentException - */ public function handle(ServerRequestInterface $request): ResponseInterface { + /** @var array $body */ $body = $request->getParsedBody(); $tags = $body['tags'] ?? []; diff --git a/module/Rest/src/Action/Tag/UpdateTagAction.php b/module/Rest/src/Action/Tag/UpdateTagAction.php index a4bce7c0..016d008b 100644 --- a/module/Rest/src/Action/Tag/UpdateTagAction.php +++ b/module/Rest/src/Action/Tag/UpdateTagAction.php @@ -23,6 +23,7 @@ class UpdateTagAction extends AbstractRestAction public function handle(ServerRequestInterface $request): ResponseInterface { + /** @var array $body */ $body = $request->getParsedBody(); $apiKey = AuthenticationMiddleware::apiKeyFromRequest($request); diff --git a/module/Rest/src/Middleware/ShortUrl/DefaultShortCodesLengthMiddleware.php b/module/Rest/src/Middleware/ShortUrl/DefaultShortCodesLengthMiddleware.php index cffc56b0..5b1bfd40 100644 --- a/module/Rest/src/Middleware/ShortUrl/DefaultShortCodesLengthMiddleware.php +++ b/module/Rest/src/Middleware/ShortUrl/DefaultShortCodesLengthMiddleware.php @@ -18,6 +18,7 @@ class DefaultShortCodesLengthMiddleware implements MiddlewareInterface public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { + /** @var array $body */ $body = $request->getParsedBody(); if (! isset($body[ShortUrlInputFilter::SHORT_CODE_LENGTH])) { $body[ShortUrlInputFilter::SHORT_CODE_LENGTH] = $this->defaultShortCodesLength; diff --git a/module/Rest/src/Middleware/ShortUrl/DropDefaultDomainFromRequestMiddleware.php b/module/Rest/src/Middleware/ShortUrl/DropDefaultDomainFromRequestMiddleware.php index 59515242..8eb98153 100644 --- a/module/Rest/src/Middleware/ShortUrl/DropDefaultDomainFromRequestMiddleware.php +++ b/module/Rest/src/Middleware/ShortUrl/DropDefaultDomainFromRequestMiddleware.php @@ -17,8 +17,10 @@ class DropDefaultDomainFromRequestMiddleware implements MiddlewareInterface public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { + /** @var array $body */ + $body = $request->getParsedBody(); $request = $request->withQueryParams($this->sanitizeDomainFromPayload($request->getQueryParams())) - ->withParsedBody($this->sanitizeDomainFromPayload($request->getParsedBody())); + ->withParsedBody($this->sanitizeDomainFromPayload($body)); return $handler->handle($request); } diff --git a/module/Rest/src/Middleware/ShortUrl/OverrideDomainMiddleware.php b/module/Rest/src/Middleware/ShortUrl/OverrideDomainMiddleware.php index 6943f986..0f4fd75e 100644 --- a/module/Rest/src/Middleware/ShortUrl/OverrideDomainMiddleware.php +++ b/module/Rest/src/Middleware/ShortUrl/OverrideDomainMiddleware.php @@ -32,6 +32,7 @@ class OverrideDomainMiddleware implements MiddlewareInterface $domain = $this->domainService->getDomain($domainId); if ($requestMethod === RequestMethodInterface::METHOD_POST) { + /** @var array $payload */ $payload = $request->getParsedBody(); $payload[ShortUrlInputFilter::DOMAIN] = $domain->getAuthority(); diff --git a/module/Rest/src/Service/ApiKeyService.php b/module/Rest/src/Service/ApiKeyService.php index 545ff310..d66e70e2 100644 --- a/module/Rest/src/Service/ApiKeyService.php +++ b/module/Rest/src/Service/ApiKeyService.php @@ -38,12 +38,12 @@ class ApiKeyService implements ApiKeyServiceInterface private function buildApiKeyWithParams(?Chronos $expirationDate, ?string $name): ApiKey { return match (true) { - $expirationDate === null && $name === null => ApiKey::create(), $expirationDate !== null && $name !== null => ApiKey::fromMeta( ApiKeyMeta::withNameAndExpirationDate($name, $expirationDate), ), - $name === null => ApiKey::fromMeta(ApiKeyMeta::withExpirationDate($expirationDate)), - default => ApiKey::fromMeta(ApiKeyMeta::withName($name)), + $expirationDate !== null => ApiKey::fromMeta(ApiKeyMeta::withExpirationDate($expirationDate)), + $name !== null => ApiKey::fromMeta(ApiKeyMeta::withName($name)), + default => ApiKey::create(), }; } diff --git a/phpstan.neon b/phpstan.neon index 80f1b083..bf3afc8e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,11 @@ +includes: + - vendor/phpstan/phpstan-doctrine/extension.neon + - vendor/phpstan/phpstan-symfony/extension.neon parameters: checkMissingIterableValueType: false checkGenericClassInNonGenericObjectType: false - ignoreErrors: - - '#If condition is always false#' + symfony: + console_application_loader: 'config/cli-app.php' + doctrine: + repositoryClass: Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository + objectManagerLoader: 'config/entity-manager.php' diff --git a/public/index.php b/public/index.php index 78bb412a..99018890 100644 --- a/public/index.php +++ b/public/index.php @@ -2,5 +2,4 @@ declare(strict_types=1); -$run = require __DIR__ . '/../config/run.php'; -$run(); +(require __DIR__ . '/../config/run.php')();