Passed
Push — master ( 2213e3...df12fd )
by Vladimir
06:23
created

ValueObject::validateParameter()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 2
cts 2
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 3
crap 2
1
<?php
2
3
namespace ValueObject;
4
5
use Symfony\Component\Validator\ConstraintViolation;
6
use Symfony\Component\Validator\ConstraintViolationList;
7
use Symfony\Component\Validator\Validation;
8
use ValueObject\Exception\ParameterNotFoundException;
9
use ValueObject\Exception\ValidationException;
10
use Symfony\Component\Validator\Validator\ValidatorInterface;
11
12
abstract class ValueObject
13
{
14
    /**
15
     * This array contains VO parameters values which is already validated.
16
     *
17
     * @var array $params Validated params, which are available through magic method getParameterNameInCamelCase.
18
     */
19
    private $params = [];
20
21
    /**
22
     * This array contains validation errors.
23
     *
24
     * @var array $errors Validation errors.
25
     */
26
    private $errors = [];
27
28
    /**
29
     * Symfony validator instance.
30
     *
31
     * @var ValidatorInterface
32
     */
33
    private $validator;
34
35
    /**
36
     * Returns validation rules.
37
     *
38
     * This rules describes REQUIRED parameters.
39
     * Optional parameter must be resolved outside VO instance,
40
     * so only in this way we can ensure that VO have all parameters and they all valid.
41
     *
42
     * @return array Array with validation rules.
43
     */
44
    abstract protected function getRules();
45
46
    /**
47
     * Constructor.
48
     *
49 10
     * Most important method.
50
     * Here we validate received parameters against rules defined in method getRules,
51 10
     * and call after validation lifecycle callback.
52 10
     *
53
     * @param array $params Parameters being validated.
54
     *
55 10
     * @throws ValidationException In case when validation failed.
56 6
     */
57
    public function __construct(array $params)
58 4
    {
59
        // Initiate instance of Symfony validator.
60
        $this->validator = Validation::createValidator();
61
62
        $this->validate($params);
63
        $this->afterValidation($params);
64
        // In case when we obtain invalid parameter we throw error here,
65 10
        // this approach ensures that our VO have only valid parameters.
66
        if (0 !== count($this->errors)) {
67
            throw new ValidationException($this->errors);
68 10
        }
69
    }
70
71 10
    /**
72
     * Most important method. Here performs all validation stuff.
73
     *
74
     * @param array $params Parameters being validated.
75
     */
76
    private function validate(array $params)
77
    {
78 10
        /** @var array $rules */
79 10
        foreach ($this->getRules() as $paramName => $rules) {
80
            $constraints = $this->getConstraints($rules);
81
            // In the beginning of validation we must be sure that required parameter is passed to VO,
82 10
            // otherwise it will be first validation error.
83 10
            if (isset($params[$paramName])) {
84 10
                $value = $params[$paramName];
85
                // Performs Symfony validation.
86 10
                $this->validateParameter($paramName, $value, $constraints);
87 10
            } else {
88
                // We can not validate this parameter against validation rules,
89
                // because this parameter was not passed to VO.
90
                $this->setError($paramName, 'This parameter is required.');
91
            }
92 10
        }
93 9
    }
94
95 9
    /**
96 9
     * Gets validation constraints out from VO rules.
97
     *
98
     * One validation rule (parameter $rules) can have lot of constraints (Symfony constraints).
99
     * We must check them all,
100 9
     * so that's why here we build array with appropriate constraints for particular validation rule.
101
     * This array contains only constraints for only one validation rule,
102
     * for only one parameter which must be validated.
103
     *
104
     * @param array $rules Array of validation rules.
105 9
     *
106
     * @return array
107
     */
108
    private function getConstraints(array $rules)
109
    {
110 10
        $constraints = [];
111
        foreach ($rules as $constraintName => $options) {
112
            $constraintOptions = $options;
113
            // Constraint can be specified in simple way,
114 10
            // like string which is constraint name (for example: `'NotBlank'` etc).
115
            // Or as array with parameters, like:
116
            // array kes - is constraint name
117
            // and array value - constrain options (for example: `'Length' => ['min' => 5]`).
118
            if (!is_array($options)) {
119
                $constraintName = $options;
120
                $constraintOptions = [];
121
            }
122 6
            $constraintClassName = 'Symfony\Component\Validator\Constraints\\' . $constraintName;
123
            $constraints[] = new $constraintClassName($constraintOptions);
124 6
        }
125 6
126
        return $constraints;
127 2
    }
128
129
    /**
130 4
     * Validate parameter value against bunch of constraints.
131
     *
132 6
     * @param string $paramName Parameter name.
133
     * @param string $value Parameter value.
134
     * @param array $constraints Array of validation constraints.
135
     */
136
    private function validateParameter($paramName, $value, array $constraints)
137
    {
138
        $violations = $this->validator->validate($value, $constraints);
139
        if (0 === count($violations)) {
140
            // Parameter valid,
141
            // and only in this case we can store this parameter inside VO.
142 3
            // Thereby we ensure that our VO contains only valid parameters with only valid values.
143 3
            $this->params[$paramName] = $value;
144
        } else {
145
            // Parameter invalid,
146
            // so we can not store it inside VO,
147
            // we only can store error message about validation fail.
148
            $this->errors[$paramName] = $violations;
149
        }
150
    }
151
152
    /**
153
     * Sets error message for certain parameter.
154
     *
155 2
     * @param string $paramName Parameter name (key in array $parameters which is passed to __constructor method).
156
     * @param string $message Custom error message.
157 2
     */
158 2
    public function setError($paramName, $message)
159 1
    {
160
        $violation = new ConstraintViolation($message, '', [], '', $paramName, null);
161 1
        if (isset($this->errors[$paramName])) {
162
            // Adds error message to ConstraintViolationList.
163
            $this->errors[$paramName]->add($violation);
164
        } else {
165
            // Creates ConstraintViolationList and puts into it first error message.
166
            $this->errors[$paramName] = new ConstraintViolationList([$violation]);
167
        }
168
    }
169 1
170
    /**
171 1
     * Lifecycle callback - after validation.
172
     *
173
     * This method can be used for custom validation purposes.
174
     * This method will be called each time after validation rules (validate method).
175
     *
176
     * @param array $params Array with parameters passed to VO.
177
     */
178
    public function afterValidation(array $params)
0 ignored issues
show
Unused Code introduced by
The parameter $params is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
179
    {}
180
181
    /**
182
     * Returns parameter value by parameter name.
183
     *
184
     * @param string $name Parameter name (key in array $parameters which is passed to __constructor method).
185
     * @param array $arguments Array with arguments.
186
     *
187
     * @throws ParameterNotFoundException In case when VO don't have needed parameter.
188
     *
189
     * @return mixed Parameter value.
190
     */
191
    public function __call($name, array $arguments)
192
    {
193
        $paramName = lcfirst(substr($name, 3));
194
        if (!isset($this->params[$paramName])) {
195
            throw new ParameterNotFoundException("Parameter: $name not found by name: $paramName.");
196
        }
197
198
        return $this->params[$paramName];
199
    }
200
201
    /**
202
     * Gets VO parameter as array.
203
     *
204
     * @return array Array which contains all VO parameters.
205
     */
206
    public function toArray()
207
    {
208
        return $this->params;
209
    }
210
}
211