Completed
Push — master ( 4859e1...c3d3e1 )
by
unknown
11:17
created

CategoriesValidator::validateOrder()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 23
rs 8.5906
c 1
b 0
f 0
cc 5
eloc 15
nc 5
nop 2
1
<?php
2
3
namespace OroCRM\Bundle\AnalyticsBundle\Validator;
4
5
use Doctrine\Common\Collections\Collection;
6
use Doctrine\Common\Collections\Criteria;
7
use Doctrine\ORM\PersistentCollection;
8
9
use Symfony\Component\Validator\Constraint;
10
use Symfony\Component\Validator\ConstraintValidator;
11
12
use OroCRM\Bundle\AnalyticsBundle\Entity\RFMMetricCategory;
13
14
class CategoriesValidator extends ConstraintValidator
15
{
16
    const MIN_CATEGORIES_COUNT = 2;
17
18
    /**
19
     * Validate collection.
20
     *
21
     * @param PersistentCollection|RFMMetricCategory[] $value
22
     * @param CategoriesConstraint $constraint
23
     *
24
     * {@inheritdoc}
25
     */
26
    public function validate($value, Constraint $constraint)
27
    {
28
        if (!$value instanceof PersistentCollection) {
29
            return;
30
        }
31
32
        if ($this->validateCount($value, $constraint) && $this->validateBlank($value, $constraint)) {
0 ignored issues
show
Compatibility introduced by
$constraint of type object<Symfony\Component\Validator\Constraint> is not a sub-type of object<OroCRM\Bundle\Ana...r\CategoriesConstraint>. It seems like you assume a child class of the class Symfony\Component\Validator\Constraint 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...
33
            $this->validateOrder($value, $constraint);
0 ignored issues
show
Compatibility introduced by
$constraint of type object<Symfony\Component\Validator\Constraint> is not a sub-type of object<OroCRM\Bundle\Ana...r\CategoriesConstraint>. It seems like you assume a child class of the class Symfony\Component\Validator\Constraint 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...
34
        }
35
    }
36
37
    /**
38
     * Check collection for empty values.
39
     *
40
     * @param PersistentCollection $value
41
     * @param CategoriesConstraint $constraint
42
     * @return bool
43
     */
44
    protected function validateBlank(PersistentCollection $value, CategoriesConstraint $constraint)
45
    {
46
        $orderedByIndex = $value->matching(new Criteria(null, ['categoryIndex' => Criteria::ASC]));
47
        $isIncreasing = $this->isIncreasing($orderedByIndex);
48
49
        if ($isIncreasing) {
50
            $firstMax = $orderedByIndex->first()->getMaxValue();
51
            $lastMin = $orderedByIndex->last()->getMinValue();
52
            $hasEmpty = $this->isEmpty($firstMax) || $this->isEmpty($lastMin);
53
        } else {
54
            $firstMin = $orderedByIndex->first()->getMinValue();
55
            $lastMax = $orderedByIndex->last()->getMaxValue();
56
            $hasEmpty = $this->isEmpty($firstMin) || $this->isEmpty($lastMax);
57
        }
58
59
        if (!$hasEmpty) {
60
            $orderedByIndexWithoutEmpty = $orderedByIndex->filter(
61
                function (RFMMetricCategory $category) use ($orderedByIndex) {
62
                    return !in_array($category, [$orderedByIndex->first(), $orderedByIndex->last()], true);
63
                }
64
            );
65
66
            /** @var RFMMetricCategory $category */
67
            foreach ($orderedByIndexWithoutEmpty->toArray() as $category) {
68
                $min = $category->getMinValue();
69
                $max = $category->getMaxValue();
70
71
                if ($this->isEmpty($min) || $this->isEmpty($max)) {
72
                    $hasEmpty = true;
73
                    break;
74
                }
75
            }
76
        }
77
78
        if ($hasEmpty) {
79
            $this->context->addViolationAt($constraint->getType(), $constraint->blankMessage);
0 ignored issues
show
Deprecated Code introduced by
The method Symfony\Component\Valida...rface::addViolationAt() has been deprecated with message: since version 2.5, to be removed in 3.0. Use {@link Context\ExecutionContextInterface::buildViolation()} instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
80
        }
81
82
        return !$hasEmpty;
83
    }
84
85
    /**
86
     * @param mixed $value
87
     * @return bool
88
     */
89
    protected function isEmpty($value)
90
    {
91
        return $value === '' || $value === null;
92
    }
93
94
    /**
95
     * Check that number of categories not less than minimum defined number.
96
     *
97
     * @param PersistentCollection $value
98
     * @param CategoriesConstraint $constraint
99
     * @return bool
100
     */
101
    protected function validateCount(PersistentCollection $value, CategoriesConstraint $constraint)
102
    {
103
        if ($value->count() >= self::MIN_CATEGORIES_COUNT) {
104
            return true;
105
        }
106
107
        $this->context->addViolationAt(
0 ignored issues
show
Deprecated Code introduced by
The method Symfony\Component\Valida...rface::addViolationAt() has been deprecated with message: since version 2.5, to be removed in 3.0. Use {@link Context\ExecutionContextInterface::buildViolation()} instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
108
            $constraint->getType(),
109
            $constraint->countMessage,
110
            ['%count%' => self::MIN_CATEGORIES_COUNT]
111
        );
112
113
        return false;
114
    }
115
116
    /**
117
     * Check that collection is in right order in next way:
118
     * 1) Compare elements that ordered by index with elements that ordered by min value
119
     * 2) Check if equality doesn't exist between different categories in min values
120
     * 3) Check if max value ( null value exclude from checking ) always greater than min value
121
     *
122
     * For increasing collection values must be in ascending order.
123
     * For decreasing collection value must be in descending order.
124
     *
125
     * @param PersistentCollection $value
126
     * @param CategoriesConstraint $constraint
127
     */
128
    protected function validateOrder(PersistentCollection $value, CategoriesConstraint $constraint)
129
    {
130
        if ($value->isEmpty()) {
131
            return;
132
        }
133
134
        $orderedByIndex = $value->matching(new Criteria(null, ['categoryIndex' => Criteria::ASC]));
135
        $isIncreasing = $this->isIncreasing($orderedByIndex);
136
        $orderedByValueArray = $value->toArray();
137
138
        if ($isIncreasing) {
139
            $inversion = 1;
140
            $criteria = Criteria::ASC;
141
        } else {
142
            $inversion = -1;
143
            $criteria = Criteria::DESC;
144
        }
145
146
        $isValid = $this->orderByValue($orderedByValueArray, $inversion);
147
        if (!$isValid || $orderedByValueArray !== $orderedByIndex->toArray()) {
148
            $this->context->addViolationAt($constraint->getType(), $constraint->message, ['%order%' => $criteria]);
0 ignored issues
show
Deprecated Code introduced by
The method Symfony\Component\Valida...rface::addViolationAt() has been deprecated with message: since version 2.5, to be removed in 3.0. Use {@link Context\ExecutionContextInterface::buildViolation()} instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
149
        }
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155
    public function validatedBy()
156
    {
157
        return 'orocrm_analytics.validator.categories';
158
    }
159
160
    /**
161
     * @param Collection $orderedByIndex
162
     * @return bool
163
     */
164
    protected function isIncreasing(Collection $orderedByIndex)
165
    {
166
        return is_null($orderedByIndex->first()->getMinValue())
167
        && is_null($orderedByIndex->last()->getMaxValue());
168
    }
169
170
    /**
171
     * @param array $value
172
     * @param int $inversion
173
     *
174
     * @return boolean
175
     */
176
    protected function orderByValue(array &$value, $inversion)
177
    {
178
        $isValid = true;
179
        uasort(
180
            $value,
181
            function (RFMMetricCategory $item1, RFMMetricCategory $item2) use (&$isValid, $inversion) {
182
                $minValue1 = $item1->getMinValue();
183
                $minValue2 = $item2->getMinValue();
184
185
                if ($minValue1 === $minValue2 ||
186
                    (!is_null($item1->getMaxValue()) && $item1->getMaxValue() <= $minValue1)) {
187
                    $isValid = false;
188
                }
189
190
                return (($minValue1 < $minValue2) ? -1 : 1) * $inversion;
191
            }
192
        );
193
194
        return $isValid;
195
    }
196
}
197