Skip to content

Commit 3dfa67f

Browse files
committed
chore(cache): improve invalidation logic
1 parent 3b3f5a3 commit 3dfa67f

File tree

12 files changed

+440
-192
lines changed

12 files changed

+440
-192
lines changed

.envrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
source_url "https://raw.githubusercontent.com/cachix/devenv/95f329d49a8a5289d31e0982652f7058a189bfca/direnvrc" "sha256-d+8cBpDfDBj41inrADaJt+bDWhOktwslgoP5YiGJ1v0="
2+
3+
use devenv

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,12 @@ build/
33
vendor/
44
.phpunit.result.cache
55
tests/cache
6+
# Devenv
7+
.devenv*
8+
devenv.local.nix
9+
10+
# direnv
11+
.direnv
12+
13+
# pre-commit
14+
.pre-commit-config.yaml

devenv.lock

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
{
2+
"nodes": {
3+
"devenv": {
4+
"locked": {
5+
"dir": "src/modules",
6+
"lastModified": 1720180409,
7+
"owner": "cachix",
8+
"repo": "devenv",
9+
"rev": "7b3ed618571f0d14655b05f7b1c6ef600904383a",
10+
"treeHash": "14b4b6bc32582a78300257c3b618d821557eb530",
11+
"type": "github"
12+
},
13+
"original": {
14+
"dir": "src/modules",
15+
"owner": "cachix",
16+
"repo": "devenv",
17+
"type": "github"
18+
}
19+
},
20+
"flake-compat": {
21+
"flake": false,
22+
"locked": {
23+
"lastModified": 1696426674,
24+
"owner": "edolstra",
25+
"repo": "flake-compat",
26+
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
27+
"treeHash": "2addb7b71a20a25ea74feeaf5c2f6a6b30898ecb",
28+
"type": "github"
29+
},
30+
"original": {
31+
"owner": "edolstra",
32+
"repo": "flake-compat",
33+
"type": "github"
34+
}
35+
},
36+
"gitignore": {
37+
"inputs": {
38+
"nixpkgs": [
39+
"pre-commit-hooks",
40+
"nixpkgs"
41+
]
42+
},
43+
"locked": {
44+
"lastModified": 1709087332,
45+
"owner": "hercules-ci",
46+
"repo": "gitignore.nix",
47+
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
48+
"treeHash": "ca14199cabdfe1a06a7b1654c76ed49100a689f9",
49+
"type": "github"
50+
},
51+
"original": {
52+
"owner": "hercules-ci",
53+
"repo": "gitignore.nix",
54+
"type": "github"
55+
}
56+
},
57+
"nixpkgs": {
58+
"locked": {
59+
"lastModified": 1716977621,
60+
"owner": "cachix",
61+
"repo": "devenv-nixpkgs",
62+
"rev": "4267e705586473d3e5c8d50299e71503f16a6fb6",
63+
"treeHash": "6d9f1f7ca0faf1bc2eeb397c78a49623260d3412",
64+
"type": "github"
65+
},
66+
"original": {
67+
"owner": "cachix",
68+
"ref": "rolling",
69+
"repo": "devenv-nixpkgs",
70+
"type": "github"
71+
}
72+
},
73+
"nixpkgs-stable": {
74+
"locked": {
75+
"lastModified": 1719957072,
76+
"owner": "NixOS",
77+
"repo": "nixpkgs",
78+
"rev": "7144d6241f02d171d25fba3edeaf15e0f2592105",
79+
"treeHash": "415bf0e03835797927c1b2cb46a557bcecc36673",
80+
"type": "github"
81+
},
82+
"original": {
83+
"owner": "NixOS",
84+
"ref": "nixos-23.11",
85+
"repo": "nixpkgs",
86+
"type": "github"
87+
}
88+
},
89+
"pre-commit-hooks": {
90+
"inputs": {
91+
"flake-compat": "flake-compat",
92+
"gitignore": "gitignore",
93+
"nixpkgs": [
94+
"nixpkgs"
95+
],
96+
"nixpkgs-stable": "nixpkgs-stable"
97+
},
98+
"locked": {
99+
"lastModified": 1719259945,
100+
"owner": "cachix",
101+
"repo": "pre-commit-hooks.nix",
102+
"rev": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07",
103+
"treeHash": "1a76ff89a9d4017b48abbb1bad8837b35d604ffc",
104+
"type": "github"
105+
},
106+
"original": {
107+
"owner": "cachix",
108+
"repo": "pre-commit-hooks.nix",
109+
"type": "github"
110+
}
111+
},
112+
"root": {
113+
"inputs": {
114+
"devenv": "devenv",
115+
"nixpkgs": "nixpkgs",
116+
"pre-commit-hooks": "pre-commit-hooks"
117+
}
118+
}
119+
},
120+
"root": "root",
121+
"version": 7
122+
}

