mirror of
https://github.com/shlinkio/shlink.git
synced 2025-02-17 07:49:54 +03:00
Implemented short code generation based on a provided long URL
This commit is contained in:
parent
b436edb991
commit
93c2c1298a
7 changed files with 182 additions and 2 deletions
|
@ -18,7 +18,8 @@
|
||||||
"zendframework/zend-expressive-aurarouter": "^1.0",
|
"zendframework/zend-expressive-aurarouter": "^1.0",
|
||||||
"zendframework/zend-servicemanager": "^3.0",
|
"zendframework/zend-servicemanager": "^3.0",
|
||||||
"zendframework/zend-expressive-twigrenderer": "^1.0",
|
"zendframework/zend-expressive-twigrenderer": "^1.0",
|
||||||
"doctrine/orm": "^2.5"
|
"doctrine/orm": "^2.5",
|
||||||
|
"guzzlehttp/guzzle": "^6.2"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^4.8",
|
"phpunit/phpunit": "^4.8",
|
||||||
|
|
|
@ -41,7 +41,9 @@ class ShortUrl extends AbstractEntity
|
||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
|
$this->dateCreated = new \DateTime();
|
||||||
$this->visits = new ArrayCollection();
|
$this->visits = new ArrayCollection();
|
||||||
|
$this->shortCode = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,7 +60,7 @@ class ShortUrl extends AbstractEntity
|
||||||
*/
|
*/
|
||||||
public function setOriginalUrl($originalUrl)
|
public function setOriginalUrl($originalUrl)
|
||||||
{
|
{
|
||||||
$this->originalUrl = $originalUrl;
|
$this->originalUrl = (string) $originalUrl;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
6
src/Exception/ExceptionInterface.php
Normal file
6
src/Exception/ExceptionInterface.php
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?php
|
||||||
|
namespace Acelaya\UrlShortener\Exception;
|
||||||
|
|
||||||
|
interface ExceptionInterface
|
||||||
|
{
|
||||||
|
}
|
11
src/Exception/InvalidUrlException.php
Normal file
11
src/Exception/InvalidUrlException.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
6
src/Exception/RuntimeException.php
Normal file
6
src/Exception/RuntimeException.php
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?php
|
||||||
|
namespace Acelaya\UrlShortener\Exception;
|
||||||
|
|
||||||
|
class RuntimeException extends \RuntimeException implements ExceptionInterface
|
||||||
|
{
|
||||||
|
}
|
131
src/Service/UrlShortener.php
Normal file
131
src/Service/UrlShortener.php
Normal 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
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
23
src/Service/UrlShortenerInterface.php
Normal file
23
src/Service/UrlShortenerInterface.php
Normal 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);
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue