Merge pull request #250 from acelaya/feature/functional

Feature/functional
This commit is contained in:
Alejandro Celaya 2018-11-02 12:19:07 +01:00 committed by GitHub
commit 5ec8c229a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 30 additions and 55 deletions

View file

@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
* [#228](https://github.com/shlinkio/shlink/issues/228) Updated how exceptions are serialized into logs, by using monlog's `PsrLogMessageProcessor`. * [#228](https://github.com/shlinkio/shlink/issues/228) Updated how exceptions are serialized into logs, by using monlog's `PsrLogMessageProcessor`.
* [#225](https://github.com/shlinkio/shlink/issues/225) Performance and maintainability slightly improved by enforcing via code sniffer that all global namespace classes, functions and constants are explicitly imported. * [#225](https://github.com/shlinkio/shlink/issues/225) Performance and maintainability slightly improved by enforcing via code sniffer that all global namespace classes, functions and constants are explicitly imported.
* [#196](https://github.com/shlinkio/shlink/issues/196) Reduced anemic model in entities, defining more expressive public APIs instead. * [#196](https://github.com/shlinkio/shlink/issues/196) Reduced anemic model in entities, defining more expressive public APIs instead.
* [#249](https://github.com/shlinkio/shlink/issues/249) Added [functional-php](https://github.com/lstrojny/functional-php) to ease collections handling.
#### Deprecated #### Deprecated

View file

@ -25,6 +25,7 @@
"endroid/qr-code": "^1.7", "endroid/qr-code": "^1.7",
"firebase/php-jwt": "^4.0", "firebase/php-jwt": "^4.0",
"guzzlehttp/guzzle": "^6.2", "guzzlehttp/guzzle": "^6.2",
"lstrojny/functional-php": "^1.8",
"mikehaertl/phpwkhtmltopdf": "^2.2", "mikehaertl/phpwkhtmltopdf": "^2.2",
"monolog/monolog": "^1.21", "monolog/monolog": "^1.21",
"roave/security-advisories": "dev-master", "roave/security-advisories": "dev-master",

View file

@ -15,7 +15,7 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
use Zend\I18n\Translator\TranslatorInterface; use Zend\I18n\Translator\TranslatorInterface;
use function array_map; use function array_map;
use function Shlinkio\Shlink\Common\pick; use function Functional\select_keys;
class GetVisitsCommand extends Command class GetVisitsCommand extends Command
{ {
@ -92,7 +92,7 @@ class GetVisitsCommand extends Command
$rows = array_map(function (Visit $visit) { $rows = array_map(function (Visit $visit) {
$rowData = $visit->jsonSerialize(); $rowData = $visit->jsonSerialize();
$rowData['country'] = $visit->getVisitLocation()->getCountryName(); $rowData['country'] = $visit->getVisitLocation()->getCountryName();
return pick($rowData, ['referer', 'date', 'userAgent', 'country']); return select_keys($rowData, ['referer', 'date', 'userAgent', 'country']);
}, $visits); }, $visits);
$io->table([ $io->table([
$this->translator->translate('Referer'), $this->translator->translate('Referer'),

View file

@ -10,7 +10,7 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
use Zend\I18n\Translator\TranslatorInterface; use Zend\I18n\Translator\TranslatorInterface;
use function array_map; use function Functional\map;
class ListTagsCommand extends Command class ListTagsCommand extends Command
{ {
@ -45,15 +45,15 @@ class ListTagsCommand extends Command
$io->table([$this->translator->translate('Name')], $this->getTagsRows()); $io->table([$this->translator->translate('Name')], $this->getTagsRows());
} }
private function getTagsRows() private function getTagsRows(): array
{ {
$tags = $this->tagService->listTags(); $tags = $this->tagService->listTags();
if (empty($tags)) { if (empty($tags)) {
return [[$this->translator->translate('No tags yet')]]; return [[$this->translator->translate('No tags yet')]];
} }
return array_map(function (Tag $tag) { return map($tags, function (Tag $tag) {
return [(string) $tag]; return [(string) $tag];
}, $tags); });
} }
} }

View file

@ -3,11 +3,8 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Common; namespace Shlinkio\Shlink\Common;
use const ARRAY_FILTER_USE_KEY;
use const JSON_ERROR_NONE; use const JSON_ERROR_NONE;
use function array_filter;
use function getenv; use function getenv;
use function in_array;
use function json_decode as spl_json_decode; use function json_decode as spl_json_decode;
use function json_last_error; use function json_last_error;
use function json_last_error_msg; use function json_last_error_msg;
@ -49,24 +46,6 @@ function env($key, $default = null)
return trim($value); return trim($value);
} }
function contains($needle, array $haystack): bool
{
return in_array($needle, $haystack, true);
}
/**
* Returns only the keys in keysToPick from provided array
*
* @param array $array
* @param array $keysToPick
*/
function pick(array $array, array $keysToPick): array
{
return array_filter($array, function (string $key) use ($keysToPick) {
return contains($key, $keysToPick);
}, ARRAY_FILTER_USE_KEY);
}
/** /**
* @throws Exception\InvalidArgumentException * @throws Exception\InvalidArgumentException
*/ */

View file

@ -11,7 +11,7 @@ use Shlinkio\Shlink\Core\Options\AppOptions;
use Zend\ServiceManager\Exception\ServiceNotCreatedException; use Zend\ServiceManager\Exception\ServiceNotCreatedException;
use Zend\ServiceManager\Exception\ServiceNotFoundException; use Zend\ServiceManager\Exception\ServiceNotFoundException;
use Zend\ServiceManager\Factory\FactoryInterface; use Zend\ServiceManager\Factory\FactoryInterface;
use function Shlinkio\Shlink\Common\contains; use function Functional\contains;
use function Shlinkio\Shlink\Common\env; use function Shlinkio\Shlink\Common\env;
class CacheFactory implements FactoryInterface class CacheFactory implements FactoryInterface
@ -53,7 +53,7 @@ class CacheFactory implements FactoryInterface
{ {
// Try to get the adapter from config // Try to get the adapter from config
$config = $container->get('config'); $config = $container->get('config');
if (isset($config['cache']['adapter']) && contains($config['cache']['adapter'], self::VALID_CACHE_ADAPTERS)) { if (isset($config['cache']['adapter']) && contains(self::VALID_CACHE_ADAPTERS, $config['cache']['adapter'])) {
return $this->resolveCacheAdapter($config['cache']); return $this->resolveCacheAdapter($config['cache']);
} }

View file

@ -9,9 +9,9 @@ use Doctrine\ORM\QueryBuilder;
use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\ShortUrl;
use function array_column; use function array_column;
use function array_key_exists; use function array_key_exists;
use function Functional\contains;
use function is_array; use function is_array;
use function key; use function key;
use function Shlinkio\Shlink\Common\contains;
class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryInterface class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryInterface
{ {
@ -63,7 +63,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
$fieldName = is_array($orderBy) ? key($orderBy) : $orderBy; $fieldName = is_array($orderBy) ? key($orderBy) : $orderBy;
$order = is_array($orderBy) ? $orderBy[$fieldName] : 'ASC'; $order = is_array($orderBy) ? $orderBy[$fieldName] : 'ASC';
if (contains($fieldName, ['visits', 'visitsCount', 'visitCount'])) { if (contains(['visits', 'visitsCount', 'visitCount'], $fieldName)) {
$qb->addSelect('COUNT(DISTINCT v) AS totalVisits') $qb->addSelect('COUNT(DISTINCT v) AS totalVisits')
->leftJoin('s.visits', 'v') ->leftJoin('s.visits', 'v')
->groupBy('s') ->groupBy('s')

View file

@ -11,7 +11,7 @@ use Zend\Diactoros\Response;
use Zend\Expressive\Template\TemplateRendererInterface; use Zend\Expressive\Template\TemplateRendererInterface;
use function array_shift; use function array_shift;
use function explode; use function explode;
use function Shlinkio\Shlink\Common\contains; use function Functional\contains;
class NotFoundHandler implements RequestHandlerInterface class NotFoundHandler implements RequestHandlerInterface
{ {
@ -47,7 +47,7 @@ class NotFoundHandler implements RequestHandlerInterface
$status = StatusCodeInterface::STATUS_NOT_FOUND; $status = StatusCodeInterface::STATUS_NOT_FOUND;
// If the first accepted type is json, return a json response // If the first accepted type is json, return a json response
if (contains($accept, ['application/json', 'text/json', 'application/x-json'])) { if (contains(['application/json', 'text/json', 'application/x-json'], $accept)) {
return new Response\JsonResponse([ return new Response\JsonResponse([
'error' => 'NOT_FOUND', 'error' => 'NOT_FOUND',
'message' => 'Not found', 'message' => 'Not found',

View file

@ -5,9 +5,8 @@ namespace Shlinkio\Shlink\Core\Transformer;
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface; use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Entity\Tag;
use Shlinkio\Shlink\Core\Util\ShortUrlBuilderTrait; use Shlinkio\Shlink\Core\Util\ShortUrlBuilderTrait;
use function array_map; use function Functional\invoke;
class ShortUrlDataTransformer implements DataTransformerInterface class ShortUrlDataTransformer implements DataTransformerInterface
{ {
@ -38,15 +37,10 @@ class ShortUrlDataTransformer implements DataTransformerInterface
'longUrl' => $longUrl, 'longUrl' => $longUrl,
'dateCreated' => $dateCreated !== null ? $dateCreated->toAtomString() : null, 'dateCreated' => $dateCreated !== null ? $dateCreated->toAtomString() : null,
'visitsCount' => $value->getVisitsCount(), 'visitsCount' => $value->getVisitsCount(),
'tags' => array_map([$this, 'serializeTag'], $value->getTags()->toArray()), 'tags' => invoke($value->getTags(), '__toString'),
// Deprecated // Deprecated
'originalUrl' => $longUrl, 'originalUrl' => $longUrl,
]; ];
} }
private function serializeTag(Tag $tag): string
{
return (string) $tag;
}
} }

View file

@ -5,7 +5,7 @@ namespace ShlinkioTest\Shlink\Core\Exception;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\Core\Exception\DeleteShortUrlException; use Shlinkio\Shlink\Core\Exception\DeleteShortUrlException;
use function array_map; use function Functional\map;
use function range; use function range;
class DeleteShortUrlExceptionTest extends TestCase class DeleteShortUrlExceptionTest extends TestCase
@ -56,8 +56,8 @@ class DeleteShortUrlExceptionTest extends TestCase
public function provideThresholds(): array public function provideThresholds(): array
{ {
return array_map(function (int $number) { return map(range(5, 50, 5), function (int $number) {
return [$number]; return [$number];
}, range(5, 50, 5)); });
} }
} }

View file

@ -15,7 +15,7 @@ use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\Options\DeleteShortUrlsOptions; use Shlinkio\Shlink\Core\Options\DeleteShortUrlsOptions;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
use Shlinkio\Shlink\Core\Service\ShortUrl\DeleteShortUrlService; use Shlinkio\Shlink\Core\Service\ShortUrl\DeleteShortUrlService;
use function array_map; use function Functional\map;
use function range; use function range;
class DeleteShortUrlServiceTest extends TestCase class DeleteShortUrlServiceTest extends TestCase
@ -32,9 +32,9 @@ class DeleteShortUrlServiceTest extends TestCase
public function setUp() public function setUp()
{ {
$shortUrl = (new ShortUrl(''))->setShortCode('abc123') $shortUrl = (new ShortUrl(''))->setShortCode('abc123')
->setVisits(new ArrayCollection(array_map(function () { ->setVisits(new ArrayCollection(map(range(0, 10), function () {
return new Visit(new ShortUrl(''), Visitor::emptyInstance()); return new Visit(new ShortUrl(''), Visitor::emptyInstance());
}, range(0, 10)))); })));
$this->em = $this->prophesize(EntityManagerInterface::class); $this->em = $this->prophesize(EntityManagerInterface::class);

View file

@ -10,7 +10,7 @@ use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Filesystem\Filesystem;
use function array_diff; use function array_diff;
use function array_keys; use function array_keys;
use function Shlinkio\Shlink\Common\contains; use function Functional\contains;
class DatabaseConfigCustomizer implements ConfigCustomizerInterface class DatabaseConfigCustomizer implements ConfigCustomizerInterface
{ {
@ -68,7 +68,7 @@ class DatabaseConfigCustomizer implements ConfigCustomizerInterface
} }
// If the driver is one of the params to ask for, ask for it first // If the driver is one of the params to ask for, ask for it first
if (contains(self::DRIVER, $keysToAskFor)) { if (contains($keysToAskFor, self::DRIVER)) {
$io->title('DATABASE'); $io->title('DATABASE');
$titlePrinted = true; $titlePrinted = true;
$db[self::DRIVER] = $this->ask($io, self::DRIVER); $db[self::DRIVER] = $this->ask($io, self::DRIVER);

View file

@ -20,8 +20,8 @@ use Shlinkio\Shlink\Rest\Util\RestUtils;
use Zend\Diactoros\Response\JsonResponse; use Zend\Diactoros\Response\JsonResponse;
use Zend\Expressive\Router\RouteResult; use Zend\Expressive\Router\RouteResult;
use Zend\I18n\Translator\TranslatorInterface; use Zend\I18n\Translator\TranslatorInterface;
use function Functional\contains;
use function implode; use function implode;
use function Shlinkio\Shlink\Common\contains;
use function sprintf; use function sprintf;
class AuthenticationMiddleware implements MiddlewareInterface, StatusCodeInterface, RequestMethodInterface class AuthenticationMiddleware implements MiddlewareInterface, StatusCodeInterface, RequestMethodInterface
@ -72,7 +72,7 @@ class AuthenticationMiddleware implements MiddlewareInterface, StatusCodeInterfa
if ($routeResult === null if ($routeResult === null
|| $routeResult->isFailure() || $routeResult->isFailure()
|| $request->getMethod() === self::METHOD_OPTIONS || $request->getMethod() === self::METHOD_OPTIONS
|| contains($routeResult->getMatchedRouteName(), $this->routesWhitelist) || contains($this->routesWhitelist, $routeResult->getMatchedRouteName())
) { ) {
return $handler->handle($request); return $handler->handle($request);
} }

View file

@ -10,8 +10,8 @@ use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface; use Psr\Http\Server\RequestHandlerInterface;
use function array_shift; use function array_shift;
use function explode; use function explode;
use function Functional\contains;
use function parse_str; use function parse_str;
use function Shlinkio\Shlink\Common\contains;
use function Shlinkio\Shlink\Common\json_decode; use function Shlinkio\Shlink\Common\json_decode;
use function trim; use function trim;
@ -32,17 +32,17 @@ class BodyParserMiddleware implements MiddlewareInterface, RequestMethodInterfac
$currentParams = $request->getParsedBody(); $currentParams = $request->getParsedBody();
// In requests that do not allow body or if the body has already been parsed, continue to next middleware // In requests that do not allow body or if the body has already been parsed, continue to next middleware
if (! empty($currentParams) || contains($method, [ if (! empty($currentParams) || contains([
self::METHOD_GET, self::METHOD_GET,
self::METHOD_HEAD, self::METHOD_HEAD,
self::METHOD_OPTIONS, self::METHOD_OPTIONS,
])) { ], $method)) {
return $handler->handle($request); return $handler->handle($request);
} }
// If the accepted content is JSON, try to parse the body from JSON // If the accepted content is JSON, try to parse the body from JSON
$contentType = $this->getRequestContentType($request); $contentType = $this->getRequestContentType($request);
if (contains($contentType, ['application/json', 'text/json', 'application/x-json'])) { if (contains(['application/json', 'text/json', 'application/x-json'], $contentType)) {
return $handler->handle($this->parseFromJson($request)); return $handler->handle($this->parseFromJson($request));
} }