Completed
Push — master ( 70cb44...646998 )
by Ivannis Suárez
05:28
created

Validator   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 339
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 2
Bugs 0 Features 1
Metric Value
wmc 48
c 2
b 0
f 1
lcom 1
cbo 8
dl 0
loc 339
rs 8.4864

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A setMetadataFactory() 0 4 1
A setDefaultGroup() 0 4 1
A create() 0 10 2
A addConstraint() 0 17 3
A getConstraintsByGroup() 0 15 3
A assert() 0 4 1
C assertConstraints() 0 82 12
A validate() 0 4 1
C validateConstraints() 0 46 9
B addObjectConstraints() 0 23 5
A addArrayConstraints() 0 21 4
A normalizeGroup() 0 4 3
A normalizeClassName() 0 4 2

How to fix   Complexity   

Complex Class

Complex classes like Validator 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 Validator, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * This file is part of the Cubiche package.
4
 *
5
 * Copyright (c) Cubiche
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace Cubiche\Core\Validator;
11
12
use Cubiche\Core\Validator\Exception\ValidationException;
13
use Cubiche\Core\Validator\Mapping\ClassMetadata;
14
use Cubiche\Core\Validator\Mapping\Driver\StaticDriver;
15
use Metadata\Driver\DriverChain;
16
use Metadata\MetadataFactory;
17
use Metadata\MetadataFactoryInterface;
18
use Respect\Validation\Exceptions\NestedValidationException;
19
20
/**
21
 * Validator class.
22
 *
23
 * @author Ivannis Suárez Jerez <[email protected]>
24
 */
25
class Validator implements ValidatorInterface
26
{
27
    /**
28
     * @var array
29
     */
30
    protected $constraints = array();
31
32
    /**
33
     * @var string
34
     */
35
    protected $defaultGroup;
36
37
    /**
38
     * @var MetadataFactoryInterface
39
     */
40
    protected $metadataFactory;
41
42
    /**
43
     * @var Validator
44
     */
45
    private static $instance = null;
46
47
    /**
48
     * Validator constructor.
49
     *
50
     * @param MetadataFactoryInterface $metadataFactory
51
     * @param string                   $defaultGroup
52
     */
53
    private function __construct(MetadataFactoryInterface $metadataFactory, $defaultGroup = Assert::DEFAULT_GROUP)
54
    {
55
        $this->metadataFactory = $metadataFactory;
56
        $this->defaultGroup = $defaultGroup;
57
    }
58
59
    /**
60
     * @param MetadataFactoryInterface $metadataFactory
61
     */
62
    public static function setMetadataFactory(MetadataFactoryInterface $metadataFactory)
63
    {
64
        static::create()->metadataFactory = $metadataFactory;
65
    }
66
67
    /**
68
     * @param $defaultGroup
69
     */
70
    public static function setDefaultGroup($defaultGroup)
71
    {
72
        static::create()->defaultGroup = $defaultGroup;
73
    }
74
75
    /**
76
     * @return Validator
77
     */
78
    public static function create()
79
    {
80
        if (static::$instance === null) {
0 ignored issues
show
Bug introduced by
Since $instance is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $instance to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
81
            static::$instance = new static(
0 ignored issues
show
Bug introduced by
Since $instance is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $instance to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
82
                new MetadataFactory(new DriverChain(array(new StaticDriver())))
83
            );
84
        }
85
86
        return static::$instance;
0 ignored issues
show
Bug introduced by
Since $instance is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $instance to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
87
    }
88
89
    /**
90
     * @param Assert $assert
91
     * @param string $className
92
     * @param string $group
93
     *
94
     * @return $this
95
     */
96
    protected function addConstraint(Assert $assert, $className = null, $group = null)
97
    {
98
        $className = $this->normalizeClassName($className);
99
        $group = $this->normalizeGroup($group);
100
101
        if (!isset($this->constraints[$className])) {
102
            $this->constraints[$className] = array();
103
        }
104
105
        if (!isset($this->constraints[$className][$group])) {
106
            $this->constraints[$className][$group] = Assert::create();
107
        }
108
109
        $this->constraints[$className][$group]->addRules($assert->getRules());
110
111
        return $this;
112
    }
113
114
    /**
115
     * @param string $className
116
     * @param string $group
117
     *
118
     * @return Assert
119
     */
120
    protected function getConstraintsByGroup($className = null, $group = null)
121
    {
122
        $className = $this->normalizeClassName($className);
123
        $group = $this->normalizeGroup($group);
124
125
        if (!isset($this->constraints[$className])) {
126
            return Assert::create()->alwaysValid();
127
        }
128
129
        if (!isset($this->constraints[$className][$group])) {
130
            return Assert::create()->alwaysValid();
131
        }
132
133
        return $this->constraints[$className][$group];
134
    }
135
136
    /**
137
     * {@inheritdoc}
138
     */
139
    public static function assert($value, $constraints = null, $group = null)
140
    {
141
        return static::create()->assertConstraints($value, $constraints, $group);
142
    }
143
144
    /**
145
     * {@inheritdoc}
146
     */
147
    protected function assertConstraints($value, $constraints = null, $group = null)
148
    {
149
        $group = $this->normalizeGroup($group);
150
151
        // If explicit constraints are passed, validate the value against
152
        // those constraints
153
        if (null !== $constraints) {
154
            if (!is_array($constraints)) {
155
                $constraints = array($constraints);
156
            }
157
158
            foreach ($constraints as $constraint) {
159
                $this->addConstraint($constraint, null, $group);
160
            }
161
162
            $constraints = $this->getConstraintsByGroup(null, $group);
163
164
            try {
165
                $returnValue = $constraints->assert($value);
166
            } catch (NestedValidationException $e) {
167
                throw new ValidationException(
168
                    $e->getMainMessage(),
169
                    $e->getMessages(),
170
                    $e->getCode(),
171
                    $e->getPrevious()
172
                );
173
            }
174
175
            return $returnValue;
176
        }
177
178
        // If an object is passed without explicit constraints, validate that
179
        // object against the constraints defined for the object's class
180
        if (is_object($value)) {
181
            $this->addObjectConstraints($value);
182
183
            $constraints = $this->getConstraintsByGroup(get_class($value), $group);
184
185
            try {
186
                $returnValue = $constraints->assert($value);
187
            } catch (NestedValidationException $e) {
188
                throw new ValidationException(
189
                    $e->getMainMessage(),
190
                    $e->getMessages(),
191
                    $e->getCode(),
192
                    $e->getPrevious()
193
                );
194
            }
195
196
            return $returnValue;
197
        }
198
199
        // If an array is passed without explicit constraints, validate each
200
        // object in the array
201
        if (is_array($value)) {
202
            $this->addArrayConstraints($value);
203
204
            $returnValue = true;
205
            foreach ($value as $item) {
206
                $constraints = $this->getConstraintsByGroup(is_object($item) ? get_class($item) : null, $group);
207
208
                try {
209
                    $returnValue = $returnValue && $constraints->assert($item);
210
                } catch (NestedValidationException $e) {
211
                    throw new ValidationException(
212
                        $e->getMainMessage(),
213
                        $e->getMessages(),
214
                        $e->getCode(),
215
                        $e->getPrevious()
216
                    );
217
                }
218
            }
219
220
            return $returnValue;
221
        }
222
223
        throw new \RuntimeException(sprintf(
224
            'Cannot validate values of type "%s" automatically. Please '.
225
            'provide a constraint.',
226
            gettype($value)
227
        ));
228
    }
229
230
    /**
231
     * {@inheritdoc}
232
     */
233
    public static function validate($value, $constraints = null, $group = null)
234
    {
235
        return static::create()->validateConstraints($value, $constraints, $group);
236
    }
237
238
    /**
239
     * {@inheritdoc}
240
     */
241
    protected function validateConstraints($value, $constraints = null, $group = null)
242
    {
243
        $group = $this->normalizeGroup($group);
244
245
        // If explicit constraints are passed, validate the value against
246
        // those constraints
247
        if (null !== $constraints) {
248
            if (!is_array($constraints)) {
249
                $constraints = array($constraints);
250
            }
251
252
            foreach ($constraints as $constraint) {
253
                $this->addConstraint($constraint, null, $group);
254
            }
255
256
            return $this->getConstraintsByGroup(null, $group)->validate($value);
257
        }
258
259
        // If an object is passed without explicit constraints, validate that
260
        // object against the constraints defined for the object's class
261
        if (is_object($value)) {
262
            $this->addObjectConstraints($value);
263
264
            return $this->getConstraintsByGroup(get_class($value), $group)->validate($value);
265
        }
266
267
        // If an array is passed without explicit constraints, validate each
268
        // object in the array
269
        if (is_array($value)) {
270
            $this->addArrayConstraints($value);
271
272
            $returnValue = true;
273
            foreach ($value as $item) {
274
                $constraints = $this->getConstraintsByGroup(is_object($item) ? get_class($item) : null, $group);
275
                $returnValue = $returnValue && $constraints->validate($item);
276
            }
277
278
            return $returnValue;
279
        }
280
281
        throw new \RuntimeException(sprintf(
282
            'Cannot validate values of type "%s" automatically. Please '.
283
            'provide a constraint.',
284
            gettype($value)
285
        ));
286
    }
287
288
    /**
289
     * @param object $object
290
     */
291
    protected function addObjectConstraints($object)
292
    {
293
        $metadata = $this->metadataFactory->getMetadataForClass(get_class($object));
294
        if ($metadata !== null) {
295
            /** @var ClassMetadata $classMetadata */
296
            $classMetadata = $metadata->getRootClassMetadata();
297
298
            foreach ($classMetadata->getPropertiesMetadata() as $propertyMetadata) {
299
                foreach ($propertyMetadata->getConstraints() as $group => $constraints) {
300
                    $allOf = Assert::create();
301
                    foreach ($constraints as $constraint) {
302
                        $allOf->addRules($constraint->getRules());
303
                    }
304
305
                    $this->addConstraint(
306
                        Assert::create()->attribute($propertyMetadata->getPropertyName(), $allOf),
0 ignored issues
show
Compatibility introduced by
\Cubiche\Core\Validator\...PropertyName(), $allOf) of type object<Respect\Validation\Validator> is not a sub-type of object<Cubiche\Core\Validator\Assert>. It seems like you assume a child class of the class Respect\Validation\Validator to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
307
                        get_class($object),
308
                        $group
309
                    );
310
                }
311
            }
312
        }
313
    }
314
315
    /**
316
     * @param mixed $array
317
     */
318
    protected function addArrayConstraints($array)
319
    {
320
        foreach ($array as $key => $value) {
321
            if (is_array($value)) {
322
                $this->addArrayConstraints($value);
323
324
                continue;
325
            }
326
327
            // Scalar and null values in the collection are ignored
328
            if (is_object($value)) {
329
                $this->addObjectConstraints($value);
330
            } else {
331
                throw new \RuntimeException(sprintf(
332
                    'Cannot validate values of type "%s" automatically. Please '.
333
                    'provide a constraint.',
334
                    gettype($value)
335
                ));
336
            }
337
        }
338
    }
339
340
    /**
341
     * Normalizes the given group.
342
     *
343
     * @param string $group
344
     *
345
     * @return string
346
     */
347
    protected function normalizeGroup($group = null)
348
    {
349
        return $group === null || empty($group)  ? $this->defaultGroup : $group;
350
    }
351
352
    /**
353
     * Normalizes the given className.
354
     *
355
     * @param string $className
356
     *
357
     * @return string
358
     */
359
    protected function normalizeClassName($className = null)
360
    {
361
        return $className !== null ? $className : self::class;
362
    }
363
}
364