From 521f6f2b18c307dda49af0d3a07078629e1c1904 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 2 Nov 2018 10:54:42 +0100 Subject: [PATCH 1/5] Added functional-php library --- composer.json | 1 + module/Common/functions/functions.php | 9 ++------- module/Common/src/Factory/CacheFactory.php | 4 ++-- module/Core/src/Repository/ShortUrlRepository.php | 4 ++-- module/Core/src/Response/NotFoundHandler.php | 4 ++-- .../src/Config/Plugin/DatabaseConfigCustomizer.php | 4 ++-- module/Rest/src/Middleware/AuthenticationMiddleware.php | 4 ++-- module/Rest/src/Middleware/BodyParserMiddleware.php | 8 ++++---- 8 files changed, 17 insertions(+), 21 deletions(-) diff --git a/composer.json b/composer.json index d31169c9..fbf77aa0 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,7 @@ "endroid/qr-code": "^1.7", "firebase/php-jwt": "^4.0", "guzzlehttp/guzzle": "^6.2", + "lstrojny/functional-php": "^1.8", "mikehaertl/phpwkhtmltopdf": "^2.2", "monolog/monolog": "^1.21", "roave/security-advisories": "dev-master", diff --git a/module/Common/functions/functions.php b/module/Common/functions/functions.php index 7ba4d515..f84957dc 100644 --- a/module/Common/functions/functions.php +++ b/module/Common/functions/functions.php @@ -6,8 +6,8 @@ namespace Shlinkio\Shlink\Common; use const ARRAY_FILTER_USE_KEY; use const JSON_ERROR_NONE; use function array_filter; +use function Functional\contains; use function getenv; -use function in_array; use function json_decode as spl_json_decode; use function json_last_error; use function json_last_error_msg; @@ -49,11 +49,6 @@ function env($key, $default = null) return trim($value); } -function contains($needle, array $haystack): bool -{ - return in_array($needle, $haystack, true); -} - /** * Returns only the keys in keysToPick from provided array * @@ -63,7 +58,7 @@ function contains($needle, array $haystack): bool function pick(array $array, array $keysToPick): array { return array_filter($array, function (string $key) use ($keysToPick) { - return contains($key, $keysToPick); + return contains($keysToPick, $key); }, ARRAY_FILTER_USE_KEY); } diff --git a/module/Common/src/Factory/CacheFactory.php b/module/Common/src/Factory/CacheFactory.php index d1ce5c1e..92f63c61 100644 --- a/module/Common/src/Factory/CacheFactory.php +++ b/module/Common/src/Factory/CacheFactory.php @@ -11,7 +11,7 @@ use Shlinkio\Shlink\Core\Options\AppOptions; use Zend\ServiceManager\Exception\ServiceNotCreatedException; use Zend\ServiceManager\Exception\ServiceNotFoundException; use Zend\ServiceManager\Factory\FactoryInterface; -use function Shlinkio\Shlink\Common\contains; +use function Functional\contains; use function Shlinkio\Shlink\Common\env; class CacheFactory implements FactoryInterface @@ -53,7 +53,7 @@ class CacheFactory implements FactoryInterface { // Try to get the adapter from 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']); } diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index 03e95ba7..b204d6f8 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -9,9 +9,9 @@ use Doctrine\ORM\QueryBuilder; use Shlinkio\Shlink\Core\Entity\ShortUrl; use function array_column; use function array_key_exists; +use function Functional\contains; use function is_array; use function key; -use function Shlinkio\Shlink\Common\contains; class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryInterface { @@ -63,7 +63,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI $fieldName = is_array($orderBy) ? key($orderBy) : $orderBy; $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') ->leftJoin('s.visits', 'v') ->groupBy('s') diff --git a/module/Core/src/Response/NotFoundHandler.php b/module/Core/src/Response/NotFoundHandler.php index 1e40457b..2350ce52 100644 --- a/module/Core/src/Response/NotFoundHandler.php +++ b/module/Core/src/Response/NotFoundHandler.php @@ -11,7 +11,7 @@ use Zend\Diactoros\Response; use Zend\Expressive\Template\TemplateRendererInterface; use function array_shift; use function explode; -use function Shlinkio\Shlink\Common\contains; +use function Functional\contains; class NotFoundHandler implements RequestHandlerInterface { @@ -47,7 +47,7 @@ class NotFoundHandler implements RequestHandlerInterface $status = StatusCodeInterface::STATUS_NOT_FOUND; // 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([ 'error' => 'NOT_FOUND', 'message' => 'Not found', diff --git a/module/Installer/src/Config/Plugin/DatabaseConfigCustomizer.php b/module/Installer/src/Config/Plugin/DatabaseConfigCustomizer.php index 3039f2af..3b1e27be 100644 --- a/module/Installer/src/Config/Plugin/DatabaseConfigCustomizer.php +++ b/module/Installer/src/Config/Plugin/DatabaseConfigCustomizer.php @@ -10,7 +10,7 @@ use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Filesystem; use function array_diff; use function array_keys; -use function Shlinkio\Shlink\Common\contains; +use function Functional\contains; 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 (contains(self::DRIVER, $keysToAskFor)) { + if (contains($keysToAskFor, self::DRIVER)) { $io->title('DATABASE'); $titlePrinted = true; $db[self::DRIVER] = $this->ask($io, self::DRIVER); diff --git a/module/Rest/src/Middleware/AuthenticationMiddleware.php b/module/Rest/src/Middleware/AuthenticationMiddleware.php index 9a9cdb10..164fb636 100644 --- a/module/Rest/src/Middleware/AuthenticationMiddleware.php +++ b/module/Rest/src/Middleware/AuthenticationMiddleware.php @@ -20,8 +20,8 @@ use Shlinkio\Shlink\Rest\Util\RestUtils; use Zend\Diactoros\Response\JsonResponse; use Zend\Expressive\Router\RouteResult; use Zend\I18n\Translator\TranslatorInterface; +use function Functional\contains; use function implode; -use function Shlinkio\Shlink\Common\contains; use function sprintf; class AuthenticationMiddleware implements MiddlewareInterface, StatusCodeInterface, RequestMethodInterface @@ -72,7 +72,7 @@ class AuthenticationMiddleware implements MiddlewareInterface, StatusCodeInterfa if ($routeResult === null || $routeResult->isFailure() || $request->getMethod() === self::METHOD_OPTIONS - || contains($routeResult->getMatchedRouteName(), $this->routesWhitelist) + || contains($this->routesWhitelist, $routeResult->getMatchedRouteName()) ) { return $handler->handle($request); } diff --git a/module/Rest/src/Middleware/BodyParserMiddleware.php b/module/Rest/src/Middleware/BodyParserMiddleware.php index 5d9b883f..ce5f6ee7 100644 --- a/module/Rest/src/Middleware/BodyParserMiddleware.php +++ b/module/Rest/src/Middleware/BodyParserMiddleware.php @@ -10,8 +10,8 @@ use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use function array_shift; use function explode; +use function Functional\contains; use function parse_str; -use function Shlinkio\Shlink\Common\contains; use function Shlinkio\Shlink\Common\json_decode; use function trim; @@ -32,17 +32,17 @@ class BodyParserMiddleware implements MiddlewareInterface, RequestMethodInterfac $currentParams = $request->getParsedBody(); // 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_HEAD, self::METHOD_OPTIONS, - ])) { + ], $method)) { return $handler->handle($request); } // If the accepted content is JSON, try to parse the body from JSON $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)); } From 664dc333acd9d367865eb1026c46dfb8a213f026 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 2 Nov 2018 11:08:20 +0100 Subject: [PATCH 2/5] Used select_keys function in place of custom pick function --- .../src/Command/ShortUrl/GetVisitsCommand.php | 4 ++-- module/Common/functions/functions.php | 18 +----------------- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/module/CLI/src/Command/ShortUrl/GetVisitsCommand.php b/module/CLI/src/Command/ShortUrl/GetVisitsCommand.php index caf9c4b7..e2451406 100644 --- a/module/CLI/src/Command/ShortUrl/GetVisitsCommand.php +++ b/module/CLI/src/Command/ShortUrl/GetVisitsCommand.php @@ -15,7 +15,7 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Zend\I18n\Translator\TranslatorInterface; use function array_map; -use function Shlinkio\Shlink\Common\pick; +use function Functional\select_keys; class GetVisitsCommand extends Command { @@ -92,7 +92,7 @@ class GetVisitsCommand extends Command $rows = array_map(function (Visit $visit) { $rowData = $visit->jsonSerialize(); $rowData['country'] = $visit->getVisitLocation()->getCountryName(); - return pick($rowData, ['referer', 'date', 'userAgent', 'country']); + return select_keys($rowData, ['referer', 'date', 'userAgent', 'country']); }, $visits); $io->table([ $this->translator->translate('Referer'), diff --git a/module/Common/functions/functions.php b/module/Common/functions/functions.php index f84957dc..cccd70be 100644 --- a/module/Common/functions/functions.php +++ b/module/Common/functions/functions.php @@ -3,10 +3,6 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Common; -use const ARRAY_FILTER_USE_KEY; -use const JSON_ERROR_NONE; -use function array_filter; -use function Functional\contains; use function getenv; use function json_decode as spl_json_decode; use function json_last_error; @@ -14,6 +10,7 @@ use function json_last_error_msg; use function sprintf; use function strtolower; use function trim; +use const JSON_ERROR_NONE; /** * Gets the value of an environment variable. Supports boolean, empty and null. @@ -49,19 +46,6 @@ function env($key, $default = null) return trim($value); } -/** - * 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($keysToPick, $key); - }, ARRAY_FILTER_USE_KEY); -} - /** * @throws Exception\InvalidArgumentException */ From f64920e51001a799878c589fc593264e31ec65f6 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 2 Nov 2018 12:05:01 +0100 Subject: [PATCH 3/5] Replaced some array_map by Functional\map --- module/CLI/src/Command/Tag/ListTagsCommand.php | 8 ++++---- .../Core/src/Transformer/ShortUrlDataTransformer.php | 10 ++-------- .../test/Exception/DeleteShortUrlExceptionTest.php | 6 +++--- .../Service/ShortUrl/DeleteShortUrlServiceTest.php | 6 +++--- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/module/CLI/src/Command/Tag/ListTagsCommand.php b/module/CLI/src/Command/Tag/ListTagsCommand.php index eac0afd2..9b848d02 100644 --- a/module/CLI/src/Command/Tag/ListTagsCommand.php +++ b/module/CLI/src/Command/Tag/ListTagsCommand.php @@ -10,7 +10,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Zend\I18n\Translator\TranslatorInterface; -use function array_map; +use function Functional\map; class ListTagsCommand extends Command { @@ -45,15 +45,15 @@ class ListTagsCommand extends Command $io->table([$this->translator->translate('Name')], $this->getTagsRows()); } - private function getTagsRows() + private function getTagsRows(): array { $tags = $this->tagService->listTags(); if (empty($tags)) { return [[$this->translator->translate('No tags yet')]]; } - return array_map(function (Tag $tag) { + return map($tags, function (Tag $tag) { return [(string) $tag]; - }, $tags); + }); } } diff --git a/module/Core/src/Transformer/ShortUrlDataTransformer.php b/module/Core/src/Transformer/ShortUrlDataTransformer.php index 04b992cc..0a7abe8c 100644 --- a/module/Core/src/Transformer/ShortUrlDataTransformer.php +++ b/module/Core/src/Transformer/ShortUrlDataTransformer.php @@ -5,9 +5,8 @@ namespace Shlinkio\Shlink\Core\Transformer; use Shlinkio\Shlink\Common\Rest\DataTransformerInterface; use Shlinkio\Shlink\Core\Entity\ShortUrl; -use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Util\ShortUrlBuilderTrait; -use function array_map; +use function Functional\invoke; class ShortUrlDataTransformer implements DataTransformerInterface { @@ -38,15 +37,10 @@ class ShortUrlDataTransformer implements DataTransformerInterface 'longUrl' => $longUrl, 'dateCreated' => $dateCreated !== null ? $dateCreated->toAtomString() : null, 'visitsCount' => $value->getVisitsCount(), - 'tags' => array_map([$this, 'serializeTag'], $value->getTags()->toArray()), + 'tags' => invoke($value->getTags(), '__toString'), // Deprecated 'originalUrl' => $longUrl, ]; } - - private function serializeTag(Tag $tag): string - { - return (string) $tag; - } } diff --git a/module/Core/test/Exception/DeleteShortUrlExceptionTest.php b/module/Core/test/Exception/DeleteShortUrlExceptionTest.php index 97f330ce..80c5ff10 100644 --- a/module/Core/test/Exception/DeleteShortUrlExceptionTest.php +++ b/module/Core/test/Exception/DeleteShortUrlExceptionTest.php @@ -5,7 +5,7 @@ namespace ShlinkioTest\Shlink\Core\Exception; use PHPUnit\Framework\TestCase; use Shlinkio\Shlink\Core\Exception\DeleteShortUrlException; -use function array_map; +use function Functional\map; use function range; class DeleteShortUrlExceptionTest extends TestCase @@ -56,8 +56,8 @@ class DeleteShortUrlExceptionTest extends TestCase public function provideThresholds(): array { - return array_map(function (int $number) { + return map(range(5, 50, 5), function (int $number) { return [$number]; - }, range(5, 50, 5)); + }); } } diff --git a/module/Core/test/Service/ShortUrl/DeleteShortUrlServiceTest.php b/module/Core/test/Service/ShortUrl/DeleteShortUrlServiceTest.php index 1094521e..db86a249 100644 --- a/module/Core/test/Service/ShortUrl/DeleteShortUrlServiceTest.php +++ b/module/Core/test/Service/ShortUrl/DeleteShortUrlServiceTest.php @@ -15,7 +15,7 @@ use Shlinkio\Shlink\Core\Model\Visitor; use Shlinkio\Shlink\Core\Options\DeleteShortUrlsOptions; use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\Service\ShortUrl\DeleteShortUrlService; -use function array_map; +use function Functional\map; use function range; class DeleteShortUrlServiceTest extends TestCase @@ -32,9 +32,9 @@ class DeleteShortUrlServiceTest extends TestCase public function setUp() { $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()); - }, range(0, 10)))); + }))); $this->em = $this->prophesize(EntityManagerInterface::class); From bfb96b0ae8db6180fd7f5b3ace09ab2064bf8012 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 2 Nov 2018 12:07:13 +0100 Subject: [PATCH 4/5] Fixed coding style --- module/Common/functions/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/Common/functions/functions.php b/module/Common/functions/functions.php index cccd70be..b0d2b434 100644 --- a/module/Common/functions/functions.php +++ b/module/Common/functions/functions.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Common; +use const JSON_ERROR_NONE; use function getenv; use function json_decode as spl_json_decode; use function json_last_error; @@ -10,7 +11,6 @@ use function json_last_error_msg; use function sprintf; use function strtolower; use function trim; -use const JSON_ERROR_NONE; /** * Gets the value of an environment variable. Supports boolean, empty and null. From 2412ec2195bf16bdfc3ce06ada638ef4bf585cd3 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 2 Nov 2018 12:08:43 +0100 Subject: [PATCH 5/5] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d54bda95..fe069713 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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`. * [#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. +* [#249](https://github.com/shlinkio/shlink/issues/249) Added [functional-php](https://github.com/lstrojny/functional-php) to ease collections handling. #### Deprecated