mirror of
				https://github.com/shlinkio/shlink.git
				synced 2025-02-25 18:45:27 -06:00 
			
		
		
		
	Migrated from standard datetime objects to chronos objects
This commit is contained in:
		@@ -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",
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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',
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										52
									
								
								module/Common/src/Type/ChronosDateTimeType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								module/Common/src/Type/ChronosDateTimeType.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
<?php
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Shlinkio\Shlink\Common\Type;
 | 
			
		||||
 | 
			
		||||
use Cake\Chronos\Chronos;
 | 
			
		||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
 | 
			
		||||
use Doctrine\DBAL\Types\ConversionException;
 | 
			
		||||
use Doctrine\DBAL\Types\DateTimeImmutableType;
 | 
			
		||||
 | 
			
		||||
class ChronosDateTimeType extends DateTimeImmutableType
 | 
			
		||||
{
 | 
			
		||||
    public const CHRONOS_DATETIME = 'chronos_datetime';
 | 
			
		||||
 | 
			
		||||
    public function getName(): string
 | 
			
		||||
    {
 | 
			
		||||
        return self::CHRONOS_DATETIME;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws ConversionException
 | 
			
		||||
     */
 | 
			
		||||
    public function convertToPHPValue($value, AbstractPlatform $platform): ?Chronos
 | 
			
		||||
    {
 | 
			
		||||
        if ($value === null) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $dateTime = parent::convertToPHPValue($value, $platform);
 | 
			
		||||
        return Chronos::instance($dateTime);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws ConversionException
 | 
			
		||||
     */
 | 
			
		||||
    public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
 | 
			
		||||
    {
 | 
			
		||||
        if (null === $value) {
 | 
			
		||||
            return $value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($value instanceof \DateTimeInterface) {
 | 
			
		||||
            return $value->format($platform->getDateTimeFormatString());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw ConversionException::conversionFailedInvalidType(
 | 
			
		||||
            $value,
 | 
			
		||||
            $this->getName(),
 | 
			
		||||
            ['null', \DateTimeInterface::class]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										94
									
								
								module/Common/test/Type/ChronosDateTimeTypeTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								module/Common/test/Type/ChronosDateTimeTypeTest.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
<?php
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace ShlinkioTest\Shlink\Common\Type;
 | 
			
		||||
 | 
			
		||||
use Cake\Chronos\Chronos;
 | 
			
		||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
 | 
			
		||||
use Doctrine\DBAL\Types\ConversionException;
 | 
			
		||||
use Doctrine\DBAL\Types\Type;
 | 
			
		||||
use PHPUnit\Framework\TestCase;
 | 
			
		||||
use Shlinkio\Shlink\Common\Type\ChronosDateTimeType;
 | 
			
		||||
 | 
			
		||||
class ChronosDateTimeTypeTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * @var ChronosDateTimeType
 | 
			
		||||
     */
 | 
			
		||||
    private $type;
 | 
			
		||||
 | 
			
		||||
    public function setUp()
 | 
			
		||||
    {
 | 
			
		||||
        if (! Type::hasType(ChronosDateTimeType::CHRONOS_DATETIME)) {
 | 
			
		||||
            Type::addType(ChronosDateTimeType::CHRONOS_DATETIME, ChronosDateTimeType::class);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->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());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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());
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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'))
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -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()),
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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]));
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = [];
 | 
			
		||||
 
 | 
			
		||||
@@ -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')
 | 
			
		||||
        )));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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());
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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 */
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user