-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement dynamic URL preloading in cache strategy
This change introduces dynamic URL preloading in cache strategy. It modifies the resource cache to accept string aliases representing predefined URL generators. These generators can then produce URLs to be included in caching. This enhancement allows for more flexible and dynamic URL caching for applications.
- Loading branch information
Showing
29 changed files
with
592 additions
and
94 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SpomkyLabs\PwaBundle\Attribute; | ||
|
||
use Attribute; | ||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; | ||
|
||
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] | ||
final readonly class PreloadUrl | ||
{ | ||
/** | ||
* @param array<string, mixed> $params | ||
*/ | ||
public function __construct( | ||
public string $alias, | ||
public array $params = [], | ||
public int $pathTypeReference = UrlGeneratorInterface::ABSOLUTE_PATH, | ||
) { | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SpomkyLabs\PwaBundle\Attribute; | ||
|
||
use ReflectionAttribute; | ||
use ReflectionClass; | ||
use ReflectionMethod; | ||
use RuntimeException; | ||
use SpomkyLabs\PwaBundle\CachingStrategy\PreloadUrlsTagGenerator; | ||
use Symfony\Component\DependencyInjection\ChildDefinition; | ||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
use Symfony\Component\Routing\Attribute\Route; | ||
use Throwable; | ||
use function array_key_exists; | ||
use function is_string; | ||
|
||
final class PreloadUrlCompilerPass implements CompilerPassInterface | ||
{ | ||
public function process(ContainerBuilder $container): void | ||
{ | ||
foreach ($this->findAllTaggedRoutes($container) as $alias => $urls) { | ||
$definitionId = sprintf('spomky_labs_pwa.preload_urls_tag_generator.%s', $alias); | ||
$definition = new ChildDefinition(PreloadUrlsTagGenerator::class); | ||
$definition | ||
->setArguments([ | ||
'$alias' => $alias, | ||
'$urls' => $urls, | ||
]) | ||
->addTag('spomky_labs_pwa.preload_urls_generator') | ||
; | ||
$container->setDefinition($definitionId, $definition); | ||
} | ||
} | ||
|
||
/** | ||
* @return array<string, array{route: string, alias: string, params?: array<string, mixed>, pathTypeReference: int}[]> | ||
*/ | ||
private function findAllTaggedRoutes(ContainerBuilder $container): array | ||
{ | ||
$routes = []; | ||
$controllers = $container->findTaggedServiceIds('controller.service_arguments'); | ||
foreach (array_keys($controllers) as $controller) { | ||
if (! is_string($controller) || ! class_exists($controller)) { | ||
continue; | ||
} | ||
$reflectionClass = new ReflectionClass($controller); | ||
$result = $this->findAllPreloadAttributesForClass($reflectionClass); | ||
foreach ($result as $route) { | ||
if (! array_key_exists($route['alias'], $routes)) { | ||
$routes[$route['alias']] = []; | ||
} | ||
$routes[$route['alias']][] = $route; | ||
} | ||
} | ||
|
||
return $routes; | ||
} | ||
|
||
/** | ||
* @param ReflectionClass<object> $reflectionClass | ||
* @return iterable<array{alias: string, route: string, params: array<string, mixed>, pathTypeReference: int}> | ||
*/ | ||
private function findAllPreloadAttributesForClass(ReflectionClass $reflectionClass): iterable | ||
{ | ||
foreach ($reflectionClass->getAttributes(PreloadUrl::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) { | ||
try { | ||
/** @var PreloadUrl $preloadAttribute */ | ||
$preloadAttribute = $attribute->newInstance(); | ||
yield from $this->findAllRoutesToPreload( | ||
$preloadAttribute, | ||
$reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) | ||
); | ||
} catch (Throwable $e) { | ||
throw new RuntimeException(sprintf('Unable to create attribute instance: %s', $e->getMessage()), 0, $e); | ||
} | ||
} | ||
foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { | ||
foreach ($method->getAttributes(PreloadUrl::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) { | ||
try { | ||
/** @var PreloadUrl $preloadAttribute */ | ||
$preloadAttribute = $attribute->newInstance(); | ||
yield from $this->findAllRoutesForMethod($preloadAttribute, $method); | ||
} catch (Throwable $e) { | ||
throw new RuntimeException(sprintf( | ||
'Unable to create attribute instance: %s', | ||
$e->getMessage() | ||
), 0, $e); | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @param array<ReflectionMethod> $methods | ||
* @return iterable<array{alias: string, route: string, params: array<string, mixed>, pathTypeReference: int}> | ||
*/ | ||
private function findAllRoutesToPreload(PreloadUrl $preloadAttribute, array $methods): iterable | ||
{ | ||
foreach ($methods as $method) { | ||
yield from $this->findAllRoutesForMethod($preloadAttribute, $method); | ||
} | ||
} | ||
|
||
/** | ||
* @return iterable<array{alias: string, route: string, params: array<string, mixed>, pathTypeReference: int}> | ||
*/ | ||
private function findAllRoutesForMethod(PreloadUrl $preloadAttribute, ReflectionMethod $method): iterable | ||
{ | ||
foreach ($method->getAttributes(Route::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) { | ||
try { | ||
/** @var Route $routeAttribute */ | ||
$routeAttribute = $attribute->newInstance(); | ||
$routeName = $routeAttribute->getName(); | ||
if ($routeName === null) { | ||
continue; | ||
} | ||
yield [ | ||
'alias' => $preloadAttribute->alias, | ||
'route' => $routeName, | ||
'params' => $preloadAttribute->params, | ||
'pathTypeReference' => $preloadAttribute->pathTypeReference, | ||
]; | ||
} catch (Throwable) { | ||
continue; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SpomkyLabs\PwaBundle\CachingStrategy; | ||
|
||
use SpomkyLabs\PwaBundle\Dto\Url; | ||
|
||
interface PreloadUrlsGeneratorInterface | ||
{ | ||
public function getAlias(): string; | ||
|
||
/** | ||
* @return iterable<Url|string> | ||
*/ | ||
public function generateUrls(): iterable; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SpomkyLabs\PwaBundle\CachingStrategy; | ||
|
||
use InvalidArgumentException; | ||
use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; | ||
use function array_key_exists; | ||
|
||
final class PreloadUrlsGeneratorManager | ||
{ | ||
/** | ||
* @var array<string, PreloadUrlsGeneratorInterface> | ||
*/ | ||
private array $generators = []; | ||
|
||
/** | ||
* @param PreloadUrlsGeneratorInterface[] $generators | ||
*/ | ||
public function __construct( | ||
#[TaggedIterator('spomky_labs_pwa.preload_urls_generator')] | ||
iterable $generators | ||
) { | ||
foreach ($generators as $generator) { | ||
$this->add($generator); | ||
} | ||
} | ||
|
||
public function add(PreloadUrlsGeneratorInterface $generator, PreloadUrlsGeneratorInterface ...$generators): void | ||
{ | ||
$this->generators[$generator->getAlias()] = $generator; | ||
foreach ($generators as $value) { | ||
$this->generators[$generator->getAlias()] = $value; | ||
} | ||
} | ||
|
||
public function get(string $alias): PreloadUrlsGeneratorInterface | ||
{ | ||
if (! array_key_exists($alias, $this->generators)) { | ||
throw new InvalidArgumentException(sprintf('The generator with alias "%s" does not exist.', $alias)); | ||
} | ||
return $this->generators[$alias]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SpomkyLabs\PwaBundle\CachingStrategy; | ||
|
||
use SpomkyLabs\PwaBundle\Dto\Url; | ||
|
||
final readonly class PreloadUrlsTagGenerator implements PreloadUrlsGeneratorInterface | ||
{ | ||
/** | ||
* @var array<Url> | ||
*/ | ||
private array $urls; | ||
|
||
/** | ||
* @param array{route: string, params: array<string, mixed>, pathTypeReference: int}[] $urls | ||
*/ | ||
public function __construct( | ||
private string $alias, | ||
array $urls | ||
) { | ||
$this->urls = array_map( | ||
static fn (array $url): Url => Url::create($url['route'], $url['params'], $url['pathTypeReference']), | ||
$urls | ||
); | ||
} | ||
|
||
public function getAlias(): string | ||
{ | ||
return $this->alias; | ||
} | ||
|
||
/** | ||
* @return iterable<Url> | ||
*/ | ||
public function generateUrls(): iterable | ||
{ | ||
return $this->urls; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.