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

diego-ninja / granite / 34017904626

06 Sep 2026 06:58AM UTC coverage: 84.549% (+1.2%) from 83.371%
34017904626

Pull #22

github

web-flow
Merge 837e19ed1 into f40f1b915
Pull Request #22: fix: harden object pipelines and improve performance

822 of 986 new or added lines in 49 files covered. (83.37%)

14 existing lines in 13 files now uncovered.

3513 of 4155 relevant lines covered (84.55%)

16.56 hits per line

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

69.44
/src/Serialization/SerializationCachePolicy.php
1
<?php
2
// ABOUTME: Defines SerializationCachePolicy as part of the serialization and date metadata pipeline.
3
// ABOUTME: Owns the SerializationCachePolicy boundary between metadata and serialized values.
4

5
// ABOUTME: Determines whether a Granite object graph is safe to cache.
6
// ABOUTME: Rejects mutable containers and temporal values conservatively.
7

8
declare(strict_types=1);
9

10
namespace Ninja\Granite\Serialization;
11

12
use DateTimeImmutable;
13
use DateTimeInterface;
14
use Ninja\Granite\Contracts\GraniteObject;
15
use Ninja\Granite\Support\ReflectionCache;
16
use ReflectionNamedType;
17
use ReflectionType;
18
use ReflectionUnionType;
19
use UnitEnum;
20

21
final class SerializationCachePolicy
22
{
23
    /**
24
     * @var array<int, true>
25
     */
26
    private array $visited = [];
27

28
    public static function isCacheable(object $instance): bool
33 ✔
29
    {
30
        return (new self())->isCacheableObject($instance);
33 ✔
31
    }
32

33
    private function isCacheableObject(object $instance): bool
33 ✔
34
    {
35
        if ($instance instanceof DateTimeImmutable) {
33 ✔
NEW
36
            return true;
×
37
        }
38

39
        if ( ! $instance instanceof GraniteObject) {
33 ✔
NEW
40
            return false;
×
41
        }
42

43
        $objectId = spl_object_id($instance);
33 ✔
44
        if (isset($this->visited[$objectId])) {
33 ✔
NEW
45
            return true;
×
46
        }
47
        $this->visited[$objectId] = true;
33 ✔
48

49
        foreach (ReflectionCache::getPublicProperties($instance::class) as $property) {
33 ✔
50
            if ($this->declaresUnsupportedType($property->getType())) {
33 ✔
51
                return false;
1 ✔
52
            }
53

54
            if ( ! $property->isInitialized($instance)) {
33 ✔
55
                continue;
9 ✔
56
            }
57

58
            if ( ! $this->isCacheableValue($property->getValue($instance))) {
33 ✔
NEW
59
                return false;
×
60
            }
61
        }
62

63
        return true;
32 ✔
64
    }
65

66
    private function isCacheableValue(mixed $value): bool
33 ✔
67
    {
68
        if (null === $value || is_scalar($value) || $value instanceof UnitEnum) {
33 ✔
69
            return true;
33 ✔
70
        }
71

72
        if ($value instanceof DateTimeImmutable) {
1 ✔
NEW
73
            return true;
×
74
        }
75

76
        if ($value instanceof DateTimeInterface || is_array($value)) {
1 ✔
NEW
77
            return false;
×
78
        }
79

80
        return is_object($value) && $this->isCacheableObject($value);
1 ✔
81
    }
82

83
    private function declaresUnsupportedType(?ReflectionType $type): bool
33 ✔
84
    {
85
        if (null === $type) {
33 ✔
NEW
86
            return true;
×
87
        }
88

89
        if ($type instanceof ReflectionUnionType) {
33 ✔
NEW
90
            foreach ($type->getTypes() as $namedType) {
×
NEW
91
                if ($namedType instanceof ReflectionNamedType && 'mixed' === $namedType->getName()) {
×
NEW
92
                    return true;
×
93
                }
94
            }
95

NEW
96
            return false;
×
97
        }
98

99
        return ! $type instanceof ReflectionNamedType || 'mixed' === $type->getName();
33 ✔
100
    }
101
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc