-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Cache tags implemented; Updated README.
- Loading branch information
Showing
6 changed files
with
407 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
{"config":{"phpVersion":80226,"phpExtensions":"74167a1a9fe1ad09c87a54206ee3fec6","tabWidth":4,"encoding":"utf-8","recordErrors":true,"annotations":true,"configData":[],"codeHash":"b5dc58ae9d1a395f86384fd9a1678bb4","rulesetHash":"670f8348153700c3a70fc6127ca0e484"},"\/home\/carlos\/Projects\/test\/mnemosyne\/src\/Cache.php":{"hash":"97880d1955eb8b46f6ca52080c211dfc33188","errors":[],"warnings":[],"metrics":{"Declarations and side effects mixed":{"values":{"no":1}},"PHP short open tag used":{"values":{"no":1}},"EOL char":{"values":{"\\n":1}},"Number of newlines at EOF":{"values":{"1":1}},"PHP closing tag at end of PHP-only file":{"values":{"no":1}},"Line length":{"values":{"80 or less":12}},"Line indent":{"values":{"spaces":6}},"PHP keyword case":{"values":{"lower":7}},"Multiple statements on same line":{"values":{"no":1}},"One class per file":{"values":{"yes":1}},"Class defined in namespace":{"values":{"yes":1}},"PascalCase class name":{"values":{"yes":1}},"Class opening brace placement":{"values":{"new line":1}},"PHP constant case":{"values":{"lower":2}},"PHP type case":{"values":{"lower":3}}},"errorCount":0,"warningCount":0,"fixableCount":0,"numTokens":89},"\/home\/carlos\/Projects\/test\/mnemosyne\/src\/CacheTrait.php":{"hash":"fb0de4cdd837b994da4cf8dffdd5083a33188","errors":[],"warnings":[],"metrics":{"Declarations and side effects mixed":{"values":{"no":1}},"PHP short open tag used":{"values":{"no":1}},"EOL char":{"values":{"\\n":1}},"Number of newlines at EOF":{"values":{"1":1}},"PHP closing tag at end of PHP-only file":{"values":{"no":1}},"Line length":{"values":{"80 or less":75,"81-120":5}},"Line indent":{"values":{"spaces":74}},"PHP keyword case":{"values":{"lower":40}},"Multiple statements on same line":{"values":{"no":30}},"One class per file":{"values":{"yes":1}},"Class defined in namespace":{"values":{"yes":1}},"PascalCase class name":{"values":{"yes":1}},"Class opening brace placement":{"values":{"new line":1}},"PHP type case":{"values":{"lower":11}},"CamelCase method name":{"values":{"yes":4}},"Function opening brace placement":{"values":{"new line":4}},"Spaces after control structure open parenthesis":{"values":[11]},"Spaces before control structure close parenthesis":{"values":[11]},"Blank lines at start of control structure":{"values":[11]},"Blank lines at end of control structure":{"values":[11]},"Control structure defined inline":{"values":{"no":11}},"PHP constant case":{"values":{"lower":5}}},"errorCount":0,"warningCount":0,"fixableCount":0,"numTokens":807},"\/home\/carlos\/Projects\/test\/mnemosyne\/src\/CacheAttribute.php":{"hash":"48936c1e0dbf098f4ecf0c9458113f2d33188","errors":[],"warnings":[],"metrics":{"Declarations and side effects mixed":{"values":{"no":1}},"PHP short open tag used":{"values":{"no":1}},"EOL char":{"values":{"\\n":1}},"Number of newlines at EOF":{"values":{"1":1}},"PHP closing tag at end of PHP-only file":{"values":{"no":1}},"Line length":{"values":{"80 or less":83,"81-120":5}},"Line indent":{"values":{"spaces":82}},"PHP keyword case":{"values":{"lower":44}},"Multiple statements on same line":{"values":{"no":27}},"One class per file":{"values":{"yes":1}},"Class defined in namespace":{"values":{"yes":1}},"PascalCase class name":{"values":{"yes":1}},"Class opening brace placement":{"values":{"new line":1}},"PHP type case":{"values":{"lower":11}},"Function opening brace placement":{"values":{"new line":4}},"Blank lines at start of control structure":{"values":[13]},"Blank lines at end of control structure":{"values":[13]},"Spaces after control structure open parenthesis":{"values":[12]},"Spaces before control structure close parenthesis":{"values":[12]},"PHP constant case":{"values":{"lower":6}},"Control structure defined inline":{"values":{"no":11}},"CamelCase method name":{"values":{"yes":3}}},"errorCount":0,"warningCount":0,"fixableCount":0,"numTokens":822}} | ||
{"config":{"phpVersion":80226,"phpExtensions":"74167a1a9fe1ad09c87a54206ee3fec6","tabWidth":4,"encoding":"utf-8","recordErrors":true,"annotations":true,"configData":[],"codeHash":"b5dc58ae9d1a395f86384fd9a1678bb4","rulesetHash":"670f8348153700c3a70fc6127ca0e484"},"\/home\/carlos\/Projects\/test\/mnemosyne\/src\/Cache.php":{"hash":"2d84bc074c6e440368e414857cfd970433204","errors":[],"warnings":[],"metrics":{"Declarations and side effects mixed":{"values":{"no":1}},"PHP short open tag used":{"values":{"no":1}},"EOL char":{"values":{"\\n":1}},"Number of newlines at EOF":{"values":{"1":1}},"PHP closing tag at end of PHP-only file":{"values":{"no":1}},"Line length":{"values":{"80 or less":36,"81-120":5}},"Line indent":{"values":{"spaces":16}},"PHP keyword case":{"values":{"lower":8}},"Multiple statements on same line":{"values":{"no":1}},"One class per file":{"values":{"yes":1}},"Class defined in namespace":{"values":{"yes":1}},"PascalCase class name":{"values":{"yes":1}},"Class opening brace placement":{"values":{"new line":1}},"PHP constant case":{"values":{"lower":2}},"PHP type case":{"values":{"lower":4}}},"errorCount":0,"warningCount":0,"fixableCount":0,"numTokens":235},"\/home\/carlos\/Projects\/test\/mnemosyne\/src\/CacheTrait.php":{"hash":"305ecb309361d700fdc43d4ae9e50bbb33204","errors":[],"warnings":[],"metrics":{"Declarations and side effects mixed":{"values":{"no":1}},"PHP short open tag used":{"values":{"no":1}},"EOL char":{"values":{"\\n":1}},"Number of newlines at EOF":{"values":{"1":1}},"PHP closing tag at end of PHP-only file":{"values":{"no":1}},"Line length":{"values":{"80 or less":181,"81-120":6}},"Line indent":{"values":{"spaces":144}},"PHP keyword case":{"values":{"lower":49}},"Multiple statements on same line":{"values":{"no":39}},"One class per file":{"values":{"yes":1}},"Class defined in namespace":{"values":{"yes":1}},"PascalCase class name":{"values":{"yes":1}},"Class opening brace placement":{"values":{"new line":1}},"PHP type case":{"values":{"lower":13}},"CamelCase method name":{"values":{"yes":5}},"Function opening brace placement":{"values":{"new line":5}},"Spaces after control structure open parenthesis":{"values":[15]},"Spaces before control structure close parenthesis":{"values":[15]},"Blank lines at start of control structure":{"values":[15]},"Blank lines at end of control structure":{"values":[15]},"Control structure defined inline":{"values":{"no":15}},"PHP constant case":{"values":{"lower":5}}},"errorCount":0,"warningCount":0,"fixableCount":0,"numTokens":1446},"\/home\/carlos\/Projects\/test\/mnemosyne\/src\/CacheAttribute.php":{"hash":"14a86252da512a54b67bd6dc8da1de0c33204","errors":[],"warnings":[],"metrics":{"Declarations and side effects mixed":{"values":{"no":1}},"PHP short open tag used":{"values":{"no":1}},"EOL char":{"values":{"\\n":1}},"Number of newlines at EOF":{"values":{"1":1}},"PHP closing tag at end of PHP-only file":{"values":{"no":1}},"Line length":{"values":{"80 or less":146,"81-120":5}},"Line indent":{"values":{"spaces":136}},"PHP keyword case":{"values":{"lower":59}},"Multiple statements on same line":{"values":{"no":37}},"One class per file":{"values":{"yes":1}},"Class defined in namespace":{"values":{"yes":1}},"PascalCase class name":{"values":{"yes":1}},"Class opening brace placement":{"values":{"new line":1}},"PHP type case":{"values":{"lower":16}},"Constant name case":{"values":{"upper":1}},"CamelCase method name":{"values":{"yes":5}},"Function opening brace placement":{"values":{"new line":6}},"Spaces after control structure open parenthesis":{"values":[16]},"Spaces before control structure close parenthesis":{"values":[16]},"Blank lines at start of control structure":{"values":[17]},"Blank lines at end of control structure":{"values":[17]},"Control structure defined inline":{"values":{"no":15}},"PHP constant case":{"values":{"lower":6}}},"errorCount":0,"warningCount":0,"fixableCount":0,"numTokens":1271}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,13 +2,42 @@ | |
|
||
namespace Mnemosyne; | ||
|
||
/** | ||
* Cache attribute for configuring method-level caching behavior. | ||
* | ||
* This attribute can be applied to methods to define their caching strategy, | ||
* including cache key template, TTL, invalidation rules, and tags. | ||
* | ||
* @see \Mnemosyne\CacheTrait For the implementation of caching behavior | ||
* | ||
* @example | ||
* ```php | ||
* #[Cache(key: 'user:{id}', ttl: 3600, tags: ['user', 'user-{id}'])] | ||
* public function getUser(int $id): array | ||
* { | ||
* return $this->cacheCall('doGetUser', func_get_args()); | ||
* } | ||
* ``` | ||
* | ||
* @author Carlos Matos <[email protected]> | ||
*/ | ||
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION)] | ||
class Cache | ||
{ | ||
/** | ||
* Configure caching behavior for a method. | ||
* | ||
* @param string|null $key Cache key template. Supports parameter interpolation using {param} syntax. | ||
* If null, a key will be auto-generated based on class, method and arguments. | ||
* @param int|null $ttl Time-to-live in seconds. If null, cache will not expire. | ||
* @param array $invalidates List of cache key templates to invalidate when this method is called. | ||
* @param array $tags List of tags to associate with this cache entry. Supports parameter interpolation. | ||
*/ | ||
public function __construct( | ||
public ?string $key = null, | ||
public ?int $ttl = null, | ||
public array $invalidates = [], | ||
public array $tags = [], | ||
) { | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,11 +4,67 @@ | |
|
||
use Psr\SimpleCache\CacheInterface; | ||
|
||
/** | ||
* Trait providing caching functionality for classes. | ||
* | ||
* This trait implements the caching behavior defined by the Cache attribute. | ||
* It handles cache key generation, storage, retrieval, and invalidation, | ||
* including support for cache tags. | ||
* | ||
* @see \Mnemosyne\Cache For the attribute that configures caching behavior | ||
* @see \Psr\SimpleCache\CacheInterface For the underlying cache implementation | ||
* | ||
* @example | ||
* ```php | ||
* class UserService | ||
* { | ||
* use CacheTrait; | ||
* | ||
* public function __construct(CacheInterface $cache) | ||
* { | ||
* $this->cache = $cache; | ||
* } | ||
* | ||
* #[Cache(key: 'user:{id}', ttl: 3600)] | ||
* public function getUser(int $id): array | ||
* { | ||
* return $this->cacheCall('doGetUser', func_get_args()); | ||
* } | ||
* | ||
* private function doGetUser(int $id): array | ||
* { | ||
* // Expensive operation here | ||
* return ['id' => $id, 'name' => 'John']; | ||
* } | ||
* } | ||
* ``` | ||
* | ||
* @author Carlos Matos <[email protected]> | ||
*/ | ||
trait CacheTrait | ||
{ | ||
private array $keyTemplates = []; | ||
/** @var CacheInterface PSR-16 cache implementation */ | ||
private CacheInterface $cache; | ||
|
||
/** @var array<string, string> Cache of compiled key templates */ | ||
private array $keyTemplates = []; | ||
|
||
/** | ||
* Handle caching for a method call. | ||
* | ||
* This method is responsible for: | ||
* - Retrieving cache configuration from the calling method's attributes | ||
* - Generating cache keys based on templates and parameters | ||
* - Handling cache invalidation | ||
* - Storing and retrieving cached values | ||
* - Managing cache tags | ||
* | ||
* @param string $method Name of the private method to call if cache misses | ||
* @param array $args Arguments to pass to the method | ||
* @return mixed The cached or freshly computed result | ||
* @throws \ReflectionException If the method doesn't exist | ||
* @throws \BadMethodCallException If the method is not accessible | ||
*/ | ||
private function cacheCall(string $method, array $args) | ||
{ | ||
// Get the public method that called us | ||
|
@@ -53,11 +109,35 @@ private function cacheCall(string $method, array $args) | |
// Cache the result if we have a key | ||
if ($key !== null) { | ||
$this->cache->set($key, $result, $config->ttl); | ||
|
||
// Store tags if any are defined | ||
if (!empty($config->tags)) { | ||
foreach ($config->tags as $tag) { | ||
$resolvedTag = $this->resolveCacheKey($tag, $reflection, $args); | ||
$tagKey = 'tag:' . $resolvedTag; | ||
$taggedKeys = $this->cache->get($tagKey) ?? []; | ||
if (!in_array($key, $taggedKeys)) { | ||
$taggedKeys[] = $key; | ||
$this->cache->set($tagKey, $taggedKeys); | ||
} | ||
} | ||
} | ||
} | ||
|
||
return $result; | ||
} | ||
|
||
/** | ||
* Resolve a cache key template using method parameters. | ||
* | ||
* Handles both automatic key generation (when template is null) and | ||
* parameter interpolation in key templates. | ||
* | ||
* @param string|null $template The key template with {param} placeholders | ||
* @param \ReflectionMethod $method The method being cached | ||
* @param array $args The arguments passed to the method | ||
* @return string|null The resolved cache key | ||
*/ | ||
private function resolveCacheKey(?string $template, \ReflectionMethod $method, array $args): ?string | ||
{ | ||
if ($template === null) { | ||
|
@@ -86,15 +166,47 @@ private function resolveCacheKey(?string $template, \ReflectionMethod $method, a | |
return $key; | ||
} | ||
|
||
/** | ||
* Invalidate a single cache key. | ||
* | ||
* @param string $key The cache key to invalidate | ||
*/ | ||
protected function invalidateCache(string $key): void | ||
{ | ||
$this->cache->delete($key); | ||
} | ||
|
||
/** | ||
* Invalidate multiple cache keys. | ||
* | ||
* @param array $keys List of cache keys to invalidate | ||
*/ | ||
protected function invalidateCacheKeys(array $keys): void | ||
{ | ||
foreach ($keys as $key) { | ||
$this->cache->delete($key); | ||
} | ||
} | ||
|
||
/** | ||
* Invalidate all cache entries associated with a tag. | ||
* | ||
* This will: | ||
* 1. Retrieve all cache keys associated with the tag | ||
* 2. Delete each cache entry | ||
* 3. Remove the tag registry itself | ||
* | ||
* @param string $tag The tag to invalidate | ||
*/ | ||
public function invalidateTag(string $tag): void | ||
{ | ||
$tagKey = 'tag:' . $tag; | ||
$taggedKeys = $this->cache->get($tagKey) ?? []; | ||
|
||
foreach ($taggedKeys as $key) { | ||
$this->cache->delete($key); | ||
} | ||
|
||
$this->cache->delete($tagKey); | ||
} | ||
} |
Oops, something went wrong.