Skip to content
25 changes: 25 additions & 0 deletions src-annotation/Cacheable.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,31 @@

use Attribute;

/**
* Marks a resource class as cacheable with TTL-based expiration
*
* Traditional time-based caching where cache expires after a specified duration.
* For event-driven cache invalidation based on resource dependencies, use
* #[CacheableResponse] or #[DonutCache] instead.
*
* Example:
* ```php
* // Cache for 1 hour
* #[Cacheable(expirySecond: 3600)]
*
* // Cache with predefined expiry preset
* #[Cacheable(expiry: 'short')] // short, medium, long, never
Comment on lines +21 to +22
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The list of expiry presets could be referenced to a source or defined.

If these presets are defined or configurable elsewhere, please reference their source or clarify their definitions for better maintainability.

Suggested change
* // Cache with predefined expiry preset
* #[Cacheable(expiry: 'short')] // short, medium, long, never
* // Cache with predefined expiry preset
* #[Cacheable(expiry: 'short')] // short, medium, long, never
* // See preset definitions in CacheInterceptor::$expiryPresets or config/cache.php

*
* // Cache until specific time from body field
* #[Cacheable(expiryAt: 'expires_at')]
* ```
*
* Interceptors bound:
* - CacheInterceptor (onGet methods)
* - CommandInterceptor (onPut/onPatch/onDelete methods)
*
* @see https://bearsunday.github.io/manuals/1.0/en/cache.html#cacheable
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class Cacheable
{
Expand Down
36 changes: 32 additions & 4 deletions src-annotation/CacheableResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,42 @@
namespace BEAR\RepositoryModule\Annotation;

use Attribute;
use BEAR\QueryRepository\DonutCacheableResponseInterceptor;
use BEAR\QueryRepository\DonutCacheModule;
use BEAR\QueryRepository\DonutCommandInterceptor;

/**
* Enables event-driven cache invalidation for fully cacheable responses
*
* For content that is fundamentally static but changes predictably via resource
* methods (onPut, onDelete, etc.). Unlike #[Cacheable] which uses TTL-based
* expiration, this enables tag-based invalidation driven by resource dependencies.
* The entire response is cached and receives an ETag for conditional requests,
* enabling 304 (Not Modified) responses to reduce network transfer costs.
*
* Example:
* ```php
* #[CacheableResponse]
* class Article extends ResourceObject
* {
* public function onGet(int $id): static
* {
* // ...
* }
*
* #[RefreshCache]
* public function onDelete(int $id): static
* {
* // ...
* }
* }
* ```
*
* Interceptors bound:
* - DonutCacheableResponseInterceptor (onGet methods when applied to class)
* - DonutCommandInterceptor (onPut/onPatch/onDelete methods when applied to class)
* - DonutCacheInterceptor (when applied to method)
*
* @see DonutCacheModule
* @see DonutCacheableResponseInterceptor
* @see DonutCommandInterceptor
* @see https://bearsunday.github.io/manuals/1.0/en/cache.html#event-driven-content
*/
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)]
final class CacheableResponse
Expand Down
27 changes: 24 additions & 3 deletions src-annotation/DonutCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,33 @@

use Attribute;
use BEAR\QueryRepository\DonutCacheModule;
use BEAR\QueryRepository\DonutCommandInterceptor;

/**
* Enables event-driven donut caching for resources with non-cacheable embedded content
*
* For content that is fundamentally static but changes predictably via resource
* methods, with some embedded resources that cannot be cached. Unlike #[Cacheable]
* which uses TTL-based expiration, this enables tag-based invalidation. Only
* cacheable portions are stored; no ETag is generated for the entire response.
*
* Example:
* ```php
* #[DonutCache]
* class BlogPosting extends ResourceObject
* {
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
* #[Embed(rel: 'comment', src: 'app://self/comments')]
* public function onGet(int $id): static
* {
* // ...
* }
* }
* ```
*
* Interceptors bound:
* - DonutCacheInterceptor (onGet methods when applied to class, or any method when applied to method)
*
* @see DonutCacheModule
* @see DonutCacheInterceptor
* @see DonutCommandInterceptor
* @see https://bearsunday.github.io/manuals/1.0/en/cache.html#donut-cache
*/
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)]
final class DonutCache
Expand Down
21 changes: 21 additions & 0 deletions src-annotation/HttpCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,28 @@
*
* Builds a complex Cache-Control header
*
* Example:
* ```php
* // Cache for 1 hour
* #[HttpCache(maxAge: 3600)]
*
* // Cache with revalidation when stale
* #[HttpCache(maxAge: 3600, mustRevalidate: true)]
*
* // Private cache (not shared cache like CDN)
* #[HttpCache(isPrivate: true, maxAge: 300)]
*
* // CDN cache for 1 hour, browser cache for 5 minutes
* #[HttpCache(maxAge: 300, sMaxAge: 3600)]
*
* // For disabling cache, use #[NoHttpCache] instead
* ```
*
* Interceptors bound:
* - HttpCacheInterceptor (onGet methods)
*
* @see HttpCacheInterceptor
* @see https://bearsunday.github.io/manuals/1.0/en/cache.html
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class HttpCache extends AbstractCacheControl
Expand Down
4 changes: 4 additions & 0 deletions src-annotation/NoHttpCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
*
* Simplified notation to say that you don't want anything cached
*
* Interceptors bound:
* - HttpCacheInterceptor (onGet methods)
Comment on lines +15 to +16
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider clarifying the effect of NoHttpCache on HTTP headers.

