Completed
Pull Request — master (#61)
by
unknown
01:35
created

ValueValidator   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 303
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 99.11%

Importance

Changes 0
Metric Value
dl 0
loc 303
ccs 111
cts 112
cp 0.9911
c 0
b 0
f 0
wmc 42
lcom 1
cbo 4
rs 9.0399

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 18 4
A setLabel() 0 6 1
B add() 0 25 8
A addMultiple() 0 18 3
A addRule() 0 7 1
A remove() 0 12 2
B parseRule() 0 31 8
B validate() 0 37 9
A validateRule() 0 9 2
A getMessages() 0 4 1
A addMessage() 0 6 1
A getRules() 0 4 1
A isEmpty() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like ValueValidator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ValueValidator, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Sirius\Validation;
4
5
use Sirius\Validation\Rule\AbstractRule;
6
7
class ValueValidator
8
{
9
10
    /**
11
     * The error messages generated after validation or set manually
12
     *
13
     * @var array
14
     */
15
    protected $messages = array();
16
17
    /**
18
     * Will be used to construct the rules
19
     *
20
     * @var \Sirius\Validation\RuleFactory
21
     */
22
    protected $ruleFactory;
23
24
    /**
25
     * The prototype that will be used to generate the error message
26
     *
27
     * @var \Sirius\Validation\ErrorMessage
28
     */
29
    protected $errorMessagePrototype;
30
31
    /**
32
     * The rule collections for the validation
33
     *
34
     * @var \Sirius\Validation\RuleCollection
35
     */
36
    protected $rules;
37
38
    /**
39
     * The label of the value to be validated
40
     *
41
     * @var string
42
     */
43
    protected $label;
44
45
46 24
    public function __construct(
47
        RuleFactory $ruleFactory = null,
48
        ErrorMessage $errorMessagePrototype = null,
49
        $label = null
50
    ) {
51 24
        if (!$ruleFactory) {
52 7
            $ruleFactory = new RuleFactory();
53 7
        }
54 24
        $this->ruleFactory = $ruleFactory;
55 24
        if (!$errorMessagePrototype) {
56 7
            $errorMessagePrototype = new ErrorMessage();
57 7
        }
58 24
        $this->errorMessagePrototype = $errorMessagePrototype;
59 24
        if ($label) {
60 2
            $this->label = $label;
61 2
        }
62 24
        $this->rules = new RuleCollection;
63 24
    }
64
65 1
    public function setLabel($label = null)
66
    {
67 1
        $this->label = $label;
68
69 1
        return $this;
70
    }
71
72
    /**
73
     * Add 1 or more validation rules
74
     *
75
     * @example
76
     * // add multiple rules at once
77
     * $validator->add(array(
78
     *   'required',
79
     *   array('required', array('email', null, '{label} must be an email', 'Field B')),
80
     * ));
81
     *
82
     * // add multiple rules using a string
83
     * $validator->add('required | email');
84
     *
85
     * // add validator with options
86
     * $validator->add('minlength', array('min' => 2), '{label} should have at least {min} characters', 'Field label');
87
     *
88
     * // add validator with string and parameters as JSON string
89
     * $validator->add('minlength({"min": 2})({label} should have at least {min} characters)(Field label)');
90
     *
91
     * // add validator with string and parameters as query string
92
     * $validator->add('minlength(min=2)({label} should have at least {min} characters)(Field label)');
93
     *
94
     * @param string|callback $name
95
     * @param string|array $options
96
     * @param string $messageTemplate
97
     * @param string $label
98
     *
99
     * @return ValueValidator
100
     */
101 24
    public function add($name, $options = null, $messageTemplate = null, $label = null)
102
    {
103 24
        if (is_array($name) && !is_callable($name)) {
104
            return $this->addMultiple($name);
105
        }
106 24
        if (is_string($name)) {
107
            // rule was supplied like 'required | email'
108 24
            if (strpos($name, ' | ') !== false) {
109 4
                return $this->addMultiple(explode(' | ', $name));
110
            }
111
            // rule was supplied like this 'length(2,10)(error message template)(label)'
112 24
            if (strpos($name, '(') !== false) {
113 7
                list($name, $options, $messageTemplate, $label) = $this->parseRule($name);
114 7
            }
115 24
        }
116
117
        // check for the default label
118 24
        if (!$label && $this->label) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $label of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
119 2
            $label = $this->label;
120 2
        }
121
122 24
        $validator = $this->ruleFactory->createRule($name, $options, $messageTemplate, $label);
123
124 22
        return $this->addRule($validator);
125
    }
126
127
    /**
128
     * @param array $rules
129
     *
130
     * @return ValueValidator
131
     */
132 4
    public function addMultiple($rules)
133
    {
134 4
        foreach ($rules as $singleRule) {
135
            // make sure the rule is an array (the parameters of subsequent calls);
136 4
            $singleRule = is_array($singleRule) ? $singleRule : array(
137
                $singleRule
138 4
            );
139 4
            call_user_func_array(
140
                array(
141 4
                    $this,
142
                    'add'
143 4
                ),
144
                $singleRule
145 4
            );
146 4
        }
147
148 4
        return $this;
149
    }
150
151
    /**
152
     * @param AbstractValidator $validationRule
153
     *
154
     * @return ValueValidator
155
     */
156 22
    public function addRule(AbstractRule $validationRule)
157
    {
158 22
        $validationRule->setErrorMessagePrototype($this->errorMessagePrototype);
159 22
        $this->rules->attach($validationRule);
160
161 22
        return $this;
162
    }
163
164
    /**
165
     * Remove validation rule
166
     *
167
     * @param mixed $name
168
     *            rule name or true if all rules should be deleted for that selector
169
     * @param mixed $options
170
     *            rule options, necessary for rules that depend on params for their ID
171
     *
172
     * @throws \InvalidArgumentException
173
     * @internal param string $selector data selector
174
     * @return self
175
     */
176 4
    public function remove($name = true, $options = null)
177
    {
178 4
        if ($name === true) {
179 2
            $this->rules = new RuleCollection();
180
181 2
            return $this;
182
        }
183 2
        $validator = $this->ruleFactory->createRule($name, $options);
0 ignored issues
show
Documentation introduced by
$name is of type *, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
184 2
        $this->rules->detach($validator);
185
186 2
        return $this;
187
    }
188
189
    /**
190
     * Converts a rule that was supplied as string into a set of options that define the rule
191
     *
192
     * @example 'minLength({"min":2})({label} must have at least {min} characters)(Street)'
193
     *
194
     *          will be converted into
195
     *
196
     *          array(
197
     *          'minLength', // validator name
198
     *          array('min' => 2'), // validator options
199
     *          '{label} must have at least {min} characters',
200
     *          'Street' // label
201
     *          )
202
     *
203
     * @param string $ruleAsString
204
     *
205
     * @return array
206
     */
207 7
    protected function parseRule($ruleAsString)
208
    {
209 7
        $ruleAsString    = trim($ruleAsString);
210 7
        $options         = array();
211 7
        $messageTemplate = null;
212 7
        $label           = null;
213
214 7
        $name         = substr($ruleAsString, 0, strpos($ruleAsString, '('));
215 7
        $ruleAsString = substr($ruleAsString, strpos($ruleAsString, '('));
216 7
        $matches      = array();
217 7
        preg_match_all('/\(([^\)]*)\)/', $ruleAsString, $matches);
218
219 7
        if (isset($matches[1])) {
220 7
            if (isset($matches[1][0]) && $matches[1][0] !== '') {
221 7
                $options = $matches[1][0];
222 7
            }
223 7
            if (isset($matches[1][1]) && $matches[1][1]) {
224 2
                $messageTemplate = $matches[1][1];
225 2
            }
226 7
            if (isset($matches[1][2]) && $matches[1][2]) {
227 2
                $label = $matches[1][2];
228 2
            }
229 7
        }
230
231
        return array(
232 7
            $name,
233 7
            $options,
234 7
            $messageTemplate,
235
            $label
236 7
        );
237
    }
238
239
240 22
    public function validate($value, $valueIdentifier = null, DataWrapper\WrapperInterface $context = null)
241
    {
242 22
        $this->messages = array();
243 22
        $isRequired     = false;
244
245
        // evaluate the required rules
246
        /* @var $rule \Sirius\Validation\Rule\AbstractValidator */
247 22
        foreach ($this->rules as $rule) {
248 22
            if ($rule instanceof Rule\Required) {
249 15
                $isRequired = true;
250
251 15
                if (!$this->validateRule($rule, $value, $valueIdentifier, $context)) {
252 9
                    return false;
253
                }
254 10
            }
255 20
        }
256
257
        // avoid future rule evaluations if value is null or empty string
258 20
        if ($this->isEmpty($value)) {
259 7
            return true;
260
        }
261
262
        // evaluate the non-required rules
263 15
        foreach ($this->rules as $rule) {
264 15
            if (!($rule instanceof Rule\Required)) {
265 15
                $this->validateRule($rule, $value, $valueIdentifier, $context);
266
267
                // if field is required and we have an error,
268
                // do not continue with the rest of rules
269 15
                if ($isRequired && count($this->messages)) {
270 8
                    break;
271
                }
272 10
            }
273 15
        }
274
275 15
        return count($this->messages) === 0;
276
    }
277
278 21
    private function validateRule($rule, $value, $valueIdentifier, $context)
279
    {
280 21
        $rule->setContext($context);
281 21
        if (!$rule->validate($value, $valueIdentifier)) {
282 20
            $this->addMessage($rule->getMessage());
283 20
            return false;
284
        }
285 12
        return true;
286
    }
287
288 19
    public function getMessages()
289
    {
290 19
        return $this->messages;
291
    }
292
293 20
    public function addMessage($message)
294
    {
295 20
        array_push($this->messages, $message);
296
297 20
        return $this;
298
    }
299
300 2
    public function getRules()
301
    {
302 2
        return $this->rules;
303
    }
304
305 20
    protected function isEmpty($value)
306
    {
307 20
        return in_array($value, array(null, ''), true);
308
    }
309
}
310