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

tempestphp / tempest-framework / 14024978163

23 Mar 2025 05:55PM UTC coverage: 79.391% (-0.05%) from 79.441%
14024978163

push

github

web-flow
feat(view): cache Blade and Twig templates in internal storage (#1061)

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

912 existing lines in 110 files now uncovered.

10478 of 13198 relevant lines covered (79.39%)

91.09 hits per line

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

95.83
/src/Tempest/Validation/src/Validator.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Tempest\Validation;
6

7
use Closure;
8
use Tempest\Reflection\ClassReflector;
9
use Tempest\Reflection\PropertyReflector;
10
use Tempest\Validation\Exceptions\ValidationException;
11
use Tempest\Validation\Rules\IsBoolean;
12
use Tempest\Validation\Rules\IsFloat;
13
use Tempest\Validation\Rules\IsInteger;
14
use Tempest\Validation\Rules\IsString;
15
use Tempest\Validation\Rules\NotNull;
16
use TypeError;
17

18
use function Tempest\Support\arr;
19

20
final readonly class Validator
21
{
22
    public function validateObject(object $object): void
1✔
23
    {
24
        $class = new ClassReflector($object);
1✔
25

26
        $failingRules = [];
1✔
27

28
        foreach ($class->getPublicProperties() as $property) {
1✔
29
            if (! $property->isInitialized($object)) {
1✔
UNCOV
30
                continue;
×
31
            }
32

33
            $value = $property->getValue($object);
1✔
34

35
            $failingRules[$property->getName()] = $this->validateValueForProperty($property, $value);
1✔
36
        }
37

38
        if ($failingRules !== []) {
1✔
39
            throw new ValidationException($object, $failingRules);
1✔
40
        }
41
    }
42

43
    /**
44
     * @param ClassReflector|class-string $class
45
     */
46
    public function validateValuesForClass(ClassReflector|string $class, ?array $values, string $prefix = ''): array
18✔
47
    {
48
        $class = is_string($class) ? new ClassReflector($class) : $class;
18✔
49

50
        $failingRules = [];
18✔
51

52
        $values = arr($values)->undot();
18✔
53

54
        foreach ($class->getPublicProperties() as $property) {
18✔
55
            if ($property->hasAttribute(SkipValidation::class)) {
18✔
56
                continue;
8✔
57
            }
58

59
            $key = $prefix . $property->getName();
17✔
60

61
            if (! $values->has($key) && $property->hasDefaultValue()) {
17✔
62
                continue;
2✔
63
            }
64

65
            $value = $values->get($key);
17✔
66

67
            $failingRulesForProperty = $this->validateValueForProperty($property, $value);
17✔
68

69
            if ($failingRulesForProperty !== []) {
17✔
70
                $failingRules[$key] = $failingRulesForProperty;
11✔
71
            }
72

73
            if ($property->isNullable() && $value === null) {
17✔
74
                continue;
3✔
75
            }
76

77
            if ($property->getType()->isClass()) {
17✔
78
                $failingRules = [
7✔
79
                    ...$failingRules,
7✔
80
                    ...$this->validateValuesForClass(
7✔
81
                        class: $property->getType()->asClass(),
7✔
82
                        values: $values->dot()->toArray(),
7✔
83
                        prefix: $key . '.',
7✔
84
                    ),
7✔
85
                ];
7✔
86
            }
87
        }
88

89
        return $failingRules;
18✔
90
    }
91

92
    public function validateValueForProperty(PropertyReflector $property, mixed $value): array
18✔
93
    {
94
        $rules = $property->getAttributes(Rule::class);
18✔
95

96
        if ($property->getType()->isScalar()) {
18✔
97
            $rules[] = match ($property->getType()->getName()) {
17✔
98
                'string' => new IsString(orNull: $property->isNullable()),
12✔
99
                'int' => new IsInteger(orNull: $property->isNullable()),
5✔
100
                'float' => new IsFloat(orNull: $property->isNullable()),
1✔
101
                'bool' => new IsBoolean(orNull: $property->isNullable()),
1✔
UNCOV
102
                default => null,
×
103
            };
17✔
104
        } elseif (! $property->isNullable()) {
10✔
105
            // We only add the NotNull rule if we're not dealing with scalar types, since the null check is included in the scalar rules
106
            $rules[] = new NotNull();
8✔
107
        }
108

109
        return $this->validateValue($value, $rules);
18✔
110
    }
111

112
    public function validateValue(mixed $value, Closure|Rule|array $rules): array
24✔
113
    {
114
        $failingRules = [];
24✔
115

116
        foreach (arr($rules) as $rule) {
24✔
117
            if (! $rule) {
24✔
UNCOV
118
                continue;
×
119
            }
120

121
            $rule = $this->convertToRule($rule, $value);
24✔
122

123
            if (! $rule->isValid($value)) {
24✔
124
                $failingRules[] = $rule;
15✔
125
            }
126
        }
127

128
        return $failingRules;
24✔
129
    }
130

131
    private function convertToRule(Rule|Closure $rule, mixed $value): Rule
24✔
132
    {
133
        if ($rule instanceof Rule) {
24✔
134
            return $rule;
19✔
135
        }
136

137
        $result = $rule($value);
5✔
138

139
        [$isValid, $message] = match (true) {
5✔
140
            is_string($result) => [false, $result],
5✔
141
            $result === false => [false, 'Value did not pass validation.'],
4✔
142
            default => [true, ''],
5✔
143
        };
5✔
144

145
        return new readonly class($isValid, $message) implements Rule {
5✔
146
            public function __construct(
147
                private bool $isValid,
148
                private string $message,
149
            ) {
150
            }
5✔
151

152
            public function isValid(mixed $value): bool
153
            {
154
                return $this->isValid;
5✔
155
            }
156

157
            public function message(): string
158
            {
159
                return $this->message;
1✔
160
            }
161
        };
5✔
162
    }
163
}
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