• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

valkyrjaio / valkyrja / 13049807881

30 Jan 2025 09:53AM UTC coverage: 47.589% (-0.03%) from 47.621%
13049807881

push

github

MelechMizrachi
PHP CS Fixer: Style fixes.

18 of 18 new or added lines in 5 files covered. (100.0%)

301 existing lines in 19 files now uncovered.

5201 of 10929 relevant lines covered (47.59%)

18.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Valkyrja/Orm/Repository/CacheRepository.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the Valkyrja Framework package.
7
 *
8
 * (c) Melech Mizrachi <melechmizrachi@gmail.com>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13

14
namespace Valkyrja\Orm\Repository;
15

16
use JsonException;
17
use Throwable;
18
use Valkyrja\Cache\Contract\Cache;
19
use Valkyrja\Cache\Driver\Contract\Driver as CacheDriver;
20
use Valkyrja\Exception\InvalidArgumentException;
21
use Valkyrja\Exception\RuntimeException;
22
use Valkyrja\Orm\Contract\Orm;
23
use Valkyrja\Orm\Driver\Contract\Driver;
24
use Valkyrja\Orm\Entity\Contract\Entity;
25
use Valkyrja\Orm\Exception\EntityNotFoundException;
26
use Valkyrja\Orm\Persister\Contract\Persister;
27
use Valkyrja\Orm\QueryBuilder\Contract\QueryBuilder;
28
use Valkyrja\Orm\Repository\Contract\CacheRepository as Contract;
29
use Valkyrja\Orm\Repository\Enum\StoreType;
30
use Valkyrja\Type\BuiltIn\Support\Arr;
31
use Valkyrja\Type\BuiltIn\Support\Obj;
32

33
use function base64_decode;
34
use function is_array;
35
use function is_int;
36
use function is_string;
37
use function md5;
38
use function method_exists;
39
use function serialize;
40
use function spl_object_id;
41
use function unserialize;
42

43
/**
44
 * Class CacheRepository.
45
 *
46
 * @author Melech Mizrachi
47
 *
48
 * @template Entity of Entity
49
 *
50
 * @extends Repository<Entity>
51
 *
52
 * @implements Contract<Entity>
53
 */
