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