devenv.nix

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{ pkgs, lib, config, inputs, ... }:
2+
3+
{
4+
languages.php = {
5+
enable = true;
6+
version = lib.mkDefault "8.2";
7+
extensions = [ "xdebug" ];
8+
9+
ini = ''
10+
memory_limit = -1
11+
opcache.enable = 1
12+
opcache.revalidate_freq = 0
13+
opcache.validate_timestamps = 1
14+
opcache.max_accelerated_files = 30000
15+
opcache.memory_consumption = 256M
16+
opcache.interned_strings_buffer = 20
17+
realpath_cache_ttl = 3600
18+
xdebug.idekey = "PHPSTORM"
19+
xdebug.start_with_request = "yes"
20+
zend.assertions = 1
21+
date.timezone = "Europe/Paris"
22+
xdebug.output_dir = ".devenv/state/xdebug"
23+
xdebug.mode = "off"
24+
'';
25+
};
26+
}

devenv.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
2+
inputs:
3+
nixpkgs:
4+
url: github:cachix/devenv-nixpkgs/rolling
5+
6+
# If you're using non-OSS software, you can set allowUnfree to true.
7+
# allowUnfree: true
8+
9+
# If you're willing to use a package that's vulnerable
10+
# permittedInsecurePackages:
11+
# - "openssl-1.1.1w"
12+
13+
# If you have more than one devenv you can merge them
14+
#imports:
15+
# - ./backend

src/Attribute/Cache/Tag.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace OpenClassrooms\ServiceProxy\Attribute\Cache;
4+
5+
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
6+
final class Tag
7+
{
8+
public function __construct(public readonly ?string $prefix = null)
9+
{
10+
11+
}
12+
}

src/Interceptor/Contract/Cache/AutoTaggable.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,4 @@
66

77
interface AutoTaggable
88
{
9-
public function getId(): string|int;
109
}

src/Interceptor/Impl/CacheInterceptor.php

Lines changed: 11 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@
99
use OpenClassrooms\ServiceProxy\Helper\TypesExtractor;
1010
use OpenClassrooms\ServiceProxy\Interceptor\Config\CacheInterceptorConfig;
1111
use OpenClassrooms\ServiceProxy\Interceptor\Contract\AbstractInterceptor;
12-
use OpenClassrooms\ServiceProxy\Interceptor\Contract\Cache\AutoTaggable;
1312
use OpenClassrooms\ServiceProxy\Interceptor\Contract\PrefixInterceptor;
1413
use OpenClassrooms\ServiceProxy\Interceptor\Contract\SuffixInterceptor;
1514
use OpenClassrooms\ServiceProxy\Interceptor\Exception\InternalCodeRetrievalException;
1615
use OpenClassrooms\ServiceProxy\Model\Request\Instance;
1716
use OpenClassrooms\ServiceProxy\Model\Response\Response;
18-
use OpenClassrooms\ServiceProxy\Util\Expression;
1917
use Symfony\Component\PropertyInfo\Type;
2018

2119
final class CacheInterceptor extends AbstractInterceptor implements SuffixInterceptor, PrefixInterceptor
2220
{
21+
use CacheTagsTrait;
22+
2323
private const DEFAULT_POOL_NAME = 'default';
2424

2525
/**
@@ -46,16 +46,18 @@ public function __construct(
4646
/**
4747
* @return array<int, string>
4848
*/
49-
public static function getHits(?string $poolName = self::DEFAULT_POOL_NAME): array
49+
public static function getHits(?string $poolName = null): array
5050
{
51+
$poolName ??= self::DEFAULT_POOL_NAME;
5152
return self::$hits[$poolName] ?? [];
5253
}
5354

5455
/**
5556
* @return array<int, string>
5657
*/
57-
public static function getMisses(?string $poolName = self::DEFAULT_POOL_NAME): array
58+
public static function getMisses(?string $poolName = null): array
5859
{
60+
$poolName ??= self::DEFAULT_POOL_NAME;
5961
return self::$misses[$poolName] ?? [];
6062
}
6163

@@ -103,18 +105,19 @@ public function prefix(Instance $instance): Response
103105

104106
self::$hits[$pool] = self::$hits[$pool] ?? [];
105107
self::$hits[$pool][] = $cacheKey;
106-
108+
$data = $data->get();
109+
$tags = $this->getTags($instance, $attribute, $data);
107110
foreach ($missedPools as $missedPool) {
108111
$handler->save(
109112
$missedPool,
110113
$cacheKey,
111-
$data->get(),
114+
$data,
112115
$attribute->ttl ?? $this->config->defaultTtl,
113-
$this->getTags($instance, $attribute, $data)
116+
$tags
114117
);
115118
}
116119

117-
return new Response($data->get(), true);
120+
return new Response($data, true);
118121
}
119122