Briefly note how NoHttpCache modifies HTTP headers to clarify its practical impact for users.

*
* @see HttpCacheInterceptor
* @see https://bearsunday.github.io/manuals/1.0/en/cache.html
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class NoHttpCache extends AbstractCacheControl
Expand Down
22 changes: 21 additions & 1 deletion src-annotation/Purge.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,27 @@

use Attribute;

/** @see RefreshInterceptor */
/**
* Purges cache for specified URI after command execution
*
* Behavior differs based on class attributes:
* - **#[Cacheable] classes**: CommandInterceptor automatically binds to all
* command methods (onPut/onPatch/onDelete) and processes #[Purge] annotations
* - **Non-Cacheable classes**: RefreshInterceptor binds only to methods explicitly
* marked with #[Purge] or #[Refresh]
*
* Example:
* ```php
* #[Purge(uri: 'app://self/user/profile?user_id={id}')]
* #[Purge(uri: 'app://self/user/friend?user_id={id}')]
* public function onDelete(int $id): static
* {
* // ...
* }
* ```
*
* @see https://bearsunday.github.io/manuals/1.0/en/cache.html#tag-based-cache-invalidation
*/
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
final class Purge extends AbstractCommand
{
Expand Down
22 changes: 20 additions & 2 deletions src-annotation/Refresh.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,27 @@
namespace BEAR\RepositoryModule\Annotation;

use Attribute;
use BEAR\QueryRepository\RefreshInterceptor;

/** @see RefreshInterceptor */
/**
* Refreshes cache for specified URI after command execution
*
* Behavior differs based on class attributes:
* - **#[Cacheable] classes**: CommandInterceptor automatically binds to all
* command methods (onPut/onPatch/onDelete) and processes #[Refresh] annotations
* - **Non-Cacheable classes**: RefreshInterceptor binds only to methods explicitly
* marked with #[Purge] or #[Refresh]
*
* Example:
* ```php
* #[Refresh(uri: 'app://self/user/profile?user_id={id}')]
* public function onPut(int $id, string $name): static
* {
* // ...
* }
* ```
*
* @see https://bearsunday.github.io/manuals/1.0/en/cache.html#event-driven-content
*/
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
final class Refresh extends AbstractCommand
{
Expand Down
10 changes: 8 additions & 2 deletions src-annotation/RefreshCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@
namespace BEAR\RepositoryModule\Annotation;

use Attribute;
use BEAR\QueryRepository\DonutCommandInterceptor;

/** @see DonutCommandInterceptor */
/**
* Refreshes donut cache after command execution
*
* Interceptors bound:
* - DonutCacheInterceptor
*
* @see https://bearsunday.github.io/manuals/1.0/en/cache.html#cache-invalidation
*/
#[Attribute(Attribute::TARGET_METHOD)]
final class RefreshCache
{
Expand Down
10 changes: 10 additions & 0 deletions src/CacheInterceptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@

use const E_USER_WARNING;

/**
* Interceptor for TTL-based caching on CQRS queries with #[Cacheable]
*
* Bound to query methods (onGet) of classes marked with #[Cacheable].
* Retrieves cached resource state if available, otherwise executes
* the method and stores the result with configured TTL.
*
* @see \BEAR\RepositoryModule\Annotation\Cacheable
* @see https://bearsunday.github.io/manuals/1.0/en/cache.html#cacheable
*/
final readonly class CacheInterceptor implements MethodInterceptor
{
public function __construct(
Expand Down
14 changes: 14 additions & 0 deletions src/CommandInterceptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@
use Ray\Aop\MethodInterceptor;
use Ray\Aop\MethodInvocation;

/**
* Interceptor for cache invalidation on CQRS commands with #[Purge] or #[Refresh]
*
* Automatically bound to all command methods (onPut/onPatch/onDelete) of #[Cacheable] classes.
* Processes #[Purge] and #[Refresh] annotations on these methods and executes cache
* invalidation after successful write operations.
*
* For non-Cacheable classes, use RefreshInterceptor instead by explicitly marking methods
* with #[Purge] or #[Refresh].
*
* @see \BEAR\RepositoryModule\Annotation\Purge
* @see \BEAR\RepositoryModule\Annotation\Refresh
* @see https://bearsunday.github.io/manuals/1.0/en/cache.html#tag-based-cache-invalidation
*/
final readonly class CommandInterceptor implements MethodInterceptor
{
/** @param CommandInterface[] $commands */
Expand Down
10 changes: 10 additions & 0 deletions src/DonutCacheInterceptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@

namespace BEAR\QueryRepository;

/**
* Interceptor for donut caching on CQRS queries with #[DonutCache]
*
* Bound to query methods (onGet) or individual methods marked with #[DonutCache].
* Caches only the cacheable portions, excluding non-cacheable embedded content.
* No ETag is generated for the entire response.
*
* @see \BEAR\RepositoryModule\Annotation\DonutCache
* @see https://bearsunday.github.io/manuals/1.0/en/cache.html#donut-cache
*/
final class DonutCacheInterceptor extends AbstractDonutCacheInterceptor
{
protected const IS_ENTIRE_CONTENT_CACHEABLE = false;
Expand Down
10 changes: 10 additions & 0 deletions src/DonutCacheableResponseInterceptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@

namespace BEAR\QueryRepository;

/**
* Interceptor for full response caching on CQRS queries with #[CacheableResponse]
*
* Bound to query methods (onGet) of classes marked with #[CacheableResponse].
* Caches the entire response and generates an ETag for conditional requests.
* Enables 304 (Not Modified) responses for efficient network transfer.
*
* @see \BEAR\RepositoryModule\Annotation\CacheableResponse
* @see https://bearsunday.github.io/manuals/1.0/en/cache.html#event-driven-content
*/
final class DonutCacheableResponseInterceptor extends AbstractDonutCacheInterceptor
{
protected const IS_ENTIRE_CONTENT_CACHEABLE = true;
Expand Down
10 changes: 10 additions & 0 deletions src/DonutCommandInterceptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@
use function is_callable;
use function sprintf;

/**
* Interceptor for donut cache invalidation on CQRS commands
*
* Bound to command methods (onPut/onPatch/onDelete) of classes marked with #[CacheableResponse].
* Refreshes donut cache and resource state after successful write operations.
*
* @see \BEAR\RepositoryModule\Annotation\CacheableResponse
* @see \BEAR\RepositoryModule\Annotation\DonutCache
* @see https://bearsunday.github.io/manuals/1.0/en/cache.html#event-driven-content
*/
final readonly class DonutCommandInterceptor implements MethodInterceptor
{
public function __construct(
Expand Down
10 changes: 10 additions & 0 deletions src/HttpCacheInterceptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@

use function assert;

/**
* Interceptor for HTTP Cache-Control header with #[HttpCache] or #[NoHttpCache]
*
* Bound to onGet methods of classes marked with #[HttpCache] or #[NoHttpCache].
* Sets the Cache-Control header based on attribute configuration for HTTP caching.
*
* @see \BEAR\RepositoryModule\Annotation\HttpCache
* @see \BEAR\RepositoryModule\Annotation\NoHttpCache
* @see https://bearsunday.github.io/manuals/1.0/en/cache.html
*/
final class HttpCacheInterceptor implements MethodInterceptor
{
/**
Expand Down
13 changes: 13 additions & 0 deletions src/RefreshInterceptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@
use Ray\Aop\MethodInterceptor;
use Ray\Aop\MethodInvocation;

/**
* Interceptor for cache refresh commands with #[Purge] or #[Refresh]
*
* Bound only to methods explicitly marked with #[Purge] or #[Refresh] on non-Cacheable classes.
* Unlike CommandInterceptor (which automatically binds to all command methods of #[Cacheable]
* classes), this interceptor requires explicit attribute annotation on each method.
*
* Executes cache invalidation commands after successful method execution.
*
* @see \BEAR\RepositoryModule\Annotation\Purge
* @see \BEAR\RepositoryModule\Annotation\Refresh
* @see https://bearsunday.github.io/manuals/1.0/en/cache.html#event-driven-content
*/
final readonly class RefreshInterceptor implements MethodInterceptor
{
public function __construct(
Expand Down
Loading