diff --git a/data/migrations/Version20210207100807.php b/data/migrations/Version20210207100807.php index 706132cc..cd0b0b12 100644 --- a/data/migrations/Version20210207100807.php +++ b/data/migrations/Version20210207100807.php @@ -8,8 +8,8 @@ use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Types\Types; use Doctrine\Migrations\AbstractMigration; -use Shlinkio\Shlink\Core\Entity\Visit; use Shlinkio\Shlink\Core\Model\Visitor; +use Shlinkio\Shlink\Core\Visit\Model\VisitType; final class Version20210207100807 extends AbstractMigration { @@ -27,7 +27,7 @@ final class Version20210207100807 extends AbstractMigration ]); $visits->addColumn('type', Types::STRING, [ 'length' => 255, - 'default' => Visit::TYPE_VALID_SHORT_URL, + 'default' => VisitType::VALID_SHORT_URL->value, ]); } diff --git a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Visit.php b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Visit.php index 969bfd1d..147c37e7 100644 --- a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Visit.php +++ b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.Entity.Visit.php @@ -6,9 +6,11 @@ namespace Shlinkio\Shlink\Core; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder; +use Doctrine\ORM\Mapping\Builder\FieldBuilder; use Doctrine\ORM\Mapping\ClassMetadata; use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType; use Shlinkio\Shlink\Core\Model\Visitor; +use Shlinkio\Shlink\Core\Visit\Model\VisitType; return static function (ClassMetadata $metadata, array $emConfig): void { $builder = new ClassMetadataBuilder($metadata); @@ -61,10 +63,13 @@ return static function (ClassMetadata $metadata, array $emConfig): void { ->nullable() ->build(); - $builder->createField('type', Types::STRING) - ->columnName('type') - ->length(255) - ->build(); + (new FieldBuilder($builder, [ + 'fieldName' => 'type', + 'type' => Types::STRING, + 'enumType' => VisitType::class, + ]))->columnName('type') + ->length(255) + ->build(); $builder->createField('potentialBot', Types::BOOLEAN) ->columnName('potential_bot') diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index 9fff1509..28c4c446 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -16,6 +16,7 @@ use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\ShortUrl\Resolver\ShortUrlRelationResolverInterface; use Shlinkio\Shlink\Core\ShortUrl\Resolver\SimpleShortUrlRelationResolver; use Shlinkio\Shlink\Core\Validation\ShortUrlInputFilter; +use Shlinkio\Shlink\Core\Visit\Model\VisitType; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Shlinkio\Shlink\Rest\Entity\ApiKey; @@ -174,7 +175,7 @@ class ShortUrl extends AbstractEntity { /** @var Selectable $visits */ $visits = $this->visits; - $criteria = Criteria::create()->where(Criteria::expr()->eq('type', Visit::TYPE_IMPORTED)) + $criteria = Criteria::create()->where(Criteria::expr()->eq('type', VisitType::IMPORTED)) ->orderBy(['id' => 'DESC']) ->setMaxResults(1); diff --git a/module/Core/src/Entity/Visit.php b/module/Core/src/Entity/Visit.php index 48344a03..23a518ca 100644 --- a/module/Core/src/Entity/Visit.php +++ b/module/Core/src/Entity/Visit.php @@ -11,30 +11,24 @@ use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; use Shlinkio\Shlink\Common\Util\IpAddress; use Shlinkio\Shlink\Core\Model\Visitor; use Shlinkio\Shlink\Core\Visit\Model\VisitLocationInterface; +use Shlinkio\Shlink\Core\Visit\Model\VisitType; use Shlinkio\Shlink\Importer\Model\ImportedShlinkVisit; use function Shlinkio\Shlink\Core\isCrawler; class Visit extends AbstractEntity implements JsonSerializable { - // TODO Convert to enum - public const TYPE_VALID_SHORT_URL = 'valid_short_url'; - public const TYPE_IMPORTED = 'imported'; - public const TYPE_INVALID_SHORT_URL = 'invalid_short_url'; - public const TYPE_BASE_URL = 'base_url'; - public const TYPE_REGULAR_404 = 'regular_404'; - private string $referer; private Chronos $date; private ?string $remoteAddr = null; private ?string $visitedUrl = null; private string $userAgent; - private string $type; + private VisitType $type; private ?ShortUrl $shortUrl; private ?VisitLocation $visitLocation = null; private bool $potentialBot; - private function __construct(?ShortUrl $shortUrl, string $type) + private function __construct(?ShortUrl $shortUrl, VisitType $type) { $this->shortUrl = $shortUrl; $this->date = Chronos::now(); @@ -43,7 +37,7 @@ class Visit extends AbstractEntity implements JsonSerializable public static function forValidShortUrl(ShortUrl $shortUrl, Visitor $visitor, bool $anonymize = true): self { - $instance = new self($shortUrl, self::TYPE_VALID_SHORT_URL); + $instance = new self($shortUrl, VisitType::VALID_SHORT_URL); $instance->hydrateFromVisitor($visitor, $anonymize); return $instance; @@ -51,7 +45,7 @@ class Visit extends AbstractEntity implements JsonSerializable public static function fromImport(ShortUrl $shortUrl, ImportedShlinkVisit $importedVisit): self { - $instance = new self($shortUrl, self::TYPE_IMPORTED); + $instance = new self($shortUrl, VisitType::IMPORTED); $instance->userAgent = $importedVisit->userAgent(); $instance->potentialBot = isCrawler($instance->userAgent); $instance->referer = $importedVisit->referer(); @@ -65,7 +59,7 @@ class Visit extends AbstractEntity implements JsonSerializable public static function forBasePath(Visitor $visitor, bool $anonymize = true): self { - $instance = new self(null, self::TYPE_BASE_URL); + $instance = new self(null, VisitType::BASE_URL); $instance->hydrateFromVisitor($visitor, $anonymize); return $instance; @@ -73,7 +67,7 @@ class Visit extends AbstractEntity implements JsonSerializable public static function forInvalidShortUrl(Visitor $visitor, bool $anonymize = true): self { - $instance = new self(null, self::TYPE_INVALID_SHORT_URL); + $instance = new self(null, VisitType::INVALID_SHORT_URL); $instance->hydrateFromVisitor($visitor, $anonymize); return $instance; @@ -81,7 +75,7 @@ class Visit extends AbstractEntity implements JsonSerializable public static function forRegularNotFound(Visitor $visitor, bool $anonymize = true): self { - $instance = new self(null, self::TYPE_REGULAR_404); + $instance = new self(null, VisitType::REGULAR_404); $instance->hydrateFromVisitor($visitor, $anonymize); return $instance; @@ -151,7 +145,7 @@ class Visit extends AbstractEntity implements JsonSerializable return $this->visitedUrl; } - public function type(): string + public function type(): VisitType { return $this->type; } @@ -160,11 +154,19 @@ class Visit extends AbstractEntity implements JsonSerializable * Needed only for ArrayCollections to be able to apply criteria filtering * @internal */ - public function getType(): string + public function getType(): VisitType { return $this->type(); } + /** + * @internal + */ + public function getDate(): Chronos + { + return $this->date; + } + public function jsonSerialize(): array { return [ @@ -175,12 +177,4 @@ class Visit extends AbstractEntity implements JsonSerializable 'potentialBot' => $this->potentialBot, ]; } - - /** - * @internal - */ - public function getDate(): Chronos - { - return $this->date; - } } diff --git a/module/Core/src/ErrorHandler/Model/NotFoundType.php b/module/Core/src/ErrorHandler/Model/NotFoundType.php index 39970dea..f95368cb 100644 --- a/module/Core/src/ErrorHandler/Model/NotFoundType.php +++ b/module/Core/src/ErrorHandler/Model/NotFoundType.php @@ -7,13 +7,13 @@ namespace Shlinkio\Shlink\Core\ErrorHandler\Model; use Mezzio\Router\RouteResult; use Psr\Http\Message\ServerRequestInterface; use Shlinkio\Shlink\Core\Action\RedirectAction; -use Shlinkio\Shlink\Core\Entity\Visit; +use Shlinkio\Shlink\Core\Visit\Model\VisitType; use function rtrim; class NotFoundType { - private function __construct(private string $type) + private function __construct(private readonly VisitType $type) { } @@ -24,10 +24,10 @@ class NotFoundType $isBaseUrl = rtrim($request->getUri()->getPath(), '/') === $basePath; $type = match (true) { - $isBaseUrl => Visit::TYPE_BASE_URL, - $routeResult->isFailure() => Visit::TYPE_REGULAR_404, - $routeResult->getMatchedRouteName() === RedirectAction::class => Visit::TYPE_INVALID_SHORT_URL, - default => self::class, + $isBaseUrl => VisitType::BASE_URL, + $routeResult->isFailure() => VisitType::REGULAR_404, + $routeResult->getMatchedRouteName() === RedirectAction::class => VisitType::INVALID_SHORT_URL, + default => VisitType::VALID_SHORT_URL, }; return new self($type); @@ -35,16 +35,16 @@ class NotFoundType public function isBaseUrl(): bool { - return $this->type === Visit::TYPE_BASE_URL; + return $this->type === VisitType::BASE_URL; } public function isRegularNotFound(): bool { - return $this->type === Visit::TYPE_REGULAR_404; + return $this->type === VisitType::REGULAR_404; } public function isInvalidShortUrl(): bool { - return $this->type === Visit::TYPE_INVALID_SHORT_URL; + return $this->type === VisitType::INVALID_SHORT_URL; } } diff --git a/module/Core/src/Visit/Model/VisitType.php b/module/Core/src/Visit/Model/VisitType.php new file mode 100644 index 00000000..0c4b8841 --- /dev/null +++ b/module/Core/src/Visit/Model/VisitType.php @@ -0,0 +1,16 @@ +jsonSerialize(); $serializedVisit['visitedUrl'] = $visit->visitedUrl(); - $serializedVisit['type'] = $visit->type(); + $serializedVisit['type'] = $visit->type()->value; return $serializedVisit; } diff --git a/module/Core/test/EventDispatcher/NotifyVisitToMercureTest.php b/module/Core/test/EventDispatcher/NotifyVisitToMercureTest.php index 0b863b69..a06eaaa1 100644 --- a/module/Core/test/EventDispatcher/NotifyVisitToMercureTest.php +++ b/module/Core/test/EventDispatcher/NotifyVisitToMercureTest.php @@ -17,6 +17,7 @@ use Shlinkio\Shlink\Core\EventDispatcher\Event\VisitLocated; use Shlinkio\Shlink\Core\EventDispatcher\NotifyVisitToMercure; use Shlinkio\Shlink\Core\Mercure\MercureUpdatesGeneratorInterface; use Shlinkio\Shlink\Core\Model\Visitor; +use Shlinkio\Shlink\Core\Visit\Model\VisitType; use Symfony\Component\Mercure\HubInterface; use Symfony\Component\Mercure\Update; @@ -160,8 +161,8 @@ class NotifyVisitToMercureTest extends TestCase { $visitor = Visitor::emptyInstance(); - yield Visit::TYPE_REGULAR_404 => [Visit::forRegularNotFound($visitor)]; - yield Visit::TYPE_INVALID_SHORT_URL => [Visit::forInvalidShortUrl($visitor)]; - yield Visit::TYPE_BASE_URL => [Visit::forBasePath($visitor)]; + yield VisitType::REGULAR_404->value => [Visit::forRegularNotFound($visitor)]; + yield VisitType::INVALID_SHORT_URL->value => [Visit::forInvalidShortUrl($visitor)]; + yield VisitType::BASE_URL->value => [Visit::forBasePath($visitor)]; } } diff --git a/module/Core/test/Mercure/MercureUpdatesGeneratorTest.php b/module/Core/test/Mercure/MercureUpdatesGeneratorTest.php index 14378b4f..779ec351 100644 --- a/module/Core/test/Mercure/MercureUpdatesGeneratorTest.php +++ b/module/Core/test/Mercure/MercureUpdatesGeneratorTest.php @@ -12,6 +12,7 @@ use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Model\Visitor; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier; use Shlinkio\Shlink\Core\ShortUrl\Transformer\ShortUrlDataTransformer; +use Shlinkio\Shlink\Core\Visit\Model\VisitType; use Shlinkio\Shlink\Core\Visit\Transformer\OrphanVisitDataTransformer; use function Shlinkio\Shlink\Common\json_decode; @@ -95,7 +96,7 @@ class MercureUpdatesGeneratorTest extends TestCase 'date' => $orphanVisit->getDate()->toAtomString(), 'potentialBot' => false, 'visitedUrl' => $orphanVisit->visitedUrl(), - 'type' => $orphanVisit->type(), + 'type' => $orphanVisit->type()->value, ], ], json_decode($update->getData())); } @@ -104,8 +105,8 @@ class MercureUpdatesGeneratorTest extends TestCase { $visitor = Visitor::emptyInstance(); - yield Visit::TYPE_REGULAR_404 => [Visit::forRegularNotFound($visitor)]; - yield Visit::TYPE_INVALID_SHORT_URL => [Visit::forInvalidShortUrl($visitor)]; - yield Visit::TYPE_BASE_URL => [Visit::forBasePath($visitor)]; + yield VisitType::REGULAR_404->value => [Visit::forRegularNotFound($visitor)]; + yield VisitType::INVALID_SHORT_URL->value => [Visit::forInvalidShortUrl($visitor)]; + yield VisitType::BASE_URL->value => [Visit::forBasePath($visitor)]; } } diff --git a/module/Core/test/Visit/Transformer/OrphanVisitDataTransformerTest.php b/module/Core/test/Visit/Transformer/OrphanVisitDataTransformerTest.php index c836cd7c..2d2561bd 100644 --- a/module/Core/test/Visit/Transformer/OrphanVisitDataTransformerTest.php +++ b/module/Core/test/Visit/Transformer/OrphanVisitDataTransformerTest.php @@ -10,6 +10,7 @@ use PHPUnit\Framework\TestCase; use Shlinkio\Shlink\Core\Entity\Visit; use Shlinkio\Shlink\Core\Entity\VisitLocation; use Shlinkio\Shlink\Core\Model\Visitor; +use Shlinkio\Shlink\Core\Visit\Model\VisitType; use Shlinkio\Shlink\Core\Visit\Transformer\OrphanVisitDataTransformer; use Shlinkio\Shlink\IpGeolocation\Model\Location; @@ -44,7 +45,7 @@ class OrphanVisitDataTransformerTest extends TestCase 'visitLocation' => null, 'potentialBot' => false, 'visitedUrl' => '', - 'type' => Visit::TYPE_BASE_URL, + 'type' => VisitType::BASE_URL->value, ], ]; yield 'invalid short url visit' => [ @@ -60,7 +61,7 @@ class OrphanVisitDataTransformerTest extends TestCase 'visitLocation' => null, 'potentialBot' => false, 'visitedUrl' => 'https://example.com/foo', - 'type' => Visit::TYPE_INVALID_SHORT_URL, + 'type' => VisitType::INVALID_SHORT_URL->value, ], ]; yield 'regular 404 visit' => [ @@ -78,7 +79,7 @@ class OrphanVisitDataTransformerTest extends TestCase 'visitLocation' => $location, 'potentialBot' => false, 'visitedUrl' => 'https://doma.in/foo/bar', - 'type' => Visit::TYPE_REGULAR_404, + 'type' => VisitType::REGULAR_404->value, ], ]; }