120123
return new Response(null, false);
@@ -272,107 +275,6 @@ private function getInnerCode(\ReflectionMethod|\ReflectionClass $reflection): s
272275
return $code;
273276
}
274277

275-
/**
276-
* @return array<int, string>
277-
*/
278-
private function getTags(Instance $instance, Cache $attribute, mixed $response = null): array
279-
{
280-
$parameters = $instance->getMethod()
281-
->getParameters();
282-
283-
$tags = array_map(
284-
static fn (string $expression) => Expression::evaluateToString($expression, $parameters),
285-
$attribute->tags
286-
);
287-
288-
if ($response !== null) {
289-
$tags = array_values(array_filter([
290-
...$tags,
291-
...$this->guessObjectsTags(
292-
$response,
293-
$this->config->autoTagsExcludedClasses
294-
),
295-
]));
296-
}
297-
298-
return $tags;
299-
}
300-
301-
/**
302-
* @param array<class-string> $excludedClasses
303-
* @param array<string, string> $registeredTags
304-
*
305-
* @return array<string, string>
306-
*/
307-
private function guessObjectsTags(mixed $object, array $excludedClasses = [], array $registeredTags = []): array
308-
{
309-
if (!\is_object($object) && !is_iterable($object)) {
310-
return $registeredTags;
311-
}
312-
313-
foreach ($excludedClasses as $excludedClass) {
314-
if ($object instanceof $excludedClass) {
315-
return $registeredTags;
316-
}
317-
}
318-
319-
if (is_iterable($object)) {
320-
foreach ($object as $item) {
321-
$registeredTags = $this->guessObjectsTags($item, $excludedClasses, $registeredTags);
322-
}
323-
324-
return $registeredTags;
325-
}
326-
327-
if (!$object instanceof AutoTaggable) {
328-
return $registeredTags;
329-
}
330-
331-
$tag = $this->buildTag($object);
332-
333-
if (isset($registeredTags[$tag])) {
334-
return $registeredTags;
335-
}
336-
337-
$registeredTags[$tag] = $tag;
338-
339-
$ref = new \ReflectionClass($object);
340-
341-
foreach ($ref->getProperties() as $propRef) {
342-
$subObject = $this->getPropertyValue($ref, $object, $propRef->getName());
343-
344-
$registeredTags = $this->guessObjectsTags($subObject, $excludedClasses, $registeredTags);
345-
}
346-
347-
return $registeredTags;
348-
}
349-
350-
private function buildTag(AutoTaggable $object): string
351-
{
352-
return str_replace('\\', '.', \get_class($object)) . '.' . $object->getId();
353-
}
354-
355-
/**
356-
* @param \ReflectionClass<object> $ref
357-
*/
358-
private function getPropertyValue(\ReflectionClass $ref, object $object, string $propertyName): mixed
359-
{
360-
$getter = 'get' . ucfirst($propertyName);
361-
$refMethod = $ref->hasMethod($getter) ? $ref->getMethod($getter) : null;
362-
if ($refMethod !== null && $refMethod->isPublic() && \count($refMethod->getParameters()) === 0) {
363-
return $refMethod->invoke($object);
364-
}
365-
366-
$propRef = $ref->getProperty($propertyName);
367-
if (!$propRef->isInitialized($object)) {
368-
return null;
369-
}
370-
371-
$propRef->setAccessible(true);
372-
373-
return $propRef->getValue($object);
374-
}
375-
376278
/**
377279
* @param array<string, mixed> $parameters
378280
*/

0 commit comments

Comments
 (0)