54
class CacheRepository extends Repository implements Contract
55
{
56
    /**
57
     * The cache store.
58
     *
59
     * @var CacheDriver
60
     */
61
    protected CacheDriver $store;
62

63
    /**
64
     * The id of a findOne (to tag if null returned).
65
     *
66
     * @var int|string|null
67
     */
68
    protected int|string|null $id;
69

70
    /**
71
     * The entities awaiting to be stored.
72
     *
73
     * @var Entity[]
74
     */
75
    protected array $storeEntities = [];
76

77
    /**
78
     * The entities awaiting to be forgotten.
79
     *
80
     * @var Entity[]
81
     */
82
    protected array $forgetEntities = [];
83

84
    /**
85
     * CacheRepository constructor.
86
     *
87
     * @param Orm                  $orm       The orm manager
88
     * @param Driver               $driver    The driver
89
     * @param Persister<Entity>    $persister The persister
90
     * @param Cache                $cache     The cache service
91
     * @param class-string<Entity> $entity    The entity class name
92
     */
93
    public function __construct(
94
        Orm $orm,
95
        Driver $driver,
96
        Persister $persister,
97
        protected Cache $cache,
98
        string $entity
99
    ) {
UNCOV
100
        $this->store = $cache->use();
×
101

102
        parent::__construct(
×
103
            orm: $orm,
×
104
            driver: $driver,
×
105
            persister: $persister,
×
106
            entity: $entity
×
UNCOV
107
        );
×
108
    }
109

110
    /**
111
     * @inheritDoc
112
     */
113
    public function findOne(int|string $id): static
114
    {
UNCOV
115
        parent::findOne($id);
×
116

UNCOV
117
        $this->id = $id;
×
118

UNCOV
119
        return $this;
×
120
    }
121

122
    /**
123
     * @inheritDoc
124
     */
125
    public function where(
126
        string $column,
127
        string|null $operator = null,
128
        mixed $value = null,
129
        bool $setType = true
130
    ): static {
131
        if (! ($value instanceof QueryBuilder) && $column === $this->entity::getIdField()) {
×
132
            if (! is_string($value) && ! is_int($value)) {
×
UNCOV
133
                throw new InvalidArgumentException('ID should be either a string or int');
×
134
            }
135

UNCOV
136
            $this->id = $value;
×
137
        }
138

UNCOV
139
        parent::where($column, $operator, $value, $setType);
×
140

UNCOV
141
        return $this;
×
142
    }
143

144
    /**
145
     * @inheritDoc
146
     *
147
     * @throws JsonException
148
     */
149
    public function getResult(): array
150
    {
UNCOV
151
        $cacheKey = $this->getCacheKey();
×
152

UNCOV
153
        if (($results = $this->store->get($cacheKey)) !== null && $results !== '') {
×
154
            try {
UNCOV
155
                $decodedResults = base64_decode($results, true);
×
156

157
                if ($decodedResults === false) {
×
UNCOV
158
                    throw new RuntimeException('Failed to decode results');
×
159
                }
160

UNCOV
161
                $results = unserialize($decodedResults, ['allowed_classes' => true]);
×
162

163
                if (! is_array($results)) {
×
UNCOV
164
                    throw new RuntimeException('Unserialized results were not an array');
×
165
                }
166

167
                if (method_exists($this, 'setRelationshipsOnEntities')) {
×
UNCOV
168
                    $this->setRelationshipsOnEntities(...$results);
×
169
                }
170

171
                return $results;
×
UNCOV
172
            } catch (Throwable) {
×
173
            }
174

175
            // Remove the bad cache
UNCOV
176
            $this->store->forget($cacheKey);
×
177
        }
178

UNCOV
179
        $results = $this->retriever->getResult();
×
180

UNCOV
181
        $this->cacheResults($cacheKey, $results);
×
182

183
        if (method_exists($this, 'setRelationshipsOnEntities')) {
×
UNCOV
184
            $this->setRelationshipsOnEntities(...$results);
×
185
        }
186

UNCOV
187
        $this->id = null;
×
188

UNCOV
189
        return $results;
×
190
    }
191

192
    /**
193
     * @inheritDoc
194
     */
195
    public function getOneOrFail(): Entity
196
    {
UNCOV
197
        $results = $this->getOneOrNull();
×
198

199
        if ($results === null) {
×
UNCOV
200
            throw new EntityNotFoundException('Entity Not Found');
×
201
        }
202

UNCOV
203
        return $results;
×
204
    }
205

206
    /**
207
     * @inheritDoc
208
     *
209
     * @throws JsonException
210
     */
211
    public function getCount(): int
212
    {
UNCOV
213
        $cacheKey = $this->getCacheKey();
×
214

215
        if (($results = $this->store->get($cacheKey)) !== null && $results !== '') {
×
UNCOV
216
            return (int) $results;
×
217
        }
218

UNCOV
219
        $results = parent::getCount();
×
220

UNCOV
221
        $this->store->forever($cacheKey, (string) $results);
×
222

UNCOV
223
        $this->store->getTagger($this->entity)->tag($cacheKey);
×
224

UNCOV
225
        return $results;
×
226
    }
227

228
    /**
229
     * @inheritDoc
230
     */
231
    public function create(Entity $entity, bool $defer = true): void
232
    {
UNCOV
233
        parent::create($entity, $defer);
×
234

UNCOV
235
        $this->deferOrCache(StoreType::store, $entity, $defer);
×
236
    }
237

238
    /**
239
     * @inheritDoc
240
     */
241
    public function save(Entity $entity, bool $defer = true): void
242
    {
UNCOV
243
        parent::save($entity, $defer);
×
244

UNCOV
245
        $this->deferOrCache(StoreType::store, $entity, $defer);
×
246
    }
247

248
    /**
249
     * @inheritDoc
250
     */
251
    public function delete(Entity $entity, bool $defer = true): void
252
    {
UNCOV
253
        parent::delete($entity, $defer);
×
254

UNCOV
255
        $this->deferOrCache(StoreType::forget, $entity, $defer);
×
256
    }
257

258
    /**
259
     * @inheritDoc
260
     */
261
    public function clear(Entity|null $entity = null): void
262
    {
UNCOV
263
        parent::clear($entity);
×
264

265
        if ($entity === null) {
×
UNCOV
266
            $this->clearDeferred();
×
267

UNCOV
268
            return;
×
269
        }
270

271
        // Get the id of the object
UNCOV
272
        $id = spl_object_id($entity);
×
273

274
        // If the model is set to be stored
UNCOV
275
        if (isset($this->storeEntities[$id])) {
×
276
            // Unset it
UNCOV
277
            unset($this->storeEntities[$id]);
×
278

UNCOV
279
            return;
×
280
        }
281

282
        // If the model is set to be forgotten
UNCOV
283
        if (isset($this->forgetEntities[$id])) {
×
284
            // Unset it
UNCOV
285
            unset($this->forgetEntities[$id]);
×
286
        }
287
    }
288

289
    /**
290
     * @inheritDoc
291
     */
292
    public function persist(): bool
293
    {
UNCOV
294
        $persist = parent::persist();
×
295

296
        $this->persistSave();
×
297
        $this->persistDelete();
×
UNCOV
298
        $this->clearDeferred();
×
299

UNCOV
300
        return $persist;
×
301
    }
302

303
    /**
304
     * Get cache key.
305
     *
306
     * @throws JsonException
307
     *
308
     * @return string
309
     */
310
    protected function getCacheKey(): string
311
    {
312
        return md5(
×
313
            Arr::toString(Obj::getAllProperties($this->retriever))
×
314
            . Arr::toString(Obj::getAllProperties($this->retriever->getQueryBuilder()))
×
UNCOV
315
        );
×
316
    }
317

318
    /**
319
     * Defer or cache.
320
     *
321
     * @param StoreType $type   Whether to store or forget
322
     * @param Entity    $entity The entity
323
     * @param bool      $defer  [optional] Whether to defer
324
     *
325
     * @return void
326
     */
327
    protected function deferOrCache(StoreType $type, Entity $entity, bool $defer = true): void
328
    {
329
        if ($defer) {
×
UNCOV
330
            $this->setDeferredEntity($type, $entity);
×
331

UNCOV
332
            return;
×
333
        }
334

UNCOV
335
        $this->forgetEntity($entity);
×
336
    }
337

338
    /**
339
     * Set a deferred entity.
340
     *
341
     * @param StoreType $type
342
     * @param Entity    $entity
343
     *
344
     * @return void
345
     */
346
    protected function setDeferredEntity(StoreType $type, Entity $entity): void
347
    {
UNCOV
348
        $id = spl_object_id($entity);
×
349

350
        match ($type) {
×
351
            StoreType::store  => $this->storeEntities[$id]  = $entity,
×
352
            StoreType::forget => $this->forgetEntities[$id] = $entity,
×
UNCOV
353
        };
×
354
    }
355

356
    /**
357
     * Forget entity in cache.
358
     *
359
     * @param Entity $entity
360
     *
361
     * @return void
362
     */
363
    protected function forgetEntity(Entity $entity): void
364
    {
UNCOV
365
        $id = $this->getEntityCacheKey($entity);
×
366

UNCOV
367
        $this->store->getTagger($id)->flush();
×
368
    }
369

370
    /**
371
     * Get entity cache key.
372
     *
373
     * @param Entity $entity
374
     *
375
     * @return string
376
     */
377
    protected function getEntityCacheKey(Entity $entity): string
378
    {
UNCOV
379
        return $entity::class . $entity->__get($entity::getIdField());
×
380
    }
381

382
    /**
383
     * Clear deferred entities.
384
     *
385
     * @return void
386
     */
387
    protected function clearDeferred(): void
388
    {
389
        $this->storeEntities  = [];
×
UNCOV
390
        $this->forgetEntities = [];
×
391
    }
392

393
    /**
394
     * Persist entities to be saved.
395
     *
396
     * @return void
397
     */
398
    protected function persistSave(): void
399
    {
400
        foreach ($this->storeEntities as $sid => $entity) {
×
UNCOV
401
            $this->forgetEntity($entity);
×
402

UNCOV
403
            unset($this->storeEntities[$sid]);
×
404
        }
405
    }
406

407
    /**
408
     * Persist entities to be deleted.
409
     *
410
     * @return void
411
     */
412
    protected function persistDelete(): void
413
    {
414
        foreach ($this->forgetEntities as $sid => $entity) {
×
UNCOV
415
            $this->forgetEntity($entity);
×
416

UNCOV
417
            unset($this->forgetEntities[$sid]);
×
418
        }
419
    }
420

421
    /**
422
     * Cache results.
423
     *
424
     * @param string                   $cacheKey
425
     * @param int|Entity|Entity[]|null $results
426
     *
427
     * @return void
428
     */
429
    protected function cacheResults(string $cacheKey, Entity|array|int|null $results): void
430
    {
431
        $id     = $this->id;
×
432
        $tags   = $id !== null && $id !== ''
×
433
            ? [(string) $this->id]
×
434
            : [];
×
UNCOV
435
        $tags[] = $this->entity;
×
436

437
        if (is_array($results)) {
×
UNCOV
438
            $tags = [];
×
439

440
            foreach ($results as $result) {
×
UNCOV
441
                $tags[] = $this->getEntityCacheKey($result);
×
442
            }
443
        }
444

UNCOV
445
        $this->store->forever($cacheKey, base64_encode(serialize($results)));
×
446

UNCOV
447
        $this->store->getTagger(...$tags)->tag($cacheKey);
×
448
    }
449
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc