Completed
Push — master ( f7e861...136cb7 )
by Nekrasov
02:32
created

BaseQuery::keyBy()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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