Create MapperBuilder delegator that decorates builder with cache

This commit is contained in:
Alejandro Celaya
2026-04-17 18:19:13 +02:00
parent daa3d0ae00
commit 218c064cb4
19 changed files with 122 additions and 37 deletions
+1 -1
View File
@@ -42,7 +42,7 @@
"pagerfanta/core": "^3.8",
"ramsey/uuid": "^4.7",
"shlinkio/doctrine-specification": "^2.2",
"shlinkio/shlink-common": "^8.1.0",
"shlinkio/shlink-common": "dev-main#02bc6d4 as 8.2.0",
"shlinkio/shlink-config": "^4.1.0",
"shlinkio/shlink-event-dispatcher": "^4.4.0",
"shlinkio/shlink-importer": "^5.7.0",
@@ -17,8 +17,8 @@ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use function Shlinkio\Shlink\Common\normalizeOptionalDate;
use function Shlinkio\Shlink\Core\arrayToString;
use function Shlinkio\Shlink\Core\normalizeOptionalDate;
use function sprintf;
#[AsCommand(
@@ -15,8 +15,8 @@ use Symfony\Component\Console\Style\SymfonyStyle;
use Throwable;
use function Shlinkio\Shlink\Common\buildDateRange;
use function Shlinkio\Shlink\Common\normalizeOptionalDate;
use function Shlinkio\Shlink\Core\dateRangeToHumanFriendly;
use function Shlinkio\Shlink\Core\normalizeOptionalDate;
use function sprintf;
#[AsCommand(
+1 -1
View File
@@ -7,7 +7,7 @@ namespace Shlinkio\Shlink\CLI\Input;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;
use function Shlinkio\Shlink\Core\normalizeOptionalDate;
use function Shlinkio\Shlink\Common\normalizeOptionalDate;
use function sprintf;
final class InputUtils
+1 -1
View File
@@ -8,7 +8,7 @@ use Shlinkio\Shlink\Common\Util\DateRange;
use Symfony\Component\Console\Attribute\Option;
use function Shlinkio\Shlink\Common\buildDateRange;
use function Shlinkio\Shlink\Core\normalizeOptionalDate;
use function Shlinkio\Shlink\Common\normalizeOptionalDate;
class VisitsListInput
{
@@ -23,9 +23,9 @@ use function implode;
use function is_numeric;
use function max;
use function min;
use function Shlinkio\Shlink\Common\normalizeDate;
use function Shlinkio\Shlink\Core\ArrayUtils\map;
use function Shlinkio\Shlink\Core\enumValues;
use function Shlinkio\Shlink\Core\normalizeDate;
use function sprintf;
use function str_pad;
use function strlen;
@@ -20,7 +20,7 @@ use Shlinkio\Shlink\Core\RedirectRule\Model\RedirectConditionType;
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
use Symfony\Component\Console\Style\StyleInterface;
use function Shlinkio\Shlink\Core\normalizeDate;
use function Shlinkio\Shlink\Common\normalizeDate;
use function sprintf;
#[AllowMockObjectsWithoutExpectations]
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core;
use CuyZ\Valinor\MapperBuilder;
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
use Laminas\ServiceManager\Factory\InvokableFactory;
use Psr\EventDispatcher\EventDispatcherInterface;
@@ -21,6 +22,13 @@ use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;
return [
'dependencies' => [
'delegators' => [
MapperBuilder::class => [
ObjectMapper\MapperBuilderWithCacheDelegatorFactory::class,
],
],
'factories' => [
ErrorHandler\NotFoundTypeResolverMiddleware::class => ConfigAbstractFactory::class,
ErrorHandler\NotFoundTrackerMiddleware::class => ConfigAbstractFactory::class,
+1 -21
View File
@@ -6,7 +6,6 @@ namespace Shlinkio\Shlink\Core;
use BackedEnum;
use Cake\Chronos\Chronos;
use DateTimeInterface;
use Doctrine\ORM\Mapping\Builder\FieldBuilder;
use donatj\UserAgent\UserAgent;
use donatj\UserAgent\UserAgentParser;
@@ -24,12 +23,12 @@ use function array_keys;
use function array_map;
use function array_pad;
use function array_reduce;
use function date_default_timezone_get;
use function explode;
use function implode;
use function is_array;
use function print_r;
use function Shlinkio\Shlink\Common\buildDateRange;
use function Shlinkio\Shlink\Common\normalizeOptionalDate;
use function Shlinkio\Shlink\Core\ArrayUtils\map;
use function sprintf;
use function str_repeat;
@@ -82,25 +81,6 @@ function dateRangeToHumanFriendly(DateRange|null $dateRange): string
};
}
/**
* @return ($date is null ? null : Chronos)
*/
function normalizeOptionalDate(string|DateTimeInterface|Chronos|null $date): Chronos|null
{
$parsedDate = match (true) {
$date === null || $date instanceof Chronos => $date,
$date instanceof DateTimeInterface => Chronos::instance($date),
default => Chronos::parse($date),
};
return $parsedDate?->setTimezone(date_default_timezone_get());
}
function normalizeDate(string|DateTimeInterface|Chronos $date): Chronos
{
return normalizeOptionalDate($date);
}
function normalizeLocale(string $locale): string
{
return trim(strtolower(str_replace('_', '-', $locale)));
@@ -23,7 +23,7 @@ use Symfony\Component\Console\Style\OutputStyle;
use Symfony\Component\Console\Style\StyleInterface;
use Throwable;
use function Shlinkio\Shlink\Core\normalizeDate;
use function Shlinkio\Shlink\Common\normalizeDate;
use function sprintf;
readonly class ImportedLinksProcessor implements ImportedLinksProcessorInterface
@@ -15,8 +15,8 @@ use Shlinkio\Shlink\Importer\Model\ImportedShlinkRedirectRule;
use Shlinkio\Shlink\Importer\Model\ImportedShlinkVisit;
use function count;
use function Shlinkio\Shlink\Common\normalizeDate;
use function Shlinkio\Shlink\Core\ArrayUtils\map;
use function Shlinkio\Shlink\Core\normalizeDate;
use function sprintf;
final readonly class ShortUrlImporting
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\ObjectMapper;
use CuyZ\Valinor\Cache\FileSystemCache;
use CuyZ\Valinor\Cache\FileWatchingCache;
use CuyZ\Valinor\MapperBuilder;
use Psr\Container\ContainerInterface;
use Shlinkio\Shlink\Core\Config\EnvVars;
/**
* Decorates a MapperBuilder with cache based on EnvVars::isProdEnv()
*/
class MapperBuilderWithCacheDelegatorFactory
{
public function __invoke(ContainerInterface $container, string $name, callable $callback): MapperBuilder
{
/** @var MapperBuilder $mapperBuilder */
$mapperBuilder = $callback();
$cache = new FileSystemCache('data/cache/valinor');
if (! EnvVars::isProdEnv()) {
$cache = new FileWatchingCache($cache);
}
return $mapperBuilder->withCache($cache);
}
}
@@ -14,11 +14,11 @@ use Shlinkio\Shlink\Core\Util\IpAddressUtils;
use Shlinkio\Shlink\Importer\Model\ImportedShlinkRedirectCondition;
use function array_key_exists;
use function Shlinkio\Shlink\Common\normalizeDate;
use function Shlinkio\Shlink\Core\acceptLanguageToLocales;
use function Shlinkio\Shlink\Core\ArrayUtils\some;
use function Shlinkio\Shlink\Core\geolocationFromRequest;
use function Shlinkio\Shlink\Core\ipAddressFromRequest;
use function Shlinkio\Shlink\Core\normalizeDate;
use function Shlinkio\Shlink\Core\normalizeLocale;
use function Shlinkio\Shlink\Core\splitLocale;
use function sprintf;
+2 -2
View File
@@ -29,9 +29,9 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey;
use function array_map;
use function count;
use function Shlinkio\Shlink\Common\normalizeDate;
use function Shlinkio\Shlink\Common\normalizeOptionalDate;
use function Shlinkio\Shlink\Core\generateRandomShortCode;
use function Shlinkio\Shlink\Core\normalizeDate;
use function Shlinkio\Shlink\Core\normalizeOptionalDate;
use function sprintf;
class ShortUrl extends AbstractEntity
@@ -11,10 +11,10 @@ use Shlinkio\Shlink\Core\ShortUrl\Helper\TitleResolutionModelInterface;
use Shlinkio\Shlink\Core\ShortUrl\Model\Validation\ShortUrlInputFilter;
use Shlinkio\Shlink\Rest\Entity\ApiKey;
use function Shlinkio\Shlink\Common\normalizeOptionalDate;
use function Shlinkio\Shlink\Core\getNonEmptyOptionalValueFromInputFilter;
use function Shlinkio\Shlink\Core\getOptionalBoolFromInputFilter;
use function Shlinkio\Shlink\Core\getOptionalIntFromInputFilter;
use function Shlinkio\Shlink\Core\normalizeOptionalDate;
use const Shlinkio\Shlink\DEFAULT_SHORT_CODES_LENGTH;
@@ -10,9 +10,9 @@ use Shlinkio\Shlink\Core\ShortUrl\Helper\TitleResolutionModelInterface;
use Shlinkio\Shlink\Core\ShortUrl\Model\Validation\ShortUrlInputFilter;
use function array_key_exists;
use function Shlinkio\Shlink\Common\normalizeOptionalDate;
use function Shlinkio\Shlink\Core\getOptionalBoolFromInputFilter;
use function Shlinkio\Shlink\Core\getOptionalIntFromInputFilter;
use function Shlinkio\Shlink\Core\normalizeOptionalDate;
final readonly class ShortUrlEdition implements TitleResolutionModelInterface
{
@@ -10,7 +10,7 @@ use Shlinkio\Shlink\Core\Model\Ordering;
use Shlinkio\Shlink\Core\ShortUrl\Model\Validation\ShortUrlsParamsInputFilter;
use function Shlinkio\Shlink\Common\buildDateRange;
use function Shlinkio\Shlink\Core\normalizeOptionalDate;
use function Shlinkio\Shlink\Common\normalizeOptionalDate;
/**
* Represents all the params that can be used to filter a list of short URLs
+1 -1
View File
@@ -15,8 +15,8 @@ use Shlinkio\Shlink\Core\Visit\Model\VisitType;
use Shlinkio\Shlink\Importer\Model\ImportedShlinkOrphanVisit;
use Shlinkio\Shlink\Importer\Model\ImportedShlinkVisit;
use function Shlinkio\Shlink\Common\normalizeDate;
use function Shlinkio\Shlink\Core\isCrawler;
use function Shlinkio\Shlink\Core\normalizeDate;
class Visit extends AbstractEntity implements JsonSerializable
{
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\Core\ObjectMapper;
use CuyZ\Valinor\Cache\FileSystemCache;
use CuyZ\Valinor\Cache\FileWatchingCache;
use CuyZ\Valinor\Library\Settings;
use CuyZ\Valinor\MapperBuilder;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestWith;
use PHPUnit\Framework\MockObject\Stub;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use ReflectionObject;
use Shlinkio\Shlink\Core\Config\EnvVars;
use Shlinkio\Shlink\Core\ObjectMapper\MapperBuilderWithCacheDelegatorFactory;
use function putenv;
class MapperBuilderWithCacheDelegatorFactoryTest extends TestCase
{
private ContainerInterface & Stub $container;
private MapperBuilder $baseBuilder;
protected function setUp(): void
{
$this->container = $this->createStub(ContainerInterface::class);
$this->baseBuilder = new MapperBuilder();
}
protected function tearDown(): void
{
putenv(EnvVars::APP_ENV->value);
}
#[Test]
#[TestWith(['dev'])]
#[TestWith(['test'])]
public function watchingFileSystemCacheIsSetForNonProdEnvs(string $env): void
{
putenv(EnvVars::APP_ENV->value . '=' . $env);
$builder = new MapperBuilderWithCacheDelegatorFactory()($this->container, '', fn () => $this->baseBuilder);
$reflection = new ReflectionObject($builder);
/** @var Settings $settings */
$settings = $reflection->getProperty('settings')->getValue($builder);
self::assertNotSame($builder, $this->baseBuilder);
self::assertInstanceOf(FileWatchingCache::class, $settings->cache);
}
#[Test]
public function regularCacheIsSetForProdEnv(): void
{
putenv(EnvVars::APP_ENV->value . '=prod');
$builder = new MapperBuilderWithCacheDelegatorFactory()($this->container, '', fn () => $this->baseBuilder);
$reflection = new ReflectionObject($builder);
/** @var Settings $settings */
$settings = $reflection->getProperty('settings')->getValue($builder);
self::assertNotSame($builder, $this->baseBuilder);
self::assertInstanceOf(FileSystemCache::class, $settings->cache);
}
}