Completed
Pull Request — master (#26)
by
unknown
01:58
created

ElementQuery::performFetchUsingSelectedMethod()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.6
c 0
b 0
f 0
cc 3
nc 3
nop 1
1
<?php
2
3
namespace Arrilot\BitrixModels\Queries;
4
5
use Arrilot\BitrixCacher\Cache;
6
use Arrilot\BitrixModels\ServiceProvider;
7
use CIBlock;
8
use Illuminate\Support\Collection;
9
use Arrilot\BitrixModels\Models\ElementModel;
10
use Exception;
11
12
/**
13
 * @method ElementQuery active()
14
 * @method ElementQuery sortByDate(string $sort = 'desc')
15
 * @method ElementQuery fromSectionWithId(int $id)
16
 * @method ElementQuery fromSectionWithCode(string $code)
17
 */
18
class ElementQuery extends OldCoreQuery
19
{
20
    /**
21
     * CIblock object or test double.
22
     *
23
     * @var object.
24
     */
25
    public static $cIblockObject;
26
27
    /**
28
     * Query sort.
29
     *
30
     * @var array
31
     */
32
    public $sort = ['SORT' => 'ASC'];
33
34
    /**
35
     * Query group by.
36
     *
37
     * @var array
38
     */
39
    public $groupBy = false;
40
41
    /**
42
     * Iblock id.
43
     *
44
     * @var int
45
     */
46
    protected $iblockId;
47
48
    /**
49
     * Iblock version.
50
     *
51
     * @var int
52
     */
53
    protected $iblockVersion;
54
55
    /**
56
     * List of standard entity fields.
57
     *
58
     * @var array
59
     */
60
    protected $standardFields = [
61
        'ID',
62
        'TIMESTAMP_X',
63
        'TIMESTAMP_X_UNIX',
64
        'MODIFIED_BY',
65
        'DATE_CREATE',
66
        'DATE_CREATE_UNIX',
67
        'CREATED_BY',
68
        'IBLOCK_ID',
69
        'IBLOCK_SECTION_ID',
70
        'ACTIVE',
71
        'ACTIVE_FROM',
72
        'ACTIVE_TO',
73
        'SORT',
74
        'NAME',
75
        'PREVIEW_PICTURE',
76
        'PREVIEW_TEXT',
77
        'PREVIEW_TEXT_TYPE',
78
        'DETAIL_PICTURE',
79
        'DETAIL_TEXT',
80
        'DETAIL_TEXT_TYPE',
81
        'SEARCHABLE_CONTENT',
82
        'IN_SECTIONS',
83
        'SHOW_COUNTER',
84
        'SHOW_COUNTER_START',
85
        'CODE',
86
        'TAGS',
87
        'XML_ID',
88
        'EXTERNAL_ID',
89
        'TMP_ID',
90
        'CREATED_USER_NAME',
91
        'DETAIL_PAGE_URL',
92
        'LIST_PAGE_URL',
93
        'CREATED_DATE',
94
    ];
95
96
    /**
97
     * Constructor.
98
     *
99
     * @param object $bxObject
100
     * @param string $modelName
101
     */
102
    public function __construct($bxObject, $modelName)
103
    {
104
        static::instantiateCIblockObject();
105
        parent::__construct($bxObject, $modelName);
106
107
        $this->iblockId = $modelName::iblockId();
108
        $this->iblockVersion = $modelName::IBLOCK_VERSION ?: 2;
109
    }
110
111
    /**
112
     * Instantiate bitrix entity object.
113
     *
114
     * @throws Exception
115
     *
116
     * @return object
117
     */
118
    public static function instantiateCIblockObject()
119
    {
120
        if (static::$cIblockObject) {
121
            return static::$cIblockObject;
122
        }
123
124
        if (class_exists('CIBlock')) {
125
            return static::$cIblockObject = new CIBlock();
126
        }
127
128
        throw new Exception('CIblock object initialization failed');
129
    }
130
131
    /**
132
     * Setter for groupBy.
133
     *
134
     * @param $value
135
     *
136
     * @return $this
137
     */
138
    public function groupBy($value)
139
    {
140
        $this->groupBy = $value;
141
142
        return $this;
143
    }
144
145
    /**
146
     * Get list of items.
147
     *
148
     * @return Collection
149
     */
150
    protected function loadModels()
151
    {
152
        $sort = $this->sort;
153
        $filter = $this->normalizeFilter();
154
        $groupBy = $this->groupBy;
155
        $navigation = $this->navigation;
156
        $select = $this->normalizeSelect();
157
        $queryType = 'ElementQuery::getList';
158
        $fetchUsing = $this->fetchUsing;
159
        $keyBy = $this->keyBy;
160
        list($select, $chunkQuery) = $this->multiplySelectForMaxJoinsRestrictionIfNeeded($select);
161
162
        $callback = function() use ($sort, $filter, $groupBy, $navigation, $select, $chunkQuery) {
163
            if (static::isManagedCacheOn()) {
164
                ServiceProvider::cacheManagerProvider()->StartTagCache(static::$cacheDir);
165
                ServiceProvider::cacheManagerProvider()->RegisterTag("iblock_id_new");
166
            }
167
            
168
            
169
            if ($chunkQuery) {
170
                $itemsChunks = [];
171
                foreach ($select as $chunkIndex => $selectForChunk) {
172
                    $rsItems = $this->bxObject->GetList($sort, $filter, $groupBy, $navigation, $selectForChunk);
173
                    while ($arItem = $this->performFetchUsingSelectedMethod($rsItems)) {
174
                        $this->addItemToResultsUsingKeyBy($itemsChunks[$chunkIndex], new $this->modelName($arItem['ID'], $arItem));
175
                    }
176
                }
177
178
                $items = $this->mergeChunks($itemsChunks);
179
            } else {
180
                $items = [];
181
                $rsItems = $this->bxObject->GetList($sort, $filter, $groupBy, $navigation, $select);
182
                while ($arItem = $this->performFetchUsingSelectedMethod($rsItems)) {
183
                    $this->addItemToResultsUsingKeyBy($items, new $this->modelName($arItem['ID'], $arItem));
184
                }
185
            }
186
    
187
            
188
            if (static::isManagedCacheOn()) {
189
                ServiceProvider::cacheManagerProvider()->EndTagCache();
190
            }
191
            return new Collection($items);
192
        };
193
194
        $cacheKeyParams = compact('sort', 'filter', 'groupBy', 'navigation', 'select', 'queryType', 'keyBy', 'fetchUsing');
195
196
        return $this->handleCacheIfNeeded($cacheKeyParams, $callback);
197
    }
198
199
    /**
200
     * Get the first element with a given code.
201
     *
202
     * @param string $code
203
     *
204
     * @return ElementModel
205
     */
206
    public function getByCode($code)
207
    {
208
        $this->filter['CODE'] = $code;
209
210
        return $this->first();
211
    }
212
213
    /**
214
     * Get the first element with a given external id.
215
     *
216
     * @param string $id
217
     *
218
     * @return ElementModel
219
     */
220
    public function getByExternalId($id)
221
    {
222
        $this->filter['EXTERNAL_ID'] = $id;
223
224
        return $this->first();
225
    }
226
227
    /**
228
     * Get count of elements that match $filter.
229
     *
230
     * @return int
231
     */
232 View Code Duplication
    public function count()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
233
    {
234
        if ($this->queryShouldBeStopped) {
235
            return 0;
236
        }
237
238
        $filter = $this->normalizeFilter();
239
        $queryType = "ElementQuery::count";
240
241
        $callback = function () use ($filter) {
242
            return (int) $this->bxObject->GetList(false, $filter, []);
243
        };
244
245
        return $this->handleCacheIfNeeded(compact('filter', 'queryType'), $callback);
246
    }
247
    
248
    /**
249
     * Normalize properties's format converting it to 'PROPERTY_"CODE"_VALUE'.
250
     *
251
     * @param array $fields
252
     *
253
     * @return null
254
     */
255
    protected function normalizePropertyResultFormat(&$fields)
256
    {
257
        if (empty($fields['PROPERTIES'])) {
258
            return;
259
        }
260
        
261
        foreach ($fields['PROPERTIES'] as $code => $prop) {
262
            $fields['PROPERTY_'.$code.'_VALUE'] = $prop['VALUE'];
263
            $fields['~PROPERTY_'.$code.'_VALUE'] = $prop['~VALUE'];
264
            $fields['PROPERTY_'.$code.'_DESCRIPTION'] = $prop['DESCRIPTION'];
265
            $fields['~PROPERTY_'.$code.'_DESCRIPTION'] = $prop['~DESCRIPTION'];
266
            $fields['PROPERTY_'.$code.'_VALUE_ID'] = $prop['PROPERTY_VALUE_ID'];
267
            if (isset($prop['VALUE_ENUM_ID'])) {
268
                $fields['PROPERTY_'.$code.'_ENUM_ID'] = $prop['VALUE_ENUM_ID'];
269
            }
270
        }
271
    }
272
273
    /**
274
     * Normalize filter before sending it to getList.
275
     * This prevents some inconsistency.
276
     *
277
     * @return array
278
     */
279
    protected function normalizeFilter()
280
    {
281
        $this->filter['IBLOCK_ID'] = $this->iblockId;
282
283
        return $this->filter;
284
    }
285
286
    /**
287
     * Normalize select before sending it to getList.
288
     * This prevents some inconsistency.
289
     *
290
     * @return array
291
     */
292 View Code Duplication
    protected function normalizeSelect()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
293
    {
294
        if ($this->fieldsMustBeSelected()) {
295
            $this->select = array_merge($this->standardFields, $this->select);
296
        }
297
298
        $this->select[] = 'ID';
299
        $this->select[] = 'IBLOCK_ID';
300
301
        return $this->clearSelectArray();
302
    }
303
304
    /**
305
     * Fetch all iblock property codes from database
306
     *
307
     * return array
308
     */
309
    protected function fetchAllPropsForSelect()
310
    {
311
        $props = [];
312
        $rsProps = static::$cIblockObject->GetProperties($this->iblockId);
313
        while ($prop = $rsProps->Fetch()) {
314
            $props[] = 'PROPERTY_'.$prop['CODE'];
315
        }
316
317
        return $props;
318
    }
319
    
320
    protected function multiplySelectForMaxJoinsRestrictionIfNeeded($select)
321
    {
322
        if (!$this->propsMustBeSelected()) {
323
            return [$select, false];
324
        }
325
326
        $chunkSize = 20;
327
        $props = $this->fetchAllPropsForSelect();
328
        if ($this->iblockVersion !== 1 || (count($props) <= $chunkSize)) {
329
            return [array_merge($select, $props), false];
330
        }
331
332
        // начинаем формировать селекты из свойств
333
        $multipleSelect = array_chunk($props, $chunkSize);
334
335
        // добавляем в каждый селект поля "несвойства"
336
        foreach ($multipleSelect as $i => $partOfProps) {
337
            $multipleSelect[$i] = array_merge($select, $partOfProps);
338
        }
339
340
        return [$multipleSelect, true];
341
    }
342
    
343
    protected function mergeChunks($chunks)
344
    {
345
        $items = [];
346
        foreach ($chunks as $chunk) {
347
            foreach ($chunk as $k => $item) {
348
                if (isset($items[$k])) {
349
                    $item->fields['_were_multiplied'] = array_merge((array) $items[$k]->fields['_were_multiplied'], (array) $item->fields['_were_multiplied']);
350
                    $items[$k]->fields = (array) $item->fields + (array) $items[$k]->fields;
351
                } else {
352
                    $items[$k] = $item;
353
                }
354
            }
355
        }
356
357
        return $items;
358
    }
359
    
360
    protected function performFetchUsingSelectedMethod($rsItems)
361
    {
362
        if ($this->fetchUsing['method'] === 'GetNextElement') {
363
            /** @var \_CIBElement $elItem */
364
            $elItem = $rsItems->GetNextElement();
365
            
366
            if (!$elItem) {
367
                return false;
368
            }
369
            
370
            $arItem = $elItem->GetFields();
371
            $arItem['PROPERTIES'] = $elItem->GetProperties();
372
            $this->normalizePropertyResultFormat($arItem);
373
            unset($arItem['PROPERTIES']);
374
        } else {
375
            $arItem = parent::performFetchUsingSelectedMethod($rsItems);
376
        }
377
        
378
        return $arItem;
379
    }
380
    
381
    /**
382
     * Set fetch using from string or array.
383
     *
384
     * @param string|array $methodAndParams
385
     * @return $this
386
     */
387
    public function fetchUsing($methodAndParams)
388
    {
389
        // simple case
390 View Code Duplication
        if (is_string($methodAndParams) || empty($methodAndParams['method'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
391
            if (in_array($methodAndParams, ['GetNextElement', 'getNextElement'])) {
392
                $this->fetchUsing = ['method' => 'GetNextElement', 'params' => [true, true]];
393
                $this->select(['FIELDS', 'PROPERTY_*']);
394
            } else {
395
                parent::fetchUsing($methodAndParams);
396
            }
397
            
398
            return $this;
399
        }
400
        
401
        // complex case
402 View Code Duplication
        if (in_array($methodAndParams['method'], ['GetNextElement', 'getNextElement'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
403
            $bTextHtmlAuto = isset($methodAndParams['params'][0]) ? $methodAndParams['params'][0] : true;
404
            $useTilda = isset($methodAndParams['params'][1]) ? $methodAndParams['params'][1] : true;
405
            $this->fetchUsing = ['method' => 'GetNextElement', 'params' => [$bTextHtmlAuto, $useTilda]];
406
            $this->select(['FIELDS', 'PROPERTY_*']);
407
        } else {
408
            parent::fetchUsing($methodAndParams);
409
        }
410
        
411
        return $this;
412
    }
413
}
414