Skip to content

Commit

Permalink
Features/better cache mgmt (#135)
Browse files Browse the repository at this point in the history
Better Cache Strategy Support
  • Loading branch information
Spomky authored Mar 17, 2024
1 parent a7eaba6 commit ade7573
Show file tree
Hide file tree
Showing 56 changed files with 1,319 additions and 889 deletions.
2 changes: 1 addition & 1 deletion assets/src/connection-status_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default class extends Controller {
});
}
dispatchEvent = (name, payload) => {
this.dispatch(name, { detail: payload, prefix: 'connection-status' });
this.dispatch(name, { detail: payload });
}

statusChanged = (data) => {
Expand Down
2 changes: 1 addition & 1 deletion assets/src/sync-broadcast_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default class extends Controller {
}

dispatchEvent = (name, payload) => {
this.dispatch(name, { detail: payload, prefix: 'connection-status' });
this.dispatch(name, { detail: payload });
}

messageReceived = async (event) => {
Expand Down
104 changes: 82 additions & 22 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
parameters:
ignoreErrors:
-
message: "#^Parameter \\#1 \\$name of static method SpomkyLabs\\\\PwaBundle\\\\CachingStrategy\\\\WorkboxCacheStrategy\\:\\:create\\(\\) expects string, string\\|null given\\.$#"
count: 1
path: src/CachingStrategy/AssetCache.php

-
message: "#^Parameter \\#8 \\$preloadUrls of static method SpomkyLabs\\\\PwaBundle\\\\CachingStrategy\\\\WorkboxCacheStrategy\\:\\:create\\(\\) expects array\\<string\\>, mixed given\\.$#"
count: 1
path: src/CachingStrategy/AssetCache.php

-
message: "#^Parameter \\#1 \\$value of function count expects array\\|Countable, mixed given\\.$#"
count: 1
path: src/CachingStrategy/FontCache.php

-
message: "#^Parameter \\#8 \\$preloadUrls of static method SpomkyLabs\\\\PwaBundle\\\\CachingStrategy\\\\WorkboxCacheStrategy\\:\\:create\\(\\) expects array\\<string\\>, mixed given\\.$#"
count: 1
path: src/CachingStrategy/FontCache.php

-
message: "#^Parameter \\#8 \\$preloadUrls of static method SpomkyLabs\\\\PwaBundle\\\\CachingStrategy\\\\WorkboxCacheStrategy\\:\\:create\\(\\) expects array\\<string\\>, mixed given\\.$#"
count: 1
path: src/CachingStrategy/ResourceCaches.php

-
message: "#^Part \\$this\\-\\>options\\['networkTimeoutSeconds'\\] \\(mixed\\) of encapsed string cannot be cast to string\\.$#"
count: 1
path: src/CachingStrategy/WorkboxCacheStrategy.php

-
message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#"
count: 1
Expand Down Expand Up @@ -91,22 +121,22 @@ parameters:
path: src/Dto/BackgroundSync.php

-
message: "#^Class SpomkyLabs\\\\PwaBundle\\\\Dto\\\\BackgroundSync has an uninitialized property \\$maxRetentionTime\\. Give it default value or assign it in the constructor\\.$#"
message: "#^Class SpomkyLabs\\\\PwaBundle\\\\Dto\\\\BackgroundSync has an uninitialized property \\$matchCallback\\. Give it default value or assign it in the constructor\\.$#"
count: 1
path: src/Dto/BackgroundSync.php

-
message: "#^Class SpomkyLabs\\\\PwaBundle\\\\Dto\\\\BackgroundSync has an uninitialized property \\$method\\. Give it default value or assign it in the constructor\\.$#"
message: "#^Class SpomkyLabs\\\\PwaBundle\\\\Dto\\\\BackgroundSync has an uninitialized property \\$maxRetentionTime\\. Give it default value or assign it in the constructor\\.$#"
count: 1
path: src/Dto/BackgroundSync.php

-
message: "#^Class SpomkyLabs\\\\PwaBundle\\\\Dto\\\\BackgroundSync has an uninitialized property \\$queueName\\. Give it default value or assign it in the constructor\\.$#"
message: "#^Class SpomkyLabs\\\\PwaBundle\\\\Dto\\\\BackgroundSync has an uninitialized property \\$method\\. Give it default value or assign it in the constructor\\.$#"
count: 1
path: src/Dto/BackgroundSync.php

-
message: "#^Class SpomkyLabs\\\\PwaBundle\\\\Dto\\\\BackgroundSync has an uninitialized property \\$regex\\. Give it default value or assign it in the constructor\\.$#"
message: "#^Class SpomkyLabs\\\\PwaBundle\\\\Dto\\\\BackgroundSync has an uninitialized property \\$queueName\\. Give it default value or assign it in the constructor\\.$#"
count: 1
path: src/Dto/BackgroundSync.php

Expand Down Expand Up @@ -176,12 +206,12 @@ parameters:
path: src/Dto/Manifest.php

-
message: "#^Class SpomkyLabs\\\\PwaBundle\\\\Dto\\\\PageCache has an uninitialized property \\$cacheName\\. Give it default value or assign it in the constructor\\.$#"
message: "#^Class SpomkyLabs\\\\PwaBundle\\\\Dto\\\\PageCache has an uninitialized property \\$matchCallback\\. Give it default value or assign it in the constructor\\.$#"
count: 1
path: src/Dto/PageCache.php

-
message: "#^Class SpomkyLabs\\\\PwaBundle\\\\Dto\\\\PageCache has an uninitialized property \\$regex\\. Give it default value or assign it in the constructor\\.$#"
message: "#^PHPDoc tag @var for property SpomkyLabs\\\\PwaBundle\\\\Dto\\\\PageCache\\:\\:\\$cacheableResponseHeaders with type array\\<string, string\\>\\|null is not subtype of native type array\\.$#"
count: 1
path: src/Dto/PageCache.php

Expand Down Expand Up @@ -346,7 +376,7 @@ parameters:
path: src/Dto/Workbox.php

-
message: "#^Class SpomkyLabs\\\\PwaBundle\\\\Dto\\\\Workbox has an uninitialized property \\$pageCaches\\. Give it default value or assign it in the constructor\\.$#"
message: "#^Class SpomkyLabs\\\\PwaBundle\\\\Dto\\\\Workbox has an uninitialized property \\$resourceCaches\\. Give it default value or assign it in the constructor\\.$#"
count: 1
path: src/Dto/Workbox.php

Expand Down Expand Up @@ -492,14 +522,19 @@ parameters:

-
message: "#^Anonymous function should return array but returns mixed\\.$#"
count: 6
count: 8
path: src/Resources/config/definition/service_worker.php

-
message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:children\\(\\)\\.$#"
count: 1
path: src/Resources/config/definition/service_worker.php

-
message: "#^Cannot access an offset on mixed\\.$#"
count: 1
path: src/Resources/config/definition/service_worker.php

-
message: "#^Cannot access offset 'asset_cache' on mixed\\.$#"
count: 2
Expand Down Expand Up @@ -560,11 +595,31 @@ parameters:
count: 1
path: src/Resources/config/definition/service_worker.php

-
message: "#^Cannot access offset 'network_timeout…' on mixed\\.$#"
count: 1
path: src/Resources/config/definition/service_worker.php

-
message: "#^Cannot access offset 'page_cache_name' on mixed\\.$#"
count: 1
path: src/Resources/config/definition/service_worker.php

-
message: "#^Cannot access offset 'resource_caches' on mixed\\.$#"
count: 2
path: src/Resources/config/definition/service_worker.php

-
message: "#^Cannot access offset 'static_regex' on mixed\\.$#"
count: 1
path: src/Resources/config/definition/service_worker.php

-
message: "#^Cannot access offset 'warm_cache_urls' on mixed\\.$#"
count: 1
path: src/Resources/config/definition/service_worker.php

-
message: "#^Strict comparison using \\!\\=\\= between mixed and null will always evaluate to true\\.$#"
count: 3
Expand Down Expand Up @@ -626,24 +681,14 @@ parameters:
path: src/Resources/config/definition/web_client.php

-
message: "#^Parameter \\#6 \\$options of static method SpomkyLabs\\\\PwaBundle\\\\Service\\\\CacheStrategy\\:\\:create\\(\\) expects array\\{maxTimeout\\?\\: int, maxAge\\?\\: int, maxEntries\\?\\: int, warmUrls\\?\\: array\\<string\\>, plugins\\?\\: array\\<string\\>\\}, array\\{maxEntries\\: int\\<0, max\\>, maxAge\\: int, warmUrls\\: mixed\\} given\\.$#"
message: "#^Parameter \\#2 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#"
count: 1
path: src/Service/Rule/AssetCache.php

-
message: "#^Parameter \\#6 \\$options of static method SpomkyLabs\\\\PwaBundle\\\\Service\\\\CacheStrategy\\:\\:create\\(\\) expects array\\{maxTimeout\\?\\: int, maxAge\\?\\: int, maxEntries\\?\\: int, warmUrls\\?\\: array\\<string\\>, plugins\\?\\: array\\<string\\>\\}, array\\{maxAge\\: int\\|null, maxEntries\\: int\\|null\\} given\\.$#"
count: 1
path: src/Service/Rule/GoogleFontCache.php
path: src/ServiceWorkerRule/AppendCacheStrategies.php

-
message: "#^Strict comparison using \\=\\=\\= between int\\<1, max\\> and 0 will always evaluate to false\\.$#"
count: 1
path: src/Service/Rule/OfflineFallback.php

-
message: "#^Parameter \\#6 \\$options of static method SpomkyLabs\\\\PwaBundle\\\\Service\\\\CacheStrategy\\:\\:create\\(\\) expects array\\{maxTimeout\\?\\: int, maxAge\\?\\: int, maxEntries\\?\\: int, warmUrls\\?\\: array\\<string\\>, plugins\\?\\: array\\<string\\>\\}, array\\{maxTimeout\\: int, plugins\\: array\\{0\\: 'CacheableResponsePl…', 1\\?\\: 'BroadcastUpdatePlug…'\\}, warmUrls\\: mixed\\} given\\.$#"
count: 1
path: src/Service/Rule/PageCaches.php
path: src/ServiceWorkerRule/OfflineFallback.php

-
message: "#^Method SpomkyLabs\\\\PwaBundle\\\\SpomkyLabsPwaBundle\\:\\:loadExtension\\(\\) has parameter \\$config with no value type specified in iterable type array\\.$#"
Expand All @@ -658,4 +703,19 @@ parameters:
-
message: "#^Property SpomkyLabs\\\\PwaBundle\\\\Subscriber\\\\PwaDevServerSubscriber\\:\\:\\$jsonOptions type has no value type specified in iterable type array\\.$#"
count: 1
path: src/Subscriber/PwaDevServerSubscriber.php
path: src/Subscriber/PwaDevServerSubscriber.php

-
message: "#^Part \\$broadcastChannel \\(mixed\\) of encapsed string cannot be cast to string\\.$#"
count: 1
path: src/WorkboxPlugin/BackgroundSyncPlugin.php

-
message: "#^Part \\$maxRetentionTime \\(mixed\\) of encapsed string cannot be cast to string\\.$#"
count: 1
path: src/WorkboxPlugin/BackgroundSyncPlugin.php

-
message: "#^Part \\$queueName \\(mixed\\) of encapsed string cannot be cast to string\\.$#"
count: 2
path: src/WorkboxPlugin/BackgroundSyncPlugin.php
84 changes: 84 additions & 0 deletions src/CachingStrategy/AssetCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

namespace SpomkyLabs\PwaBundle\CachingStrategy;

use SpomkyLabs\PwaBundle\Dto\ServiceWorker;
use SpomkyLabs\PwaBundle\Dto\Workbox;
use SpomkyLabs\PwaBundle\WorkboxPlugin\ExpirationPlugin;
use Symfony\Component\AssetMapper\AssetMapperInterface;
use Symfony\Component\AssetMapper\Path\PublicAssetsPathResolverInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Serializer\Encoder\JsonEncode;
use Symfony\Component\Serializer\SerializerInterface;
use function count;
use const JSON_PRETTY_PRINT;
use const JSON_THROW_ON_ERROR;
use const JSON_UNESCAPED_SLASHES;
use const JSON_UNESCAPED_UNICODE;

final readonly class AssetCache implements HasCacheStrategies
{
private int $jsonOptions;

private string $assetPublicPrefix;

private Workbox $workbox;

public function __construct(
ServiceWorker $serviceWorker,
#[Autowire(service: 'asset_mapper.public_assets_path_resolver')]
PublicAssetsPathResolverInterface $publicAssetsPathResolver,
private AssetMapperInterface $assetMapper,
private SerializerInterface $serializer,
#[Autowire('%kernel.debug%')]
bool $debug,
) {
$this->workbox = $serviceWorker->workbox;
$this->assetPublicPrefix = rtrim($publicAssetsPathResolver->resolvePublicPath(''), '/');
$options = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR;
if ($debug === true) {
$options |= JSON_PRETTY_PRINT;
}
$this->jsonOptions = $options;
}

public function getCacheStrategies(): array
{
$urls = json_decode($this->serializer->serialize($this->getAssets(), 'json', [
JsonEncode::OPTIONS => $this->jsonOptions,
]), true);
return [
WorkboxCacheStrategy::create(
$this->workbox->assetCache->cacheName,
CacheStrategy::STRATEGY_CACHE_FIRST,
sprintf("({url}) => url.pathname.startsWith('%s')", $this->assetPublicPrefix),
$this->workbox->enabled && $this->workbox->assetCache->enabled,
true,
null,
[
ExpirationPlugin::create(
count($this->getAssets()) * 2,
$this->workbox->assetCache->maxAgeInSeconds() ?? 60 * 60 * 24 * 365,
),
],
$urls
),
];
}

/**
* @return array<string>
*/
private function getAssets(): array
{
$assets = [];
foreach ($this->assetMapper->allAssets() as $asset) {
if (preg_match($this->workbox->assetCache->regex, $asset->sourcePath) === 1) {
$assets[] = $asset->publicPath;
}
}
return $assets;
}
}
66 changes: 66 additions & 0 deletions src/CachingStrategy/BackgroundSync.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

declare(strict_types=1);

namespace SpomkyLabs\PwaBundle\CachingStrategy;

use SpomkyLabs\PwaBundle\Dto\ServiceWorker;
use SpomkyLabs\PwaBundle\Dto\Workbox;
use SpomkyLabs\PwaBundle\MatchCallbackHandler\MatchCallbackHandler;
use SpomkyLabs\PwaBundle\WorkboxPlugin\BackgroundSyncPlugin;
use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;

final readonly class BackgroundSync implements HasCacheStrategies
{
private Workbox $workbox;

/**
* @param iterable<MatchCallbackHandler> $matchCallbackHandlers
*/
public function __construct(
ServiceWorker $serviceWorker,
#[TaggedIterator('spomky_labs_pwa.match_callback_handler')]
private iterable $matchCallbackHandlers,
) {
$this->workbox = $serviceWorker->workbox;
}

/**
* @return array<CacheStrategy>
*/
public function getCacheStrategies(): array
{
$strategies = [];
foreach ($this->workbox->backgroundSync as $sync) {
$strategies[] = WorkboxCacheStrategy::create(
'BackgroundSync API',
CacheStrategy::STRATEGY_NETWORK_ONLY,
$this->prepareMatchCallback($sync->matchCallback),
$this->workbox->enabled,
true,
null,
[
BackgroundSyncPlugin::create(
$sync->queueName,
$sync->maxRetentionTime,
$sync->forceSyncFallback,
$sync->broadcastChannel
),
]
);
}

return $strategies;
}

private function prepareMatchCallback(string $matchCallback): string
{
foreach ($this->matchCallbackHandlers as $handler) {
if ($handler->supports($matchCallback)) {
return $handler->handle($matchCallback);
}
}

return $matchCallback;
}
}
35 changes: 35 additions & 0 deletions src/CachingStrategy/CacheStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace SpomkyLabs\PwaBundle\CachingStrategy;

abstract readonly class CacheStrategy
{
public const STRATEGY_CACHE_FIRST = 'CacheFirst';

public const STRATEGY_CACHE_ONLY = 'CacheOnly';

public const STRATEGY_NETWORK_FIRST = 'NetworkFirst';

public const STRATEGY_NETWORK_ONLY = 'NetworkOnly';

public const STRATEGY_STALE_WHILE_REVALIDATE = 'StaleWhileRevalidate';

public const STRATEGIES = [
self::STRATEGY_CACHE_FIRST,
self::STRATEGY_CACHE_ONLY,
self::STRATEGY_NETWORK_FIRST,
self::STRATEGY_NETWORK_ONLY,
self::STRATEGY_STALE_WHILE_REVALIDATE,
];

public function __construct(
public string $name,
public bool $enabled,
public bool $requireWorkbox,
) {
}

abstract public function render(string $cacheObjectName, int $jsonOptions = 0): string;
}
Loading

0 comments on commit ade7573

Please sign in to comment.