mirror of
https://github.com/shlinkio/shlink.git
synced 2024-10-22 20:25:35 +03:00
Removed more functional-php usages
This commit is contained in:
parent
549c6605f0
commit
bff4bd12ae
20 changed files with 156 additions and 91 deletions
|
@ -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",
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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)),
|
}
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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(...));
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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}',
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
|
|
Loading…
Reference in a new issue