diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d6e866b..766f87bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # CHANGELOG +## 1.10.1 - 2018-08-02 + +#### Added + +* *Nothing* + +#### Changed + +* [#167](https://github.com/shlinkio/shlink/issues/167) Shlink version is now set at build time to avoid older version numbers to be kept in newer builds. + +#### Deprecated + +* *Nothing* + +#### Removed + +* *Nothing* + +#### Fixed + +* [#165](https://github.com/shlinkio/shlink/issues/165) Fixed custom slugs failing when they are longer than 10 characters. +* [#166](https://github.com/shlinkio/shlink/issues/166) Fixed unusual edge case in which visits were not properly counted when ordering by visit and filtering by search term in `[GET] /short-codes` API endpoint. +* [#174](https://github.com/shlinkio/shlink/issues/174) Fixed geolocation not working due to a deprecation on used service. +* [#172](https://github.com/shlinkio/shlink/issues/172) Documented missing filtering params for `[GET] /short-codes/{shortCode}/visits` API endpoint, which allow the list to be filtered by date range. + + For example: `https://doma.in/rest/v1/short-urls/abc123/visits?startDate=2017-05-23&endDate=2017-10-05` + +* [#169](https://github.com/shlinkio/shlink/issues/169) Fixed unhandled error when parsing `ShortUrlMeta` and date fields are already `DateTime` instances. + + ## 1.10.0 - 2018-07-09 #### Added diff --git a/build.sh b/build.sh index de9102d6..65e4b291 100755 --- a/build.sh +++ b/build.sh @@ -46,6 +46,9 @@ rm -rf data/{cache,log,proxies}/{*,.gitignore} rm -rf config/params/{*,.gitignore} rm -rf config/autoload/{{,*.}local.php{,.dist},.gitignore} +# Update shlink version in config +sed -i "s/%SHLINK_VERSION%/${version}/g" config/autoload/app_options.global.php + # Compressing file rm -f "${projectdir}"/build/shlink_${version}_dist.zip zip -ry "${projectdir}"/build/shlink_${version}_dist.zip "../shlink_${version}_dist" diff --git a/config/autoload/app_options.global.php b/config/autoload/app_options.global.php index 2a32f31b..12e22c0f 100644 --- a/config/autoload/app_options.global.php +++ b/config/autoload/app_options.global.php @@ -7,7 +7,7 @@ return [ 'app_options' => [ 'name' => 'Shlink', - 'version' => '1.7.0', + 'version' => '%SHLINK_VERSION%', 'secret_key' => env('SECRET_KEY'), ], diff --git a/data/migrations/Version20180801183328.php b/data/migrations/Version20180801183328.php new file mode 100644 index 00000000..b87415f6 --- /dev/null +++ b/data/migrations/Version20180801183328.php @@ -0,0 +1,45 @@ +setSize($schema, self::NEW_SIZE); + } + + /** + * @param Schema $schema + * @throws SchemaException + */ + public function down(Schema $schema): void + { + $this->setSize($schema, self::OLD_SIZE); + } + + /** + * @param Schema $schema + * @param int $size + * @throws SchemaException + */ + private function setSize(Schema $schema, int $size): void + { + $schema->getTable('short_urls')->getColumn('short_code')->setLength($size); + } +} diff --git a/docs/swagger/paths/v1_short-codes_{shortCode}_visits.json b/docs/swagger/paths/v1_short-codes_{shortCode}_visits.json index ff390fce..f431fc00 100644 --- a/docs/swagger/paths/v1_short-codes_{shortCode}_visits.json +++ b/docs/swagger/paths/v1_short-codes_{shortCode}_visits.json @@ -15,6 +15,24 @@ "schema": { "type": "string" } + }, + { + "name": "startDate", + "in": "query", + "description": "The date (in ISO-8601 format) from which we want to get visits.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "endDate", + "in": "query", + "description": "The date (in ISO-8601 format) until which we want to get visits.", + "required": false, + "schema": { + "type": "string" + } } ], "security": [ diff --git a/module/CLI/config/dependencies.config.php b/module/CLI/config/dependencies.config.php index 6f692af7..4dd461be 100644 --- a/module/CLI/config/dependencies.config.php +++ b/module/CLI/config/dependencies.config.php @@ -3,7 +3,7 @@ declare(strict_types=1); use Shlinkio\Shlink\CLI\Command; use Shlinkio\Shlink\CLI\Factory\ApplicationFactory; -use Shlinkio\Shlink\Common\Service\IpLocationResolver; +use Shlinkio\Shlink\Common\Service\IpApiLocationResolver; use Shlinkio\Shlink\Common\Service\PreviewGenerator; use Shlinkio\Shlink\Core\Service; use Shlinkio\Shlink\Rest\Service\ApiKeyService; @@ -51,7 +51,7 @@ return [ ], Command\Visit\ProcessVisitsCommand::class => [ Service\VisitService::class, - IpLocationResolver::class, + IpApiLocationResolver::class, 'translator', ], Command\Config\GenerateCharsetCommand::class => ['translator'], diff --git a/module/CLI/src/Command/Visit/ProcessVisitsCommand.php b/module/CLI/src/Command/Visit/ProcessVisitsCommand.php index f3e174d6..81200e22 100644 --- a/module/CLI/src/Command/Visit/ProcessVisitsCommand.php +++ b/module/CLI/src/Command/Visit/ProcessVisitsCommand.php @@ -15,8 +15,8 @@ use Zend\I18n\Translator\TranslatorInterface; class ProcessVisitsCommand extends Command { - const LOCALHOST = '127.0.0.1'; - const NAME = 'visit:process'; + private const LOCALHOST = '127.0.0.1'; + public const NAME = 'visit:process'; /** * @var VisitServiceInterface @@ -57,10 +57,10 @@ class ProcessVisitsCommand extends Command foreach ($visits as $visit) { $ipAddr = $visit->getRemoteAddr(); - $io->write(sprintf('%s %s', $this->translator->translate('Processing IP'), $ipAddr)); + $io->write(\sprintf('%s %s', $this->translator->translate('Processing IP'), $ipAddr)); if ($ipAddr === self::LOCALHOST) { $io->writeln( - sprintf(' (%s)', $this->translator->translate('Ignored localhost address')) + \sprintf(' (%s)', $this->translator->translate('Ignored localhost address')) ); continue; } @@ -73,12 +73,17 @@ class ProcessVisitsCommand extends Command $visit->setVisitLocation($location); $this->visitService->saveVisit($visit); - $io->writeln(sprintf( + $io->writeln(\sprintf( ' (' . $this->translator->translate('Address located at "%s"') . ')', $location->getCityName() )); } catch (WrongIpException $e) { - continue; + $io->writeln( + \sprintf(' %s', $this->translator->translate('An error occurred while locating IP')) + ); + if ($io->isVerbose()) { + $this->getApplication()->renderException($e, $output); + } } } diff --git a/module/CLI/test/Command/Api/GenerateKeyCommandTest.php b/module/CLI/test/Command/Api/GenerateKeyCommandTest.php index 7c9482f7..88a44ab8 100644 --- a/module/CLI/test/Command/Api/GenerateKeyCommandTest.php +++ b/module/CLI/test/Command/Api/GenerateKeyCommandTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\TestCase; 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 Symfony\Component\Console\Application; use Symfony\Component\Console\Tester\CommandTester; @@ -37,7 +38,8 @@ class GenerateKeyCommandTest extends TestCase */ public function noExpirationDateIsDefinedIfNotProvided() { - $this->apiKeyService->create(null)->shouldBeCalledTimes(1); + $this->apiKeyService->create(null)->shouldBeCalledTimes(1) + ->willReturn(new ApiKey()); $this->commandTester->execute([ 'command' => 'api-key:generate', ]); @@ -46,9 +48,10 @@ class GenerateKeyCommandTest extends TestCase /** * @test */ - public function expirationDateIsDefinedIfWhenProvided() + public function expirationDateIsDefinedIfProvided() { - $this->apiKeyService->create(Argument::type(\DateTime::class))->shouldBeCalledTimes(1); + $this->apiKeyService->create(Argument::type(\DateTime::class))->shouldBeCalledTimes(1) + ->willReturn(new ApiKey()); $this->commandTester->execute([ 'command' => 'api-key:generate', '--expirationDate' => '2016-01-01', diff --git a/module/CLI/test/Command/Tag/CreateTagCommandTest.php b/module/CLI/test/Command/Tag/CreateTagCommandTest.php index a81a2383..39b6f59f 100644 --- a/module/CLI/test/Command/Tag/CreateTagCommandTest.php +++ b/module/CLI/test/Command/Tag/CreateTagCommandTest.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Tag; +use Doctrine\Common\Collections\ArrayCollection; use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\MethodProphecy; use Prophecy\Prophecy\ObjectProphecy; @@ -52,7 +53,7 @@ class CreateTagCommandTest extends TestCase { $tagNames = ['foo', 'bar']; /** @var MethodProphecy $createTags */ - $createTags = $this->tagService->createTags($tagNames)->willReturn([]); + $createTags = $this->tagService->createTags($tagNames)->willReturn(new ArrayCollection()); $this->commandTester->execute([ '--name' => $tagNames, diff --git a/module/CLI/test/Command/Visit/ProcessVisitsCommandTest.php b/module/CLI/test/Command/Visit/ProcessVisitsCommandTest.php index c80f5223..bc62124f 100644 --- a/module/CLI/test/Command/Visit/ProcessVisitsCommandTest.php +++ b/module/CLI/test/Command/Visit/ProcessVisitsCommandTest.php @@ -7,7 +7,7 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\CLI\Command\Visit\ProcessVisitsCommand; -use Shlinkio\Shlink\Common\Service\IpLocationResolver; +use Shlinkio\Shlink\Common\Service\IpApiLocationResolver; use Shlinkio\Shlink\Core\Entity\Visit; use Shlinkio\Shlink\Core\Service\VisitService; use Symfony\Component\Console\Application; @@ -32,7 +32,7 @@ class ProcessVisitsCommandTest extends TestCase public function setUp() { $this->visitService = $this->prophesize(VisitService::class); - $this->ipResolver = $this->prophesize(IpLocationResolver::class); + $this->ipResolver = $this->prophesize(IpApiLocationResolver::class); $command = new ProcessVisitsCommand( $this->visitService->reveal(), $this->ipResolver->reveal(), diff --git a/module/Common/config/dependencies.config.php b/module/Common/config/dependencies.config.php index 19093549..e9422d38 100644 --- a/module/Common/config/dependencies.config.php +++ b/module/Common/config/dependencies.config.php @@ -32,7 +32,7 @@ return [ Image\ImageBuilder::class => Image\ImageBuilderFactory::class, - Service\IpLocationResolver::class => ConfigAbstractFactory::class, + Service\IpApiLocationResolver::class => ConfigAbstractFactory::class, Service\PreviewGenerator::class => ConfigAbstractFactory::class, ], 'aliases' => [ @@ -51,7 +51,7 @@ return [ ConfigAbstractFactory::class => [ TranslatorExtension::class => ['translator'], LocaleMiddleware::class => ['translator'], - Service\IpLocationResolver::class => ['httpClient'], + Service\IpApiLocationResolver::class => ['httpClient'], Service\PreviewGenerator::class => [ ImageBuilder::class, Filesystem::class, diff --git a/module/Common/src/Factory/EntityManagerFactory.php b/module/Common/src/Factory/EntityManagerFactory.php index d8622121..a219f2e6 100644 --- a/module/Common/src/Factory/EntityManagerFactory.php +++ b/module/Common/src/Factory/EntityManagerFactory.php @@ -23,8 +23,7 @@ class EntityManagerFactory implements FactoryInterface * @param null|array $options * @return object * @throws ServiceNotFoundException if unable to resolve the service. - * @throws ServiceNotCreatedException if an exception is raised when - * creating a service. + * @throws ServiceNotCreatedException if an exception is raised when creating a service. * @throws ContainerException if any other error occurs */ public function __invoke(ContainerInterface $container, $requestedName, array $options = null) @@ -32,14 +31,14 @@ class EntityManagerFactory implements FactoryInterface $globalConfig = $container->get('config'); $isDevMode = isset($globalConfig['debug']) ? ((bool) $globalConfig['debug']) : false; $cache = $container->has(Cache::class) ? $container->get(Cache::class) : new ArrayCache(); - $emConfig = isset($globalConfig['entity_manager']) ? $globalConfig['entity_manager'] : []; - $connecitonConfig = isset($emConfig['connection']) ? $emConfig['connection'] : []; - $ormConfig = isset($emConfig['orm']) ? $emConfig['orm'] : []; + $emConfig = $globalConfig['entity_manager'] ?? []; + $connectionConfig = $emConfig['connection'] ?? []; + $ormConfig = $emConfig['orm'] ?? []; - return EntityManager::create($connecitonConfig, Setup::createAnnotationMetadataConfiguration( - isset($ormConfig['entities_paths']) ? $ormConfig['entities_paths'] : [], + return EntityManager::create($connectionConfig, Setup::createAnnotationMetadataConfiguration( + $ormConfig['entities_paths'] ?? [], $isDevMode, - isset($ormConfig['proxies_dir']) ? $ormConfig['proxies_dir'] : null, + $ormConfig['proxies_dir'] ?? null, $cache, false )); diff --git a/module/Common/src/Service/IpApiLocationResolver.php b/module/Common/src/Service/IpApiLocationResolver.php new file mode 100644 index 00000000..9787770c --- /dev/null +++ b/module/Common/src/Service/IpApiLocationResolver.php @@ -0,0 +1,51 @@ +httpClient = $httpClient; + } + + /** + * @param string $ipAddress + * @return array + * @throws WrongIpException + */ + public function resolveIpLocation(string $ipAddress): array + { + try { + $response = $this->httpClient->get(\sprintf(self::SERVICE_PATTERN, $ipAddress)); + return $this->mapFields(\json_decode((string) $response->getBody(), true)); + } catch (GuzzleException $e) { + throw WrongIpException::fromIpAddress($ipAddress, $e); + } + } + + private function mapFields(array $entry): array + { + return [ + 'country_code' => $entry['countryCode'] ?? '', + 'country_name' => $entry['country'] ?? '', + 'region_name' => $entry['regionName'] ?? '', + 'city' => $entry['city'] ?? '', + 'latitude' => $entry['lat'] ?? '', + 'longitude' => $entry['lon'] ?? '', + 'time_zone' => $entry['timezone'] ?? '', + ]; + } +} diff --git a/module/Common/src/Service/IpLocationResolver.php b/module/Common/src/Service/IpLocationResolver.php deleted file mode 100644 index 017bf89c..00000000 --- a/module/Common/src/Service/IpLocationResolver.php +++ /dev/null @@ -1,38 +0,0 @@ -httpClient = $httpClient; - } - - /** - * @param string $ipAddress - * @return array - * @throws WrongIpException - */ - public function resolveIpLocation(string $ipAddress): array - { - try { - $response = $this->httpClient->get(sprintf(self::SERVICE_PATTERN, $ipAddress)); - return json_decode((string) $response->getBody(), true); - } catch (GuzzleException $e) { - throw WrongIpException::fromIpAddress($ipAddress, $e); - } - } -} diff --git a/module/Common/test/Service/IpLocationResolverTest.php b/module/Common/test/Service/IpApiLocationResolverTest.php similarity index 50% rename from module/Common/test/Service/IpLocationResolverTest.php rename to module/Common/test/Service/IpApiLocationResolverTest.php index f5fb162c..1d36ce9a 100644 --- a/module/Common/test/Service/IpLocationResolverTest.php +++ b/module/Common/test/Service/IpApiLocationResolverTest.php @@ -8,12 +8,12 @@ use GuzzleHttp\Exception\TransferException; use GuzzleHttp\Psr7\Response; use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\ObjectProphecy; -use Shlinkio\Shlink\Common\Service\IpLocationResolver; +use Shlinkio\Shlink\Common\Service\IpApiLocationResolver; -class IpLocationResolverTest extends TestCase +class IpApiLocationResolverTest extends TestCase { /** - * @var IpLocationResolver + * @var IpApiLocationResolver */ protected $ipResolver; /** @@ -24,7 +24,7 @@ class IpLocationResolverTest extends TestCase public function setUp() { $this->client = $this->prophesize(Client::class); - $this->ipResolver = new IpLocationResolver($this->client->reveal()); + $this->ipResolver = new IpApiLocationResolver($this->client->reveal()); } /** @@ -32,16 +32,26 @@ class IpLocationResolverTest extends TestCase */ public function correctIpReturnsDecodedInfo() { + $actual = [ + 'countryCode' => 'bar', + 'lat' => 5, + 'lon' => 10, + ]; $expected = [ - 'foo' => 'bar', - 'baz' => 'foo', + 'country_code' => 'bar', + 'country_name' => '', + 'region_name' => '', + 'city' => '', + 'latitude' => 5, + 'longitude' => 10, + 'time_zone' => '', ]; $response = new Response(); - $response->getBody()->write(json_encode($expected)); + $response->getBody()->write(\json_encode($actual)); $response->getBody()->rewind(); - $this->client->get('http://freegeoip.net/json/1.2.3.4')->willReturn($response) - ->shouldBeCalledTimes(1); + $this->client->get('http://ip-api.com/json/1.2.3.4')->willReturn($response) + ->shouldBeCalledTimes(1); $this->assertEquals($expected, $this->ipResolver->resolveIpLocation('1.2.3.4')); } @@ -51,8 +61,8 @@ class IpLocationResolverTest extends TestCase */ public function guzzleExceptionThrowsShlinkException() { - $this->client->get('http://freegeoip.net/json/1.2.3.4')->willThrow(new TransferException()) - ->shouldBeCalledTimes(1); + $this->client->get('http://ip-api.com/json/1.2.3.4')->willThrow(new TransferException()) + ->shouldBeCalledTimes(1); $this->ipResolver->resolveIpLocation('1.2.3.4'); } } diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index b1ffe60f..ff21c47b 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -29,7 +29,7 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable * name="short_code", * type="string", * nullable=false, - * length=10, + * length=255, * unique=true * ) */ diff --git a/module/Core/src/Model/ShortUrlMeta.php b/module/Core/src/Model/ShortUrlMeta.php index d9acf6b9..b7e0275a 100644 --- a/module/Core/src/Model/ShortUrlMeta.php +++ b/module/Core/src/Model/ShortUrlMeta.php @@ -78,19 +78,34 @@ final class ShortUrlMeta throw ValidationException::fromInputFilter($inputFilter); } - $this->validSince = $inputFilter->getValue(ShortUrlMetaInputFilter::VALID_SINCE); - $this->validSince = $this->validSince !== null ? new \DateTime($this->validSince) : null; - $this->validUntil = $inputFilter->getValue(ShortUrlMetaInputFilter::VALID_UNTIL); - $this->validUntil = $this->validUntil !== null ? new \DateTime($this->validUntil) : null; + $this->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; } /** + * @param string|\DateTime|null $date * @return \DateTime|null */ - public function getValidSince() + private function parseDateField($date): ?\DateTime + { + if ($date === null || $date instanceof \DateTime) { + return $date; + } + + if (\is_string($date)) { + return new \DateTime($date); + } + + return null; + } + + /** + * @return \DateTime|null + */ + public function getValidSince(): ?\DateTime { return $this->validSince; } @@ -103,7 +118,7 @@ final class ShortUrlMeta /** * @return \DateTime|null */ - public function getValidUntil() + public function getValidUntil(): ?\DateTime { return $this->validUntil; } diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index a959f818..04a5b6e6 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -51,16 +51,17 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI $order = \is_array($orderBy) ? $orderBy[$fieldName] : 'ASC'; if (\in_array($fieldName, ['visits', 'visitsCount', 'visitCount'], true)) { - $qb->addSelect('COUNT(v) AS totalVisits') + $qb->addSelect('COUNT(DISTINCT v) AS totalVisits') ->leftJoin('s.visits', 'v') ->groupBy('s') ->orderBy('totalVisits', $order); return \array_column($qb->getQuery()->getResult(), 0); - } elseif (\in_array($fieldName, ['originalUrl', 'shortCode', 'dateCreated'], true)) { - $qb->orderBy('s.' . $fieldName, $order); } + if (\in_array($fieldName, ['originalUrl', 'shortCode', 'dateCreated'], true)) { + $qb->orderBy('s.' . $fieldName, $order); + } return $qb->getQuery()->getResult(); } diff --git a/module/Core/src/Service/Tag/TagService.php b/module/Core/src/Service/Tag/TagService.php index 65ae733a..5369769d 100644 --- a/module/Core/src/Service/Tag/TagService.php +++ b/module/Core/src/Service/Tag/TagService.php @@ -28,16 +28,18 @@ class TagService implements TagServiceInterface * @return Tag[] * @throws \UnexpectedValueException */ - public function listTags() + public function listTags(): array { - return $this->em->getRepository(Tag::class)->findBy([], ['name' => 'ASC']); + /** @var Tag[] $tags */ + $tags = $this->em->getRepository(Tag::class)->findBy([], ['name' => 'ASC']); + return $tags; } /** * @param array $tagNames * @return void */ - public function deleteTags(array $tagNames) + public function deleteTags(array $tagNames): void { /** @var TagRepository $repo */ $repo = $this->em->getRepository(Tag::class); @@ -50,7 +52,7 @@ class TagService implements TagServiceInterface * @param string[] $tagNames * @return Collection|Tag[] */ - public function createTags(array $tagNames) + public function createTags(array $tagNames): Collection { $tags = $this->tagNamesToEntities($this->em, $tagNames); $this->em->flush(); @@ -65,7 +67,7 @@ class TagService implements TagServiceInterface * @throws EntityDoesNotExistException * @throws ORM\OptimisticLockException */ - public function renameTag($oldName, $newName) + public function renameTag($oldName, $newName): Tag { $criteria = ['name' => $oldName]; /** @var Tag|null $tag */ diff --git a/module/Core/src/Service/Tag/TagServiceInterface.php b/module/Core/src/Service/Tag/TagServiceInterface.php index 7ee57bda..a70b9e48 100644 --- a/module/Core/src/Service/Tag/TagServiceInterface.php +++ b/module/Core/src/Service/Tag/TagServiceInterface.php @@ -12,13 +12,13 @@ interface TagServiceInterface /** * @return Tag[] */ - public function listTags(); + public function listTags(): array; /** * @param string[] $tagNames * @return void */ - public function deleteTags(array $tagNames); + public function deleteTags(array $tagNames): void; /** * Provided a list of tag names, creates all that do not exist yet @@ -26,7 +26,7 @@ interface TagServiceInterface * @param string[] $tagNames * @return Collection|Tag[] */ - public function createTags(array $tagNames); + public function createTags(array $tagNames): Collection; /** * @param string $oldName @@ -34,5 +34,5 @@ interface TagServiceInterface * @return Tag * @throws EntityDoesNotExistException */ - public function renameTag($oldName, $newName); + public function renameTag($oldName, $newName): Tag; } diff --git a/module/Core/test-func/Repository/ShortUrlRepositoryTest.php b/module/Core/test-func/Repository/ShortUrlRepositoryTest.php index 4616f6c8..184ca054 100644 --- a/module/Core/test-func/Repository/ShortUrlRepositoryTest.php +++ b/module/Core/test-func/Repository/ShortUrlRepositoryTest.php @@ -38,7 +38,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $bar = new ShortUrl(); $bar->setOriginalUrl('bar') - ->setShortCode('bar') + ->setShortCode('bar_very_long_text') ->setValidSince((new \DateTime())->add(new \DateInterval('P1M'))); $this->getEntityManager()->persist($bar); diff --git a/module/Rest/src/Service/ApiKeyService.php b/module/Rest/src/Service/ApiKeyService.php index e886bcf1..815bbed3 100644 --- a/module/Rest/src/Service/ApiKeyService.php +++ b/module/Rest/src/Service/ApiKeyService.php @@ -25,7 +25,7 @@ class ApiKeyService implements ApiKeyServiceInterface * @param \DateTime $expirationDate * @return ApiKey */ - public function create(\DateTime $expirationDate = null) + public function create(\DateTime $expirationDate = null): ApiKey { $key = new ApiKey(); if ($expirationDate !== null) { @@ -44,7 +44,7 @@ class ApiKeyService implements ApiKeyServiceInterface * @param string $key * @return bool */ - public function check(string $key) + public function check(string $key): bool { /** @var ApiKey|null $apiKey */ $apiKey = $this->getByKey($key); @@ -58,7 +58,7 @@ class ApiKeyService implements ApiKeyServiceInterface * @return ApiKey * @throws InvalidArgumentException */ - public function disable(string $key) + public function disable(string $key): ApiKey { /** @var ApiKey|null $apiKey */ $apiKey = $this->getByKey($key); @@ -77,10 +77,12 @@ class ApiKeyService implements ApiKeyServiceInterface * @param bool $enabledOnly Tells if only enabled keys should be returned * @return ApiKey[] */ - public function listKeys(bool $enabledOnly = false) + public function listKeys(bool $enabledOnly = false): array { $conditions = $enabledOnly ? ['enabled' => true] : []; - return $this->em->getRepository(ApiKey::class)->findBy($conditions); + /** @var ApiKey[] $apiKeys */ + $apiKeys = $this->em->getRepository(ApiKey::class)->findBy($conditions); + return $apiKeys; } /** @@ -89,7 +91,7 @@ class ApiKeyService implements ApiKeyServiceInterface * @param string $key * @return ApiKey|null */ - public function getByKey(string $key) + public function getByKey(string $key): ?ApiKey { /** @var ApiKey|null $apiKey */ $apiKey = $this->em->getRepository(ApiKey::class)->findOneBy([ diff --git a/module/Rest/src/Service/ApiKeyServiceInterface.php b/module/Rest/src/Service/ApiKeyServiceInterface.php index eab38503..3d62adbe 100644 --- a/module/Rest/src/Service/ApiKeyServiceInterface.php +++ b/module/Rest/src/Service/ApiKeyServiceInterface.php @@ -14,7 +14,7 @@ interface ApiKeyServiceInterface * @param \DateTime $expirationDate * @return ApiKey */ - public function create(\DateTime $expirationDate = null); + public function create(\DateTime $expirationDate = null): ApiKey; /** * Checks if provided key is a valid api key @@ -22,7 +22,7 @@ interface ApiKeyServiceInterface * @param string $key * @return bool */ - public function check(string $key); + public function check(string $key): bool; /** * Disables provided api key @@ -31,7 +31,7 @@ interface ApiKeyServiceInterface * @return ApiKey * @throws InvalidArgumentException */ - public function disable(string $key); + public function disable(string $key): ApiKey; /** * Lists all existing api keys @@ -39,7 +39,7 @@ interface ApiKeyServiceInterface * @param bool $enabledOnly Tells if only enabled keys should be returned * @return ApiKey[] */ - public function listKeys(bool $enabledOnly = false); + public function listKeys(bool $enabledOnly = false): array; /** * Tries to find one API key by its key string @@ -47,5 +47,5 @@ interface ApiKeyServiceInterface * @param string $key * @return ApiKey|null */ - public function getByKey(string $key); + public function getByKey(string $key): ?ApiKey; } diff --git a/phpstan.neon b/phpstan.neon index f1c8ab6d..c67d210b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,4 +4,3 @@ parameters: - module/Rest/src/Util/RestUtils.php ignoreErrors: - '#is not subtype of Throwable#' - - '#Cannot access offset#'