1
|
|
|
<?php namespace GeneaLabs\LaravelModelCaching; |
2
|
|
|
|
3
|
|
|
use Exception; |
4
|
|
|
use GeneaLabs\LaravelModelCaching\Traits\CachePrefixing; |
5
|
|
|
use Illuminate\Support\Arr; |
6
|
|
|
use Illuminate\Support\Collection; |
7
|
|
|
use Illuminate\Support\Str; |
8
|
|
|
use Ramsey\Uuid\Uuid; |
9
|
|
|
|
10
|
|
|
class CacheKey |
11
|
|
|
{ |
12
|
|
|
use CachePrefixing; |
13
|
|
|
|
14
|
|
|
protected $currentBinding = 0; |
15
|
|
|
protected $eagerLoad; |
16
|
|
|
protected $macroKey; |
17
|
|
|
protected $model; |
18
|
|
|
protected $query; |
19
|
|
|
|
20
|
|
|
public function __construct( |
21
|
|
|
array $eagerLoad, |
22
|
|
|
$model, |
23
|
|
|
$query, |
24
|
|
|
$macroKey |
25
|
|
|
) { |
26
|
|
|
$this->eagerLoad = $eagerLoad; |
27
|
|
|
$this->macroKey = $macroKey; |
28
|
|
|
$this->model = $model; |
29
|
|
|
$this->query = $query; |
30
|
|
|
} |
31
|
|
|
|
32
|
|
|
public function make( |
33
|
|
|
array $columns = ["*"], |
34
|
|
|
$idColumn = null, |
35
|
|
|
string $keyDifferentiator = "" |
36
|
|
|
) : string { |
37
|
|
|
$key = $this->getCachePrefix(); |
38
|
|
|
$key .= $this->getTableSlug(); |
39
|
|
|
$key .= $this->getModelSlug(); |
40
|
|
|
$key .= $this->getIdColumn($idColumn ?: ""); |
41
|
|
|
$key .= $this->getQueryColumns($columns); |
42
|
|
|
$key .= $this->getWhereClauses(); |
43
|
|
|
$key .= $this->getWithModels(); |
44
|
|
|
$key .= $this->getOrderByClauses(); |
45
|
|
|
$key .= $this->getOffsetClause(); |
46
|
|
|
$key .= $this->getLimitClause(); |
47
|
|
|
$key .= $this->getBindingsSlug(); |
48
|
|
|
$key .= $keyDifferentiator; |
49
|
|
|
$key .= $this->macroKey; |
50
|
|
|
// dump($key); |
51
|
|
|
return $key; |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
protected function getIdColumn(string $idColumn) : string |
55
|
|
|
{ |
56
|
|
|
return $idColumn ? "_{$idColumn}" : ""; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
protected function getLimitClause() : string |
60
|
|
|
{ |
61
|
|
|
if (! property_exists($this->query, "limit") |
62
|
|
|
|| ! $this->query->limit |
63
|
|
|
) { |
64
|
|
|
return ""; |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
return "-limit_{$this->query->limit}"; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
protected function getTableSlug() : string |
71
|
|
|
{ |
72
|
|
|
return (new Str)->slug($this->model->getTable()) |
73
|
|
|
. ":"; |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
protected function getModelSlug() : string |
77
|
|
|
{ |
78
|
|
|
return (new Str)->slug(get_class($this->model)); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
protected function getOffsetClause() : string |
82
|
|
|
{ |
83
|
|
|
if (! property_exists($this->query, "offset") |
84
|
|
|
|| ! $this->query->offset |
85
|
|
|
) { |
86
|
|
|
return ""; |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
return "-offset_{$this->query->offset}"; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
protected function getOrderByClauses() : string |
93
|
|
|
{ |
94
|
|
|
if (! property_exists($this->query, "orders") |
95
|
|
|
|| ! $this->query->orders |
96
|
|
|
) { |
97
|
|
|
return ""; |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
$orders = collect($this->query->orders); |
101
|
|
|
|
102
|
|
|
return $orders |
103
|
|
|
->reduce(function ($carry, $order) { |
104
|
|
|
if (($order["type"] ?? "") === "Raw") { |
105
|
|
|
return $carry . "_orderByRaw_" . (new Str)->slug($order["sql"]); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
return $carry . "_orderBy_" . $order["column"] . "_" . $order["direction"]; |
109
|
|
|
}) |
110
|
|
|
?: ""; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
protected function getQueryColumns(array $columns) : string |
114
|
|
|
{ |
115
|
|
|
if (($columns === ["*"] |
116
|
|
|
|| $columns === []) |
117
|
|
|
&& (! property_exists($this->query, "columns") |
118
|
|
|
|| ! $this->query->columns) |
119
|
|
|
) { |
120
|
|
|
return ""; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
if (property_exists($this->query, "columns") |
124
|
|
|
&& $this->query->columns |
125
|
|
|
) { |
126
|
|
|
return "_" . implode("_", $this->query->columns); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
return "_" . implode("_", $columns); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
protected function getTypeClause($where) : string |
133
|
|
|
{ |
134
|
|
|
$type = in_array($where["type"], ["InRaw", "In", "NotIn", "Null", "NotNull", "between", "NotInSub", "InSub", "JsonContains"]) |
135
|
|
|
? strtolower($where["type"]) |
136
|
|
|
: strtolower($where["operator"]); |
137
|
|
|
|
138
|
|
|
return str_replace(" ", "_", $type); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
protected function getValuesClause(array $where = []) : string |
142
|
|
|
{ |
143
|
|
|
if (! $where |
|
|
|
|
144
|
|
|
|| in_array($where["type"], ["NotNull", "Null"]) |
145
|
|
|
) { |
146
|
|
|
return ""; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
$values = $this->getValuesFromWhere($where); |
150
|
|
|
$values = $this->getValuesFromBindings($where, $values); |
151
|
|
|
|
152
|
|
|
return "_" . $values; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
protected function getValuesFromWhere(array $where) : string |
156
|
|
|
{ |
157
|
|
|
if (array_key_exists("value", $where) |
158
|
|
|
&& is_object($where["value"]) |
159
|
|
|
&& get_class($where["value"]) === "DateTime" |
160
|
|
|
) { |
161
|
|
|
return $where["value"]->format("Y-m-d-H-i-s"); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
View Code Duplication |
if (is_array((new Arr)->get($where, "values"))) { |
|
|
|
|
165
|
|
|
return implode("_", collect($where["values"])->flatten()->toArray()); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
View Code Duplication |
if (is_array((new Arr)->get($where, "value"))) { |
|
|
|
|
169
|
|
|
return implode("_", collect($where["value"])->flatten()->toArray()); |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
return (new Arr)->get($where, "value", ""); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
protected function getValuesFromBindings(array $where, string $values) : string |
176
|
|
|
{ |
177
|
|
|
if (($this->query->bindings["where"][$this->currentBinding] ?? false) !== false) { |
178
|
|
|
$values = $this->query->bindings["where"][$this->currentBinding]; |
179
|
|
|
$this->currentBinding++; |
180
|
|
|
|
181
|
|
|
if ($where["type"] === "between") { |
182
|
|
|
$values .= "_" . $this->query->bindings["where"][$this->currentBinding]; |
183
|
|
|
$this->currentBinding++; |
184
|
|
|
} |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
if (is_object($values) |
188
|
|
|
&& get_class($values) === "DateTime" |
189
|
|
|
) { |
190
|
|
|
$values = $values->format("Y-m-d-H-i-s"); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
return $values; |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
protected function getWhereClauses(array $wheres = []) : string |
197
|
|
|
{ |
198
|
|
|
return "" . $this->getWheres($wheres) |
199
|
|
|
->reduce(function ($carry, $where) { |
200
|
|
|
$value = $carry; |
201
|
|
|
$value .= $this->getNestedClauses($where); |
202
|
|
|
$value .= $this->getColumnClauses($where); |
203
|
|
|
$value .= $this->getRawClauses($where); |
204
|
|
|
$value .= $this->getInAndNotInClauses($where); |
205
|
|
|
$value .= $this->getOtherClauses($where); |
206
|
|
|
|
207
|
|
|
return $value; |
208
|
|
|
}); |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
protected function getNestedClauses(array $where) : string |
212
|
|
|
{ |
213
|
|
|
if (! in_array($where["type"], ["Exists", "Nested", "NotExists"])) { |
214
|
|
|
return ""; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
return "-" . strtolower($where["type"]) . $this->getWhereClauses($where["query"]->wheres); |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
protected function getColumnClauses(array $where) : string |
221
|
|
|
{ |
222
|
|
|
if ($where["type"] !== "Column") { |
223
|
|
|
return ""; |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
return "-{$where["boolean"]}_{$where["first"]}_{$where["operator"]}_{$where["second"]}"; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
protected function getInAndNotInClauses(array $where) : string |
230
|
|
|
{ |
231
|
|
|
if (! in_array($where["type"], ["In", "NotIn", "InRaw"])) { |
232
|
|
|
return ""; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
$type = strtolower($where["type"]); |
236
|
|
|
$subquery = $this->getValuesFromWhere($where); |
237
|
|
|
$values = collect($this->query->bindings["where"][$this->currentBinding] ?? []); |
238
|
|
|
|
239
|
|
|
if (Str::startsWith($subquery, $values->first())) { |
240
|
|
|
$this->currentBinding += count($where["values"]); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
if (! is_numeric($subquery) && ! is_numeric(str_replace("_", "", $subquery))) { |
244
|
|
|
try { |
245
|
|
|
$subquery = Uuid::fromBytes($subquery); |
246
|
|
|
$values = $this->recursiveImplode([$subquery], "_"); |
247
|
|
|
|
248
|
|
|
return "-{$where["column"]}_{$type}{$values}"; |
249
|
|
|
} catch (Exception $exception) { |
250
|
|
|
// do nothing |
251
|
|
|
} |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
$subquery = preg_replace('/\?(?=(?:[^"]*"[^"]*")*[^"]*\Z)/m', "_??_", $subquery); |
255
|
|
|
$subquery = collect(vsprintf(str_replace("_??_", "%s", $subquery), $values->toArray())); |
256
|
|
|
$values = $this->recursiveImplode($subquery->toArray(), "_"); |
257
|
|
|
|
258
|
|
|
return "-{$where["column"]}_{$type}{$values}"; |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
protected function recursiveImplode(array $items, string $glue = ",") : string |
262
|
|
|
{ |
263
|
|
|
$result = ""; |
264
|
|
|
|
265
|
|
|
foreach ($items as $value) { |
266
|
|
|
if (is_string($value)) { |
267
|
|
|
$value = str_replace('"', '', $value); |
268
|
|
|
$value = explode(" ", $value); |
269
|
|
|
|
270
|
|
|
if (count($value) === 1) { |
271
|
|
|
$value = $value[0]; |
272
|
|
|
} |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
if (is_array($value)) { |
276
|
|
|
$result .= $this->recursiveImplode($value, $glue); |
277
|
|
|
|
278
|
|
|
continue; |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
$result .= $glue . $value; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
return $result; |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
protected function getRawClauses(array $where) : string |
288
|
|
|
{ |
289
|
|
|
if (! in_array($where["type"], ["raw"])) { |
290
|
|
|
return ""; |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
$queryParts = explode("?", $where["sql"]); |
294
|
|
|
$clause = "_{$where["boolean"]}"; |
295
|
|
|
|
296
|
|
|
while (count($queryParts) > 1) { |
297
|
|
|
$clause .= "_" . array_shift($queryParts); |
298
|
|
|
$clause .= $this->query->bindings["where"][$this->currentBinding]; |
299
|
|
|
$this->currentBinding++; |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
$lastPart = array_shift($queryParts); |
303
|
|
|
|
304
|
|
|
if ($lastPart) { |
305
|
|
|
$clause .= "_" . $lastPart; |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
return "-" . str_replace(" ", "_", $clause); |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
protected function getOtherClauses(array $where) : string |
312
|
|
|
{ |
313
|
|
|
if (in_array($where["type"], ["Exists", "Nested", "NotExists", "Column", "raw", "In", "NotIn", "InRaw"])) { |
314
|
|
|
return ""; |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
$value = $this->getTypeClause($where); |
318
|
|
|
$value .= $this->getValuesClause($where); |
319
|
|
|
|
320
|
|
|
return "-{$where["column"]}_{$value}"; |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
protected function getWheres(array $wheres) : Collection |
324
|
|
|
{ |
325
|
|
|
$wheres = collect($wheres); |
326
|
|
|
|
327
|
|
|
if ($wheres->isEmpty() |
328
|
|
|
&& property_exists($this->query, "wheres") |
329
|
|
|
) { |
330
|
|
|
$wheres = collect($this->query->wheres); |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
return $wheres; |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
protected function getWithModels() : string |
337
|
|
|
{ |
338
|
|
|
$eagerLoads = collect($this->eagerLoad); |
339
|
|
|
|
340
|
|
|
if ($eagerLoads->isEmpty()) { |
341
|
|
|
return ""; |
342
|
|
|
} |
343
|
|
|
|
344
|
|
|
return $eagerLoads->keys()->reduce(function ($carry, $related) { |
345
|
|
|
if (! method_exists($this->model, $related)) { |
346
|
|
|
return "{$carry}-{$related}"; |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
$relatedModel = $this->model->$related()->getRelated(); |
350
|
|
|
$relatedConnection = $relatedModel->getConnection()->getName(); |
351
|
|
|
$relatedDatabase = $relatedModel->getConnection()->getDatabaseName(); |
352
|
|
|
|
353
|
|
|
return "{$carry}-{$relatedConnection}:{$relatedDatabase}:{$related}"; |
354
|
|
|
}); |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
protected function getBindingsSlug() : string |
358
|
|
|
{ |
359
|
|
|
if (! method_exists($this->model, 'query')) { |
360
|
|
|
return ''; |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
return Arr::query($this->model->query()->getBindings()); |
364
|
|
|
} |
365
|
|
|
} |
366
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.