Added shortUrl field to serialized ShortUrl objects, both from CLI and REST

This commit is contained in:
Alejandro Celaya
2018-08-10 23:14:45 +02:00
parent 30297ac5ac
commit 2d6d35a398
12 changed files with 144 additions and 44 deletions

View File

@@ -13,6 +13,7 @@
], ],
"require": { "require": {
"php": "^7.1", "php": "^7.1",
"ext-json": "*",
"acelaya/ze-content-based-error-handler": "^2.2", "acelaya/ze-content-based-error-handler": "^2.2",
"cocur/slugify": "^3.0", "cocur/slugify": "^3.0",
"doctrine/cache": "^1.6", "doctrine/cache": "^1.6",

View File

@@ -42,7 +42,11 @@ return [
'config.url_shortener.domain', 'config.url_shortener.domain',
], ],
Command\Shortcode\ResolveUrlCommand::class => [Service\UrlShortener::class, 'translator'], Command\Shortcode\ResolveUrlCommand::class => [Service\UrlShortener::class, 'translator'],
Command\Shortcode\ListShortcodesCommand::class => [Service\ShortUrlService::class, 'translator'], Command\Shortcode\ListShortcodesCommand::class => [
Service\ShortUrlService::class,
'translator',
'config.url_shortener.domain',
],
Command\Shortcode\GetVisitsCommand::class => [Service\VisitsTracker::class, 'translator'], Command\Shortcode\GetVisitsCommand::class => [Service\VisitsTracker::class, 'translator'],
Command\Shortcode\GeneratePreviewCommand::class => [ Command\Shortcode\GeneratePreviewCommand::class => [
Service\ShortUrlService::class, Service\ShortUrlService::class,

View File

@@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\CLI\Command\Shortcode;
use Shlinkio\Shlink\Common\Paginator\Adapter\PaginableRepositoryAdapter; use Shlinkio\Shlink\Common\Paginator\Adapter\PaginableRepositoryAdapter;
use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait; use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait;
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface; use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
@@ -27,15 +28,23 @@ class ListShortcodesCommand extends Command
* @var TranslatorInterface * @var TranslatorInterface
*/ */
private $translator; private $translator;
/**
* @var array
*/
private $domainConfig;
public function __construct(ShortUrlServiceInterface $shortUrlService, TranslatorInterface $translator) public function __construct(
{ ShortUrlServiceInterface $shortUrlService,
TranslatorInterface $translator,
array $domainConfig
) {
$this->shortUrlService = $shortUrlService; $this->shortUrlService = $shortUrlService;
$this->translator = $translator; $this->translator = $translator;
parent::__construct(); parent::__construct();
$this->domainConfig = $domainConfig;
} }
public function configure() protected function configure(): void
{ {
$this->setName(self::NAME) $this->setName(self::NAME)
->setDescription($this->translator->translate('List all short URLs')) ->setDescription($this->translator->translate('List all short URLs'))
@@ -79,7 +88,7 @@ class ListShortcodesCommand extends Command
); );
} }
public function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
$io = new SymfonyStyle($input, $output); $io = new SymfonyStyle($input, $output);
$page = (int) $input->getOption('page'); $page = (int) $input->getOption('page');
@@ -87,6 +96,7 @@ class ListShortcodesCommand extends Command
$tags = $input->getOption('tags'); $tags = $input->getOption('tags');
$tags = ! empty($tags) ? \explode(',', $tags) : []; $tags = ! empty($tags) ? \explode(',', $tags) : [];
$showTags = $input->getOption('showTags'); $showTags = $input->getOption('showTags');
$transformer = new ShortUrlDataTransformer($this->domainConfig);
do { do {
$result = $this->shortUrlService->listShortUrls($page, $searchTerm, $tags, $this->processOrderBy($input)); $result = $this->shortUrlService->listShortUrls($page, $searchTerm, $tags, $this->processOrderBy($input));
@@ -94,7 +104,8 @@ class ListShortcodesCommand extends Command
$headers = [ $headers = [
$this->translator->translate('Short code'), $this->translator->translate('Short code'),
$this->translator->translate('Original URL'), $this->translator->translate('Short URL'),
$this->translator->translate('Long URL'),
$this->translator->translate('Date created'), $this->translator->translate('Date created'),
$this->translator->translate('Visits count'), $this->translator->translate('Visits count'),
]; ];
@@ -104,17 +115,14 @@ class ListShortcodesCommand extends Command
$rows = []; $rows = [];
foreach ($result as $row) { foreach ($result as $row) {
$shortUrl = $row->jsonSerialize(); $shortUrl = $transformer->transform($row);
if ($showTags) { if ($showTags) {
$shortUrl['tags'] = [];
foreach ($row->getTags() as $tag) {
$shortUrl['tags'][] = $tag->getName();
}
$shortUrl['tags'] = implode(', ', $shortUrl['tags']); $shortUrl['tags'] = implode(', ', $shortUrl['tags']);
} else { } else {
unset($shortUrl['tags']); unset($shortUrl['tags']);
} }
unset($shortUrl['originalUrl']);
$rows[] = \array_values($shortUrl); $rows[] = \array_values($shortUrl);
} }
$io->table($headers, $rows); $io->table($headers, $rows);

View File

@@ -30,7 +30,7 @@ class ListShortcodesCommandTest extends TestCase
{ {
$this->shortUrlService = $this->prophesize(ShortUrlServiceInterface::class); $this->shortUrlService = $this->prophesize(ShortUrlServiceInterface::class);
$app = new Application(); $app = new Application();
$command = new ListShortcodesCommand($this->shortUrlService->reveal(), Translator::factory([])); $command = new ListShortcodesCommand($this->shortUrlService->reveal(), Translator::factory([]), []);
$app->add($command); $app->add($command);
$this->commandTester = new CommandTester($command); $this->commandTester = new CommandTester($command);
} }
@@ -55,7 +55,7 @@ class ListShortcodesCommandTest extends TestCase
// The paginator will return more than one page for the first 3 times // The paginator will return more than one page for the first 3 times
$data = []; $data = [];
for ($i = 0; $i < 50; $i++) { for ($i = 0; $i < 50; $i++) {
$data[] = new ShortUrl(); $data[] = (new ShortUrl())->setLongUrl('url_' . $i);
} }
$this->shortUrlService->listShortUrls(Argument::cetera())->will(function () use (&$data) { $this->shortUrlService->listShortUrls(Argument::cetera())->will(function () use (&$data) {
@@ -74,7 +74,7 @@ class ListShortcodesCommandTest extends TestCase
// The paginator will return more than one page // The paginator will return more than one page
$data = []; $data = [];
for ($i = 0; $i < 30; $i++) { for ($i = 0; $i < 30; $i++) {
$data[] = new ShortUrl(); $data[] = (new ShortUrl())->setLongUrl('url_' . $i);
} }
$this->shortUrlService->listShortUrls(Argument::cetera())->willReturn(new Paginator(new ArrayAdapter($data))) $this->shortUrlService->listShortUrls(Argument::cetera())->willReturn(new Paginator(new ArrayAdapter($data)))

View File

@@ -3,15 +3,16 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Common\Paginator\Util; namespace Shlinkio\Shlink\Common\Paginator\Util;
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
use Zend\Paginator\Paginator; use Zend\Paginator\Paginator;
use Zend\Stdlib\ArrayUtils; use Zend\Stdlib\ArrayUtils;
trait PaginatorUtilsTrait trait PaginatorUtilsTrait
{ {
protected function serializePaginator(Paginator $paginator): array private function serializePaginator(Paginator $paginator, ?DataTransformerInterface $transformer = null): array
{ {
return [ return [
'data' => ArrayUtils::iteratorToArray($paginator->getCurrentItems()), 'data' => $this->serializeItems(ArrayUtils::iteratorToArray($paginator->getCurrentItems()), $transformer),
'pagination' => [ 'pagination' => [
'currentPage' => $paginator->getCurrentPageNumber(), 'currentPage' => $paginator->getCurrentPageNumber(),
'pagesCount' => $paginator->count(), 'pagesCount' => $paginator->count(),
@@ -22,13 +23,18 @@ trait PaginatorUtilsTrait
]; ];
} }
private function serializeItems(array $items, ?DataTransformerInterface $transformer = null): array
{
return $transformer === null ? $items : \array_map([$transformer, 'transform'], $items);
}
/** /**
* Checks if provided paginator is in last page * Checks if provided paginator is in last page
* *
* @param Paginator $paginator * @param Paginator $paginator
* @return bool * @return bool
*/ */
protected function isLastPage(Paginator $paginator): bool private function isLastPage(Paginator $paginator): bool
{ {
return $paginator->getCurrentPageNumber() >= $paginator->count(); return $paginator->getCurrentPageNumber() >= $paginator->count();
} }

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Common\Rest;
interface DataTransformerInterface
{
public function transform($value): array;
}

View File

@@ -16,7 +16,7 @@ use Shlinkio\Shlink\Common\Entity\AbstractEntity;
* @ORM\Entity(repositoryClass="Shlinkio\Shlink\Core\Repository\ShortUrlRepository") * @ORM\Entity(repositoryClass="Shlinkio\Shlink\Core\Repository\ShortUrlRepository")
* @ORM\Table(name="short_urls") * @ORM\Table(name="short_urls")
*/ */
class ShortUrl extends AbstractEntity implements \JsonSerializable class ShortUrl extends AbstractEntity
{ {
/** /**
* @var string * @var string
@@ -84,21 +84,40 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable
/** /**
* @return string * @return string
*/ */
public function getOriginalUrl(): string public function getLongUrl(): string
{ {
return $this->originalUrl; return $this->originalUrl;
} }
/** /**
* @param string $originalUrl * @param string $longUrl
* @return $this * @return $this
*/ */
public function setOriginalUrl(string $originalUrl) public function setLongUrl(string $longUrl): self
{ {
$this->originalUrl = $originalUrl; $this->originalUrl = $longUrl;
return $this; return $this;
} }
/**
* @return string
* @deprecated Use getLongUrl() instead
*/
public function getOriginalUrl(): string
{
return $this->getLongUrl();
}
/**
* @param string $originalUrl
* @return $this
* @deprecated Use setLongUrl() instead
*/
public function setOriginalUrl(string $originalUrl): self
{
return $this->setLongUrl($originalUrl);
}
/** /**
* @return string * @return string
*/ */
@@ -237,22 +256,4 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable
{ {
return $this->maxVisits !== null && $this->getVisitsCount() >= $this->maxVisits; return $this->maxVisits !== null && $this->getVisitsCount() >= $this->maxVisits;
} }
/**
* Specify data which should be serialized to JSON
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource.
* @since 5.4.0
*/
public function jsonSerialize()
{
return [
'shortCode' => $this->shortCode,
'originalUrl' => $this->originalUrl,
'dateCreated' => $this->dateCreated !== null ? $this->dateCreated->format(\DateTime::ATOM) : null,
'visitsCount' => $this->getVisitsCount(),
'tags' => $this->tags->toArray(),
];
}
} }

View File

@@ -23,7 +23,7 @@ class UrlShortener implements UrlShortenerInterface
{ {
use TagManagerTrait; use TagManagerTrait;
const DEFAULT_CHARS = '123456789bcdfghjkmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ'; public const DEFAULT_CHARS = '123456789bcdfghjkmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ';
/** /**
* @var ClientInterface * @var ClientInterface

View File

@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Transformer;
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Entity\Tag;
class ShortUrlDataTransformer implements DataTransformerInterface
{
/**
* @var array
*/
private $domainConfig;
public function __construct(array $domainConfig)
{
$this->domainConfig = $domainConfig;
}
/**
* @param ShortUrl $value
* @return array
*/
public function transform($value): array
{
$dateCreated = $value->getDateCreated();
$longUrl = $value->getLongUrl();
$shortCode = $value->getShortCode();
return [
'shortCode' => $shortCode,
'shortUrl' => \sprintf(
'%s://%s/%s',
$this->domainConfig['schema'] ?? 'http',
$this->domainConfig['hostname'] ?? '',
$shortCode
),
'longUrl' => $longUrl,
'dateCreated' => $dateCreated !== null ? $dateCreated->format(\DateTime::ATOM) : null,
'visitsCount' => $value->getVisitsCount(),
'tags' => \array_map([$this, 'serializeTag'], $value->getTags()->toArray()),
// Deprecated
'originalUrl' => $longUrl,
];
}
private function serializeTag(Tag $tag): string
{
return $tag->getName();
}
}

View File

@@ -61,7 +61,12 @@ return [
Action\ShortCode\EditShortCodeAction::class => [Service\ShortUrlService::class, 'translator', 'Logger_Shlink',], Action\ShortCode\EditShortCodeAction::class => [Service\ShortUrlService::class, 'translator', 'Logger_Shlink',],
Action\ShortCode\ResolveUrlAction::class => [Service\UrlShortener::class, 'translator'], Action\ShortCode\ResolveUrlAction::class => [Service\UrlShortener::class, 'translator'],
Action\Visit\GetVisitsAction::class => [Service\VisitsTracker::class, 'translator', 'Logger_Shlink'], Action\Visit\GetVisitsAction::class => [Service\VisitsTracker::class, 'translator', 'Logger_Shlink'],
Action\ShortCode\ListShortCodesAction::class => [Service\ShortUrlService::class, 'translator', 'Logger_Shlink'], Action\ShortCode\ListShortCodesAction::class => [
Service\ShortUrlService::class,
'translator',
'config.url_shortener.domain',
'Logger_Shlink',
],
Action\ShortCode\EditShortCodeTagsAction::class => [ Action\ShortCode\EditShortCodeTagsAction::class => [
Service\ShortUrlService::class, Service\ShortUrlService::class,
'translator', 'translator',

View File

@@ -8,6 +8,7 @@ use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait; use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait;
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface; use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction; use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
use Shlinkio\Shlink\Rest\Util\RestUtils; use Shlinkio\Shlink\Rest\Util\RestUtils;
use Zend\Diactoros\Response\JsonResponse; use Zend\Diactoros\Response\JsonResponse;
@@ -28,15 +29,21 @@ class ListShortCodesAction extends AbstractRestAction
* @var TranslatorInterface * @var TranslatorInterface
*/ */
private $translator; private $translator;
/**
* @var array
*/
private $domainConfig;
public function __construct( public function __construct(
ShortUrlServiceInterface $shortUrlService, ShortUrlServiceInterface $shortUrlService,
TranslatorInterface $translator, TranslatorInterface $translator,
array $domainConfig,
LoggerInterface $logger = null LoggerInterface $logger = null
) { ) {
parent::__construct($logger); parent::__construct($logger);
$this->shortUrlService = $shortUrlService; $this->shortUrlService = $shortUrlService;
$this->translator = $translator; $this->translator = $translator;
$this->domainConfig = $domainConfig;
} }
/** /**
@@ -49,7 +56,9 @@ class ListShortCodesAction extends AbstractRestAction
try { try {
$params = $this->queryToListParams($request->getQueryParams()); $params = $this->queryToListParams($request->getQueryParams());
$shortUrls = $this->shortUrlService->listShortUrls(...$params); $shortUrls = $this->shortUrlService->listShortUrls(...$params);
return new JsonResponse(['shortUrls' => $this->serializePaginator($shortUrls)]); return new JsonResponse(['shortUrls' => $this->serializePaginator($shortUrls, new ShortUrlDataTransformer(
$this->domainConfig
))]);
} catch (\Exception $e) { } catch (\Exception $e) {
$this->logger->error('Unexpected error while listing short URLs.' . PHP_EOL . $e); $this->logger->error('Unexpected error while listing short URLs.' . PHP_EOL . $e);
return new JsonResponse([ return new JsonResponse([

View File

@@ -26,7 +26,10 @@ class ListShortCodesActionTest extends TestCase
public function setUp() public function setUp()
{ {
$this->service = $this->prophesize(ShortUrlService::class); $this->service = $this->prophesize(ShortUrlService::class);
$this->action = new ListShortCodesAction($this->service->reveal(), Translator::factory([])); $this->action = new ListShortCodesAction($this->service->reveal(), Translator::factory([]), [
'hostname' => 'doma.in',
'schema' => 'https',
]);
} }
/** /**