forked from API-Skeletons/doctrine-orm-graphql
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathQueryResultCache.php
More file actions
135 lines (112 loc) · 3.32 KB
/
QueryResultCache.php
File metadata and controls
135 lines (112 loc) · 3.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
<?php
declare(strict_types=1);
namespace ApiSkeletons\Doctrine\ORM\GraphQL\Cache;
use Doctrine\ORM\Query;
use function count;
use function implode;
use function is_array;
use function md5;
use function serialize;
/**
* Request-scoped cache for query results.
*
* Caches query results based on SQL + parameters signature to prevent
* duplicate database queries within a single GraphQL request. This is
* particularly useful for:
* - Circular references in the graph
* - Queries accessing the same entity multiple times
* - Duplicate association queries
*
* The cache is stored in memory and is automatically cleared after
* the request completes.
*/
final class QueryResultCache
{
/** @var array<string, mixed[]> */
private array $cache = [];
/** @var array<string, int> */
private array $hits = [];
/** @var array<string, int> */
private array $misses = [];
/**
* Get cached results for a query if available
*
* @return mixed[]|null Returns cached results or null if not cached
*/
public function get(Query $query): array|null
{
$cacheKey = $this->getCacheKey($query);
if (isset($this->cache[$cacheKey])) {
$this->hits[$cacheKey] = ($this->hits[$cacheKey] ?? 0) + 1;
return $this->cache[$cacheKey];
}
$this->misses[$cacheKey] = ($this->misses[$cacheKey] ?? 0) + 1;
return null;
}
/**
* Store query results in cache
*
* @param mixed[] $results
*/
public function set(Query $query, array $results): void
{
$cacheKey = $this->getCacheKey($query);
$this->cache[$cacheKey] = $results;
}
/**
* Check if query results are cached
*/
public function has(Query $query): bool
{
return isset($this->cache[$this->getCacheKey($query)]);
}
/**
* Clear all cached results
*/
public function clear(): void
{
$this->cache = [];
$this->hits = [];
$this->misses = [];
}
/**
* Get cache statistics
*
* @return array{size: int, hits: int, misses: int, hitRate: float}
*/
public function getStats(): array
{
$totalHits = 0;
$totalMisses = 0;
foreach ($this->hits as $hits) {
$totalHits += $hits;
}
foreach ($this->misses as $misses) {
$totalMisses += $misses;
}
$totalRequests = $totalHits + $totalMisses;
$hitRate = $totalRequests > 0 ? $totalHits / $totalRequests : 0.0;
return [
'size' => count($this->cache),
'hits' => $totalHits,
'misses' => $totalMisses,
'hitRate' => $hitRate,
];
}
/**
* Generate cache key from query SQL and parameters
*/
private function getCacheKey(Query $query): string
{
$sql = $query->getSQL();
$sql = is_array($sql) ? implode(';', $sql) : $sql;
$parameters = $query->getParameters()->toArray();
// Normalize parameters for consistent cache keys
$normalizedParams = [];
foreach ($parameters as $param) {
/** @psalm-suppress MixedAssignment */
$normalizedParams[$param->getName()] = $param->getValue();
}
return md5($sql . serialize($normalizedParams));
}
}