Implemented short code generation based on a provided long URL

This commit is contained in:
Alejandro Celaya 2016-04-17 11:29:23 +02:00
parent b436edb991
commit 93c2c1298a
7 changed files with 182 additions and 2 deletions

View file

@ -18,7 +18,8 @@
"zendframework/zend-expressive-aurarouter": "^1.0",
"zendframework/zend-servicemanager": "^3.0",
"zendframework/zend-expressive-twigrenderer": "^1.0",
"doctrine/orm": "^2.5"
"doctrine/orm": "^2.5",
"guzzlehttp/guzzle": "^6.2"
},
"require-dev": {
"phpunit/phpunit": "^4.8",

View file

@ -41,7 +41,9 @@ class ShortUrl extends AbstractEntity
*/
public function __construct()
{
$this->dateCreated = new \DateTime();
$this->visits = new ArrayCollection();
$this->shortCode = '';
}
/**
@ -58,7 +60,7 @@ class ShortUrl extends AbstractEntity
*/
public function setOriginalUrl($originalUrl)
{
$this->originalUrl = $originalUrl;
$this->originalUrl = (string) $originalUrl;
return $this;
}

View file

@ -0,0 +1,6 @@
<?php
namespace Acelaya\UrlShortener\Exception;
interface ExceptionInterface
{
}

View file

@ -0,0 +1,11 @@
<?php
namespace Acelaya\UrlShortener\Exception;
class InvalidUrlException extends RuntimeException
{
public static function fromUrl($url, \Exception $previous = null)
{
$code = isset($previous) ? $previous->getCode() : -1;
return new static(sprintf('Provided URL "%s" is not an exisitng and valid URL', $url), $code, $previous);
}
}

View file

@ -0,0 +1,6 @@
<?php
namespace Acelaya\UrlShortener\Exception;
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}

View file

@ -0,0 +1,131 @@
<?php
namespace Acelaya\UrlShortener\Service;
use Acelaya\UrlShortener\Entity\ShortUrl;
use Acelaya\UrlShortener\Exception\InvalidUrlException;
use Acelaya\UrlShortener\Exception\RuntimeException;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\ORMException;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Http\Message\UriInterface;
class UrlShortener implements UrlShortenerInterface
{
const DEFAULT_CHARS = '123456789bcdfghjkmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ';
/**
* @var ClientInterface
*/
private $httpClient;
/**
* @var EntityManagerInterface
*/
private $em;
/**
* @var string
*/
private $chars;
public function __construct(
ClientInterface $httpClient,
EntityManagerInterface $em,
$chars = self::DEFAULT_CHARS
) {
$this->httpClient = $httpClient;
$this->em = $em;
$this->chars = $chars;
}
/**
* @param UriInterface $url
* @return string
* @throws InvalidUrlException
* @throws RuntimeException
*/
public function urlToShortCode(UriInterface $url)
{
// If the url already exists in the database, just return its short code
$shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([
'originalUrl' => $url
]);
if (isset($shortUrl)) {
return $shortUrl->getShortCode();
}
// Check that the URL exists
$this->checkUrlExists($url);
// Transactionally insert the short url, then generate the short code and finally update the short code
try {
$this->em->beginTransaction();
// First, create the short URL with an empty short code
$shortUrl = new ShortUrl();
$shortUrl->setOriginalUrl($url);
$this->em->persist($shortUrl);
$this->em->flush();
// Generate the short code and persist it
$shortCode = $this->convertAutoincrementIdToShortCode($shortUrl->getId());
$shortUrl->setShortCode($shortCode);
$this->em->flush();
$this->em->commit();
return $shortCode;
} catch (ORMException $e) {
if ($this->em->getConnection()->isTransactionActive()) {
$this->em->rollback();
$this->em->close();
}
throw new RuntimeException('An error occured while persisting the short URL', -1, $e);
}
}
/**
* Tries to perform a GET request to provided url, returning true on success and false on failure
*
* @param UriInterface $url
* @return bool
*/
protected function checkUrlExists(UriInterface $url)
{
try {
$this->httpClient->request('GET', $url);
} catch (GuzzleException $e) {
throw InvalidUrlException::fromUrl($url, $e);
}
}
/**
* Generates the unique shortcode for an autoincrement ID
*
* @param int $id
* @return string
*/
protected function convertAutoincrementIdToShortCode($id)
{
$id = intval($id);
$length = strlen($this->chars);
$code = '';
while ($id > $length - 1) {
// Determine the value of the next higher character in the short code and prepend it
$code = $this->chars[fmod($id, $length)] . $code;
$id = floor($id / $length);
}
return $this->chars[$id] . $code;
}
/**
* @param string $shortCode
* @return string
*/
public function shortCodeToUrl($shortCode)
{
// Validate short code format
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Acelaya\UrlShortener\Service;
use Acelaya\UrlShortener\Exception\InvalidUrlException;
use Acelaya\UrlShortener\Exception\RuntimeException;
use Psr\Http\Message\UriInterface;
interface UrlShortenerInterface
{
/**
* @param UriInterface $url
* @return string
* @throws InvalidUrlException
* @throws RuntimeException
*/
public function urlToShortCode(UriInterface $url);
/**
* @param string $shortCode
* @return string
*/
public function shortCodeToUrl($shortCode);
}