Select   A
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 328
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 39
eloc 105
dl 0
loc 328
ccs 118
cts 118
cp 1
rs 9.28
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A rewind() 0 3 1
A fetchAll() 0 8 2
A rawIterator() 0 13 3
A hydrate() 0 16 3
A orderBy() 0 4 1
A next() 0 4 2
A whereRaw() 0 14 2
A getOrderingRules() 0 13 4
A iterator() 0 6 2
A getPropertiesData() 0 20 3
A __construct() 0 3 1
A getFieldsData() 0 19 2
A where() 0 14 2
A from() 0 3 1
A key() 0 4 2
A fetch() 0 9 2
A valid() 0 4 2
A assert() 0 4 2
A current() 0 4 2
1
<?php
2
3
namespace BitrixToolkit\BitrixEntityMapper\Query;
4
5
use _CIBElement;
6
use CIBlockElement;
7
use Doctrine\Common\Annotations\AnnotationException;
8
use Exception;
9
use Generator;
10
use InvalidArgumentException;
11
use Iterator;
12
use ReflectionClass;
13
use ReflectionException;
14
use ReflectionObject;
15
use BitrixToolkit\BitrixEntityMapper\Annotation\Property\Field;
16
use BitrixToolkit\BitrixEntityMapper\Annotation\Property\Property;
17
use BitrixToolkit\BitrixEntityMapper\Map\EntityMap;
18
use BitrixToolkit\BitrixEntityMapper\Map\PropertyMap;
19
20
class Select implements Iterator
21
{
22
    protected $entityMap;
23
    protected $whereRaw = [];
24
    protected $where = [];
25
    protected $orderBy = [];
26
    protected $generator;
27
28
    /**
29
     * Select constructor.
30
     * @param string $class
31
     * @throws AnnotationException
32
     * @throws ReflectionException
33
     * @throws InvalidArgumentException
34
     */
35 20
    public function __construct($class)
36
    {
37 20
        $this->entityMap = EntityMap::fromClass($class);
38
    }
39
40
    /**
41
     * @param string $class
42
     * @return Select
43
     * @throws AnnotationException
44
     * @throws ReflectionException
45
     * @throws InvalidArgumentException
46
     */
47 20
    public static function from($class)
48
    {
49 20
        return new self($class);
50
    }
51
52
    /**
53
     * Может быть вызвано с двумя или тремя аргументами.
54
     *
55
     * Если 2 аргумента, то название свойства и значение для фильтрации.
56
     * Например: $this->where('name', 'bender');
57
     * По-умолчанию будет использован оператор сравнения "=".
58
     *
59
     * Если 3 аргумента, то название свойства, оператор сравнения и значение для фильтрации.
60
     * Например: $this->where('age', '>', 18);
61
     *
62
     * @param string $p Название свойства класса для фильтрации.
63
     * @param mixed $_ Если 3 аргумента то оператор сравнения, иначе значение для фильтрации.
64
     * @param mixed Если 3 аргумента то значение для фильтрации.
65
     * @return $this
66
     */
67 10
    public function where($p, $_)
68
    {
69 10
        if (func_num_args() > 2) {
70 2
            $property = $p;
71 2
            $operator = $_;
72 2
            $value = func_get_arg(2);
73
        } else {
74 10
            $property = $p;
75 10
            $operator = '=';
76 10
            $value = $_;
77
        }
78
79 10
        $this->where[] = [$property, $operator, $value];
80 10
        return $this;
81
    }
82
83
    /**
84
     * @param string $f
85
     * @param mixed $_
86
     * @param mixed
87
     * @return $this
88
     */
89 16
    public function whereRaw($f, $_)
90
    {
91 16
        if (func_num_args() > 2) {
92 1
            $field = $f;
93 1
            $operator = $_;
94 1
            $value = func_get_arg(2);
95
        } else {
96 16
            $field = $f;
97 16
            $operator = '=';
98 16
            $value = $_;
99
        }
100
101 16
        $this->whereRaw[$operator . $field] = $value;
102 16
        return $this;
103
    }
104
105
    /**
106
     * @param string $p
107
     * @param string $d
108
     * @return $this
109
     */
110 4
    public function orderBy($p, $d = 'asc')
111
    {
112 4
        $this->orderBy[$p] = $d;
113 4
        return $this;
114
    }
115
116
    /**
117
     * @return Generator|RawResult[]
118
     * @throws InvalidArgumentException
119
     * @throws Exception
120
     */
121 20
    public function rawIterator()
122
    {
123 20
        $filterBuilder = new FilterBuilder($this->entityMap, $this->where, $this->whereRaw);
124
125 20
        $filter = $filterBuilder->getFilter();
126 19
        $order = $this->getOrderingRules();
127
128 19
        $rs = CIBlockElement::GetList($order, $filter);
129 19
        while ($element = $rs->GetNextElement()) {
130 18
            if ($element instanceof _CIBElement) {
131 18
                $data = array_merge($this->getFieldsData($element), $this->getPropertiesData($element));
132 18
                $elementFields = $element->GetFields();
133 18
                yield new RawResult($elementFields['ID'], $elementFields['IBLOCK_ID'], $data);
134
            }
135
        }
136
    }
137
138
    /**
139
     * @return Generator
140
     * @throws ReflectionException
141
     * @throws InvalidArgumentException
142
     * @throws Exception
143
     */
144 19
    public function iterator()
145
    {
146 19
        $classRef = new ReflectionClass($this->entityMap->getClass());
147 19
        foreach ($this->rawIterator() as $rawResult) {
148 18
            $object = $classRef->newInstanceWithoutConstructor();
149 18
            yield self::hydrate($object, $rawResult->getData());
150
        }
151
    }
152
153
    /**
154
     * @return object
155
     * @throws ReflectionException
156
     * @throws InvalidArgumentException
157
     * @throws Exception
158
     */
159 19
    public function fetch()
160
    {
161 19
        if ($this->generator instanceof Generator) {
162 1
            $this->generator->next();
163
        } else {
164 19
            $this->generator = $this->iterator();
165
        }
166
167 19
        return $this->generator->current();
168
    }
169
170
    /**
171
     * @return object[]
172
     * @throws ReflectionException
173
     * @throws InvalidArgumentException
174
     * @throws Exception
175
     */
176 7
    public function fetchAll()
177
    {
178 7
        $array = [];
179 7
        foreach ($this->iterator() as $object) {
180 7
            $array[] = $object;
181
        }
182
183 7
        return $array;
184
    }
185
186
    /**
187
     * @param mixed $term
188
     * @param string $msg
189
     */
190 19
    protected static function assert($term, $msg)
191
    {
192 19
        if (!$term) {
193 1
            throw new InvalidArgumentException($msg);
194
        }
195
    }
196
197
    /**
198
     * @return array
199
     */
200 19
    protected function getOrderingRules()
201
    {
202 19
        $order = [];
203 19
        foreach ($this->orderBy as $property => $direction) {
204 4
            $propertyAnnotation = $this->entityMap->getProperty($property)->getAnnotation();
205 4
            if ($propertyAnnotation instanceof Field) {
206 2
                $order[$propertyAnnotation->getCode()] = $direction;
207 2
            } elseif ($propertyAnnotation instanceof Property) {
208 2
                $order['PROPERTY_' . $propertyAnnotation->getCode()] = $direction;
209
            }
210
        }
211
212 19
        return $order;
213
    }
214
215
    /**
216
     * @param _CIBElement $element
217
     * @return array
218
     * @throws Exception
219
     */
220 18
    protected function getFieldsData(_CIBElement $element)
221
    {
222 18
        $fields = array_filter($this->entityMap->getProperties(), function (PropertyMap $propertyMap) {
223 18
            return $propertyMap->getAnnotation() instanceof Field;
224 18
        });
225
226 18
        $data = [];
227 18
        $elementFields = $element->GetFields();
228 18
        foreach ($fields as $field) {
229 18
            $key = $field->getAnnotation()->getCode();
230 18
            self::assert(
231 18
                array_key_exists($key, $elementFields),
232 18
                "Поле $key не найдено в результатах CIBlockElement::GetList()."
233 18
            );
234
235 18
            $data[$field->getCode()] = RawResult::normalizeValue($field, $elementFields[$key]);
236
        }
237
238 18
        return $data;
239
    }
240
241
    /**
242
     * @param _CIBElement $element
243
     * @return array
244
     * @throws Exception
245
     */
246 18
    protected function getPropertiesData(_CIBElement $element)
247
    {
248 18
        $properties = array_filter($this->entityMap->getProperties(), function (PropertyMap $propertyMap) {
249 18
            return $propertyMap->getAnnotation() instanceof Property;
250 18
        });
251
252 18
        $data = [];
253 18
        $elementProperties = $element->GetProperties();
254 18
        foreach ($properties as $property) {
255 18
            $key = $property->getAnnotation()->getCode();
256 18
            self::assert(
257 18
                array_key_exists($key, $elementProperties) && array_key_exists('VALUE', $elementProperties[$key]),
258 18
                "Свойство $key не найдено в результатах CIBlockElement::GetList()."
259 18
            );
260
261 18
            $rawValue = $elementProperties[$key]['VALUE'];
262 18
            $data[$property->getCode()] = RawResult::normalizePropertyValue($property, $rawValue);
263
        }
264
265 18
        return $data;
266
    }
267
268
    /**
269
     * @param object $object
270
     * @param array $data
271
     * @return object
272
     * @throws ReflectionException
273
     * @throws InvalidArgumentException
274
     */
275 18
    protected static function hydrate($object, array $data)
276
    {
277 18
        self::assert(is_object($object), 'Аргумент $object не является объектом.');
278 18
        $objectRef = new ReflectionObject($object);
279 18
        foreach ($data as $key => $value) {
280 18
            $propRef = $objectRef->getProperty($key);
281 18
            if (!$propRef->isPublic()) {
282 18
                $propRef->setAccessible(true);
283 18
                $propRef->setValue($object, $value);
284 18
                $propRef->setAccessible(false);
285
            } else {
286 18
                $propRef->setValue($object, $value);
287
            }
288
        }
289
290 18
        return $object;
291
    }
292
293
    /**
294
     * Вернуть текущий объект.
295
     *
296
     * @return object
297
     * @throws ReflectionException
298
     */
299 1
    public function current()
300
    {
301 1
        $this->generator = isset($this->generator) ? $this->generator : $this->iterator();
302 1
        return $this->generator->current();
303
    }
304
305
    /**
306
     * Переместить курсор к следующему объекту.
307
     *
308
     * @throws ReflectionException
309
     */
310 1
    public function next()
311
    {
312 1
        $this->generator = isset($this->generator) ? $this->generator : $this->iterator();
313 1
        $this->generator->next();
314
    }
315
316
    /**
317
     * Вернуть индекс текущего объекта.
318
     *
319
     * @return mixed
320
     * @throws ReflectionException
321
     */
322 1
    public function key()
323
    {
324 1
        $this->generator = isset($this->generator) ? $this->generator : $this->iterator();
325 1
        return $this->generator->key();
326
    }
327
328
    /**
329
     * Проверить текущую позицию курсора.
330
     *
331
     * @return bool
332
     * @throws ReflectionException
333
     */
334 1
    public function valid()
335
    {
336 1
        $this->generator = isset($this->generator) ? $this->generator : $this->iterator();
337 1
        return $this->generator->valid();
338
    }
339
340
    /**
341
     * Начать новую итерацию.
342
     *
343
     * @throws ReflectionException
344
     */
345 1
    public function rewind()
346
    {
347 1
        $this->generator = $this->iterator();
348
    }
349
}