Test Setup Failed
Branch master (3f89d7)
by Mike
20:24
created

CacheKey::getTypeClause()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
c 0
b 0
f 0
rs 10
cc 2
nc 2
nop 1
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
0 ignored issues
show
Bug Best Practice introduced by
The expression $where of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
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"))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
165
            return implode("_", collect($where["values"])->flatten()->toArray());
166
        }
167
168 View Code Duplication
        if (is_array((new Arr)->get($where, "value"))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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