Skip to content

Commit

Permalink
feat: add LSCachePurging event
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaucau committed Sep 12, 2024
1 parent f65b2b2 commit 0e21077
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,23 @@

use ACPL\FlarumCache\Middleware\AbstractPurgeCacheMiddleware;
use Flarum\User\User;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

class FofMasqueradePurgeCacheMiddleware extends AbstractPurgeCacheMiddleware
{
protected function processPurge(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response,
): ResponseInterface {
// Purge user profile cache when updating FriendsOfFlarum/masquerade fields
protected function preparePurgeData(ServerRequestInterface $request): void
{
if ($this->currentRouteName === 'masquerade.api.configure.save') {
$userID = $this->getRouteParams($request)['id'];
$user = User::find($userID);

return $this->addPurgeParamsToResponse(
$response,
[
"tag=user_$user->id",
"tag=user_$user->username",
"tag=masquerade_$user->id",
],
);
if ($user) {
$this->cachePurger->addPurgeTags([
"user_$user->id",
"user_$user->username",
"masquerade_$user->id",
]);
}
}

return $response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@

namespace ACPL\FlarumCache\Compatibility\v17development\FlarumBlog;

use ACPL\FlarumCache\Event\LSCachePurging;
use ACPL\FlarumCache\Listener\AbstractCachePurgeSubscriber;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Arr;
use V17Development\FlarumBlog\Event\BlogMetaSaving;

class FlarumBlogEventSubscriber extends AbstractCachePurgeSubscriber
{
public function subscribe(Dispatcher $events): void
{
$this->addPurgeListener($events, BlogMetaSaving::class, [$this, 'handle']);
$this->addPurgeListener($events, LSCachePurging::class, [$this, 'handleLSCachePurging']);
}

public function handle(BlogMetaSaving $event): void
Expand All @@ -20,4 +23,19 @@ public function handle(BlogMetaSaving $event): void
"blog_{$event->blogMeta->discussion_id}",

Check failure on line 23 in src/Compatibility/v17development/FlarumBlog/FlarumBlogEventSubscriber.php

View workflow job for this annotation

GitHub Actions / run / PHPStan PHP 8.1

Access to an undefined property V17Development\FlarumBlog\BlogMeta\BlogMeta::$discussion_id.

Check failure on line 23 in src/Compatibility/v17development/FlarumBlog/FlarumBlogEventSubscriber.php

View workflow job for this annotation

GitHub Actions / run / PHPStan PHP 8.2

Access to an undefined property V17Development\FlarumBlog\BlogMeta\BlogMeta::$discussion_id.

Check failure on line 23 in src/Compatibility/v17development/FlarumBlog/FlarumBlogEventSubscriber.php

View workflow job for this annotation

GitHub Actions / run / PHPStan PHP 8.3

Access to an undefined property V17Development\FlarumBlog\BlogMeta\BlogMeta::$discussion_id.
]);
}

/**
* If discussion is detected, also purge blog, because blog is a discussion.
*/
public function handleLSCachePurging(LSCachePurging $event): void
{
if (in_array('index', $event->data['tags'])) {
$this->purger->addPurgeTag('blog.overview');
}

$discussion = Arr::first($event->data['tags'], fn (string $tag) => str_starts_with($tag, 'discussion_'));
if ($discussion) {
$this->purger->addPurgeTag('blog_'.explode('_', $discussion)[1]);
}
}
}
19 changes: 19 additions & 0 deletions src/Event/LSCachePurging.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace ACPL\FlarumCache\Event;

use Flarum\User\User;

/**
* The LSCache is going to be purged.
*/
class LSCachePurging
{
/**
* @param array{
* paths: string[],
* tags: string[]
* } $data
*/
public function __construct(public array $data, public ?User $actor = null) { }
}
1 change: 0 additions & 1 deletion src/Listener/DiscussionEventSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ protected function handle(Deleted|Hidden|Started|Restored|Renamed $event): void
"discussion_{$event->discussion->id}",
"user_{$event->discussion->user->id}",
"user_{$event->discussion->user->username}",
"blog_{$event->discussion->id}",
]);
}

Expand Down
1 change: 0 additions & 1 deletion src/Listener/PostEventSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ protected function handle(Hidden|Posted|Restored|PostWasApproved $event): void
"discussion_{$event->post->discussion_id}",
"user_{$event->post->user_id}",
"user_{$event->post->user_id}",
"blog_{$event->post->discussion->id}",
]);
}

Expand Down
72 changes: 50 additions & 22 deletions src/Middleware/AbstractPurgeCacheMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

namespace ACPL\FlarumCache\Middleware;

use ACPL\FlarumCache\Event\LSCachePurging;
use ACPL\FlarumCache\LSCacheHeader;
use ACPL\FlarumCache\Utility\LSCachePurger;
use Flarum\Settings\SettingsRepositoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Illuminate\Events\Dispatcher;
use Psr\Http\Message\{ResponseInterface, ServerRequestInterface};
use Psr\Http\Server\{MiddlewareInterface, RequestHandlerInterface};

abstract class AbstractPurgeCacheMiddleware implements MiddlewareInterface
{
Expand All @@ -17,51 +17,79 @@ abstract class AbstractPurgeCacheMiddleware implements MiddlewareInterface
public function __construct(
protected SettingsRepositoryInterface $settings,
protected LSCachePurger $cachePurger,
protected Dispatcher $events,
) {
}

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$response = $handler->handle($request);
$this->currentRouteName = $request->getAttribute('routeName');

if (
! in_array($request->getMethod(), ['POST', 'PUT', 'PATCH', 'DELETE'])
|| $response->getStatusCode() >= 400
) {
return $response;
if ($this->shouldProcessPurge($request, $response)) {
$this->preparePurgeData($request);
$this->dispatchLSCachePurgingEvent();
$response = $this->addPurgeParamsToResponse($response);
}

$this->currentRouteName = $request->getAttribute('routeName');
return $response;
}

return $this->processPurge($request, $handler, $response);
protected function shouldProcessPurge(ServerRequestInterface $request, ResponseInterface $response): bool
{
return in_array($request->getMethod(), ['POST', 'PUT', 'PATCH', 'DELETE'])
&& $response->getStatusCode() < 400;
}

abstract protected function processPurge(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response,
): ResponseInterface;
abstract protected function preparePurgeData(ServerRequestInterface $request): void;

protected function addPurgeParamsToResponse(ResponseInterface $response, array $newPurgeParams): ResponseInterface
protected function addPurgeParamsToResponse(ResponseInterface $response): ResponseInterface
{
$purgeData = $this->cachePurger->getPurgeData();
$newPurgeParams = $this->formatPurgeParams($purgeData);

if ($response->hasHeader(LSCacheHeader::PURGE)) {
$existingPurgeParams = explode(',', $response->getHeaderLine(LSCacheHeader::PURGE));
$newPurgeParams = array_unique(array_merge($existingPurgeParams, $newPurgeParams));
$newPurgeParams = array_merge($existingPurgeParams, $newPurgeParams);
}

if (count($newPurgeParams) < 1) {
if (empty($newPurgeParams)) {
return $response;
}

if ($this->settings->get('acpl-lscache.serve_stale') && ! array_key_exists('stale', $newPurgeParams)) {
array_unshift($newPurgeParams, 'stale');
$this->addStaleParamIfNeeded($newPurgeParams);
$this->cachePurger->clearPurgeData();

return $response->withHeader(LSCacheHeader::PURGE, implode(',', array_unique($newPurgeParams)));
}

protected function formatPurgeParams(array $purgeData): array
{
$params = $purgeData['paths'] ?? [];
if (! empty($purgeData['tags'])) {
$params = array_merge(
$params,
array_map(fn (string $tag) => "tag=$tag", $purgeData['tags']),
);
}
return $params;
}

return $response->withHeader(LSCacheHeader::PURGE, implode(',', $newPurgeParams));
protected function addStaleParamIfNeeded(array &$params): void
{
if ($this->settings->get('acpl-lscache.serve_stale') && ! in_array('stale', $params)) {
array_unshift($params, 'stale');
}
}

protected function getRouteParams(ServerRequestInterface $request): array
{
return $request->getAttribute('routeParameters');
}

protected function dispatchLSCachePurgingEvent(): void
{
$purgeData = $this->cachePurger->getPurgeData();
$this->events->dispatch(new LSCachePurging($purgeData));
}
}
29 changes: 2 additions & 27 deletions src/Middleware/PurgeCacheMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,19 @@

use ACPL\FlarumCache\LSCache;
use Illuminate\Support\Str;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

class PurgeCacheMiddleware extends AbstractPurgeCacheMiddleware
{
protected function processPurge(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response,
): ResponseInterface {
$purgeParams = $this->getPurgeParamsFromCachePurger();
$purgeParams = array_merge($purgeParams, $this->getPurgeParamsForRoute($request));
$this->cachePurger->clearPurgeData();

return $this->addPurgeParamsToResponse($response, array_unique($purgeParams));
}

private function getPurgeParamsFromCachePurger(): array
{
$purgeData = $this->cachePurger->getPurgeData();
$paths = $purgeData['paths'] ?? [];
$tags = array_map(fn ($tag) => "tag=$tag", $purgeData['tags'] ?? []);

return array_merge($paths, $tags);
}

private function getPurgeParamsForRoute(ServerRequestInterface $request): array
protected function preparePurgeData(ServerRequestInterface $request): void
{
$routeName = $this->currentRouteName;
$rootRouteName = LSCache::extractRootRouteName($routeName);
$params = $this->getRouteParams($request);

if (! empty($params['id']) && $this->shouldPurgeRoute($rootRouteName, $routeName)) {
return ["tag={$rootRouteName}_{$params['id']}"];
$this->cachePurger->addPurgeTag("tag={$rootRouteName}_{$params['id']}");
}

return [];
}

private function shouldPurgeRoute(string $rootRouteName, string $routeName): bool
Expand Down
24 changes: 19 additions & 5 deletions src/Utility/LSCachePurger.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
namespace ACPL\FlarumCache\Utility;

use ACPL\FlarumCache\Command\LSCacheClearCommand;
use Illuminate\Contracts\Queue\Queue;
use ACPL\FlarumCache\Event\LSCachePurging;
use Illuminate\Events\Dispatcher;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;

use const PHP_SAPI;

/**
Expand All @@ -15,14 +15,23 @@
*/
class LSCachePurger
{
private static array $purgeData = [];
/**
* @var array{
* paths: string[],
* tags: string[]
* } $purgeData
*/
private static array $purgeData = [
'paths' => [],
'tags' => [],
];

/**
* @var array|string[]
*/
public static array $resourcesSupportedByEvent = ['discussion', 'post', 'user'];

public function __construct(protected readonly LSCacheClearCommand $cacheClearCommand, protected Queue $queue)
public function __construct(protected readonly LSCacheClearCommand $cacheClearCommand, protected Dispatcher $events)
{
}

Expand Down Expand Up @@ -59,7 +68,10 @@ public function getPurgeData(): array

public function clearPurgeData(): void
{
self::$purgeData = [];
self::$purgeData = [
'paths' => [],
'tags' => [],
];
}

public function executePurge(): void
Expand All @@ -78,6 +90,8 @@ private function purgeViaCli(): void
{
$input = [];

$this->events->dispatch(new LSCachePurging(self::$purgeData));

if (! empty(self::$purgeData['paths'])) {
$input['--path'] = self::$purgeData['paths'];
}
Expand Down

0 comments on commit 0e21077

Please sign in to comment.