diff --git a/config/autoload/services.global.php b/config/autoload/services.global.php index 414b2e56..300f1bf3 100644 --- a/config/autoload/services.global.php +++ b/config/autoload/services.global.php @@ -2,7 +2,7 @@ use Acelaya\UrlShortener\Factory\CacheFactory; use Acelaya\UrlShortener\Factory\EntityManagerFactory; use Acelaya\UrlShortener\Middleware; -use Acelaya\UrlShortener\Service\UrlShortener; +use Acelaya\UrlShortener\Service; use Acelaya\ZsmAnnotatedServices\Factory\V3\AnnotatedFactory; use Doctrine\Common\Cache\Cache; use Doctrine\ORM\EntityManager; @@ -34,7 +34,8 @@ return [ // Services EntityManager::class => EntityManagerFactory::class, GuzzleHttp\Client::class => InvokableFactory::class, - UrlShortener::class => AnnotatedFactory::class, + Service\UrlShortener::class => AnnotatedFactory::class, + Service\VisitsTracker::class => AnnotatedFactory::class, Cache::class => CacheFactory::class, // Middleware diff --git a/src/Entity/Visit.php b/src/Entity/Visit.php index 01c3ab84..2e8fad6f 100644 --- a/src/Entity/Visit.php +++ b/src/Entity/Visit.php @@ -15,7 +15,7 @@ class Visit extends AbstractEntity { /** * @var string - * @ORM\Column(type="string", length=256) + * @ORM\Column(type="string", length=256, nullable=true) */ protected $referer; /** @@ -25,19 +25,14 @@ class Visit extends AbstractEntity protected $date; /** * @var string - * @ORM\Column(type="string", length=256) + * @ORM\Column(type="string", length=256, name="remote_addr", nullable=true) */ - protected $country; + protected $remoteAddr; /** * @var string - * @ORM\Column(type="string", length=256) + * @ORM\Column(type="string", length=256, name="user_agent", nullable=true) */ - protected $platform; - /** - * @var string - * @ORM\Column(type="string", length=256) - */ - protected $browser; + protected $userAgent; /** * @var ShortUrl * @ORM\ManyToOne(targetEntity=ShortUrl::class) @@ -45,6 +40,11 @@ class Visit extends AbstractEntity */ protected $shortUrl; + public function __construct() + { + $this->date = new \DateTime(); + } + /** * @return string */ @@ -81,60 +81,6 @@ class Visit extends AbstractEntity return $this; } - /** - * @return string - */ - public function getCountry() - { - return $this->country; - } - - /** - * @param string $country - * @return $this - */ - public function setCountry($country) - { - $this->country = $country; - return $this; - } - - /** - * @return string - */ - public function getPlatform() - { - return $this->platform; - } - - /** - * @param string $platform - * @return $this - */ - public function setPlatform($platform) - { - $this->platform = $platform; - return $this; - } - - /** - * @return string - */ - public function getBrowser() - { - return $this->browser; - } - - /** - * @param string $browser - * @return $this - */ - public function setBrowser($browser) - { - $this->browser = $browser; - return $this; - } - /** * @return ShortUrl */ @@ -152,4 +98,40 @@ class Visit extends AbstractEntity $this->shortUrl = $shortUrl; return $this; } + + /** + * @return string + */ + public function getRemoteAddr() + { + return $this->remoteAddr; + } + + /** + * @param string $remoteAddr + * @return $this + */ + public function setRemoteAddr($remoteAddr) + { + $this->remoteAddr = $remoteAddr; + return $this; + } + + /** + * @return string + */ + public function getUserAgent() + { + return $this->userAgent; + } + + /** + * @param string $userAgent + * @return $this + */ + public function setUserAgent($userAgent) + { + $this->userAgent = $userAgent; + return $this; + } } diff --git a/src/Middleware/Routable/RedirectMiddleware.php b/src/Middleware/Routable/RedirectMiddleware.php index 29870d46..104acd44 100644 --- a/src/Middleware/Routable/RedirectMiddleware.php +++ b/src/Middleware/Routable/RedirectMiddleware.php @@ -3,6 +3,8 @@ namespace Acelaya\UrlShortener\Middleware\Routable; use Acelaya\UrlShortener\Service\UrlShortener; use Acelaya\UrlShortener\Service\UrlShortenerInterface; +use Acelaya\UrlShortener\Service\VisitsTracker; +use Acelaya\UrlShortener\Service\VisitsTrackerInterface; use Acelaya\ZsmAnnotatedServices\Annotation\Inject; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; @@ -15,16 +17,22 @@ class RedirectMiddleware implements MiddlewareInterface * @var UrlShortenerInterface */ private $urlShortener; + /** + * @var VisitsTracker|VisitsTrackerInterface + */ + private $visitTracker; /** * RedirectMiddleware constructor. * @param UrlShortenerInterface|UrlShortener $urlShortener + * @param VisitsTrackerInterface|VisitsTracker $visitTracker * - * @Inject({UrlShortener::class}) + * @Inject({UrlShortener::class, VisitsTracker::class}) */ - public function __construct(UrlShortenerInterface $urlShortener) + public function __construct(UrlShortenerInterface $urlShortener, VisitsTrackerInterface $visitTracker) { $this->urlShortener = $urlShortener; + $this->visitTracker = $visitTracker; } /** @@ -55,7 +63,7 @@ class RedirectMiddleware implements MiddlewareInterface public function __invoke(Request $request, Response $response, callable $out = null) { $shortCode = $request->getAttribute('shortCode', ''); - + try { $longUrl = $this->urlShortener->shortCodeToUrl($shortCode); @@ -65,8 +73,12 @@ class RedirectMiddleware implements MiddlewareInterface return $out($request, $response); } - // Return a redirect response to the long URL - return new RedirectResponse($longUrl, 301); + // Track visit to this shortcode + $this->visitTracker->track($shortCode); + + // Return a redirect response to the long URL. + // Use a temporary redirect to make sure browsers aleways hit the server for analytics purposes + return new RedirectResponse($longUrl); } catch (\Exception $e) { // In case of error, dispatch 404 error return $out($request, $response); diff --git a/src/Service/VisitsTracker.php b/src/Service/VisitsTracker.php new file mode 100644 index 00000000..80598053 --- /dev/null +++ b/src/Service/VisitsTracker.php @@ -0,0 +1,61 @@ +em = $em; + } + + /** + * Tracks a new visit to provided short code, using an array of data to look up information + * + * @param string $shortCode + * @param array $visitorData Defaults to global $_SERVER + */ + public function track($shortCode, array $visitorData = null) + { + $visitorData = $visitorData ?: $_SERVER; + + /** @var ShortUrl $shortUrl */ + $shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([ + 'shortCode' => $shortCode, + ]); + + $visit = new Visit(); + $visit->setShortUrl($shortUrl) + ->setUserAgent($this->getArrayValue($visitorData, 'HTTP_USER_AGENT')) + ->setReferer($this->getArrayValue($visitorData, 'REFERER')) + ->setRemoteAddr($this->getArrayValue($visitorData, 'REMOTE_ADDR')); + $this->em->persist($visit); + $this->em->flush(); + } + + /** + * @param array $array + * @param $key + * @param null $default + * @return mixed|null + */ + protected function getArrayValue(array $array, $key, $default = null) + { + return isset($array[$key]) ? $array[$key] : $default; + } +}