Completed
Push — master ( 36acf6...c2375b )
by
unknown
13:20
created

CategoriesValidator::validateOrder()   D

Complexity

Conditions 9
Paths 5

Size

Total Lines 39
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 39
rs 4.909
cc 9
eloc 24
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
        $isValid = true;
139
140
        if ($isIncreasing) {
141
            $inversion = 1;
142
            $criteria = Criteria::ASC;
143
        } else {
144
            $inversion = -1;
145
            $criteria = Criteria::DESC;
146
        }
147
148
        usort(
149
            $orderedByValueArray,
150
            function (RFMMetricCategory $item1, RFMMetricCategory $item2) use (&$isValid, $inversion) {
151
                $minValue1 = $item1->getMinValue();
152
                $minValue2 = $item2->getMinValue();
153
154
                if ($minValue1 === $minValue2 ||
155
                    (!is_null($item1->getMaxValue()) && $item1->getMaxValue() <= $minValue1)) {
156
                    $isValid = false;
157
                }
158
159
                return (($minValue1 < $minValue2) ? 1 : -1) * $inversion;
160
            }
161
        );
162
163
        if (!$isValid || $orderedByValueArray !== $orderedByIndex->toArray()) {
164
            $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...
165
        }
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     */
171
    public function validatedBy()
172
    {
173
        return 'orocrm_analytics.validator.categories';
174
    }
175
176
    /**
177
     * @param Collection $orderedByIndex
178
     * @return bool
179
     */
180
    protected function isIncreasing(Collection $orderedByIndex)
181
    {
182
        return is_null($orderedByIndex->first()->getMinValue())
183
        && is_null($orderedByIndex->last()->getMaxValue());
184
    }
185
}
186