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

diego-ninja / granite / 34018063255

06 Sep 2026 07:02AM UTC coverage: 84.549% (+1.2%) from 83.371%
34018063255

push

github

web-flow
Merge pull request #22 from diego-ninja/codex/fix-all-audit-findings

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

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

5
// ABOUTME: Combines Carbon property, class, and global configuration.
6
// ABOUTME: Produces one transformer with deterministic precedence rules.
7

8
declare(strict_types=1);
9

10
namespace Ninja\Granite\Serialization;
11

12
use Exception;
13
use Ninja\Granite\Config\GraniteConfig;
14
use Ninja\Granite\Serialization\Attributes\CarbonDate;
15
use Ninja\Granite\Serialization\Attributes\CarbonRange;
16
use Ninja\Granite\Serialization\Attributes\CarbonRelative;
17
use Ninja\Granite\Serialization\Attributes\DateTimeProvider;
18
use Ninja\Granite\Support\ReflectionCache;
19
use Ninja\Granite\Transformers\CarbonTransformer;
20
use ReflectionAttribute;
21
use ReflectionProperty;
22

23
final class CarbonTransformerFactory
24
{
25
    /** @var array<string, CarbonTransformer|null> */
26
    private static array $cache = [];
27

28
    /** @var array<class-string, DateTimeProvider|null> */
29
    private static array $classProviderCache = [];
30

31
    public static function create(
46 ✔
32
        ReflectionProperty $property,
33
        ?DateTimeProvider $classProvider = null,
34
    ): ?CarbonTransformer {
35
        $cacheKey = self::cacheKey($property, $classProvider);
46 ✔
36
        if (array_key_exists($cacheKey, self::$cache)) {
46 ✔
37
            return self::$cache[$cacheKey];
19 ✔
38
        }
39

40
        $carbonDate = self::attribute($property, CarbonDate::class);
28 ✔
41
        $carbonRange = self::attribute($property, CarbonRange::class);
28 ✔
42
        $carbonRelative = self::attribute($property, CarbonRelative::class);
28 ✔
43
        $config = GraniteConfig::getInstance();
28 ✔
44

45
        if (null === $carbonDate && null === $carbonRange && null === $carbonRelative
28 ✔
46
            && (null === $classProvider || ! $classProvider->isCarbonProvider())) {
28 ✔
47
            self::$cache[$cacheKey] = null;
20 ✔
48
            return self::$cache[$cacheKey];
20 ✔
49
        }
50

51
        $format = null !== $carbonDate ? $carbonDate->format : null;
8 ✔
52
        $format ??= null !== $classProvider ? $classProvider->format : $config->getCarbonParseFormat();
8 ✔
53
        $timezone = null !== $carbonDate ? $carbonDate->timezone : null;
8 ✔
54
        $timezone ??= null !== $classProvider ? $classProvider->timezone : $config->getCarbonTimezone();
8 ✔
55
        $locale = null !== $carbonDate ? $carbonDate->locale : null;
8 ✔
56
        $locale ??= null !== $classProvider ? $classProvider->locale : $config->getCarbonLocale();
8 ✔
57
        $serializeFormat = null !== $carbonDate ? $carbonDate->serializeFormat : null;
8 ✔
58
        $serializeFormat ??= null !== $classProvider ? $classProvider->serializeFormat : $config->getCarbonSerializeFormat();
8 ✔
59

60
        self::$cache[$cacheKey] = new CarbonTransformer(
8 ✔
61
            format: $format,
8 ✔
62
            timezone: $timezone,
8 ✔
63
            locale: $locale,
8 ✔
64
            immutable: null !== $carbonDate
8 ✔
65
                ? $carbonDate->immutable
4 ✔
66
                : (null !== $classProvider ? $classProvider->isCarbonImmutable() : $config->shouldPreferCarbonImmutable()),
8 ✔
67
            parseRelative: null !== $carbonDate
8 ✔
68
                ? $carbonDate->parseRelative
4 ✔
69
                : (null !== $carbonRelative
4 ✔
70
                    ? $carbonRelative->enabled
2 ✔
71
                    : (null !== $classProvider ? $classProvider->parseRelative : $config->isCarbonParseRelativeEnabled())),
8 ✔
72
            serializeFormat: $serializeFormat,
8 ✔
73
            serializeTimezone: null !== $carbonDate ? $carbonDate->serializeTimezone : $config->getCarbonSerializeTimezone(),
8 ✔
74
            min: null !== $carbonDate ? $carbonDate->min : (null !== $carbonRange ? $carbonRange->min : null),
8 ✔
75
            max: null !== $carbonDate ? $carbonDate->max : (null !== $carbonRange ? $carbonRange->max : null),
8 ✔
76
            relativeBaseDate: null !== $carbonRelative ? $carbonRelative->baseDate : null,
8 ✔
77
        );
8 ✔
78

79
        return self::$cache[$cacheKey];
8 ✔
80
    }
81

82
    public static function clearCache(): void
33 ✔
83
    {
84
        self::$cache = [];
33 ✔
85
        self::$classProviderCache = [];
33 ✔
86
    }
87

88
    /** @param class-string $class */
89
    public static function classProvider(string $class): ?DateTimeProvider
72 ✔
90
    {
91
        if (array_key_exists($class, self::$classProviderCache)) {
72 ✔
92
            return self::$classProviderCache[$class];
53 ✔
93
        }
94

95
        try {
96
            $reflection = ReflectionCache::getClass($class);
34 ✔
97
            $attributes = $reflection->getAttributes(DateTimeProvider::class, ReflectionAttribute::IS_INSTANCEOF);
34 ✔
98
            self::$classProviderCache[$class] = [] === $attributes ? null : $attributes[0]->newInstance();
34 ✔
NEW
99
        } catch (Exception) {
×
NEW
100
            self::$classProviderCache[$class] = null;
×
101
        }
102

103
        return self::$classProviderCache[$class];
34 ✔
104
    }
105

106
    /**
107
     * @template T of object
108
     * @param class-string<T> $attributeClass
109
     * @return T|null
110
     */
111
    private static function attribute(ReflectionProperty $property, string $attributeClass): ?object
28 ✔
112
    {
113
        $attributes = $property->getAttributes($attributeClass, ReflectionAttribute::IS_INSTANCEOF);
28 ✔
114
        if ([] === $attributes) {
28 ✔
115
            return null;
28 ✔
116
        }
117

118
        $attribute = $attributes[0]->newInstance();
7 ✔
119
        return $attribute instanceof $attributeClass ? $attribute : null;
7 ✔
120
    }
121

122
    private static function cacheKey(ReflectionProperty $property, ?DateTimeProvider $classProvider): string
46 ✔
123
    {
124
        return $property->getDeclaringClass()->getName()
46 ✔
125
            . '::' . $property->getName()
46 ✔
126
            . ':' . (null === $classProvider ? '' : serialize($classProvider));
46 ✔
127
    }
128
}
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