UniqueObjectValidator::assertIsTraversable()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 8
rs 9.4285
cc 3
eloc 4
nc 2
nop 1
1
<?php
2
3
namespace steevanb\SymfonyValidatorConstraints\Constraints;
4
5
use Symfony\Component\Validator\Constraint;
6
use Symfony\Component\Validator\ConstraintValidator;
7
8
class UniqueObjectValidator extends ConstraintValidator
9
{
10
    /** @var array */
11
    protected $traversablesValues = array();
12
13
    /**
14
     * @param mixed $traversable
15
     * @param UniqueObject $constraint
16
     */
17
    public function validate($traversable, Constraint $constraint)
18
    {
19
        $this
20
            ->assertIsTraversable($traversable)
21
            ->defineTraversablesValues($traversable, $constraint);
22
23
        foreach ($traversable as $data) {
24
            $this->compare($data, $constraint);
25
        }
26
    }
27
28
    /**
29
     * @param mixed $data
30
     * @throws \Exception
31
     * @return $this
32
     */
33
    protected function assertIsTraversable($data)
34
    {
35
        if (is_array($data) === false && $data instanceof \Traversable === false) {
36
            throw new \Exception(gettype($data) . ' is not iterable.');
37
        }
38
39
        return $this;
40
    }
41
42
    /**
43
     * @param array|\Traversable $traversable
44
     * @param UniqueObject $uniqueObject
45
     * @return $this
46
     */
47
    protected function defineTraversablesValues($traversable, UniqueObject $uniqueObject)
48
    {
49
        foreach ($traversable as $data) {
50
            $traversableHash = spl_object_hash($data);
51
            $this->traversablesValues[$traversableHash] = array('properties' => array(), 'getters' => array());
52
53
            foreach ($uniqueObject->getProperties() as $property) {
54
                $this->traversablesValues[$traversableHash]['properties'][$property] =
55
                    $this->getTraversableValue($data->$property);
56
            }
57
58
            foreach ($uniqueObject->getGetters() as $getter) {
59
                $this->traversablesValues[$traversableHash]['getters'][$getter] =
60
                    $this->getTraversableValue($data->$getter());
61
            }
62
        }
63
64
        return $this;
65
    }
66
67
    /**
68
     * @param mixed $value
69
     * @return string
70
     */
71
    protected function getTraversableValue($value)
72
    {
73
        return (is_object($value)) ? spl_object_hash($value) : $value;
74
    }
75
76
    /**
77
     * @param mixed $data
78
     * @param UniqueObject $uniqueObject
79
     */
80
    protected function compare($data, UniqueObject $uniqueObject)
81
    {
82
        static $alreadyTested = array();
83
        if (in_array($uniqueObject->getUniqid(), $alreadyTested)) {
84
            return;
85
        }
86
87
        $dataHash = spl_object_hash($data);
88
        $dataValues = $this->traversablesValues[$dataHash];
89
        foreach ($this->traversablesValues as $objectHash => $values) {
90
            if (
91
                $objectHash === $dataHash
92
                || (
93
                    count($values['properties']) !== count($dataValues['properties'])
94
                    || count($values['getters']) !== count($dataValues['getters'])
95
                )
96
            ) {
97
                continue;
98
            }
99
100
            $isSame = true;
101
            foreach (array('properties', 'getters') as $type) {
102
                foreach ($dataValues[$type] as $name => $value) {
103
                    if (array_key_exists($name, $values[$type])) {
104
                        if ($this->compareValues($values[$type][$name], $value, $uniqueObject->getStrict()) === false) {
105
                            $isSame = false;
106
                            break 2;
107
                        }
108
                    } else {
109
                        $isSame = false;
110
                        break 2;
111
                    }
112
                }
113
            }
114
            if ($isSame) {
115
                $this
116
                    ->context
117
                    ->buildViolation($uniqueObject->getMessage())
118
                    ->atPath($this->context->getPropertyName())
119
                    ->addViolation();
120
                $alreadyTested[] = $uniqueObject->getUniqid();
121
                break;
122
            }
123
        }
124
    }
125
126
    /**
127
     * @param mixed $value1
128
     * @param mixed $value2
129
     * @param bool $strict
130
     * @return bool
131
     */
132
    protected function compareValues($value1, $value2, $strict)
133
    {
134
        return ($strict) ? $value1 === $value2 : $value1 == $value2;
135
    }
136
}
137