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

Annotation Cache Bundle Laravel Package

emag-tech-labs/annotation-cache-bundle

Symfony bundle that adds annotation or PHP 8 attribute based caching to service method responses. It scans services, generates proxy classes, and stores results in any PSR-6 cache implementation such as Redis, Memcache, or a simple array.

View on GitHub
Deep Wiki
Context7

Getting Started

First, calibrate expectations: this is a Symfony DI bundle, not a Laravel package. It works by running a compiler pass over the container, finding services whose methods carry the Cache annotation/attribute, and generating an ocramius/proxy-manager subclass that overrides those methods with getCached(). There is no Laravel service-provider equivalent because Laravel's container does not do compile-time method proxying. So there are two paths:

  • You actually have a Symfony app/service in the stack → install it there.
  • You're in Laravel → don't fight it. Read it as a reference implementation and reproduce the behavior with a decorator + Cache::remember() (pattern below). Installing it in Laravel means pulling in symfony/dependency-injection, a compiler pass, and proxy generation — not worth it for 19 stars and a 2023 last release.

If you are on Symfony:

composer require emag-tech-labs/annotation-cache-bundle

Flex registers it; otherwise add eMAG\Bundle\AnnotationCacheBundle\AnnotationCacheBundle to config/bundles.php.

Then look at these three files before writing code — they are the real documentation:

  1. vendor/emag-tech-labs/annotation-cache-bundle/src/Annotation/Cache.php — the actual option names (cache pool id, key, expiresAfter, etc.). Do not trust blog posts; option names drifted between releases.
  2. bin/console config:dump-reference annotation_cache — tells you exactly which config keys exist in your pinned version (the scanned service paths/namespaces live here; nothing is cached unless the service is in scope).
  3. The generated proxies in var/cache/{env}/ after one warmup — this is where "why isn't it caching?" gets answered.

First use case: a service method that hits a slow, idempotent external source.

use eMAG\Bundle\AnnotationCacheBundle\Annotation\Cache;

final class CurrencyRateClient
{
    #[Cache(cache: 'cache.app', expiresAfter: 300)]
    public function ratesFor(string $base, string $quote): array
    {
        return $this->http->get("/rates/{$base}/{$quote}")->toArray();
    }
}

Note final class there is a bug, not a style choice — see Gotchas. Verify it worked with bin/console debug:container CurrencyRateClient (class should be the generated proxy) and by calling the method twice while watching the profiler's cache panel.

Implementation Patterns

Per-argument caching. The key is derived from the method and its arguments, so the annotation is essentially Cache::remember('method:'.md5(serialize($args))). This is the sweet spot for repository/API-gateway methods with scalar parameters:

#[Cache(cache: 'cache.redis', expiresAfter: 3600)]
public function productCountByCategory(int $categoryId, bool $includeChildren = false): int

Explicit keys for non-serializable arguments. If a parameter is an entity, a DateTimeImmutable, or a closure, the default key generation will either blow up or silently collide. Pass an explicit key and keep the argument list out of the hash:

#[Cache(cache: 'cache.app', key: 'exchange_rates_base_eur')]
public function rates(Base $base): array

Decorator pattern (the Laravel-native equivalent). When you leave Symfony, keep the contract the same and swap the mechanism:

class CachedRatesClient
{
    public function __construct(private RatesClient $inner) {}

    public function ratesFor(string $base, string $quote): array
    {
        return Cache::remember("rates:{$base}:{$quote}", 300, fn () => $this->inner->ratesFor($base, $quote));
    }
}

Bind it in a service provider and you have identical semantics without a compiler pass, proxy files, or non-final classes. Prefer this in Laravel 100% of the time.

Warmup command. Because entries are only populated on first call, add a console command that invokes the expensive annotated methods for your top-N keys, and run it as a post-deploy step. Pair it with cache:pool:clear on the old pool if the payload shape changed.

Test profile. Point the pool at an in-memory/array PSR-6 adapter in the test environment so caches never leak between test cases, and write one test that asserts the inner collaborator is called once across two invocations — that's the only assertion that proves the proxy is actually wired.

Layering with HTTP cache. These are server-side memoization, not response caching. Don't annotate controller actions; annotate the service methods underneath so the cache is reusable across endpoints.

Gotchas and Tips

  • Proxies subclass your service. The class must not be final, annotated methods must not be final/private/static, and the constructor must be overridable. This is the #1 cause of silent no-ops and fatal errors. If your codebase is all final class, this bundle is the wrong tool.
  • Self-invocation bypasses the proxy. $this->ratesFor() inside the same class calls the real method, not the cached one. Same trap as Laravel decorators. Extract the annotated method into its own service.
  • Return values must be PSR-6-serializable. No resources, closures, PDO handles, or un-serializable entities. Cache scalars/arrays/DTOs, or serialize deliberately and accept the cost.
  • No automatic invalidation. Despite living in a Symfony app, the bundle just does getItem/save — TagAwareAdapter tags are not applied for you. Key rotation or TTL is your only invalidation strategy.
  • expiresAfter unset ≠ forever. It falls through to the pool's default lifetime (cache.app has one). If you need "cache until deploy", set a long explicit TTL rather than assuming.
  • Read-only filesystems break warmup. Proxy classes are written to var/cache. In containerized deploys, run cache:warmup at build time in the image, not at runtime, or you'll get permission errors.
  • Attribute vs annotation paths are separate. PHP 8 attributes and Doctrine @Cache annotations are handled by different code; if you're mid-migration and see partial coverage, check which one your class actually uses.
  • Debug recipe: no cache? → debug:container <id> (proxy class name?), check var/cache for the proxy file, check the scanned paths config, then check for final. Entry never expires? → confirm the pool id string matches a real service (debug:container cache.app).
  • Maturity risk. 19 stars, 0 dependents, last release March 2023, CI on Travis. Pin an exact version, and treat the Cache annotation + CacheableClassTrait as vendored code you may need to fork. Budget for PHP 8.3/8.4 + ocramius/proxy-manager deprecation noise. If you're choosing today, symfony/cache's #[Cache]-free CacheInterface::get() + a thin decorator gets you 90% of this with zero supply-chain exposure.
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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core