diff --git a/config/invalidations.php b/config/invalidations.php index 09b5e67..a3ba70f 100644 --- a/config/invalidations.php +++ b/config/invalidations.php @@ -156,6 +156,25 @@ */ 'default' => 'invalidate-dependents', ], + + 'on-all-events' => [ + /** + * On all events, also invalidate those + */ + 'default' => 'invalidate-none', + + 'when-models' => [ + [ + 'models' => ['*'], + + 'strategy' => 'invalidate-urls', + + 'urls' => [ + '%sitemap.xml%', + ] + ], + ], + ], ], ], ]; diff --git a/src/Listeners/EloquentObserver.php b/src/Listeners/EloquentObserver.php index 1aa4d0c..b636758 100644 --- a/src/Listeners/EloquentObserver.php +++ b/src/Listeners/EloquentObserver.php @@ -68,6 +68,11 @@ public function invalidate(Model $model, string $event, array $relation = []): v return; } + if ($event !== 'on-all-events') { + // Always dispatch an 'on-all-events' event + $this->invalidate($model, 'on-all-events', $relation); + } + $entity = new Entity($model, $event); $entity->setRelation($relation); diff --git a/src/Services/Invalidation.php b/src/Services/Invalidation.php index 8a4ae62..d646654 100644 --- a/src/Services/Invalidation.php +++ b/src/Services/Invalidation.php @@ -273,6 +273,11 @@ public function setPaths(Collection $paths): self return $this; } + public function itemsList(string|null $type = null): Collection + { + return $this->items($type); + } + public function queryItemsList(string|null $type = null): string { return $this->items($type) @@ -355,7 +360,15 @@ public function urlNames(): Collection return $this->urlNames; } - return $this->urlNames = $this->urls()->map->url; + $urls = $this->urls()->map(function (Url|string $url) { + if ($url instanceof Url) { + return $url->url; + } + + return $url; + }); + + return $this->urlNames = $urls; } public function urlHashes(): Collection diff --git a/src/Services/Strategy.php b/src/Services/Strategy.php new file mode 100644 index 0000000..8aad0d7 --- /dev/null +++ b/src/Services/Strategy.php @@ -0,0 +1,25 @@ +name = $strategy['strategy']; + + $this->urls = $strategy['urls'] ?? []; + + $this->models = $strategy['models'] ?? []; + + $this->onChange = $strategy['onChange'] ?? []; + } +} diff --git a/src/Services/Tags.php b/src/Services/Tags.php index a1def95..9b1d730 100644 --- a/src/Services/Tags.php +++ b/src/Services/Tags.php @@ -2,6 +2,7 @@ namespace A17\EdgeFlush\Services; +use PHPUnit\TextUI\Help; use Illuminate\Support\Str; use A17\EdgeFlush\EdgeFlush; use Illuminate\Http\Request; @@ -228,13 +229,13 @@ protected function dispatchInvalidationsForCrud(Entity $entity): void $strategy = $this->getCrudStrategy($entity); - if ($strategy === Constants::INVALIDATION_STRATEGY_NONE) { + if ($strategy->name === Constants::INVALIDATION_STRATEGY_NONE) { Helpers::debug('NO INVALIDATION needed for model ' . $entity->modelName); return; } - if ($strategy === Constants::INVALIDATION_STRATEGY_ALL) { + if ($strategy->name === Constants::INVALIDATION_STRATEGY_ALL) { Helpers::debug('INVALIDATING ALL tags'); $this->markAsDispatched($entity); @@ -244,7 +245,7 @@ protected function dispatchInvalidationsForCrud(Entity $entity): void return; } - if ($strategy === Constants::INVALIDATION_STRATEGY_DEPENDENTS) { + if ($strategy->name === Constants::INVALIDATION_STRATEGY_DEPENDENTS) { Helpers::debug('INVALIDATING tags for model ' . $entity->modelName); $invalidation = new Invalidation(); @@ -258,10 +259,22 @@ protected function dispatchInvalidationsForCrud(Entity $entity): void return; } - throw new \Exception("Strategy '{$strategy}' Not implemented"); + if ($strategy->name === Constants::INVALIDATION_STRATEGY_URLS) { + Helpers::debug('Invalidate URLs ' . json_encode($strategy->urls)); + + $invalidation = new Invalidation(); + + $invalidation->setUrls($strategy->urls); + + $this->invalidateTags($invalidation); + + return; + } + + throw new \Exception("Strategy '{$strategy->name}' Not implemented"); } - public function getCrudStrategy(Entity $entity): string + public function getCrudStrategy(Entity $entity): Strategy { if (!$entity->isDirty()) { return Constants::INVALIDATION_STRATEGY_NONE; @@ -269,21 +282,41 @@ public function getCrudStrategy(Entity $entity): string $strategy = Helpers::configArray("edge-flush.invalidations.crud-strategy.{$entity->event}"); - $defaultStrategy = $strategy['default'] ?? Constants::INVALIDATION_STRATEGY_DEPENDENTS; + $defaultStrategy = new Strategy(['strategy' => $strategy['default'] ?? Constants::INVALIDATION_STRATEGY_DEPENDENTS]); if (blank($strategy)) { return $defaultStrategy; } foreach ($strategy['when-models'] ?? [] as $modelStrategy) { - // Model is not in the list of models - if (!in_array($entity->modelClass, $modelStrategy['models'])) { + // Loop through each model or pattern in the models list + $matchesPattern = false; + + foreach ($modelStrategy['models'] as $model) { + // Check if it's a full class name match + if ($entity->modelClass === $model) { + $matchesPattern = true; + + break; + } + + // Check for a wildcard match (e.g., using '*' or other patterns) + if (fnmatch($model, $entity->modelClass)) { + $matchesPattern = true; + + break; + } + } + + // If no match is found, continue to the next strategy + if (!$matchesPattern) { continue; } + // Your logic when a match is found // There's no on-change condition if (blank($modelStrategy['on-change'] ?? null)) { - return $modelStrategy['strategy']; + return new Strategy($modelStrategy); } // Check if the attribute has changed to the expected value @@ -292,7 +325,7 @@ public function getCrudStrategy(Entity $entity): string // Is the expected value the same as the current value? // If key == value, then we're checking if the attribute was just changed if ($entity->isDirty($key) && ($key === $value || $entity->attributeEquals($key, $value))) { - return $modelStrategy['strategy']; + return new Strategy($modelStrategy); } } } @@ -314,7 +347,7 @@ public function invalidateTags(Invalidation $invalidation): void } Helpers::configString('edge-flush.invalidations.type') === 'batch' - ? $this->markTagsAsObsolete($invalidation) + ? $this->markAsObsolete($invalidation) : $this->dispatchInvalidations($invalidation); } @@ -383,10 +416,21 @@ protected function invalidateObsoleteTags(): void $this->dispatchInvalidations($invalidation); } + protected function markAsObsolete(Invalidation $invalidation): void + { + $this->markTagsAsObsolete($invalidation); + + $this->markUrlsAsObsolete($invalidation); + } + protected function markTagsAsObsolete(Invalidation $invalidation): void { $type = $invalidation->type(); + if ($type !== 'tag') { + return; + } + $list = $invalidation->queryItemsList(); if ($list === "''" || is_null($type) || blank($type)) { @@ -398,6 +442,25 @@ protected function markTagsAsObsolete(Invalidation $invalidation): void $this->dbStatement($this->markTagsAsObsoleteSql($type, $list)); } + protected function markUrlsAsObsolete(Invalidation $invalidation): void + { + $type = $invalidation->type(); + + if ($type !== 'url') { + return; + } + + $list = $invalidation->itemsList(); + + if ($list->isEmpty() || blank($type)) { + return; + } + + Helpers::debug("Marking urls as obsolete: {$type} in ".json_encode($list)); + + $this->dbStatement($this->markUrlsAsObsoleteSql($list)); + } + protected function markTagsAsObsoleteSql(string $type, string $list): string { if ($this->isMySQL()) { @@ -452,6 +515,29 @@ protected function markTagsAsObsoleteSql(string $type, string $list): string "; } + protected function markUrlsAsObsoleteSql(Collection $list): string + { + $wheres = $list->map(function (string $url) { + if (strpos($url, "%") !== false) { + return "url like '{$url}'"; + } + + return "url_hash = '{$url->url_hash}'"; + })->join(' or '); + + Helpers::debug("Marking urls as obsolete: "." + update edge_flush_urls efu + set obsolete = true + where(obsolete = false and $wheres) + "); + + return " + update edge_flush_urls efu + set obsolete = true + where(obsolete = false and $wheres) + "; + } + protected function dispatchInvalidations(Invalidation $invalidation): void { if ($invalidation->isEmpty() || !$this->enabled()) { diff --git a/src/Support/Constants.php b/src/Support/Constants.php index ad7206c..fa7cc63 100644 --- a/src/Support/Constants.php +++ b/src/Support/Constants.php @@ -53,4 +53,6 @@ class Constants const INVALIDATION_STRATEGY_DEPENDENTS = 'invalidate-dependents'; const INVALIDATION_STRATEGY_NONE = 'invalidate-none'; + + const INVALIDATION_STRATEGY_URLS = 'invalidate-urls'; }