ValidationTrait::processRules()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 6
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 18
rs 10
1
<?php
2
3
namespace bTokman\Validation;
4
5
use Symfony\Component\Validator\{
6
    Constraint,
7
    ConstraintViolation,
8
    ConstraintViolationListInterface,
9
    Exception\LogicException,
10
    Exception\UnexpectedTypeException,
11
    Validation,
12
    Validator\ValidatorInterface
13
};
14
15
/**
16
 * Trait ValidationTrait
17
 * @package bTokman\Validation
18
 */
19
trait ValidationTrait
20
{
21
    /**
22
     * List of rules (Symfony validation)
23
     *
24
     * [
25
     *  'propertyName' => [NotBlank::class],
26
     *  'propertyName' => [Assert\Length::class, ["min" => 3]],
27
     *  'propertyName' => [NotBlank::class],
28
     * ]
29
     *
30
     * @var array
31
     */
32
    public $validationRules = [];
33
34
    /**
35
     * List of errors
36
     * [
37
     *   'propertyName' => ['Error message1', 'Error message2', ...]
38
     * ]
39
     *
40
     * @var array
41
     */
42
    public $validationErrors = [];
43
44
    /**
45
     * Validate any model by Symfony validation
46
     *
47
     * @link https://symfony.com/doc/current/components/validator.html
48
     *
49
     * @return array|null
50
     * @throws \ReflectionException | UnexpectedTypeException
51
     */
52
    public function validate(): ?array
53
    {
54
        foreach ($this->validationRules as $field => $rules) {
55
            if (!\is_array($rules)) {
56
                throw new UnexpectedTypeException($field, 'array');
57
            }
58
59
            $this->processRules($rules, $field);
60
        }
61
62
        return \count($this->validationErrors) ? $this->validationErrors : null;
63
    }
64
65
    /**
66
     * Process list of rules and validate class properties
67
     *
68
     * @param array $rules
69
     * @param string $field
70
     * @throws \ReflectionException
71
     */
72
    private function processRules(array $rules, string $field)
73
    {
74
        /** Create new Symfony validator instance */
75
        $validator = $this->getValidator();
76
77
        /** Build an array of Symfony validation constraints */
78
        $validators = $this->buildValidation($rules);
79
80
        /** Get property value  */
81
        $property = $this->getProperty($field);
82
83
        /** Validate property value
84
         * @link https://symfony.com/doc/master/validation/raw_values.html
85
         */
86
        $errors = $validator->validate($property, $validators);
87
88
        if (\count($errors)) {
89
            $this->processErrors($errors, $field);
90
        }
91
    }
92
93
    /**
94
     * @return ValidatorInterface
95
     */
96
    protected function getValidator(): ValidatorInterface
97
    {
98
        return Validation::createValidator();
99
    }
100
101
    /**
102
     * Build array of validation constraint from rules
103
     *
104
     * @param $rules
105
     * @return array
106
     */
107
    private function buildValidation($rules): array
108
    {
109
        $validators = [];
110
111
        foreach ($rules as $rule) {
112
            if (is_array($rule)) {
113
                $class = array_shift($rule);
114
                $object = new $class(...$rule);
115
            } else {
116
                $object = new $rule();
117
            }
118
119
            if (!$object instanceof Constraint) {
120
                throw new LogicException('Rule not allowed - ' . $rules[0]);
121
            }
122
123
            $validators[] = $object;
124
        }
125
126
        return $validators;
127
    }
128
129
    /**
130
     * Get child class property
131
     *
132
     * @param string $name
133
     * @return mixed
134
     * @throws \ReflectionException
135
     */
136
    private function getProperty(string $name)
137
    {
138
        $property = new \ReflectionProperty(static::class, $name);
139
        $property->setAccessible(true);
140
141
        return $property->getValue($this) ?? false;
142
    }
143
144
    /**
145
     * Generate output format.Example : [
146
     *  'name' => [
147
     *              "This value should not be blank.",
148
     *              "This value is too short. It should have 2 characters or more."
149
     *            ]
150
     * ]
151
     *
152
     * @param ConstraintViolationListInterface $errors
153
     * @param $field
154
     */
155
    private function processErrors(ConstraintViolationListInterface $errors, $field): void
156
    {
157
        foreach ($errors as $error) {
158
            /** @var ConstraintViolation $error */
159
            $this->validationErrors[$field][] = $error->getMessage();
160
        }
161
    }
162
}