Removed more functional-php usages

This commit is contained in:
Alejandro Celaya 2023-11-30 14:34:21 +01:00
parent 549c6605f0
commit bff4bd12ae
20 changed files with 156 additions and 91 deletions

View file

@ -80,6 +80,9 @@
"symfony/var-dumper": "^6.3", "symfony/var-dumper": "^6.3",
"veewee/composer-run-parallel": "^1.3" "veewee/composer-run-parallel": "^1.3"
}, },
"conflict": {
"symfony/var-exporter": ">=6.3.9,<=6.4.0"
},
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Shlinkio\\Shlink\\CLI\\": "module/CLI/src", "Shlinkio\\Shlink\\CLI\\": "module/CLI/src",

View file

@ -11,7 +11,7 @@ use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration; use Doctrine\Migrations\AbstractMigration;
use function Functional\some; use function Shlinkio\Shlink\Core\some;
final class Version20200105165647 extends AbstractMigration final class Version20200105165647 extends AbstractMigration
{ {
@ -23,11 +23,12 @@ final class Version20200105165647 extends AbstractMigration
public function preUp(Schema $schema): void public function preUp(Schema $schema): void
{ {
$visitLocations = $schema->getTable('visit_locations'); $visitLocations = $schema->getTable('visit_locations');
$this->skipIf(some( $this->skipIf(some(
self::COLUMNS, self::COLUMNS,
fn (string $v, string $newColName) => $visitLocations->hasColumn($newColName), fn (string $v, string $newColName) => $visitLocations->hasColumn($newColName),
), 'New columns already exist'); ), 'New columns already exist');
foreach (self::COLUMNS as $columnName) { foreach (self::COLUMNS as $columnName) {
$qb = $this->connection->createQueryBuilder(); $qb = $this->connection->createQueryBuilder();
$qb->update('visit_locations') $qb->update('visit_locations')

View file

@ -7,11 +7,10 @@ namespace ShlinkMigrations;
use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration; use Doctrine\Migrations\AbstractMigration;
use function Functional\none;
final class Version20200106215144 extends AbstractMigration final class Version20200106215144 extends AbstractMigration
{ {
private const COLUMNS = ['latitude', 'longitude']; private const COLUMNS = ['latitude', 'longitude'];
@ -22,16 +21,24 @@ final class Version20200106215144 extends AbstractMigration
public function up(Schema $schema): void public function up(Schema $schema): void
{ {
$visitLocations = $schema->getTable('visit_locations'); $visitLocations = $schema->getTable('visit_locations');
$this->skipIf(none( $this->skipIf($this->oldColumnsDoNotExist($visitLocations), 'Old columns do not exist');
self::COLUMNS,
fn (string $oldColName) => $visitLocations->hasColumn($oldColName),
), 'Old columns do not exist');
foreach (self::COLUMNS as $colName) { foreach (self::COLUMNS as $colName) {
$visitLocations->dropColumn($colName); $visitLocations->dropColumn($colName);
} }
} }
public function oldColumnsDoNotExist(Table $visitLocations): bool
{
foreach (self::COLUMNS as $oldColName) {
if ($visitLocations->hasColumn($oldColName)) {
return false;
}
}
return true;
}
/** /**
* @throws Exception * @throws Exception
*/ */

View file

@ -9,9 +9,6 @@ use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration; use Doctrine\Migrations\AbstractMigration;
use function Functional\each;
use function Functional\partial_left;
final class Version20200110182849 extends AbstractMigration final class Version20200110182849 extends AbstractMigration
{ {
private const DEFAULT_EMPTY_VALUE = ''; private const DEFAULT_EMPTY_VALUE = '';
@ -31,11 +28,11 @@ final class Version20200110182849 extends AbstractMigration
public function up(Schema $schema): void public function up(Schema $schema): void
{ {
each( foreach (self::COLUMN_DEFAULTS_MAP as $tableName => $columns) {
self::COLUMN_DEFAULTS_MAP, foreach ($columns as $columnName) {
fn (array $columns, string $tableName) => $this->setDefaultValueForColumnInTable($tableName, $columnName);
each($columns, partial_left([$this, 'setDefaultValueForColumnInTable'], $tableName)), }
); }
} }
/** /**

View file

@ -14,8 +14,8 @@ 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 function Functional\filter; use function array_filter;
use function Functional\invoke; use function array_map;
use function sprintf; use function sprintf;
use function str_contains; use function str_contains;
@ -23,7 +23,7 @@ class DomainRedirectsCommand extends Command
{ {
public const NAME = 'domain:redirects'; public const NAME = 'domain:redirects';
public function __construct(private DomainServiceInterface $domainService) public function __construct(private readonly DomainServiceInterface $domainService)
{ {
parent::__construct(); parent::__construct();
} }
@ -52,9 +52,9 @@ class DomainRedirectsCommand extends Command
$askNewDomain = static fn () => $io->ask('Domain authority for which you want to set specific redirects'); $askNewDomain = static fn () => $io->ask('Domain authority for which you want to set specific redirects');
/** @var string[] $availableDomains */ /** @var string[] $availableDomains */
$availableDomains = invoke( $availableDomains = array_map(
filter($this->domainService->listDomains(), static fn (DomainItem $item) => ! $item->isDefault), static fn (DomainItem $item) => $item->toString(),
'toString', array_filter($this->domainService->listDomains(), static fn (DomainItem $item) => ! $item->isDefault),
); );
if (empty($availableDomains)) { if (empty($availableDomains)) {
$input->setArgument('domain', $askNewDomain()); $input->setArgument('domain', $askNewDomain());

View file

@ -8,30 +8,30 @@ use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableSeparator; use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use function Functional\intersperse; use function array_pop;
final class ShlinkTable final class ShlinkTable
{ {
private const DEFAULT_STYLE_NAME = 'default'; private const DEFAULT_STYLE_NAME = 'default';
private const TABLE_TITLE_STYLE = '<options=bold> %s </>'; private const TABLE_TITLE_STYLE = '<options=bold> %s </>';
private function __construct(private readonly Table $baseTable, private readonly bool $withRowSeparators) private function __construct(private readonly Table $baseTable, private readonly bool $withRowSeparators = false)
{ {
} }
public static function default(OutputInterface $output): self public static function default(OutputInterface $output): self
{ {
return new self(new Table($output), false); return new self(new Table($output));
} }
public static function withRowSeparators(OutputInterface $output): self public static function withRowSeparators(OutputInterface $output): self
{ {
return new self(new Table($output), true); return new self(new Table($output), withRowSeparators: true);
} }
public static function fromBaseTable(Table $baseTable): self public static function fromBaseTable(Table $baseTable): self
{ {
return new self($baseTable, false); return new self($baseTable);
} }
public function render(array $headers, array $rows, ?string $footerTitle = null, ?string $headerTitle = null): void public function render(array $headers, array $rows, ?string $footerTitle = null, ?string $headerTitle = null): void
@ -39,7 +39,7 @@ final class ShlinkTable
$style = Table::getStyleDefinition(self::DEFAULT_STYLE_NAME); $style = Table::getStyleDefinition(self::DEFAULT_STYLE_NAME);
$style->setFooterTitleFormat(self::TABLE_TITLE_STYLE) $style->setFooterTitleFormat(self::TABLE_TITLE_STYLE)
->setHeaderTitleFormat(self::TABLE_TITLE_STYLE); ->setHeaderTitleFormat(self::TABLE_TITLE_STYLE);
$tableRows = $this->withRowSeparators ? intersperse($rows, new TableSeparator()) : $rows; $tableRows = $this->withRowSeparators ? $this->addRowSeparators($rows) : $rows;
$table = clone $this->baseTable; $table = clone $this->baseTable;
$table->setStyle($style) $table->setStyle($style)
@ -49,4 +49,20 @@ final class ShlinkTable
->setHeaderTitle($headerTitle) ->setHeaderTitle($headerTitle)
->render(); ->render();
} }
private function addRowSeparators(array $rows): array
{
$aggregation = [];
$separator = new TableSeparator();
foreach ($rows as $row) {
$aggregation[] = $row;
$aggregation[] = $separator;
}
// Remove last separator
array_pop($aggregation);
return $aggregation;
}
} }

View file

@ -16,10 +16,10 @@ use PUGX\Shortid\Factory as ShortIdFactory;
use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Common\Util\DateRange;
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlMode; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlMode;
use function array_keys;
use function array_map; use function array_map;
use function array_reduce; use function array_reduce;
use function date_default_timezone_get; use function date_default_timezone_get;
use function Functional\reduce_left;
use function in_array; use function in_array;
use function is_array; use function is_array;
use function print_r; use function print_r;
@ -95,10 +95,12 @@ function getNonEmptyOptionalValueFromInputFilter(InputFilter $inputFilter, strin
function arrayToString(array $array, int $indentSize = 4): string function arrayToString(array $array, int $indentSize = 4): string
{ {
$indent = str_repeat(' ', $indentSize); $indent = str_repeat(' ', $indentSize);
$names = array_keys($array);
$index = 0; $index = 0;
return reduce_left($array, static function ($messages, string $name, $_, string $acc) use (&$index, $indent) { return array_reduce($names, static function (string $acc, string $name) use (&$index, $indent, $array) {
$index++; $index++;
$messages = $array[$name];
return $acc . sprintf( return $acc . sprintf(
"%s%s'%s' => %s", "%s%s'%s' => %s",
@ -199,3 +201,33 @@ function flatten(array $multiArray): array
initial: [], initial: [],
); );
} }
/**
* Checks if a callback returns true for at least one item in a collection.
* @param callable(mixed $value, string|number $key): bool $callback
*/
function some(iterable $collection, callable $callback): bool
{
foreach ($collection as $key => $value) {
if ($callback($value, $key)) {
return true;
}
}
return false;
}
/**
* Checks if a callback returns true for all item in a collection.
* @param callable(mixed $value, string|number $key): bool $callback
*/
function every(iterable $collection, callable $callback): bool
{
foreach ($collection as $key => $value) {
if (! $callback($value, $key)) {
return false;
}
}
return true;
}

View file

@ -13,7 +13,6 @@ use Shlinkio\Shlink\Core\ErrorHandler\Model\NotFoundType;
use Shlinkio\Shlink\Core\Util\RedirectResponseHelperInterface; use Shlinkio\Shlink\Core\Util\RedirectResponseHelperInterface;
use function Functional\compose; use function Functional\compose;
use function Functional\id;
use function str_replace; use function str_replace;
use function urlencode; use function urlencode;
@ -23,8 +22,8 @@ class NotFoundRedirectResolver implements NotFoundRedirectResolverInterface
private const ORIGINAL_PATH_PLACEHOLDER = '{ORIGINAL_PATH}'; private const ORIGINAL_PATH_PLACEHOLDER = '{ORIGINAL_PATH}';
public function __construct( public function __construct(
private RedirectResponseHelperInterface $redirectResponseHelper, private readonly RedirectResponseHelperInterface $redirectResponseHelper,
private LoggerInterface $logger, private readonly LoggerInterface $logger,
) { ) {
} }
@ -73,7 +72,7 @@ class NotFoundRedirectResolver implements NotFoundRedirectResolverInterface
$replacePlaceholderForPattern(self::ORIGINAL_PATH_PLACEHOLDER, $path, $modifier), $replacePlaceholderForPattern(self::ORIGINAL_PATH_PLACEHOLDER, $path, $modifier),
); );
$replacePlaceholdersInPath = compose( $replacePlaceholdersInPath = compose(
$replacePlaceholders(id(...)), $replacePlaceholders(static fn (mixed $v) => $v),
static fn (?string $path) => $path === null ? null : str_replace('//', '/', $path), static fn (?string $path) => $path === null ? null : str_replace('//', '/', $path),
); );
$replacePlaceholdersInQuery = $replacePlaceholders(urlencode(...)); $replacePlaceholdersInQuery = $replacePlaceholders(urlencode(...));

View file

@ -9,25 +9,34 @@ use Mezzio\Router\Route;
use Shlinkio\Shlink\Core\Action\RedirectAction; use Shlinkio\Shlink\Core\Action\RedirectAction;
use Shlinkio\Shlink\Core\Util\RedirectStatus; use Shlinkio\Shlink\Core\Util\RedirectStatus;
use function array_values;
use function count;
use function Functional\partition;
use const Shlinkio\Shlink\DEFAULT_REDIRECT_STATUS_CODE; use const Shlinkio\Shlink\DEFAULT_REDIRECT_STATUS_CODE;
/**
* Sets the appropriate allowed methods on the redirect route, based on the redirect status code that was configured.
* * For "legacy" status codes (301 and 302) the redirect URL will work only on GET method.
* * For other status codes (307 and 308) the redirect URL will work on any method.
*/
class ShortUrlMethodsProcessor class ShortUrlMethodsProcessor
{ {
public function __invoke(array $config): array public function __invoke(array $config): array
{ {
[$redirectRoutes, $rest] = partition( $allRoutes = $config['routes'] ?? [];
$config['routes'] ?? [], $redirectRoute = null;
static fn (array $route) => $route['name'] === RedirectAction::class, $rest = [];
);
if (count($redirectRoutes) === 0) { // Get default route from routes array
foreach ($allRoutes as $route) {
if ($route['name'] === RedirectAction::class) {
$redirectRoute ??= $route;
} else {
$rest[] = $route;
}
}
if ($redirectRoute === null) {
return $config; return $config;
} }
[$redirectRoute] = array_values($redirectRoutes);
$redirectStatus = RedirectStatus::tryFrom( $redirectStatus = RedirectStatus::tryFrom(
$config['redirects']['redirect_status_code'] ?? 0, $config['redirects']['redirect_status_code'] ?? 0,
) ?? DEFAULT_REDIRECT_STATUS_CODE; ) ?? DEFAULT_REDIRECT_STATUS_CODE;

View file

@ -15,8 +15,6 @@ use Shlinkio\Shlink\Rest\ApiKey\Role;
use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Entity\ApiKey;
use function array_map; use function array_map;
use function Functional\first;
use function Functional\group;
class DomainService implements DomainServiceInterface class DomainService implements DomainServiceInterface
{ {
@ -49,12 +47,19 @@ class DomainService implements DomainServiceInterface
{ {
/** @var DomainRepositoryInterface $repo */ /** @var DomainRepositoryInterface $repo */
$repo = $this->em->getRepository(Domain::class); $repo = $this->em->getRepository(Domain::class);
$groups = group( $allDomains = $repo->findDomains($apiKey);
$repo->findDomains($apiKey), $defaultDomain = null;
fn (Domain $domain) => $domain->authority === $this->defaultDomain ? 'default' : 'domains', $restOfDomains = [];
);
return [first($groups['default'] ?? []), $groups['domains'] ?? []]; foreach ($allDomains as $domain) {
if ($domain->authority === $this->defaultDomain) {
$defaultDomain = $domain;
} else {
$restOfDomains[] = $domain;
}
}
return [$defaultDomain, $restOfDomains];
} }
/** /**

View file

@ -13,7 +13,7 @@ use Shlinkio\Shlink\Core\EventDispatcher\PublishingUpdatesGeneratorInterface;
use Shlinkio\Shlink\Core\Visit\Entity\Visit; use Shlinkio\Shlink\Core\Visit\Entity\Visit;
use Throwable; use Throwable;
use function Functional\each; use function array_walk;
abstract class AbstractNotifyVisitListener extends AbstractAsyncListener abstract class AbstractNotifyVisitListener extends AbstractAsyncListener
{ {
@ -46,7 +46,7 @@ abstract class AbstractNotifyVisitListener extends AbstractAsyncListener
$updates = $this->determineUpdatesForVisit($visit); $updates = $this->determineUpdatesForVisit($visit);
try { try {
each($updates, fn (Update $update) => $this->publishingHelper->publishUpdate($update)); array_walk($updates, fn (Update $update) => $this->publishingHelper->publishUpdate($update));
} catch (Throwable $e) { } catch (Throwable $e) {
$this->logger->debug( $this->logger->debug(
'Error while trying to notify {name} with new visit. {e}', 'Error while trying to notify {name} with new visit. {e}',

View file

@ -6,7 +6,6 @@ namespace Shlinkio\Shlink\Core\ShortUrl\Model;
use Shlinkio\Shlink\Core\Model\DeviceType; use Shlinkio\Shlink\Core\Model\DeviceType;
use function Functional\group;
use function trim; use function trim;
final class DeviceLongUrlPair final class DeviceLongUrlPair
@ -25,27 +24,20 @@ final class DeviceLongUrlPair
* * The first one is a list of mapped instances for those entries in the map with non-null value * * The first one is a list of mapped instances for those entries in the map with non-null value
* * The second is a list of DeviceTypes which have been provided with value null * * The second is a list of DeviceTypes which have been provided with value null
* *
* @param array<string, string> $map * @param array<string, string | null> $map
* @return array{array<string, self>, DeviceType[]} * @return array{array<string, self>, DeviceType[]}
*/ */
public static function fromMapToChangeSet(array $map): array public static function fromMapToChangeSet(array $map): array
{ {
$toRemove = []; // TODO Use when group is removed
$toKeep = []; // TODO Use when group is removed
$typesWithNullUrl = group($map, static fn (?string $longUrl) => $longUrl === null ? 'remove' : 'keep');
$deviceTypesToRemove = [];
foreach ($typesWithNullUrl['remove'] ?? [] as $deviceType => $_) {
$deviceTypesToRemove[] = DeviceType::from($deviceType);
}
$pairsToKeep = []; $pairsToKeep = [];
/** $deviceTypesToRemove = [];
* @var string $deviceType
* @var string $longUrl foreach ($map as $deviceType => $longUrl) {
*/ if ($longUrl === null) {
foreach ($typesWithNullUrl['keep'] ?? [] as $deviceType => $longUrl) { $deviceTypesToRemove[] = DeviceType::from($deviceType);
$pairsToKeep[$deviceType] = self::fromRawTypeAndLongUrl($deviceType, $longUrl); } else {
$pairsToKeep[$deviceType] = self::fromRawTypeAndLongUrl($deviceType, $longUrl);
}
} }
return [$pairsToKeep, $deviceTypesToRemove]; return [$pairsToKeep, $deviceTypesToRemove];

View file

@ -10,10 +10,10 @@ use Shlinkio\Shlink\Core\Model\DeviceType;
use function array_keys; use function array_keys;
use function array_values; use function array_values;
use function Functional\every;
use function is_array; use function is_array;
use function Shlinkio\Shlink\Core\contains; use function Shlinkio\Shlink\Core\contains;
use function Shlinkio\Shlink\Core\enumValues; use function Shlinkio\Shlink\Core\enumValues;
use function Shlinkio\Shlink\Core\every;
class DeviceLongUrlsValidator extends AbstractValidator class DeviceLongUrlsValidator extends AbstractValidator
{ {

View file

@ -18,7 +18,7 @@ use Shlinkio\Shlink\Rest\ApiKey\Spec\WithApiKeySpecsEnsuringJoin;
use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Entity\ApiKey;
use function array_map; use function array_map;
use function Functional\each; use function array_walk;
use function Shlinkio\Shlink\Core\camelCaseToSnakeCase; use function Shlinkio\Shlink\Core\camelCaseToSnakeCase;
use const PHP_INT_MAX; use const PHP_INT_MAX;
@ -95,7 +95,8 @@ class TagRepository extends EntitySpecificationRepository implements TagReposito
$nonBotVisitsSubQb = $buildVisitsSubQb(true, 'non_bot_visits'); $nonBotVisitsSubQb = $buildVisitsSubQb(true, 'non_bot_visits');
// Apply API key specification to all sub-queries // Apply API key specification to all sub-queries
each([$tagsSubQb, $allVisitsSubQb, $nonBotVisitsSubQb], $applyApiKeyToNativeQb); $queryBuilders = [$tagsSubQb, $allVisitsSubQb, $nonBotVisitsSubQb];
array_walk($queryBuilders, $applyApiKeyToNativeQb);
// A native query builder needs to be used here, because DQL and ORM query builders do not support // A native query builder needs to be used here, because DQL and ORM query builders do not support
// sub-queries at "from" and "join" level. // sub-queries at "from" and "join" level.

View file

@ -27,9 +27,8 @@ use Shlinkio\Shlink\Core\Visit\Model\Visitor;
use Shlinkio\Shlink\Core\Visit\Transformer\OrphanVisitDataTransformer; use Shlinkio\Shlink\Core\Visit\Transformer\OrphanVisitDataTransformer;
use Throwable; use Throwable;
use function array_walk;
use function count; use function count;
use function Functional\each;
use function Functional\noop;
class NotifyVisitToRabbitMqTest extends TestCase class NotifyVisitToRabbitMqTest extends TestCase
{ {
@ -77,7 +76,7 @@ class NotifyVisitToRabbitMqTest extends TestCase
{ {
$visitId = '123'; $visitId = '123';
$this->em->expects($this->once())->method('find')->with(Visit::class, $visitId)->willReturn($visit); $this->em->expects($this->once())->method('find')->with(Visit::class, $visitId)->willReturn($visit);
each($expectedChannels, function (string $method): void { array_walk($expectedChannels, function (string $method): void {
$this->updatesGenerator->expects($this->once())->method($method)->with( $this->updatesGenerator->expects($this->once())->method($method)->with(
$this->isInstanceOf(Visit::class), $this->isInstanceOf(Visit::class),
)->willReturn(Update::forTopicAndPayload('', [])); )->willReturn(Update::forTopicAndPayload('', []));
@ -153,7 +152,7 @@ class NotifyVisitToRabbitMqTest extends TestCase
yield 'legacy non-orphan visit' => [ yield 'legacy non-orphan visit' => [
true, true,
$visit = Visit::forValidShortUrl(ShortUrl::withLongUrl('https://longUrl'), Visitor::emptyInstance()), $visit = Visit::forValidShortUrl(ShortUrl::withLongUrl('https://longUrl'), Visitor::emptyInstance()),
noop(...), static fn () => null,
function (MockObject & PublishingHelperInterface $helper) use ($visit): void { function (MockObject & PublishingHelperInterface $helper) use ($visit): void {
$helper->method('publishUpdate')->with(self::callback(function (Update $update) use ($visit): bool { $helper->method('publishUpdate')->with(self::callback(function (Update $update) use ($visit): bool {
$payload = $update->payload; $payload = $update->payload;
@ -170,7 +169,7 @@ class NotifyVisitToRabbitMqTest extends TestCase
yield 'legacy orphan visit' => [ yield 'legacy orphan visit' => [
true, true,
Visit::forBasePath(Visitor::emptyInstance()), Visit::forBasePath(Visitor::emptyInstance()),
noop(...), static fn () => null,
function (MockObject & PublishingHelperInterface $helper): void { function (MockObject & PublishingHelperInterface $helper): void {
$helper->method('publishUpdate')->with(self::callback(function (Update $update): bool { $helper->method('publishUpdate')->with(self::callback(function (Update $update): bool {
$payload = $update->payload; $payload = $update->payload;

View file

@ -32,8 +32,8 @@ use stdClass;
use Symfony\Component\Console\Style\StyleInterface; use Symfony\Component\Console\Style\StyleInterface;
use function count; use function count;
use function Functional\some;
use function Shlinkio\Shlink\Core\contains; use function Shlinkio\Shlink\Core\contains;
use function Shlinkio\Shlink\Core\some;
use function sprintf; use function sprintf;
use function str_contains; use function str_contains;

View file

@ -20,8 +20,8 @@ use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl;
use Shlinkio\Shlink\Importer\Sources\ImportSource; use Shlinkio\Shlink\Importer\Sources\ImportSource;
use function array_map; use function array_map;
use function Functional\every;
use function range; use function range;
use function Shlinkio\Shlink\Core\every;
use function strlen; use function strlen;
use function strtolower; use function strtolower;

View file

@ -16,9 +16,6 @@ use Psr\Http\Server\RequestHandlerInterface;
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
use Shlinkio\Shlink\Core\ShortUrl\Middleware\TrimTrailingSlashMiddleware; use Shlinkio\Shlink\Core\ShortUrl\Middleware\TrimTrailingSlashMiddleware;
use function Functional\compose;
use function Functional\const_function;
class TrimTrailingSlashMiddlewareTest extends TestCase class TrimTrailingSlashMiddlewareTest extends TestCase
{ {
private MockObject & RequestHandlerInterface $requestHandler; private MockObject & RequestHandlerInterface $requestHandler;
@ -34,7 +31,10 @@ class TrimTrailingSlashMiddlewareTest extends TestCase
ServerRequestInterface $inputRequest, ServerRequestInterface $inputRequest,
callable $assertions, callable $assertions,
): void { ): void {
$arg = compose($assertions, const_function(true)); $arg = static function (...$args) use ($assertions): bool {
$assertions(...$args);
return true;
};
$this->requestHandler->expects($this->once())->method('handle')->with($this->callback($arg))->willReturn( $this->requestHandler->expects($this->once())->method('handle')->with($this->callback($arg))->willReturn(
new Response(), new Response(),
); );

View file

@ -4,8 +4,9 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Rest; namespace Shlinkio\Shlink\Rest;
use function array_filter;
use function array_map; use function array_map;
use function Functional\first; use function reset;
use function Shlinkio\Shlink\Config\loadConfigFromGlob; use function Shlinkio\Shlink\Config\loadConfigFromGlob;
use function sprintf; use function sprintf;
@ -34,8 +35,9 @@ class ConfigProvider
private static function buildUnversionedHealthRouteFromExistingRoutes(array $routes): ?array private static function buildUnversionedHealthRouteFromExistingRoutes(array $routes): ?array
{ {
$healthRoute = first($routes, fn (array $route) => $route['path'] === '/health'); $healthRoutes = array_filter($routes, fn (array $route) => $route['path'] === '/health');
if ($healthRoute === null) { $healthRoute = reset($healthRoutes);
if ($healthRoute === false) {
return null; return null;
} }

View file

@ -15,8 +15,8 @@ use Shlinkio\Shlink\Core\Exception\TagConflictException;
use Shlinkio\Shlink\Core\Exception\TagNotFoundException; use Shlinkio\Shlink\Core\Exception\TagNotFoundException;
use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Exception\ValidationException;
use function end;
use function explode; use function explode;
use function Functional\last;
/** @deprecated */ /** @deprecated */
class BackwardsCompatibleProblemDetailsException extends RuntimeException implements ProblemDetailsExceptionInterface class BackwardsCompatibleProblemDetailsException extends RuntimeException implements ProblemDetailsExceptionInterface
@ -77,7 +77,9 @@ class BackwardsCompatibleProblemDetailsException extends RuntimeException implem
private function remapType(string $wrappedType): string private function remapType(string $wrappedType): string
{ {
$lastSegment = last(explode('/', $wrappedType)); $segments = explode('/', $wrappedType);
$lastSegment = end($segments);
return match ($lastSegment) { return match ($lastSegment) {
ValidationException::ERROR_CODE => 'INVALID_ARGUMENT', ValidationException::ERROR_CODE => 'INVALID_ARGUMENT',
DeleteShortUrlException::ERROR_CODE => 'INVALID_SHORT_URL_DELETION', DeleteShortUrlException::ERROR_CODE => 'INVALID_SHORT_URL_DELETION',