Completed
Pull Request — master (#28)
by
unknown
01:38
created

ElementQuery::loadModels()   B

Complexity

Conditions 7
Paths 1

Size

Total Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 47
rs 8.223
c 0
b 0
f 0
cc 7
nc 1
nop 0
1
<?php
2
3
namespace Arrilot\BitrixModels\Queries;
4
5
use Arrilot\BitrixCacher\Cache;
6
use Arrilot\BitrixModels\BitrixWrapper;
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
                BitrixWrapper::cacheManagerProvider()->StartTagCache(static::$cacheDir);
165
                BitrixWrapper::cacheManagerProvider()->RegisterTag("iblock_id_new");
166
            }
167
            
168
            if ($chunkQuery) {
169
                $itemsChunks = [];
170
                foreach ($select as $chunkIndex => $selectForChunk) {
171
                    $rsItems = $this->bxObject->GetList($sort, $filter, $groupBy, $navigation, $selectForChunk);
172
                    while ($arItem = $this->performFetchUsingSelectedMethod($rsItems)) {
173
                        $this->addItemToResultsUsingKeyBy($itemsChunks[$chunkIndex], new $this->modelName($arItem['ID'], $arItem));
174
                    }
175
                }
176
177
                $items = $this->mergeChunks($itemsChunks);
178
            } else {
179
                $items = [];
180
                $rsItems = $this->bxObject->GetList($sort, $filter, $groupBy, $navigation, $select);
181
                while ($arItem = $this->performFetchUsingSelectedMethod($rsItems)) {
182
                    $this->addItemToResultsUsingKeyBy($items, new $this->modelName($arItem['ID'], $arItem));
183
                }
184
            }
185
            
186
            if (static::isManagedCacheOn()) {
187
                BitrixWrapper::cacheManagerProvider()->EndTagCache();
188
            }
189
            
190
            return new Collection($items);
191
        };
192
193
        $cacheKeyParams = compact('sort', 'filter', 'groupBy', 'navigation', 'select', 'queryType', 'keyBy', 'fetchUsing');
194
195
        return $this->handleCacheIfNeeded($cacheKeyParams, $callback);
196
    }
197
198
    /**
199
     * Get the first element with a given code.
200
     *
201
     * @param string $code
202
     *
203
     * @return ElementModel
204
     */
205
    public function getByCode($code)
206
    {
207
        $this->filter['CODE'] = $code;
208
209
        return $this->first();
210
    }
211
212
    /**
213
     * Get the first element with a given external id.
214
     *
215
     * @param string $id
216
     *
217
     * @return ElementModel
218
     */
219
    public function getByExternalId($id)
220
    {
221
        $this->filter['EXTERNAL_ID'] = $id;
222
223
        return $this->first();
224
    }
225
226
    /**
227
     * Get count of elements that match $filter.
228
     *
229
     * @return int
230
     */
231 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...
232
    {
233
        if ($this->queryShouldBeStopped) {
234
            return 0;
235
        }
236
237
        $filter = $this->normalizeFilter();
238
        $queryType = "ElementQuery::count";
239
240
        $callback = function () use ($filter) {
241
            return (int) $this->bxObject->GetList(false, $filter, []);
242
        };
243
244
        return $this->handleCacheIfNeeded(compact('filter', 'queryType'), $callback);
245
    }
246
247
//    /**
248
//     * Normalize properties's format converting it to 'PROPERTY_"CODE"_VALUE'.
249
//     *
250
//     * @param array $fields
251
//     *
252
//     * @return null
253
//     */
254
//    protected function normalizePropertyResultFormat(&$fields)
255
//    {
256
//        if (empty($fields['PROPERTIES'])) {
257
//            return;
258
//        }
259
//
260
//        foreach ($fields['PROPERTIES'] as $code => $prop) {
261
//            $fields['PROPERTY_'.$code.'_VALUE'] = $prop['VALUE'];
262
//            $fields['~PROPERTY_'.$code.'_VALUE'] = $prop['~VALUE'];
263
//            $fields['PROPERTY_'.$code.'_DESCRIPTION'] = $prop['DESCRIPTION'];
264
//            $fields['~PROPERTY_'.$code.'_DESCRIPTION'] = $prop['~DESCRIPTION'];
265
//            $fields['PROPERTY_'.$code.'_VALUE_ID'] = $prop['PROPERTY_VALUE_ID'];
266
//            if (isset($prop['VALUE_ENUM_ID'])) {
267
//                $fields['PROPERTY_'.$code.'_ENUM_ID'] = $prop['VALUE_ENUM_ID'];
268
//            }
269
//        }
270
//    }
271
272
    /**
273
     * Normalize filter before sending it to getList.
274
     * This prevents some inconsistency.
275
     *
276
     * @return array
277
     */
278
    protected function normalizeFilter()
279
    {
280
        $this->filter['IBLOCK_ID'] = $this->iblockId;
281
282
        return $this->filter;
283
    }
284
285
    /**
286
     * Normalize select before sending it to getList.
287
     * This prevents some inconsistency.
288
     *
289
     * @return array
290
     */
291 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...
292
    {
293
        if ($this->fieldsMustBeSelected()) {
294
            $this->select = array_merge($this->standardFields, $this->select);
295
        }
296
297
        $this->select[] = 'ID';
298
        $this->select[] = 'IBLOCK_ID';
299
300
        return $this->clearSelectArray();
301
    }
302
303
    /**
304
     * Fetch all iblock property codes from database
305
     *
306
     * return array
307
     */
308
    protected function fetchAllPropsForSelect()
309
    {
310
        $props = [];
311
        $rsProps = static::$cIblockObject->GetProperties($this->iblockId);
312
        while ($prop = $rsProps->Fetch()) {
313
            $props[] = 'PROPERTY_'.$prop['CODE'];
314
        }
315
316
        return $props;
317
    }
318
    
319
    protected function multiplySelectForMaxJoinsRestrictionIfNeeded($select)
320
    {
321
        if (!$this->propsMustBeSelected()) {
322
            return [$select, false];
323
        }
324
325
        $chunkSize = 20;
326
        $props = $this->fetchAllPropsForSelect();
327
        if ($this->iblockVersion !== 1 || (count($props) <= $chunkSize)) {
328
            return [array_merge($select, $props), false];
329
        }
330
331
        // начинаем формировать селекты из свойств
332
        $multipleSelect = array_chunk($props, $chunkSize);
333
334
        // добавляем в каждый селект поля "несвойства"
335
        foreach ($multipleSelect as $i => $partOfProps) {
336
            $multipleSelect[$i] = array_merge($select, $partOfProps);
337
        }
338
339
        return [$multipleSelect, true];
340
    }
341
    
342
    protected function mergeChunks($chunks)
343
    {
344
        $items = [];
345
        foreach ($chunks as $chunk) {
346
            foreach ($chunk as $k => $item) {
347
                if (isset($items[$k])) {
348
                    $item->fields['_were_multiplied'] = array_merge((array) $items[$k]->fields['_were_multiplied'], (array) $item->fields['_were_multiplied']);
349
                    $items[$k]->fields = (array) $item->fields + (array) $items[$k]->fields;
350
                } else {
351
                    $items[$k] = $item;
352
                }
353
            }
354
        }
355
356
        return $items;
357
    }
358
}
359