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

tempestphp / tempest-framework / 14346656354

08 Apr 2025 01:08PM UTC coverage: 81.323% (+0.06%) from 81.259%
14346656354

push

github

web-flow
ci: fix isolated testing

11491 of 14130 relevant lines covered (81.32%)

105.9 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

17
use function Tempest\Support\arr;
18

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

25
        $failingRules = [];
1✔
26

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

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

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

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

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

49
        $failingRules = [];
19✔
50

51
        $values = arr($values)->undot();
19✔
52

53
        foreach ($class->getPublicProperties() as $property) {
19✔
54
            if ($property->hasAttribute(SkipValidation::class)) {
19✔
55
                continue;
10✔
56
            }
57

58
            $key = $prefix . $property->getName();
18✔
59

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

64
            $value = $values->get($key);
18✔
65

66
            $failingRulesForProperty = $this->validateValueForProperty($property, $value);
18✔
67

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

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

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

88
        return $failingRules;
19✔
89
    }
90

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

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

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

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

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

120
            $rule = $this->convertToRule($rule, $value);
25✔
121

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

127
        return $failingRules;
25✔
128
    }
129

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

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

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

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

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

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