Passed
Push — master ( e84e4b...dd6318 )
by Smoren
02:33
created

Form::create()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 1
c 1
b 0
f 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Smoren\Validator\Models;
6
7
use Smoren\Validator\Exceptions\FormInvalidConfigError;
8
use Smoren\Validator\Exceptions\FormValidationError;
9
use Smoren\Validator\Exceptions\ValidationError;
10
use Smoren\Validator\Factories\Value;
11
use Smoren\Validator\Interfaces\FormInterface;
12
use Smoren\Validator\Interfaces\MixedRuleInterface;
13
14
abstract class Form implements FormInterface
15
{
16
    /**
17
     * @var array<string>
18
     */
19
    protected array $allowedAttributes = [];
20
    /**
21
     * @var array<string, MixedRuleInterface>
22
     */
23
    protected array $attributeRules = [];
24
25
    /**
26
     * {@inheritDoc}
27
     */
28
    public static function create(array $source): FormInterface
29
    {
30
        return new static($source);
31
    }
32
33
    /**
34
     * {@inheritDoc}
35
     */
36
    public function validate(): void
37
    {
38
        $errorMap = [];
39
        foreach ($this->getAttributes() as $attrName => $attrValue) {
40
            try {
41
                $this->attributeRules[$attrName]->validate($attrValue);
42
            } catch (ValidationError $e) {
43
                $errorMap[$attrName] = $e;
44
            }
45
        }
46
        if (\count($errorMap) > 0) {
47
            throw new FormValidationError(static::class, $errorMap);
48
        }
49
    }
50
51
    /**
52
     * {@inheritDoc}
53
     */
54
    public function getAttributes(): array
55
    {
56
        $result = [];
57
        foreach ($this->allowedAttributes as $attrName) {
58
            $result[$attrName] = $this->{$attrName};
59
        }
60
        return $result;
61
    }
62
63
    /**
64
     * {@inheritDoc}
65
     */
66
    public function setAttributes(array $source): void
67
    {
68
        foreach ($this->allowedAttributes as $attrName) {
69
            if (\array_key_exists($attrName, $source)) {
70
                $this->{$attrName} = $source[$attrName];
71
            }
72
        }
73
    }
74
75
    /**
76
     * @return array<array{array<string>, MixedRuleInterface}>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array{array<string>, MixedRuleInterface}> at position 4 could not be parsed: Expected ':' at position 4, but found 'array'.
Loading history...
77
     */
78
    abstract protected function getRules(): array;
79
80
    /**
81
     * @return void
82
     */
83
    protected function registerAttributes(): void
84
    {
85
        $reflector = new \ReflectionClass($this);
86
        foreach ($reflector->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) {
87
            $attrName = $prop->getName();
88
            $this->allowedAttributes[] = $attrName;
89
        }
90
    }
91
92
    /**
93
     * @return void
94
     */
95
    protected function registerRules(): void
96
    {
97
        /** @var array<string, array<MixedRuleInterface>> $attributeRulesMap */
98
        $attributeRulesMap = [];
99
        foreach ($this->allowedAttributes as $attrName) {
100
            $attributeRulesMap[$attrName] = [];
101
        }
102
103
        foreach ($this->getRules() as [$attrNames, $rule]) {
104
            if (!($rule instanceof MixedRuleInterface)) {
105
                $className = MixedRuleInterface::class;
106
                throw new FormInvalidConfigError(
107
                    "Some rule is not an instance of '{$className}'",
108
                    static::class
109
                );
110
            }
111
112
            foreach ($attrNames as $attrName) {
113
                if (!\array_key_exists($attrName, $attributeRulesMap)) {
114
                    throw new FormInvalidConfigError(
115
                        "Attribute '{$attrName}' is not allowed in form",
116
                        static::class
117
                    );
118
                }
119
                $attributeRulesMap[$attrName][] = $rule;
120
            }
121
        }
122
123
        foreach ($attributeRulesMap as $attrName => $rules) {
124
            if (\count($rules) === 1) {
125
                $this->attributeRules[$attrName] = $rules[0];
126
            } elseif (\count($rules) > 1) {
127
                $this->attributeRules[$attrName] = Value::allOf($rules);
128
            } else {
129
                throw new FormInvalidConfigError(
130
                    "No rules specified for attribute '{$attrName}'",
131
                    static::class
132
                );
133
            }
134
        }
135
    }
136
137
    /**
138
     * @param array<string, mixed> $source
139
     */
140
    final protected function __construct(array $source)
141
    {
142
        $this->registerAttributes();
143
        $this->registerRules();
144
        $this->setAttributes($source);
145
    }
146
}
147