From 2d6d35a398c099405859132e1f2cd8241fb3f317 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya <alejandro@alejandrocelaya.com> Date: Fri, 10 Aug 2018 23:14:45 +0200 Subject: [PATCH] Added shortUrl field to serialized ShortUrl objects, both from CLI and REST --- composer.json | 1 + module/CLI/config/dependencies.config.php | 6 ++- .../Shortcode/ListShortcodesCommand.php | 28 ++++++---- .../Shortcode/ListShortcodesCommandTest.php | 6 +-- .../Paginator/Util/PaginatorUtilsTrait.php | 12 +++-- .../src/Rest/DataTransformerInterface.php | 9 ++++ module/Core/src/Entity/ShortUrl.php | 47 ++++++++-------- module/Core/src/Service/UrlShortener.php | 2 +- .../Transformer/ShortUrlDataTransformer.php | 54 +++++++++++++++++++ module/Rest/config/dependencies.config.php | 7 ++- .../Action/ShortCode/ListShortCodesAction.php | 11 +++- .../ShortCode/ListShortCodesActionTest.php | 5 +- 12 files changed, 144 insertions(+), 44 deletions(-) create mode 100644 module/Common/src/Rest/DataTransformerInterface.php create mode 100644 module/Core/src/Transformer/ShortUrlDataTransformer.php diff --git a/composer.json b/composer.json index d12596d7..9d46a882 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ ], "require": { "php": "^7.1", + "ext-json": "*", "acelaya/ze-content-based-error-handler": "^2.2", "cocur/slugify": "^3.0", "doctrine/cache": "^1.6", diff --git a/module/CLI/config/dependencies.config.php b/module/CLI/config/dependencies.config.php index 4dd461be..236b8d87 100644 --- a/module/CLI/config/dependencies.config.php +++ b/module/CLI/config/dependencies.config.php @@ -42,7 +42,11 @@ return [ 'config.url_shortener.domain', ], 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\GeneratePreviewCommand::class => [ Service\ShortUrlService::class, diff --git a/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php b/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php index 1e8ce12c..f0e9efa3 100644 --- a/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php +++ b/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php @@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\CLI\Command\Shortcode; use Shlinkio\Shlink\Common\Paginator\Adapter\PaginableRepositoryAdapter; use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait; use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface; +use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -27,15 +28,23 @@ class ListShortcodesCommand extends Command * @var TranslatorInterface */ 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->translator = $translator; parent::__construct(); + $this->domainConfig = $domainConfig; } - public function configure() + protected function configure(): void { $this->setName(self::NAME) ->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); $page = (int) $input->getOption('page'); @@ -87,6 +96,7 @@ class ListShortcodesCommand extends Command $tags = $input->getOption('tags'); $tags = ! empty($tags) ? \explode(',', $tags) : []; $showTags = $input->getOption('showTags'); + $transformer = new ShortUrlDataTransformer($this->domainConfig); do { $result = $this->shortUrlService->listShortUrls($page, $searchTerm, $tags, $this->processOrderBy($input)); @@ -94,7 +104,8 @@ class ListShortcodesCommand extends Command $headers = [ $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('Visits count'), ]; @@ -104,17 +115,14 @@ class ListShortcodesCommand extends Command $rows = []; foreach ($result as $row) { - $shortUrl = $row->jsonSerialize(); + $shortUrl = $transformer->transform($row); if ($showTags) { - $shortUrl['tags'] = []; - foreach ($row->getTags() as $tag) { - $shortUrl['tags'][] = $tag->getName(); - } $shortUrl['tags'] = implode(', ', $shortUrl['tags']); } else { unset($shortUrl['tags']); } + unset($shortUrl['originalUrl']); $rows[] = \array_values($shortUrl); } $io->table($headers, $rows); diff --git a/module/CLI/test/Command/Shortcode/ListShortcodesCommandTest.php b/module/CLI/test/Command/Shortcode/ListShortcodesCommandTest.php index 8aede93a..c79a2bb1 100644 --- a/module/CLI/test/Command/Shortcode/ListShortcodesCommandTest.php +++ b/module/CLI/test/Command/Shortcode/ListShortcodesCommandTest.php @@ -30,7 +30,7 @@ class ListShortcodesCommandTest extends TestCase { $this->shortUrlService = $this->prophesize(ShortUrlServiceInterface::class); $app = new Application(); - $command = new ListShortcodesCommand($this->shortUrlService->reveal(), Translator::factory([])); + $command = new ListShortcodesCommand($this->shortUrlService->reveal(), Translator::factory([]), []); $app->add($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 $data = []; for ($i = 0; $i < 50; $i++) { - $data[] = new ShortUrl(); + $data[] = (new ShortUrl())->setLongUrl('url_' . $i); } $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 $data = []; 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))) diff --git a/module/Common/src/Paginator/Util/PaginatorUtilsTrait.php b/module/Common/src/Paginator/Util/PaginatorUtilsTrait.php index 40f51d90..1b01de42 100644 --- a/module/Common/src/Paginator/Util/PaginatorUtilsTrait.php +++ b/module/Common/src/Paginator/Util/PaginatorUtilsTrait.php @@ -3,15 +3,16 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Common\Paginator\Util; +use Shlinkio\Shlink\Common\Rest\DataTransformerInterface; use Zend\Paginator\Paginator; use Zend\Stdlib\ArrayUtils; trait PaginatorUtilsTrait { - protected function serializePaginator(Paginator $paginator): array + private function serializePaginator(Paginator $paginator, ?DataTransformerInterface $transformer = null): array { return [ - 'data' => ArrayUtils::iteratorToArray($paginator->getCurrentItems()), + 'data' => $this->serializeItems(ArrayUtils::iteratorToArray($paginator->getCurrentItems()), $transformer), 'pagination' => [ 'currentPage' => $paginator->getCurrentPageNumber(), '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 * * @param Paginator $paginator * @return bool */ - protected function isLastPage(Paginator $paginator): bool + private function isLastPage(Paginator $paginator): bool { return $paginator->getCurrentPageNumber() >= $paginator->count(); } diff --git a/module/Common/src/Rest/DataTransformerInterface.php b/module/Common/src/Rest/DataTransformerInterface.php new file mode 100644 index 00000000..933f6cce --- /dev/null +++ b/module/Common/src/Rest/DataTransformerInterface.php @@ -0,0 +1,9 @@ +<?php +declare(strict_types=1); + +namespace Shlinkio\Shlink\Common\Rest; + +interface DataTransformerInterface +{ + public function transform($value): array; +} diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index ff21c47b..def81f01 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -16,7 +16,7 @@ use Shlinkio\Shlink\Common\Entity\AbstractEntity; * @ORM\Entity(repositoryClass="Shlinkio\Shlink\Core\Repository\ShortUrlRepository") * @ORM\Table(name="short_urls") */ -class ShortUrl extends AbstractEntity implements \JsonSerializable +class ShortUrl extends AbstractEntity { /** * @var string @@ -84,21 +84,40 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable /** * @return string */ - public function getOriginalUrl(): string + public function getLongUrl(): string { return $this->originalUrl; } /** - * @param string $originalUrl + * @param string $longUrl * @return $this */ - public function setOriginalUrl(string $originalUrl) + public function setLongUrl(string $longUrl): self { - $this->originalUrl = $originalUrl; + $this->originalUrl = $longUrl; 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 */ @@ -237,22 +256,4 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable { 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(), - ]; - } } diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index 87621060..e1696adc 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -23,7 +23,7 @@ class UrlShortener implements UrlShortenerInterface { use TagManagerTrait; - const DEFAULT_CHARS = '123456789bcdfghjkmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ'; + public const DEFAULT_CHARS = '123456789bcdfghjkmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ'; /** * @var ClientInterface diff --git a/module/Core/src/Transformer/ShortUrlDataTransformer.php b/module/Core/src/Transformer/ShortUrlDataTransformer.php new file mode 100644 index 00000000..c1113a81 --- /dev/null +++ b/module/Core/src/Transformer/ShortUrlDataTransformer.php @@ -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(); + } +} diff --git a/module/Rest/config/dependencies.config.php b/module/Rest/config/dependencies.config.php index 304e667d..42147707 100644 --- a/module/Rest/config/dependencies.config.php +++ b/module/Rest/config/dependencies.config.php @@ -61,7 +61,12 @@ return [ Action\ShortCode\EditShortCodeAction::class => [Service\ShortUrlService::class, 'translator', 'Logger_Shlink',], Action\ShortCode\ResolveUrlAction::class => [Service\UrlShortener::class, 'translator'], 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 => [ Service\ShortUrlService::class, 'translator', diff --git a/module/Rest/src/Action/ShortCode/ListShortCodesAction.php b/module/Rest/src/Action/ShortCode/ListShortCodesAction.php index 58f26d77..e69840a5 100644 --- a/module/Rest/src/Action/ShortCode/ListShortCodesAction.php +++ b/module/Rest/src/Action/ShortCode/ListShortCodesAction.php @@ -8,6 +8,7 @@ use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Log\LoggerInterface; use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait; use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface; +use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer; use Shlinkio\Shlink\Rest\Action\AbstractRestAction; use Shlinkio\Shlink\Rest\Util\RestUtils; use Zend\Diactoros\Response\JsonResponse; @@ -28,15 +29,21 @@ class ListShortCodesAction extends AbstractRestAction * @var TranslatorInterface */ private $translator; + /** + * @var array + */ + private $domainConfig; public function __construct( ShortUrlServiceInterface $shortUrlService, TranslatorInterface $translator, + array $domainConfig, LoggerInterface $logger = null ) { parent::__construct($logger); $this->shortUrlService = $shortUrlService; $this->translator = $translator; + $this->domainConfig = $domainConfig; } /** @@ -49,7 +56,9 @@ class ListShortCodesAction extends AbstractRestAction try { $params = $this->queryToListParams($request->getQueryParams()); $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) { $this->logger->error('Unexpected error while listing short URLs.' . PHP_EOL . $e); return new JsonResponse([ diff --git a/module/Rest/test/Action/ShortCode/ListShortCodesActionTest.php b/module/Rest/test/Action/ShortCode/ListShortCodesActionTest.php index 59912f5a..7c414e44 100644 --- a/module/Rest/test/Action/ShortCode/ListShortCodesActionTest.php +++ b/module/Rest/test/Action/ShortCode/ListShortCodesActionTest.php @@ -26,7 +26,10 @@ class ListShortCodesActionTest extends TestCase public function setUp() { $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', + ]); } /**