Completed
Pull Request — master (#26)
by
unknown
06:22
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 CIBlock;
7
use Illuminate\Support\Collection;
8
use Arrilot\BitrixModels\Models\ElementModel;
9
use Exception;
10
11
/**
12
 * @method ElementQuery active()
13
 * @method ElementQuery sortByDate(string $sort = 'desc')
14
 * @method ElementQuery fromSectionWithId(int $id)
15
 * @method ElementQuery fromSectionWithCode(string $code)
16
 */
17
class ElementQuery extends OldCoreQuery
18
{
19
    /**
20
     * CIblock object or test double.
21
     *
22
     * @var object.
23
     */
24
    public static $cIblockObject;
25
26
    /**
27
     * Query sort.
28
     *
29
     * @var array
30
     */
31
    public $sort = ['SORT' => 'ASC'];
32
33
    /**
34
     * Query group by.
35
     *
36
     * @var array
37
     */
38
    public $groupBy = false;
39
40
    /**
41
     * Iblock id.
42
     *
43
     * @var int
44
     */
45
    protected $iblockId;
46
47
    /**
48
     * Iblock version.
49
     *
50
     * @var int
51
     */
52
    protected $iblockVersion;
53
54
    /**
55
     * List of standard entity fields.
56
     *
57
     * @var array
58
     */
59
    protected $standardFields = [
60
        'ID',
61
        'TIMESTAMP_X',
62
        'TIMESTAMP_X_UNIX',
63
        'MODIFIED_BY',
64
        'DATE_CREATE',
65
        'DATE_CREATE_UNIX',
66
        'CREATED_BY',
67
        'IBLOCK_ID',
68
        'IBLOCK_SECTION_ID',
69
        'ACTIVE',
70
        'ACTIVE_FROM',
71
        'ACTIVE_TO',
72
        'SORT',
73
        'NAME',
74
        'PREVIEW_PICTURE',
75
        'PREVIEW_TEXT',
76
        'PREVIEW_TEXT_TYPE',
77
        'DETAIL_PICTURE',
78
        'DETAIL_TEXT',
79
        'DETAIL_TEXT_TYPE',
80
        'SEARCHABLE_CONTENT',
81
        'IN_SECTIONS',
82
        'SHOW_COUNTER',
83
        'SHOW_COUNTER_START',
84
        'CODE',
85
        'TAGS',
86
        'XML_ID',
87
        'EXTERNAL_ID',
88
        'TMP_ID',
89
        'CREATED_USER_NAME',
90
        'DETAIL_PAGE_URL',
91
        'LIST_PAGE_URL',
92
        'CREATED_DATE',
93
    ];
94
95
    /**
96
     * Constructor.
97
     *
98
     * @param object $bxObject
99
     * @param string $modelName
100
     */
101
    public function __construct($bxObject, $modelName)
102
    {
103
        static::instantiateCIblockObject();
104
        parent::__construct($bxObject, $modelName);
105
106
        $this->iblockId = $modelName::iblockId();
107
        $this->iblockVersion = $modelName::IBLOCK_VERSION ?: 2;
108
    }
109
110
    /**
111
     * Instantiate bitrix entity object.
112
     *
113
     * @throws Exception
114
     *
115
     * @return object
116
     */
117
    public static function instantiateCIblockObject()
118
    {
119
        if (static::$cIblockObject) {
120
            return static::$cIblockObject;
121
        }
122
123
        if (class_exists('CIBlock')) {
124
            return static::$cIblockObject = new CIBlock();
125
        }
126
127
        throw new Exception('CIblock object initialization failed');
128
    }
129
130
    /**
131
     * Setter for groupBy.
132
     *
133
     * @param $value
134
     *
135
     * @return $this
136
     */
137
    public function groupBy($value)
138
    {
139
        $this->groupBy = $value;
140
141
        return $this;
142
    }
143
144
    /**
145
     * Get list of items.
146
     *
147
     * @return Collection
148
     */
149
    protected function loadModels()
150
    {
151
        $sort = $this->sort;
152
        $filter = $this->normalizeFilter();
153
        $groupBy = $this->groupBy;
154
        $navigation = $this->navigation;
155
        $select = $this->normalizeSelect();
156
        $queryType = 'ElementQuery::getList';
157
        $fetchUsing = $this->fetchUsing;
158
        $keyBy = $this->keyBy;
159
        list($select, $chunkQuery) = $this->multiplySelectForMaxJoinsRestrictionIfNeeded($select);
160
161
        $callback = function() use ($sort, $filter, $groupBy, $navigation, $select, $chunkQuery) {
162
            if (static::isManagedCacheOn()) {
163
                global $CACHE_MANAGER;
164
                $CACHE_MANAGER->StartTagCache(static::$cacheDir);
165
                $CACHE_MANAGER->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
                $CACHE_MANAGER->EndTagCache();
0 ignored issues
show
Bug introduced by
The variable $CACHE_MANAGER does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
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