Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Api Platform Operation Cache Laravel Package

jacyimp/api-platform-operation-cache

jacyimp/api-platform-operation-cache adds operation-level caching for API Platform in Laravel/PHP applications. Cache expensive or frequently requested operations to reduce latency, lower backend load, and improve API performance and scalability with simple configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a caching layer for operations (controller actions, API operations, service methods) with a first-class invalidation model built around cache groups and generations. It solves the usual "I cached it, now how do I safely blow it away?" problem: instead of guessing keys, you cache under a group and invalidate by group.

Minimal setup:

  1. composer require the package, then publish its config (php artisan vendor:publish --tag=...) and review the default_vary_headers and store/prefix options. Everything else has a working default.
  2. Register the provided service provider/middleware bundle if it isn't auto-discovered. Nothing else is required for the first request to be cached.
  3. First use case: take one expensive read endpoint, mark it with an OperationCache instance declaring a group, and clear that group from the write path (a model observer or an event listener).

Look first at the config file, then at the OperationCache metadata API (the instance-based syntax is the one to learn), then at the PSR-14 event classes — the events are the fastest way to confirm what the package actually thinks it is doing.

Start with a single group and a single invalidation call. Turn on hit/miss event logging before you turn on caching in production; it makes the generation model obvious within minutes.

Implementation Patterns

Group-per-resource, not per-route. Declare a group like product:{id} and rely on URI interpolation so the group resolves per entity. Writes then invalidate product:42 exactly. For collection pages, declare a second broader group (product:*) and invalidate both on write.

Three-level invalidation is the core mental model.

  • Exact — one entity changed (product:42).
  • Prefix — a family changed (product:*); use it after bulk imports or when a related entity changes a parent's representation.
  • Global — *; reserve this for deploys, schema changes, or config flips, never for normal writes.

Generation-based means cheap invalidation. Bumping a generation is O(1) and the stale entries fall out naturally. Design your groups so the common write path only needs an exact or prefix bump; if you find yourself reaching for global invalidation on user actions, your group scheme is too coarse.

Runtime group resolvers for tenancy and A/B. Where URI interpolation can't express the group (tenant ID from the container, feature flag variant, locale from a header), register a resolver. Keep resolvers pure and cheap — they run on both the store and invalidate paths, so an expensive resolver taxes every request.

Conditional and dynamic invalidation belongs in the write path. invalidateIf($condition, ...) keeps "should this bump?" logic next to the domain rule rather than smeared across observers. Dynamic operation invalidation is the right tool when the set of affected groups depends on the payload (e.g. a product moving between categories invalidates both old and new).

Imperative API for the awkward cases. Console commands, queue jobs, and backfill scripts should call the imperative invalidation API directly rather than faking a request through HTTP middleware. This is also the correct approach in tests that assert invalidation behaviour.

Vary headers: application-wide default, per-operation override. Set the default in config (typically Accept, Accept-Language, and your tenant/variant header) so every operation keys correctly by default, then widen deliberately per operation. Overriding at the operation level should be additive, not an invitation to zero out the default.

Wire observability through the PSR-14 events. Subscribe to hit/miss/store/invalidate and emit metrics (hit ratio per group, invalidation volume, top invalidated prefixes). This is the single highest-value integration — cache bugs are invisible without it. In Laravel, bridge the events into Event::listen on the dispatched event objects; in Symfony they go straight into the dispatcher. The Symfony flag clean-up in this release means a stock Symfony app works without an extra config toggle, so remove that key if you were carrying it.

Gotchas and Tips

Interpolation failures are silent. A typo'd or missing route parameter yields a literal {placeholder} in the group, which caches fine and invalidates never. Guard this by asserting resolved groups in tests, not just hit/miss counts.

Global invalidation is a footgun with a friendly name. * is deploy-shaped work. If a normal write path touches it, you have effectively disabled caching — and you'll only notice via the miss-ratio metric.

Conditional invalidation is evaluated at runtime on the write path. An expensive resolver or a closure that hits the database turns a cheap save into a slow one. Keep the condition a field check; push anything heavier into a queued job using the imperative API.

Vary header normalisation matters. Unsorted or differently-cased headers produce distinct keys for semantically identical requests, which looks like a low hit rate and is genuinely hard to spot. Fix normalisation in config, not per operation.

Don't mix operation metadata syntaxes. This release introduces the instance-based OperationCache form. Pick it and use it consistently; mixing with older/static declarations makes duplicate-group bugs very hard to reason about.

Prefix invalidation is prefix on the group, not the cache key. If you construct groups dynamically, make sure the prefix you invalidate is an actual string prefix of the stored group — trailing separators (product: vs product) are the classic near-miss.

Debugging recipe: enable all four lifecycle events at debug level, hit one endpoint, confirm a miss→store pair, perform a write, confirm an invalidation event carries the group you expect. If the invalidation fires with the right group but the next read still misses, the problem is key construction (headers/interpolation), not invalidation.

Extension points to prefer: a custom group resolver for tenancy, a config-level default vary set, and event listeners for metrics. Avoid subclassing the cache/invalidation internals — the generation semantics assume the package owns key construction, and overriding it quietly breaks exact/prefix invalidation.

Config quirks: the application-wide vary default and per-operation overrides interact multiplicatively in your key space. When adding a new default header, expect a one-time cache-wide miss storm — schedule it, or pair it with a global invalidation at a low-traffic window.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons