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#'