From 0183c8a4b761aa4c9252229ea8e30cd399042131 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 29 Sep 2018 12:52:32 +0200 Subject: [PATCH] Migrated from standard datetime objects to chronos objects --- composer.json | 3 +- .../src/Command/Api/GenerateKeyCommand.php | 3 +- .../CLI/src/Command/Api/ListKeysCommand.php | 2 +- .../ShortUrl/GenerateShortUrlCommand.php | 5 +- .../src/Command/ShortUrl/GetVisitsCommand.php | 7 +- .../Command/Api/GenerateKeyCommandTest.php | 5 +- .../Command/ShortUrl/GetVisitsCommandTest.php | 3 +- .../src/Factory/EntityManagerFactory.php | 16 ++-- .../Common/src/Type/ChronosDateTimeType.php | 52 ++++++++++ module/Common/src/Util/DateRange.php | 23 ++--- .../test/Type/ChronosDateTimeTypeTest.php | 94 +++++++++++++++++++ module/Common/test/Util/DateRangeTest.php | 5 +- module/Core/src/Entity/ShortUrl.php | 27 +++--- module/Core/src/Entity/Visit.php | 13 +-- module/Core/src/Model/ShortUrlMeta.php | 32 +++---- .../src/Repository/ShortUrlRepository.php | 3 +- module/Core/src/Service/UrlShortener.php | 17 +--- .../src/Service/UrlShortenerInterface.php | 17 +--- .../Transformer/ShortUrlDataTransformer.php | 3 +- .../Validation/ShortUrlMetaInputFilter.php | 12 +-- .../Repository/ShortUrlRepositoryTest.php | 3 +- .../Repository/VisitRepositoryTest.php | 10 +- module/Core/test/Model/ShortUrlMetaTest.php | 5 +- .../Core/test/Service/ShortUrlServiceTest.php | 9 +- .../Action/ShortUrl/CreateShortUrlAction.php | 5 +- .../Rest/src/Action/Visit/GetVisitsAction.php | 14 +-- module/Rest/src/Entity/ApiKey.php | 11 ++- module/Rest/src/Service/ApiKeyService.php | 31 +----- .../src/Service/ApiKeyServiceInterface.php | 31 +----- .../test/Action/Visit/GetVisitsActionTest.php | 3 +- .../Rest/test/Service/ApiKeyServiceTest.php | 5 +- 31 files changed, 271 insertions(+), 198 deletions(-) create mode 100644 module/Common/src/Type/ChronosDateTimeType.php create mode 100644 module/Common/test/Type/ChronosDateTimeTypeTest.php diff --git a/composer.json b/composer.json index 6111bd80..f6671aa2 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ "ext-json": "*", "ext-pdo": "*", "acelaya/ze-content-based-error-handler": "^2.2", + "cakephp/chronos": "^1.2", "cocur/slugify": "^3.0", "doctrine/cache": "^1.6", "doctrine/migrations": "^1.4", @@ -32,7 +33,7 @@ "theorchard/monolog-cascade": "^0.4", "zendframework/zend-config": "^3.0", "zendframework/zend-config-aggregator": "^1.0", - "zendframework/zend-diactoros": "^1.7", + "zendframework/zend-diactoros": "^2.0", "zendframework/zend-expressive": "^3.0", "zendframework/zend-expressive-fastroute": "^3.0", "zendframework/zend-expressive-helpers": "^5.0", diff --git a/module/CLI/src/Command/Api/GenerateKeyCommand.php b/module/CLI/src/Command/Api/GenerateKeyCommand.php index cfe747f0..0b7fc476 100644 --- a/module/CLI/src/Command/Api/GenerateKeyCommand.php +++ b/module/CLI/src/Command/Api/GenerateKeyCommand.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\Api; +use Cake\Chronos\Chronos; use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -46,7 +47,7 @@ class GenerateKeyCommand extends Command public function execute(InputInterface $input, OutputInterface $output) { $expirationDate = $input->getOption('expirationDate'); - $apiKey = $this->apiKeyService->create(isset($expirationDate) ? new \DateTime($expirationDate) : null); + $apiKey = $this->apiKeyService->create(isset($expirationDate) ? Chronos::parse($expirationDate) : null); (new SymfonyStyle($input, $output))->success( sprintf($this->translator->translate('Generated API key: "%s"'), $apiKey) diff --git a/module/CLI/src/Command/Api/ListKeysCommand.php b/module/CLI/src/Command/Api/ListKeysCommand.php index 88b2cf7e..b67343d8 100644 --- a/module/CLI/src/Command/Api/ListKeysCommand.php +++ b/module/CLI/src/Command/Api/ListKeysCommand.php @@ -66,7 +66,7 @@ class ListKeysCommand extends Command if (! $enabledOnly) { $rowData[] = \sprintf($messagePattern, $this->getEnabledSymbol($row)); } - $rowData[] = $expiration !== null ? $expiration->format(\DateTime::ATOM) : '-'; + $rowData[] = $expiration !== null ? $expiration->toAtomString() : '-'; $rows[] = $rowData; } diff --git a/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php b/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php index 86b8d131..0aacbcc8 100644 --- a/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php +++ b/module/CLI/src/Command/ShortUrl/GenerateShortUrlCommand.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\ShortUrl; +use Cake\Chronos\Chronos; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; @@ -143,9 +144,9 @@ class GenerateShortUrlCommand extends Command } } - private function getOptionalDate(InputInterface $input, string $fieldName): ?\DateTime + private function getOptionalDate(InputInterface $input, string $fieldName): ?Chronos { $since = $input->getOption($fieldName); - return $since !== null ? new \DateTime($since) : null; + return $since !== null ? Chronos::parse($since) : null; } } diff --git a/module/CLI/src/Command/ShortUrl/GetVisitsCommand.php b/module/CLI/src/Command/ShortUrl/GetVisitsCommand.php index 373b9169..ada4d72d 100644 --- a/module/CLI/src/Command/ShortUrl/GetVisitsCommand.php +++ b/module/CLI/src/Command/ShortUrl/GetVisitsCommand.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\ShortUrl; +use Cake\Chronos\Chronos; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface; use Symfony\Component\Console\Command\Command; @@ -107,10 +108,6 @@ class GetVisitsCommand extends Command private function getDateOption(InputInterface $input, $key) { $value = $input->getOption($key); - if (! empty($value)) { - $value = new \DateTime($value); - } - - return $value; + return ! empty($value) ? Chronos::parse($value) : $value; } } diff --git a/module/CLI/test/Command/Api/GenerateKeyCommandTest.php b/module/CLI/test/Command/Api/GenerateKeyCommandTest.php index 88a44ab8..badf7979 100644 --- a/module/CLI/test/Command/Api/GenerateKeyCommandTest.php +++ b/module/CLI/test/Command/Api/GenerateKeyCommandTest.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\Api; +use Cake\Chronos\Chronos; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; @@ -50,8 +51,8 @@ class GenerateKeyCommandTest extends TestCase */ public function expirationDateIsDefinedIfProvided() { - $this->apiKeyService->create(Argument::type(\DateTime::class))->shouldBeCalledTimes(1) - ->willReturn(new ApiKey()); + $this->apiKeyService->create(Argument::type(Chronos::class))->shouldBeCalledTimes(1) + ->willReturn(new ApiKey()); $this->commandTester->execute([ 'command' => 'api-key:generate', '--expirationDate' => '2016-01-01', diff --git a/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php b/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php index 54ce2f6f..ba2a9fb5 100644 --- a/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\CLI\Command\ShortUrl; +use Cake\Chronos\Chronos; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; @@ -58,7 +59,7 @@ class GetVisitsCommandTest extends TestCase $shortCode = 'abc123'; $startDate = '2016-01-01'; $endDate = '2016-02-01'; - $this->visitsTracker->info($shortCode, new DateRange(new \DateTime($startDate), new \DateTime($endDate))) + $this->visitsTracker->info($shortCode, new DateRange(Chronos::parse($startDate), Chronos::parse($endDate))) ->willReturn([]) ->shouldBeCalledTimes(1); diff --git a/module/Common/src/Factory/EntityManagerFactory.php b/module/Common/src/Factory/EntityManagerFactory.php index a219f2e6..59285601 100644 --- a/module/Common/src/Factory/EntityManagerFactory.php +++ b/module/Common/src/Factory/EntityManagerFactory.php @@ -5,10 +5,13 @@ namespace Shlinkio\Shlink\Common\Factory; use Doctrine\Common\Cache\ArrayCache; use Doctrine\Common\Cache\Cache; +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Types\Type; use Doctrine\ORM\EntityManager; +use Doctrine\ORM\ORMException; use Doctrine\ORM\Tools\Setup; use Interop\Container\ContainerInterface; -use Interop\Container\Exception\ContainerException; +use Shlinkio\Shlink\Common\Type\ChronosDateTimeType; use Zend\ServiceManager\Exception\ServiceNotCreatedException; use Zend\ServiceManager\Exception\ServiceNotFoundException; use Zend\ServiceManager\Factory\FactoryInterface; @@ -16,15 +19,10 @@ use Zend\ServiceManager\Factory\FactoryInterface; class EntityManagerFactory implements FactoryInterface { /** - * Create an object - * - * @param ContainerInterface $container - * @param string $requestedName - * @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 ContainerException if any other error occurs + * @throws ORMException + * @throws DBALException */ public function __invoke(ContainerInterface $container, $requestedName, array $options = null) { @@ -35,6 +33,8 @@ class EntityManagerFactory implements FactoryInterface $connectionConfig = $emConfig['connection'] ?? []; $ormConfig = $emConfig['orm'] ?? []; + Type::addType(ChronosDateTimeType::CHRONOS_DATETIME, ChronosDateTimeType::class); + return EntityManager::create($connectionConfig, Setup::createAnnotationMetadataConfiguration( $ormConfig['entities_paths'] ?? [], $isDevMode, diff --git a/module/Common/src/Type/ChronosDateTimeType.php b/module/Common/src/Type/ChronosDateTimeType.php new file mode 100644 index 00000000..9771ecfe --- /dev/null +++ b/module/Common/src/Type/ChronosDateTimeType.php @@ -0,0 +1,52 @@ +format($platform->getDateTimeFormatString()); + } + + throw ConversionException::conversionFailedInvalidType( + $value, + $this->getName(), + ['null', \DateTimeInterface::class] + ); + } +} diff --git a/module/Common/src/Util/DateRange.php b/module/Common/src/Util/DateRange.php index f58f2033..53da7665 100644 --- a/module/Common/src/Util/DateRange.php +++ b/module/Common/src/Util/DateRange.php @@ -3,42 +3,35 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Common\Util; -class DateRange +use Cake\Chronos\Chronos; + +final class DateRange { /** - * @var \DateTimeInterface|null + * @var Chronos|null */ private $startDate; /** - * @var \DateTimeInterface|null + * @var Chronos|null */ private $endDate; - public function __construct(\DateTimeInterface $startDate = null, \DateTimeInterface $endDate = null) + public function __construct(?Chronos $startDate = null, ?Chronos $endDate = null) { $this->startDate = $startDate; $this->endDate = $endDate; } - /** - * @return \DateTimeInterface|null - */ - public function getStartDate() + public function getStartDate(): ?Chronos { return $this->startDate; } - /** - * @return \DateTimeInterface|null - */ - public function getEndDate() + public function getEndDate(): ?Chronos { return $this->endDate; } - /** - * @return bool - */ public function isEmpty(): bool { return $this->startDate === null && $this->endDate === null; diff --git a/module/Common/test/Type/ChronosDateTimeTypeTest.php b/module/Common/test/Type/ChronosDateTimeTypeTest.php new file mode 100644 index 00000000..4b7123bb --- /dev/null +++ b/module/Common/test/Type/ChronosDateTimeTypeTest.php @@ -0,0 +1,94 @@ +type = Type::getType(ChronosDateTimeType::CHRONOS_DATETIME); + } + + /** + * @test + */ + public function nameIsReturned() + { + $this->assertEquals(ChronosDateTimeType::CHRONOS_DATETIME, $this->type->getName()); + } + + /** + * @test + * @dataProvider provideValues + */ + public function valueIsConverted(?string $value, ?string $expected) + { + $platform = $this->prophesize(AbstractPlatform::class); + $platform->getDateTimeFormatString()->willReturn('Y-m-d H:i:s'); + + $result = $this->type->convertToPHPValue($value, $platform->reveal()); + + if ($expected === null) { + $this->assertNull($result); + } else { + $this->assertInstanceOf($expected, $result); + } + } + + public function provideValues(): array + { + return [ + [null, null], + ['now', Chronos::class], + ['2017-01-01', Chronos::class], + ]; + } + + /** + * @test + * @dataProvider providePhpValues + */ + public function valueIsConvertedToDatabaseFormat(?\DateTimeInterface $value, ?string $expected) + { + $platform = $this->prophesize(AbstractPlatform::class); + $platform->getDateTimeFormatString()->willReturn('Y-m-d'); + + $this->assertEquals($expected, $this->type->convertToDatabaseValue($value, $platform->reveal())); + } + + public function providePhpValues(): array + { + return [ + [null, null], + [new \DateTimeImmutable('2017-01-01'), '2017-01-01'], + [Chronos::parse('2017-02-01'), '2017-02-01'], + [new \DateTime('2017-03-01'), '2017-03-01'], + ]; + } + + /** + * @test + */ + public function exceptionIsThrownIfInvalidValueIsParsedToDatabase() + { + $this->expectException(ConversionException::class); + $this->type->convertToDatabaseValue(new \stdClass(), $this->prophesize(AbstractPlatform::class)->reveal()); + } +} diff --git a/module/Common/test/Util/DateRangeTest.php b/module/Common/test/Util/DateRangeTest.php index 4cde5004..e5e48d46 100644 --- a/module/Common/test/Util/DateRangeTest.php +++ b/module/Common/test/Util/DateRangeTest.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Common\Util; +use Cake\Chronos\Chronos; use PHPUnit\Framework\TestCase; use Shlinkio\Shlink\Common\Util\DateRange; @@ -24,8 +25,8 @@ class DateRangeTest extends TestCase */ public function providedDatesAreSet() { - $startDate = new \DateTime(); - $endDate = new \DateTime(); + $startDate = Chronos::now(); + $endDate = Chronos::now(); $range = new DateRange($startDate, $endDate); $this->assertSame($startDate, $range->getStartDate()); $this->assertSame($endDate, $range->getEndDate()); diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index fcaaa5dc..635db67b 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Entity; +use Cake\Chronos\Chronos; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; @@ -36,8 +37,8 @@ class ShortUrl extends AbstractEntity */ private $shortCode; /** - * @var \DateTime - * @ORM\Column(name="date_created", type="datetime") + * @var Chronos + * @ORM\Column(name="date_created", type="chronos_datetime") */ private $dateCreated; /** @@ -56,13 +57,13 @@ class ShortUrl extends AbstractEntity */ private $tags; /** - * @var \DateTime - * @ORM\Column(name="valid_since", type="datetime", nullable=true) + * @var Chronos|null + * @ORM\Column(name="valid_since", type="chronos_datetime", nullable=true) */ private $validSince; /** - * @var \DateTime - * @ORM\Column(name="valid_until", type="datetime", nullable=true) + * @var Chronos|null + * @ORM\Column(name="valid_until", type="chronos_datetime", nullable=true) */ private $validUntil; /** @@ -74,7 +75,7 @@ class ShortUrl extends AbstractEntity public function __construct() { $this->shortCode = ''; - $this->dateCreated = new \DateTime(); + $this->dateCreated = Chronos::now(); $this->visits = new ArrayCollection(); $this->tags = new ArrayCollection(); } @@ -117,12 +118,12 @@ class ShortUrl extends AbstractEntity return $this; } - public function getDateCreated(): \DateTime + public function getDateCreated(): Chronos { return $this->dateCreated; } - public function setDateCreated(\DateTime $dateCreated): self + public function setDateCreated(Chronos $dateCreated): self { $this->dateCreated = $dateCreated; return $this; @@ -151,23 +152,23 @@ class ShortUrl extends AbstractEntity return $this; } - public function getValidSince(): ?\DateTime + public function getValidSince(): ?Chronos { return $this->validSince; } - public function setValidSince(?\DateTime $validSince): self + public function setValidSince(?Chronos $validSince): self { $this->validSince = $validSince; return $this; } - public function getValidUntil(): ?\DateTime + public function getValidUntil(): ?Chronos { return $this->validUntil; } - public function setValidUntil(?\DateTime $validUntil): self + public function setValidUntil(?Chronos $validUntil): self { $this->validUntil = $validUntil; return $this; diff --git a/module/Core/src/Entity/Visit.php b/module/Core/src/Entity/Visit.php index f23657d4..81b01ba4 100644 --- a/module/Core/src/Entity/Visit.php +++ b/module/Core/src/Entity/Visit.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Entity; +use Cake\Chronos\Chronos; use Doctrine\ORM\Mapping as ORM; use Shlinkio\Shlink\Common\Entity\AbstractEntity; use Shlinkio\Shlink\Common\Exception\WrongIpException; @@ -25,8 +26,8 @@ class Visit extends AbstractEntity implements \JsonSerializable */ private $referer; /** - * @var \DateTime - * @ORM\Column(type="datetime", nullable=false) + * @var Chronos + * @ORM\Column(type="chronos_datetime", nullable=false) */ private $date; /** @@ -54,7 +55,7 @@ class Visit extends AbstractEntity implements \JsonSerializable public function __construct() { - $this->date = new \DateTime(); + $this->date = Chronos::now(); } public function getReferer(): string @@ -68,12 +69,12 @@ class Visit extends AbstractEntity implements \JsonSerializable return $this; } - public function getDate(): \DateTime + public function getDate(): Chronos { return $this->date; } - public function setDate(\DateTime $date): self + public function setDate(Chronos $date): self { $this->date = $date; return $this; @@ -148,7 +149,7 @@ class Visit extends AbstractEntity implements \JsonSerializable { return [ 'referer' => $this->referer, - 'date' => isset($this->date) ? $this->date->format(\DateTime::ATOM) : null, + 'date' => isset($this->date) ? $this->date->toAtomString() : null, 'userAgent' => $this->userAgent, 'visitLocation' => $this->visitLocation, diff --git a/module/Core/src/Model/ShortUrlMeta.php b/module/Core/src/Model/ShortUrlMeta.php index b7e0275a..66d1a075 100644 --- a/module/Core/src/Model/ShortUrlMeta.php +++ b/module/Core/src/Model/ShortUrlMeta.php @@ -3,17 +3,19 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Model; +use Cake\Chronos\Chronos; use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Validation\ShortUrlMetaInputFilter; +use function is_string; final class ShortUrlMeta { /** - * @var \DateTime|null + * @var Chronos|null */ private $validSince; /** - * @var \DateTime|null + * @var Chronos|null */ private $validUntil; /** @@ -43,8 +45,8 @@ final class ShortUrlMeta } /** - * @param string|\DateTimeInterface|null $validSince - * @param string|\DateTimeInterface|null $validUntil + * @param string|Chronos|null $validSince + * @param string|Chronos|null $validUntil * @param string|null $customSlug * @param int|null $maxVisits * @return ShortUrlMeta @@ -86,26 +88,23 @@ final class ShortUrlMeta } /** - * @param string|\DateTime|null $date - * @return \DateTime|null + * @param string|Chronos|null $date + * @return Chronos|null */ - private function parseDateField($date): ?\DateTime + private function parseDateField($date): ?Chronos { - if ($date === null || $date instanceof \DateTime) { + if ($date === null || $date instanceof Chronos) { return $date; } - if (\is_string($date)) { - return new \DateTime($date); + if (is_string($date)) { + return Chronos::parse($date); } return null; } - /** - * @return \DateTime|null - */ - public function getValidSince(): ?\DateTime + public function getValidSince(): ?Chronos { return $this->validSince; } @@ -115,10 +114,7 @@ final class ShortUrlMeta return $this->validSince !== null; } - /** - * @return \DateTime|null - */ - public function getValidUntil(): ?\DateTime + public function getValidUntil(): ?Chronos { return $this->validUntil; } diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index 6a96f45f..79b10ea3 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Repository; +use Cake\Chronos\Chronos; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\QueryBuilder; use Shlinkio\Shlink\Core\Entity\ShortUrl; @@ -131,7 +132,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI */ public function findOneByShortCode(string $shortCode): ?ShortUrl { - $now = new \DateTimeImmutable(); + $now = Chronos::now(); $qb = $this->createQueryBuilder('s'); $qb->where($qb->expr()->eq('s.shortCode', ':shortCode')) diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index 4ae4c510..1d207496 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Service; +use Cake\Chronos\Chronos; use Cocur\Slugify\Slugify; use Cocur\Slugify\SlugifyInterface; use Doctrine\ORM\EntityManagerInterface; @@ -61,14 +62,6 @@ class UrlShortener implements UrlShortenerInterface } /** - * Creates and persists a unique shortcode generated for provided url - * - * @param UriInterface $url - * @param string[] $tags - * @param \DateTime|null $validSince - * @param \DateTime|null $validUntil - * @param string|null $customSlug - * @param int|null $maxVisits * @throws NonUniqueSlugException * @throws InvalidUrlException * @throws RuntimeException @@ -76,10 +69,10 @@ class UrlShortener implements UrlShortenerInterface public function urlToShortCode( UriInterface $url, array $tags = [], - \DateTime $validSince = null, - \DateTime $validUntil = null, - string $customSlug = null, - int $maxVisits = null + ?Chronos $validSince = null, + ?Chronos $validUntil = null, + ?string $customSlug = null, + ?int $maxVisits = null ): ShortUrl { // If the URL validation is enabled, check that the URL actually exists if ($this->urlValidationEnabled) { diff --git a/module/Core/src/Service/UrlShortenerInterface.php b/module/Core/src/Service/UrlShortenerInterface.php index a12b78ff..ff9780d9 100644 --- a/module/Core/src/Service/UrlShortenerInterface.php +++ b/module/Core/src/Service/UrlShortenerInterface.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Service; +use Cake\Chronos\Chronos; use Psr\Http\Message\UriInterface; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; @@ -14,14 +15,6 @@ use Shlinkio\Shlink\Core\Exception\RuntimeException; interface UrlShortenerInterface { /** - * Creates and persists a unique shortcode generated for provided url - * - * @param UriInterface $url - * @param string[] $tags - * @param \DateTime|null $validSince - * @param \DateTime|null $validUntil - * @param string|null $customSlug - * @param int|null $maxVisits * @throws NonUniqueSlugException * @throws InvalidUrlException * @throws RuntimeException @@ -29,10 +22,10 @@ interface UrlShortenerInterface public function urlToShortCode( UriInterface $url, array $tags = [], - \DateTime $validSince = null, - \DateTime $validUntil = null, - string $customSlug = null, - int $maxVisits = null + ?Chronos $validSince = null, + ?Chronos $validUntil = null, + ?string $customSlug = null, + ?int $maxVisits = null ): ShortUrl; /** diff --git a/module/Core/src/Transformer/ShortUrlDataTransformer.php b/module/Core/src/Transformer/ShortUrlDataTransformer.php index 0c0fb2cd..52965213 100644 --- a/module/Core/src/Transformer/ShortUrlDataTransformer.php +++ b/module/Core/src/Transformer/ShortUrlDataTransformer.php @@ -24,7 +24,6 @@ class ShortUrlDataTransformer implements DataTransformerInterface /** * @param ShortUrl $value - * @return array */ public function transform($value): array { @@ -36,7 +35,7 @@ class ShortUrlDataTransformer implements DataTransformerInterface 'shortCode' => $shortCode, 'shortUrl' => $this->buildShortUrl($this->domainConfig, $shortCode), 'longUrl' => $longUrl, - 'dateCreated' => $dateCreated !== null ? $dateCreated->format(\DateTime::ATOM) : null, + 'dateCreated' => $dateCreated !== null ? $dateCreated->toAtomString() : null, 'visitsCount' => $value->getVisitsCount(), 'tags' => \array_map([$this, 'serializeTag'], $value->getTags()->toArray()), diff --git a/module/Core/src/Validation/ShortUrlMetaInputFilter.php b/module/Core/src/Validation/ShortUrlMetaInputFilter.php index 435ef441..5bdb20ee 100644 --- a/module/Core/src/Validation/ShortUrlMetaInputFilter.php +++ b/module/Core/src/Validation/ShortUrlMetaInputFilter.php @@ -12,12 +12,12 @@ class ShortUrlMetaInputFilter extends InputFilter { use InputFactoryTrait; - const VALID_SINCE = 'validSince'; - const VALID_UNTIL = 'validUntil'; - const CUSTOM_SLUG = 'customSlug'; - const MAX_VISITS = 'maxVisits'; + public const VALID_SINCE = 'validSince'; + public const VALID_UNTIL = 'validUntil'; + public const CUSTOM_SLUG = 'customSlug'; + public const MAX_VISITS = 'maxVisits'; - public function __construct(array $data = null) + public function __construct(?array $data = null) { $this->initialize(); if ($data !== null) { @@ -25,7 +25,7 @@ class ShortUrlMetaInputFilter extends InputFilter } } - private function initialize() + private function initialize(): void { $validSince = $this->createInput(self::VALID_SINCE, false); $validSince->getValidatorChain()->attach(new Date(['format' => \DateTime::ATOM])); diff --git a/module/Core/test-func/Repository/ShortUrlRepositoryTest.php b/module/Core/test-func/Repository/ShortUrlRepositoryTest.php index aa544fa6..c6766e43 100644 --- a/module/Core/test-func/Repository/ShortUrlRepositoryTest.php +++ b/module/Core/test-func/Repository/ShortUrlRepositoryTest.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Repository; +use Cake\Chronos\Chronos; use Doctrine\Common\Collections\ArrayCollection; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\Tag; @@ -41,7 +42,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $bar = new ShortUrl(); $bar->setOriginalUrl('bar') ->setShortCode('bar_very_long_text') - ->setValidSince((new \DateTime())->add(new \DateInterval('P1M'))); + ->setValidSince(Chronos::now()->addMonth()); $this->getEntityManager()->persist($bar); $visits = []; diff --git a/module/Core/test-func/Repository/VisitRepositoryTest.php b/module/Core/test-func/Repository/VisitRepositoryTest.php index 00c2eaf3..1cae6720 100644 --- a/module/Core/test-func/Repository/VisitRepositoryTest.php +++ b/module/Core/test-func/Repository/VisitRepositoryTest.php @@ -3,12 +3,14 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Repository; +use Cake\Chronos\Chronos; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\Visit; use Shlinkio\Shlink\Core\Entity\VisitLocation; use Shlinkio\Shlink\Core\Repository\VisitRepository; use ShlinkioTest\Shlink\Common\DbUnit\DatabaseTestCase; +use function sprintf; class VisitRepositoryTest extends DatabaseTestCase { @@ -61,7 +63,7 @@ class VisitRepositoryTest extends DatabaseTestCase for ($i = 0; $i < 6; $i++) { $visit = new Visit(); $visit->setShortUrl($shortUrl) - ->setDate(new \DateTime('2016-01-0' . ($i + 1))); + ->setDate(Chronos::parse(sprintf('2016-01-0%s', $i + 1))); $this->getEntityManager()->persist($visit); } @@ -70,11 +72,11 @@ class VisitRepositoryTest extends DatabaseTestCase $this->assertCount(0, $this->repo->findVisitsByShortUrl('invalid')); $this->assertCount(6, $this->repo->findVisitsByShortUrl($shortUrl->getId())); $this->assertCount(2, $this->repo->findVisitsByShortUrl($shortUrl->getId(), new DateRange( - new \DateTime('2016-01-02'), - new \DateTime('2016-01-03') + Chronos::parse('2016-01-02'), + Chronos::parse('2016-01-03') ))); $this->assertCount(4, $this->repo->findVisitsByShortUrl($shortUrl->getId(), new DateRange( - new \DateTime('2016-01-03') + Chronos::parse('2016-01-03') ))); } } diff --git a/module/Core/test/Model/ShortUrlMetaTest.php b/module/Core/test/Model/ShortUrlMetaTest.php index 8341fbb7..26a574d3 100644 --- a/module/Core/test/Model/ShortUrlMetaTest.php +++ b/module/Core/test/Model/ShortUrlMetaTest.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Model; +use Cake\Chronos\Chronos; use PHPUnit\Framework\TestCase; use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; @@ -42,10 +43,10 @@ class ShortUrlMetaTest extends TestCase */ public function properlyCreatedInstanceReturnsValues() { - $meta = ShortUrlMeta::createFromParams((new \DateTime('2015-01-01'))->format(\DateTime::ATOM), null, 'foobar'); + $meta = ShortUrlMeta::createFromParams(Chronos::parse('2015-01-01')->toAtomString(), null, 'foobar'); $this->assertTrue($meta->hasValidSince()); - $this->assertEquals(new \DateTime('2015-01-01'), $meta->getValidSince()); + $this->assertEquals(Chronos::parse('2015-01-01'), $meta->getValidSince()); $this->assertFalse($meta->hasValidUntil()); $this->assertNull($meta->getValidUntil()); diff --git a/module/Core/test/Service/ShortUrlServiceTest.php b/module/Core/test/Service/ShortUrlServiceTest.php index cc406713..8c4f6618 100644 --- a/module/Core/test/Service/ShortUrlServiceTest.php +++ b/module/Core/test/Service/ShortUrlServiceTest.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Service; +use Cake\Chronos\Chronos; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use PHPUnit\Framework\TestCase; @@ -104,15 +105,15 @@ class ShortUrlServiceTest extends TestCase $flush = $this->em->flush($shortUrl)->willReturn(null); $result = $this->service->updateMetadataByShortCode('abc123', ShortUrlMeta::createFromParams( - (new \DateTime('2017-01-01 00:00:00'))->format(\DateTime::ATOM), - (new \DateTime('2017-01-05 00:00:00'))->format(\DateTime::ATOM), + Chronos::parse('2017-01-01 00:00:00')->toAtomString(), + Chronos::parse('2017-01-05 00:00:00')->toAtomString(), null, 5 )); $this->assertSame($shortUrl, $result); - $this->assertEquals(new \DateTime('2017-01-01 00:00:00'), $shortUrl->getValidSince()); - $this->assertEquals(new \DateTime('2017-01-05 00:00:00'), $shortUrl->getValidUntil()); + $this->assertEquals(Chronos::parse('2017-01-01 00:00:00'), $shortUrl->getValidSince()); + $this->assertEquals(Chronos::parse('2017-01-05 00:00:00'), $shortUrl->getValidUntil()); $this->assertEquals(5, $shortUrl->getMaxVisits()); $findShortUrl->shouldHaveBeenCalled(); $getRepo->shouldHaveBeenCalled(); diff --git a/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php b/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php index 696c47b3..b7ba983c 100644 --- a/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php +++ b/module/Rest/src/Action/ShortUrl/CreateShortUrlAction.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Action\ShortUrl; +use Cake\Chronos\Chronos; use Psr\Http\Message\ServerRequestInterface as Request; use Shlinkio\Shlink\Core\Exception\InvalidArgumentException; use Shlinkio\Shlink\Core\Model\CreateShortUrlData; @@ -40,8 +41,8 @@ class CreateShortUrlAction extends AbstractCreateShortUrlAction ); } - private function getOptionalDate(array $postData, string $fieldName) + private function getOptionalDate(array $postData, string $fieldName): ?Chronos { - return isset($postData[$fieldName]) ? new \DateTime($postData[$fieldName]) : null; + return isset($postData[$fieldName]) ? Chronos::parse($postData[$fieldName]) : null; } } diff --git a/module/Rest/src/Action/Visit/GetVisitsAction.php b/module/Rest/src/Action/Visit/GetVisitsAction.php index 3d5890cc..8f320907 100644 --- a/module/Rest/src/Action/Visit/GetVisitsAction.php +++ b/module/Rest/src/Action/Visit/GetVisitsAction.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Action\Visit; +use Cake\Chronos\Chronos; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Log\LoggerInterface; @@ -75,18 +76,9 @@ class GetVisitsAction extends AbstractRestAction } } - /** - * @param Request $request - * @param string $key - * @return \DateTime|null - */ - private function getDateQueryParam(Request $request, string $key) + private function getDateQueryParam(Request $request, string $key): ?Chronos { $query = $request->getQueryParams(); - if (! isset($query[$key]) || empty($query[$key])) { - return null; - } - - return new \DateTime($query[$key]); + return ! isset($query[$key]) || empty($query[$key]) ? null : Chronos::parse($query[$key]); } } diff --git a/module/Rest/src/Entity/ApiKey.php b/module/Rest/src/Entity/ApiKey.php index 2246f909..b13629f8 100644 --- a/module/Rest/src/Entity/ApiKey.php +++ b/module/Rest/src/Entity/ApiKey.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Entity; +use Cake\Chronos\Chronos; use Doctrine\ORM\Mapping as ORM; use Shlinkio\Shlink\Common\Entity\AbstractEntity; use Shlinkio\Shlink\Common\Util\StringUtilsTrait; @@ -25,8 +26,8 @@ class ApiKey extends AbstractEntity */ private $key; /** - * @var \DateTime|null - * @ORM\Column(name="expiration_date", nullable=true, type="datetime") + * @var Chronos|null + * @ORM\Column(name="expiration_date", nullable=true, type="chronos_datetime") */ private $expirationDate; /** @@ -52,12 +53,12 @@ class ApiKey extends AbstractEntity return $this; } - public function getExpirationDate(): ?\DateTime + public function getExpirationDate(): ?Chronos { return $this->expirationDate; } - public function setExpirationDate(\DateTime $expirationDate): self + public function setExpirationDate(Chronos $expirationDate): self { $this->expirationDate = $expirationDate; return $this; @@ -69,7 +70,7 @@ class ApiKey extends AbstractEntity return false; } - return $this->expirationDate < new \DateTime(); + return $this->expirationDate->lt(Chronos::now()); } public function isEnabled(): bool diff --git a/module/Rest/src/Service/ApiKeyService.php b/module/Rest/src/Service/ApiKeyService.php index ca8d80ec..bad09f3b 100644 --- a/module/Rest/src/Service/ApiKeyService.php +++ b/module/Rest/src/Service/ApiKeyService.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Service; +use Cake\Chronos\Chronos; use Doctrine\ORM\EntityManagerInterface; use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; use Shlinkio\Shlink\Rest\Entity\ApiKey; @@ -20,13 +21,7 @@ class ApiKeyService implements ApiKeyServiceInterface $this->em = $em; } - /** - * Creates a new ApiKey with provided expiration date - * - * @param \DateTime $expirationDate - * @return ApiKey - */ - public function create(\DateTime $expirationDate = null): ApiKey + public function create(?Chronos $expirationDate = null): ApiKey { $key = new ApiKey(); if ($expirationDate !== null) { @@ -39,12 +34,6 @@ class ApiKeyService implements ApiKeyServiceInterface return $key; } - /** - * Checks if provided key is a valid api key - * - * @param string $key - * @return bool - */ public function check(string $key): bool { /** @var ApiKey|null $apiKey */ @@ -53,10 +42,6 @@ class ApiKeyService implements ApiKeyServiceInterface } /** - * Disables provided api key - * - * @param string $key - * @return ApiKey * @throws InvalidArgumentException */ public function disable(string $key): ApiKey @@ -72,12 +57,6 @@ class ApiKeyService implements ApiKeyServiceInterface return $apiKey; } - /** - * Lists all existing api keys - * - * @param bool $enabledOnly Tells if only enabled keys should be returned - * @return ApiKey[] - */ public function listKeys(bool $enabledOnly = false): array { $conditions = $enabledOnly ? ['enabled' => true] : []; @@ -86,12 +65,6 @@ class ApiKeyService implements ApiKeyServiceInterface return $apiKeys; } - /** - * Tries to find one API key by its key string - * - * @param string $key - * @return ApiKey|null - */ public function getByKey(string $key): ?ApiKey { /** @var ApiKey|null $apiKey */ diff --git a/module/Rest/src/Service/ApiKeyServiceInterface.php b/module/Rest/src/Service/ApiKeyServiceInterface.php index 3d62adbe..8aaf86c7 100644 --- a/module/Rest/src/Service/ApiKeyServiceInterface.php +++ b/module/Rest/src/Service/ApiKeyServiceInterface.php @@ -3,49 +3,22 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\Service; +use Cake\Chronos\Chronos; use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; use Shlinkio\Shlink\Rest\Entity\ApiKey; interface ApiKeyServiceInterface { - /** - * Creates a new ApiKey with provided expiration date - * - * @param \DateTime $expirationDate - * @return ApiKey - */ - public function create(\DateTime $expirationDate = null): ApiKey; + public function create(?Chronos $expirationDate = null): ApiKey; - /** - * Checks if provided key is a valid api key - * - * @param string $key - * @return bool - */ public function check(string $key): bool; /** - * Disables provided api key - * - * @param string $key - * @return ApiKey * @throws InvalidArgumentException */ public function disable(string $key): ApiKey; - /** - * Lists all existing api keys - * - * @param bool $enabledOnly Tells if only enabled keys should be returned - * @return ApiKey[] - */ public function listKeys(bool $enabledOnly = false): array; - /** - * Tries to find one API key by its key string - * - * @param string $key - * @return ApiKey|null - */ public function getByKey(string $key): ?ApiKey; } diff --git a/module/Rest/test/Action/Visit/GetVisitsActionTest.php b/module/Rest/test/Action/Visit/GetVisitsActionTest.php index c6dc03cb..ae9c518c 100644 --- a/module/Rest/test/Action/Visit/GetVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/GetVisitsActionTest.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Action\Visit; +use Cake\Chronos\Chronos; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; @@ -77,7 +78,7 @@ class GetVisitsActionTest extends TestCase public function datesAreReadFromQuery() { $shortCode = 'abc123'; - $this->visitsTracker->info($shortCode, new DateRange(null, new \DateTime('2016-01-01 00:00:00'))) + $this->visitsTracker->info($shortCode, new DateRange(null, Chronos::parse('2016-01-01 00:00:00'))) ->willReturn([]) ->shouldBeCalledTimes(1); diff --git a/module/Rest/test/Service/ApiKeyServiceTest.php b/module/Rest/test/Service/ApiKeyServiceTest.php index fb6c8c0d..e6db1a5d 100644 --- a/module/Rest/test/Service/ApiKeyServiceTest.php +++ b/module/Rest/test/Service/ApiKeyServiceTest.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Rest\Service; +use Cake\Chronos\Chronos; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityRepository; use PHPUnit\Framework\TestCase; @@ -48,7 +49,7 @@ class ApiKeyServiceTest extends TestCase $this->em->flush()->shouldBeCalledTimes(1); $this->em->persist(Argument::type(ApiKey::class))->shouldBeCalledTimes(1); - $date = new \DateTime('2030-01-01'); + $date = Chronos::parse('2030-01-01'); $key = $this->service->create($date); $this->assertSame($date, $key->getExpirationDate()); } @@ -87,7 +88,7 @@ class ApiKeyServiceTest extends TestCase public function checkReturnsFalseWhenKeyIsExpired() { $key = new ApiKey(); - $key->setExpirationDate((new \DateTime())->sub(new \DateInterval('P1D'))); + $key->setExpirationDate(Chronos::now()->subDay()); $repo = $this->prophesize(EntityRepository::class); $repo->findOneBy(['key' => '12345'])->willReturn($key) ->shouldBeCalledTimes(1);