Created VisitsTracker service to track visits to shortcodes

This commit is contained in:
Alejandro Celaya 2016-04-30 19:18:42 +02:00
parent b2615d0de6
commit 4ae08c02ec
4 changed files with 127 additions and 71 deletions

View file

@ -2,7 +2,7 @@
use Acelaya\UrlShortener\Factory\CacheFactory; use Acelaya\UrlShortener\Factory\CacheFactory;
use Acelaya\UrlShortener\Factory\EntityManagerFactory; use Acelaya\UrlShortener\Factory\EntityManagerFactory;
use Acelaya\UrlShortener\Middleware; use Acelaya\UrlShortener\Middleware;
use Acelaya\UrlShortener\Service\UrlShortener; use Acelaya\UrlShortener\Service;
use Acelaya\ZsmAnnotatedServices\Factory\V3\AnnotatedFactory; use Acelaya\ZsmAnnotatedServices\Factory\V3\AnnotatedFactory;
use Doctrine\Common\Cache\Cache; use Doctrine\Common\Cache\Cache;
use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManager;
@ -34,7 +34,8 @@ return [
// Services // Services
EntityManager::class => EntityManagerFactory::class, EntityManager::class => EntityManagerFactory::class,
GuzzleHttp\Client::class => InvokableFactory::class, GuzzleHttp\Client::class => InvokableFactory::class,
UrlShortener::class => AnnotatedFactory::class, Service\UrlShortener::class => AnnotatedFactory::class,
Service\VisitsTracker::class => AnnotatedFactory::class,
Cache::class => CacheFactory::class, Cache::class => CacheFactory::class,
// Middleware // Middleware

View file

@ -15,7 +15,7 @@ class Visit extends AbstractEntity
{ {
/** /**
* @var string * @var string
* @ORM\Column(type="string", length=256) * @ORM\Column(type="string", length=256, nullable=true)
*/ */
protected $referer; protected $referer;
/** /**
@ -25,19 +25,14 @@ class Visit extends AbstractEntity
protected $date; protected $date;
/** /**
* @var string * @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 * @var string
* @ORM\Column(type="string", length=256) * @ORM\Column(type="string", length=256, name="user_agent", nullable=true)
*/ */
protected $platform; protected $userAgent;
/**
* @var string
* @ORM\Column(type="string", length=256)
*/
protected $browser;
/** /**
* @var ShortUrl * @var ShortUrl
* @ORM\ManyToOne(targetEntity=ShortUrl::class) * @ORM\ManyToOne(targetEntity=ShortUrl::class)
@ -45,6 +40,11 @@ class Visit extends AbstractEntity
*/ */
protected $shortUrl; protected $shortUrl;
public function __construct()
{
$this->date = new \DateTime();
}
/** /**
* @return string * @return string
*/ */
@ -81,60 +81,6 @@ class Visit extends AbstractEntity
return $this; 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 * @return ShortUrl
*/ */
@ -152,4 +98,40 @@ class Visit extends AbstractEntity
$this->shortUrl = $shortUrl; $this->shortUrl = $shortUrl;
return $this; 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;
}
} }

View file

@ -3,6 +3,8 @@ namespace Acelaya\UrlShortener\Middleware\Routable;
use Acelaya\UrlShortener\Service\UrlShortener; use Acelaya\UrlShortener\Service\UrlShortener;
use Acelaya\UrlShortener\Service\UrlShortenerInterface; use Acelaya\UrlShortener\Service\UrlShortenerInterface;
use Acelaya\UrlShortener\Service\VisitsTracker;
use Acelaya\UrlShortener\Service\VisitsTrackerInterface;
use Acelaya\ZsmAnnotatedServices\Annotation\Inject; use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
@ -15,16 +17,22 @@ class RedirectMiddleware implements MiddlewareInterface
* @var UrlShortenerInterface * @var UrlShortenerInterface
*/ */
private $urlShortener; private $urlShortener;
/**
* @var VisitsTracker|VisitsTrackerInterface
*/
private $visitTracker;
/** /**
* RedirectMiddleware constructor. * RedirectMiddleware constructor.
* @param UrlShortenerInterface|UrlShortener $urlShortener * @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->urlShortener = $urlShortener;
$this->visitTracker = $visitTracker;
} }
/** /**
@ -55,7 +63,7 @@ class RedirectMiddleware implements MiddlewareInterface
public function __invoke(Request $request, Response $response, callable $out = null) public function __invoke(Request $request, Response $response, callable $out = null)
{ {
$shortCode = $request->getAttribute('shortCode', ''); $shortCode = $request->getAttribute('shortCode', '');
try { try {
$longUrl = $this->urlShortener->shortCodeToUrl($shortCode); $longUrl = $this->urlShortener->shortCodeToUrl($shortCode);
@ -65,8 +73,12 @@ class RedirectMiddleware implements MiddlewareInterface
return $out($request, $response); return $out($request, $response);
} }
// Return a redirect response to the long URL // Track visit to this shortcode
return new RedirectResponse($longUrl, 301); $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) { } catch (\Exception $e) {
// In case of error, dispatch 404 error // In case of error, dispatch 404 error
return $out($request, $response); return $out($request, $response);

View file

@ -0,0 +1,61 @@
<?php
namespace Acelaya\UrlShortener\Service;
use Acelaya\UrlShortener\Entity\ShortUrl;
use Acelaya\UrlShortener\Entity\Visit;
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
use Doctrine\ORM\EntityManagerInterface;
class VisitsTracker implements VisitsTrackerInterface
{
/**
* @var EntityManagerInterface
*/
private $em;
/**
* VisitsTracker constructor.
* @param EntityManagerInterface $em
*
* @Inject({"em"})
*/
public function __construct(EntityManagerInterface $em)
{
$this->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;
}
}