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

tempestphp / tempest-framework / 14370002535

09 Apr 2025 12:32PM UTC coverage: 81.363% (+0.04%) from 81.323%
14370002535

push

github

web-flow
feat(database): model validation before update, create, and save (#1131)

36 of 38 new or added lines in 4 files covered. (94.74%)

3 existing lines in 1 file now uncovered.

11525 of 14165 relevant lines covered (81.36%)

106.7 hits per line

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

95.95
/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\IsEnum;
13
use Tempest\Validation\Rules\IsFloat;
14
use Tempest\Validation\Rules\IsInteger;
15
use Tempest\Validation\Rules\IsString;
16
use Tempest\Validation\Rules\NotNull;
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
22✔
47
    {
48
        $class = is_string($class) ? new ClassReflector($class) : $class;
22✔
49

50
        $failingRules = [];
22✔
51

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

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

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

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

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

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

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

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

77
            if ($property->getType()->isClass() && ! $property->getType()->isEnum()) {
21✔
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;
22✔
90
    }
91

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

96
        if ($property->getType()->isScalar()) {
89✔
97
            $rules[] = match ($property->getType()->getName()) {
85✔
98
                'string' => new IsString(orNull: $property->isNullable()),
76✔
99
                'int' => new IsInteger(orNull: $property->isNullable()),
10✔
100
                'float' => new IsFloat(orNull: $property->isNullable()),
2✔
101
                'bool' => new IsBoolean(orNull: $property->isNullable()),
2✔
UNCOV
102
                default => null,
×
103
            };
85✔
104
        } elseif (! $property->isNullable()) {
47✔
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();
29✔
107
        }
108

109
        if ($property->getType()->isEnum()) {
89✔
110
            $rules[] = new IsEnum($property->getType()->getName());
10✔
111
        }
112

113
        return $this->validateValue($value, $rules);
89✔
114
    }
115

116
    public function validateValue(mixed $value, Closure|Rule|array $rules): array
95✔
117
    {
118
        $failingRules = [];
95✔
119

120
        foreach (arr($rules) as $rule) {
95✔
121
            if (! $rule) {
95✔
UNCOV
122
                continue;
×
123
            }
124

125
            $rule = $this->convertToRule($rule, $value);
95✔
126

127
            if (! $rule->isValid($value)) {
95✔
128
                $failingRules[] = $rule;
21✔
129
            }
130
        }
131

132
        return $failingRules;
95✔
133
    }
134

135
    private function convertToRule(Rule|Closure $rule, mixed $value): Rule
95✔
136
    {
137
        if ($rule instanceof Rule) {
95✔
138
            return $rule;
90✔
139
        }
140

141
        $result = $rule($value);
5✔
142

143
        [$isValid, $message] = match (true) {
5✔
144
            is_string($result) => [false, $result],
5✔
145
            $result === false => [false, 'Value did not pass validation.'],
4✔
146
            default => [true, ''],
5✔
147
        };
5✔
148

149
        return new class($isValid, $message) implements Rule {
5✔
150
            public function __construct(
151
                private bool $isValid,
152
                private string $message,
153
            ) {}
5✔
154

155
            public function isValid(mixed $value): bool
156
            {
157
                return $this->isValid;
5✔
158
            }
159

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

© 2025 Coveralls, Inc