Completed
Push — master ( b8b49a...ba401b )
by Nekrasov
43:30
created

BaseQuery::rememberInCache()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
rs 8.8817
c 0
b 0
f 0
cc 6
nc 7
nop 3
1
<?php
2
3
namespace Arrilot\BitrixModels\Queries;
4
5
use Arrilot\BitrixModels\Models\BaseBitrixModel;
6
use BadMethodCallException;
7
use Bitrix\Main\Data\Cache;
8
use Closure;
9
use CPHPCache;
10
use Illuminate\Pagination\Paginator;
11
use Illuminate\Pagination\LengthAwarePaginator;
12
use Illuminate\Support\Collection;
13
use LogicException;
14
15
abstract class BaseQuery
16
{
17
    use BaseRelationQuery;
18
19
    /**
20
     * Query select.
21
     *
22
     * @var array
23
     */
24
    public $select = [];
25
    /**
26
     * Bitrix object to be queried.
27
     *
28
     * @var object|string
29
     */
30
    protected $bxObject;
31
32
    /**
33
     * Name of the model that calls the query.
34
     *
35
     * @var string
36
     */
37
    protected $modelName;
38
39
    /**
40
     * Model that calls the query.
41
     *
42
     * @var object
43
     */
44
    protected $model;
45
46
    /**
47
     * Query sort.
48
     *
49
     * @var array
50
     */
51
    public $sort = [];
52
53
    /**
54
     * Query filter.
55
     *
56
     * @var array
57
     */
58
    public $filter = [];
59
60
    /**
61
     * Query navigation.
62
     *
63
     * @var array|bool
64
     */
65
    public $navigation = false;
66
67
    /**
68
     * The key to list items in array of results.
69
     * Set to false to have auto incrementing integer.
70
     *
71
     * @var string|bool
72
     */
73
    public $keyBy = 'ID';
74
75
    /**
76
     * Number of minutes to cache a query
77
     *
78
     * @var double|int
79
     */
80
    public $cacheTtl = 0;
81
82
    /**
83
     * Indicates that the query should be stopped instead of touching the DB.
84
     * Can be set in query scopes or manually.
85
     *
86
     * @var bool
87
     */
88
    protected $queryShouldBeStopped = false;
89
90
    /**
91
     * Get count of users that match $filter.
92
     *
93
     * @return int
94
     */
95
    abstract public function count();
96
97
    /**
98
     * Подготавливает запрос и вызывает loadModels()
99
     *
100
     * @return Collection
101
     */
102
    public function getList()
103
    {
104
        if (!is_null($this->primaryModel)) {
105
            // Запрос - подгрузка релейшена. Надо добавить filter
106
            $this->filterByModels([$this->primaryModel]);
107
        }
108
109
        if ($this->queryShouldBeStopped) {
110
            return new Collection();
111
        }
112
113
        $models = $this->loadModels();
114
115
        if (!empty($this->with)) {
116
            $this->findWith($this->with, $models);
117
        }
118
119
        return $models;
0 ignored issues
show
Bug Compatibility introduced by
The expression return $models; of type Illuminate\Support\Colle...odels\BaseBitrixModel[] is incompatible with the return type documented by Arrilot\BitrixModels\Queries\BaseQuery::getList of type Illuminate\Support\Collection as it can also be of type Arrilot\BitrixModels\Models\BaseBitrixModel[] which is not included in this return type.
Loading history...
120
    }
121
122
    /**
123
     * Get list of items.
124
     *
125
     * @return Collection
126
     */
127
    abstract protected function loadModels();
128
129
    /**
130
     * Constructor.
131
     *
132
     * @param object|string $bxObject
133
     * @param string $modelName
134
     */
135
    public function __construct($bxObject, $modelName)
136
    {
137
        $this->bxObject = $bxObject;
138
        $this->modelName = $modelName;
139
        $this->model = new $modelName();
140
    }
141
142
    /**
143
     * Get the first item that matches query params.
144
     *
145
     * @return mixed
146
     */
147
    public function first()
148
    {
149
        return $this->limit(1)->getList()->first(null, false);
150
    }
151
152
    /**
153
     * Get item by its id.
154
     *
155
     * @param int $id
156
     *
157
     * @return mixed
158
     */
159
    public function getById($id)
160
    {
161
        if (!$id || $this->queryShouldBeStopped) {
162
            return false;
163
        }
164
165
        $this->sort = [];
166
        $this->filter['ID'] = $id;
167
168
        return $this->getList()->first(null, false);
169
    }
170
171
    /**
172
     * Setter for sort.
173
     *
174
     * @param mixed  $by
175
     * @param string $order
176
     *
177
     * @return $this
178
     */
179
    public function sort($by, $order = 'ASC')
180
    {
181
        $this->sort = is_array($by) ? $by : [$by => $order];
182
183
        return $this;
184
    }
185
186
    /**
187
     * Another setter for sort.
188
     *
189
     * @param mixed  $by
190
     * @param string $order
191
     *
192
     * @return $this
193
     */
194
    public function order($by, $order = 'ASC')
195
    {
196
        return $this->sort($by, $order);
197
    }
198
199
    /**
200
     * Setter for filter.
201
     *
202
     * @param array $filter
203
     *
204
     * @return $this
205
     */
206
    public function filter($filter)
207
    {
208
        $this->filter = array_merge($this->filter, $filter);
209
210
        return $this;
211
    }
212
213
    /**
214
     * Reset filter.
215
     *
216
     * @return $this
217
     */
218
    public function resetFilter()
219
    {
220
        $this->filter = [];
221
222
        return $this;
223
    }
224
225
    /**
226
     * Add another filter to filters array.
227
     *
228
     * @param array $filters
229
     *
230
     * @return $this
231
     */
232
    public function addFilter($filters)
233
    {
234
        foreach ($filters as $field => $value) {
235
            $this->filter[$field] = $value;
236
        }
237
238
        return $this;
239
    }
240
241
    /**
242
     * Setter for navigation.
243
     *
244
     * @param $value
245
     *
246
     * @return $this
247
     */
248
    public function navigation($value)
249
    {
250
        $this->navigation = $value;
251
252
        return $this;
253
    }
254
255
    /**
256
     * Setter for select.
257
     *
258
     * @param $value
259
     *
260
     * @return $this
261
     */
262
    public function select($value)
263
    {
264
        $this->select = is_array($value) ? $value : func_get_args();
265
266
        return $this;
267
    }
268
269
    /**
270
     * Setter for cache ttl.
271
     *
272
     * @param float|int $minutes
273
     *
274
     * @return $this
275
     */
276
    public function cache($minutes)
277
    {
278
        $this->cacheTtl = $minutes;
279
280
        return $this;
281
    }
282
283
    /**
284
     * Setter for keyBy.
285
     *
286
     * @param string $value
287
     *
288
     * @return $this
289
     */
290
    public function keyBy($value)
291
    {
292
        $this->keyBy = $value;
293
294
        return $this;
295
    }
296
297
    /**
298
     * Set the "limit" value of the query.
299
     *
300
     * @param int $value
301
     *
302
     * @return $this
303
     */
304
    public function limit($value)
305
    {
306
        $this->navigation['nPageSize'] = $value;
307
308
        return $this;
309
    }
310
311
    /**
312
     * Set the "page number" value of the query.
313
     *
314
     * @param int $num
315
     *
316
     * @return $this
317
     */
318
    public function page($num)
319
    {
320
        $this->navigation['iNumPage'] = $num;
321
322
        return $this;
323
    }
324
325
    /**
326
     * Alias for "limit".
327
     *
328
     * @param int $value
329
     *
330
     * @return $this
331
     */
332
    public function take($value)
333
    {
334
        return $this->limit($value);
335
    }
336
337
    /**
338
     * Set the limit and offset for a given page.
339
     *
340
     * @param  int  $page
341
     * @param  int  $perPage
342
     * @return $this
343
     */
344
    public function forPage($page, $perPage = 15)
345
    {
346
        return $this->page($page)->take($perPage);
347
    }
348
349
    /**
350
     * Paginate the given query into a paginator.
351
     *
352
     * @param  int  $perPage
353
     * @param  string  $pageName
354
     *
355
     * @return \Illuminate\Pagination\LengthAwarePaginator
356
     */
357 View Code Duplication
    public function paginate($perPage = 15, $pageName = 'page')
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...
358
    {
359
        $page = Paginator::resolveCurrentPage($pageName);
360
        $total = $this->count();
361
        $results = $this->forPage($page, $perPage)->getList();
362
363
        return new LengthAwarePaginator($results, $total, $perPage, $page, [
364
            'path' => Paginator::resolveCurrentPath(),
365
            'pageName' => $pageName,
366
        ]);
367
    }
368
369
    /**
370
     * Get a paginator only supporting simple next and previous links.
371
     *
372
     * This is more efficient on larger data-sets, etc.
373
     *
374
     * @param  int  $perPage
375
     * @param  string  $pageName
376
     *
377
     * @return \Illuminate\Pagination\Paginator
378
     */
379 View Code Duplication
    public function simplePaginate($perPage = 15, $pageName = 'page')
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...
380
    {
381
        $page = Paginator::resolveCurrentPage($pageName);
382
        $results = $this->forPage($page, $perPage + 1)->getList();
383
384
        return new Paginator($results, $perPage, $page, [
385
            'path' => Paginator::resolveCurrentPath(),
386
            'pageName' => $pageName,
387
        ]);
388
    }
389
390
    /**
391
     * Stop the query from touching DB.
392
     *
393
     * @return $this
394
     */
395
    public function stopQuery()
396
    {
397
        $this->queryShouldBeStopped = true;
398
399
        return $this;
400
    }
401
402
    /**
403
     * Adds $item to $results using keyBy value.
404
     *
405
     * @param $results
406
     * @param BaseBitrixModel $object
407
     *
408
     * @return void
409
     */
410
    protected function addItemToResultsUsingKeyBy(&$results, BaseBitrixModel $object)
411
    {
412
        $item = $object->fields;
413
        if (!isset($item[$this->keyBy])) {
414
            throw new LogicException("Field {$this->keyBy} is not found in object");
415
        }
416
417
        $keyByValue = $item[$this->keyBy];
418
419
        if (!isset($results[$keyByValue])) {
420
            $results[$keyByValue] = $object;
421
        } else {
422
            $oldFields = $results[$keyByValue]->fields;
423
            foreach ($oldFields as $field => $oldValue) {
424
                // пропускаем служебные поля.
425
                if (in_array($field, ['_were_multiplied', 'PROPERTIES'])) {
426
                    continue;
427
                }
428
429
                $alreadyMultiplied = !empty($oldFields['_were_multiplied'][$field]);
430
431
                // мультиплицируем только несовпадающие значения полей
432
                $newValue = $item[$field];
433
                if ($oldValue !== $newValue) {
434
                    // если еще не мультиплицировали поле, то его надо превратить в массив.
435
                    if (!$alreadyMultiplied) {
436
                        $oldFields[$field] = [
437
                            $oldFields[$field]
438
                        ];
439
                        $oldFields['_were_multiplied'][$field] = true;
440
                    }
441
442
                    // добавляем новое значению поле если такого еще нет.
443
                    if (empty($oldFields[$field]) || (is_array($oldFields[$field]) && !in_array($newValue, $oldFields[$field]))) {
444
                        $oldFields[$field][] = $newValue;
445
                    }
446
                }
447
            }
448
449
            $results[$keyByValue]->fields = $oldFields;
450
        }
451
    }
452
453
    /**
454
     * Determine if all fields must be selected.
455
     *
456
     * @return bool
457
     */
458
    protected function fieldsMustBeSelected()
459
    {
460
        return in_array('FIELDS', $this->select);
461
    }
462
463
    /**
464
     * Determine if all fields must be selected.
465
     *
466
     * @return bool
467
     */
468
    protected function propsMustBeSelected()
469
    {
470
        return in_array('PROPS', $this->select)
471
            || in_array('PROPERTIES', $this->select)
472
            || in_array('PROPERTY_VALUES', $this->select);
473
    }
474
475
    /**
476
     * Set $array[$new] as $array[$old] and delete $array[$old].
477
     *
478
     * @param array $array
479
     * @param $old
480
     * @param $new
481
     *
482
     * return null
483
     */
484
    protected function substituteField(&$array, $old, $new)
485
    {
486
        if (isset($array[$old]) && !isset($array[$new])) {
487
            $array[$new] = $array[$old];
488
        }
489
490
        unset($array[$old]);
491
    }
492
493
    /**
494
     * Clear select array from duplication and additional fields.
495
     *
496
     * @return array
497
     */
498
    protected function clearSelectArray()
499
    {
500
        $strip = ['FIELDS', 'PROPS', 'PROPERTIES', 'PROPERTY_VALUES', 'GROUPS', 'GROUP_ID', 'GROUPS_ID'];
501
502
        return array_values(array_diff(array_unique($this->select), $strip));
503
    }
504
505
    /**
506
     * Store closure's result in the cache for a given number of minutes.
507
     *
508
     * @param string $key
509
     * @param double $minutes
510
     * @param Closure $callback
511
     * @return mixed
512
     */
513
    protected function rememberInCache($key, $minutes, Closure $callback)
514
    {
515
        $minutes = (double) $minutes;
516
        if ($minutes <= 0) {
517
            return $callback();
518
        }
519
520
        $cache = Cache::createInstance();
521
        if ($cache->initCache($minutes * 60, $key, '/bitrix-models')) {
522
            $vars = $cache->getVars();
523
            return !empty($vars['isCollection']) ? new Collection($vars['cache']) : $vars['cache'];
524
        }
525
526
        $cache->startDataCache();
527
        $result = $callback();
528
529
        // Bitrix cache is bad for storing collections. Let's convert it to array.
530
        $isCollection = $result instanceof Collection;
531
        if ($isCollection) {
532
            $result = $result->all();
533
        }
534
535
        $cache->endDataCache(['cache' => $result, 'isCollection' => $isCollection]);
536
537
        return $isCollection ? new Collection($result) : $result;
538
    }
539
540
    protected function handleCacheIfNeeded($cacheKeyParams, Closure $callback)
541
    {
542
        return $this->cacheTtl
543
            ? $this->rememberInCache(md5(json_encode($cacheKeyParams)), $this->cacheTtl, $callback)
544
            : $callback();
545
    }
546
547
    /**
548
     * Handle dynamic method calls into the method.
549
     *
550
     * @param string $method
551
     * @param array  $parameters
552
     *
553
     * @throws BadMethodCallException
554
     *
555
     * @return $this
556
     */
557
    public function __call($method, $parameters)
558
    {
559
        if (method_exists($this->model, 'scope'.$method)) {
560
            array_unshift($parameters, $this);
561
562
            $query = call_user_func_array([$this->model, 'scope'.$method], $parameters);
563
564
            if ($query === false) {
565
                $this->stopQuery();
566
            }
567
568
            return $query instanceof static ? $query : $this;
569
        }
570
571
        $className = get_class($this);
572
573
        throw new BadMethodCallException("Call to undefined method {$className}::{$method}()");
574
    }
575
    
576
    protected function prepareMultiFilter(&$key, &$value)
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
577
    {
578
    
579
    }
580
}
581