1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace NwLaravel\Repositories\Eloquent; |
4
|
|
|
|
5
|
|
|
use Illuminate\Database\Eloquent\Builder; |
6
|
|
|
use Prettus\Repository\Eloquent\BaseRepository; |
7
|
|
|
use NwLaravel\Repositories\RepositoryInterface; |
8
|
|
|
use NwLaravel\Repositories\Criterias\InputCriteria; |
9
|
|
|
use NwLaravel\Resultset\BuilderResultset; |
10
|
|
|
use Prettus\Validator\Contracts\ValidatorInterface; |
11
|
|
|
use Prettus\Repository\Events\RepositoryEntityCreated; |
12
|
|
|
use Prettus\Repository\Events\RepositoryEntityUpdated; |
13
|
|
|
use Prettus\Repository\Events\RepositoryEntityDeleted; |
14
|
|
|
use Illuminate\Database\Query\Expression; |
15
|
|
|
use Illuminate\Database\Query\Grammars; |
16
|
|
|
use BadMethodCallException; |
17
|
|
|
use RuntimeException; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Class AbstractRepository |
21
|
|
|
* |
22
|
|
|
* @abstract |
23
|
|
|
*/ |
24
|
|
|
abstract class AbstractRepository extends BaseRepository implements RepositoryInterface |
25
|
|
|
{ |
26
|
|
|
/** |
27
|
|
|
* @var string |
28
|
|
|
*/ |
29
|
|
|
protected $orderBy; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* @var bool |
33
|
|
|
*/ |
34
|
|
|
protected $skipPresenter = true; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* Reset Model |
38
|
|
|
* |
39
|
|
|
* @return AbstractRepository |
40
|
|
|
* @throws RepositoryException |
41
|
|
|
*/ |
42
|
11 |
|
public function resetModel() |
43
|
|
|
{ |
44
|
11 |
|
parent::resetModel(); |
45
|
11 |
|
return $this; |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* Get Query |
50
|
|
|
* |
51
|
|
|
* @return Builder |
52
|
|
|
*/ |
53
|
2 |
|
public function getQuery() |
54
|
|
|
{ |
55
|
2 |
|
$this->applyCriteria(); |
56
|
2 |
|
$this->applyScope(); |
57
|
|
|
|
58
|
2 |
|
return ($this->model instanceof Builder) ? $this->model : $this->model->newQuery(); |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* Search All |
63
|
|
|
* |
64
|
|
|
* @param array $input Array Imput |
65
|
|
|
* @param string $orderBy String Order By |
66
|
|
|
* @param int $limit Integer Limit |
67
|
|
|
* @param bool $skipPresenter Boolean Skip Presenter |
68
|
|
|
* |
69
|
|
|
* @return BuilderResultset |
70
|
|
|
*/ |
71
|
1 |
|
public function searchAll(array $input, $orderBy = '', $limit = null, $skipPresenter = true) |
72
|
|
|
{ |
73
|
1 |
|
$orderBy = $orderBy?:$this->orderBy; |
74
|
|
|
|
75
|
1 |
|
$query = $this |
76
|
1 |
|
->whereInputCriteria($input) |
77
|
1 |
|
->orderBy($orderBy) |
78
|
1 |
|
->skipPresenter($skipPresenter) |
79
|
1 |
|
->getQuery() |
80
|
1 |
|
->limit($limit); |
81
|
|
|
|
82
|
1 |
|
$this->resetModel(); |
83
|
1 |
|
return app(BuilderResultset::class, [$query]); |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* Search Paginator |
88
|
|
|
* |
89
|
|
|
* @param array $input Array Input |
90
|
|
|
* @param string $orderBy String Order By |
91
|
|
|
* @param int|null $limit Integer Limit |
92
|
|
|
* @param bool $skipPresenter Boolean Skip Presenter |
93
|
|
|
* |
94
|
|
|
* @return Paginator |
95
|
|
|
*/ |
96
|
1 |
|
public function search(array $input, $orderBy = '', $limit = null, $skipPresenter = true) |
97
|
|
|
{ |
98
|
1 |
|
$orderBy = $orderBy?:$this->orderBy; |
99
|
|
|
|
100
|
1 |
|
return $this |
101
|
1 |
|
->whereInputCriteria($input) |
102
|
1 |
|
->orderBy($orderBy) |
103
|
1 |
|
->skipPresenter($skipPresenter) |
104
|
1 |
|
->paginate($limit); |
|
|
|
|
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* Get an array with the values of a given column. |
109
|
|
|
* |
110
|
|
|
* @param string $column String Column |
111
|
|
|
* @param string $key String Key |
112
|
|
|
* |
113
|
|
|
* @return \Illuminate\Support\Collection |
114
|
|
|
*/ |
115
|
1 |
|
public function pluck($column, $key = null) |
116
|
|
|
{ |
117
|
1 |
|
$this->applyCriteria(); |
118
|
1 |
|
$this->applyScope(); |
119
|
|
|
|
120
|
1 |
|
$lists = $this->model->pluck($column, $key); |
121
|
|
|
|
122
|
1 |
|
$this->resetModel(); |
123
|
1 |
|
return $lists; |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* Add an "order by" clause to the query. |
128
|
|
|
* |
129
|
|
|
* @param string $columns String Columns |
130
|
|
|
* @param string $direction String Direction |
131
|
|
|
* |
132
|
|
|
* @return RepositoryInterface |
133
|
|
|
*/ |
134
|
3 |
|
public function orderBy($columns, $direction = 'asc') |
135
|
|
|
{ |
136
|
3 |
|
if (!empty($columns)) { |
137
|
3 |
|
$columns = explode(',', $columns); |
138
|
3 |
|
foreach ($columns as $key => $column) { |
139
|
3 |
|
$column = explode(' ', $column); |
140
|
3 |
|
$column = array_filter($column); |
141
|
3 |
|
$column = array_pad($column, 2, ''); |
142
|
3 |
|
list($field, $sort) = array_values($column); |
143
|
3 |
|
if (!empty($sort)) { |
144
|
2 |
|
$direction = $sort; |
145
|
2 |
|
} |
146
|
3 |
|
$direction = strtoupper($direction); |
147
|
3 |
|
$direction = in_array($direction, ['ASC', 'DESC']) ? $direction : 'ASC'; |
148
|
3 |
|
$this->model = $this->model->orderBy($field, $direction); |
149
|
3 |
|
} |
150
|
3 |
|
} |
151
|
|
|
|
152
|
3 |
|
return $this; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* Random |
157
|
|
|
* |
158
|
|
|
* @return RepositoryInterface |
159
|
|
|
*/ |
160
|
4 |
|
public function random() |
161
|
|
|
{ |
162
|
4 |
|
$grammar = $this->model->getConnection()->getQueryGrammar(); |
163
|
|
|
|
164
|
4 |
|
switch (true) { |
165
|
4 |
|
case $grammar instanceof Grammars\MySqlGrammar: |
166
|
4 |
|
case $grammar instanceof Grammars\SqlServerGrammar: |
167
|
2 |
|
$random = 'RAND()'; |
168
|
2 |
|
break; |
169
|
2 |
|
case $grammar instanceof Grammars\PostgresGrammar: |
170
|
2 |
|
case $grammar instanceof Grammars\SQLiteGrammar: |
171
|
2 |
|
$random = 'RANDOM()'; |
172
|
2 |
|
} |
173
|
|
|
|
174
|
4 |
|
$this->model = $this->model->orderBy(new Expression($random)); |
|
|
|
|
175
|
|
|
|
176
|
4 |
|
return $this; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* Count |
181
|
|
|
* |
182
|
|
|
* @param array $input Array Input |
183
|
|
|
* |
184
|
|
|
* @return int |
185
|
|
|
*/ |
186
|
1 |
|
public function count(array $input = array()) |
187
|
|
|
{ |
188
|
1 |
|
$this->applyCriteria(); |
189
|
1 |
|
$this->applyScope(); |
190
|
|
|
|
191
|
1 |
|
$this->whereInputCriteria($input); |
192
|
|
|
|
193
|
1 |
|
$count = $this->model->count(); |
194
|
|
|
|
195
|
1 |
|
$this->resetModel(); |
196
|
1 |
|
return $count; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* Max |
201
|
|
|
* |
202
|
|
|
* @param mixed $field Mixed Field |
203
|
|
|
* @param array $input Array Input |
204
|
|
|
* |
205
|
|
|
* @return mixed |
206
|
|
|
*/ |
207
|
1 |
|
public function max($field, array $input = array()) |
208
|
|
|
{ |
209
|
1 |
|
$this->applyCriteria(); |
210
|
1 |
|
$this->applyScope(); |
211
|
|
|
|
212
|
1 |
|
$this->whereInputCriteria($input); |
213
|
|
|
|
214
|
1 |
|
$max = $this->model->max($field); |
215
|
|
|
|
216
|
1 |
|
$this->resetModel(); |
217
|
1 |
|
return $max; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* Min |
222
|
|
|
* |
223
|
|
|
* @param mixed $field Mixed Field |
224
|
|
|
* @param array $input Array Input |
225
|
|
|
* |
226
|
|
|
* @return mixed |
227
|
|
|
*/ |
228
|
1 |
|
public function min($field, array $input = array()) |
229
|
|
|
{ |
230
|
1 |
|
$this->applyCriteria(); |
231
|
1 |
|
$this->applyScope(); |
232
|
|
|
|
233
|
1 |
|
$this->whereInputCriteria($input); |
234
|
|
|
|
235
|
1 |
|
$max = $this->model->min($field); |
236
|
|
|
|
237
|
1 |
|
$this->resetModel(); |
238
|
1 |
|
return $max; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* Sum |
243
|
|
|
* |
244
|
|
|
* @param mixed $field Mixed Field |
245
|
|
|
* @param array $input Array Input |
246
|
|
|
* |
247
|
|
|
* @return float |
248
|
|
|
*/ |
249
|
1 |
|
public function sum($field, array $input = array()) |
250
|
|
|
{ |
251
|
1 |
|
$this->applyCriteria(); |
252
|
1 |
|
$this->applyScope(); |
253
|
|
|
|
254
|
1 |
|
$this->whereInputCriteria($input); |
255
|
|
|
|
256
|
1 |
|
$max = $this->model->sum($field); |
257
|
|
|
|
258
|
1 |
|
$this->resetModel(); |
259
|
1 |
|
return $max; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
/** |
263
|
|
|
* Average |
264
|
|
|
* |
265
|
|
|
* @param mixed $field Mixed Field |
266
|
|
|
* @param array $input Array Input |
267
|
|
|
* |
268
|
|
|
* @return int |
269
|
|
|
*/ |
270
|
1 |
|
public function avg($field, array $input = array()) |
271
|
|
|
{ |
272
|
1 |
|
$this->applyCriteria(); |
273
|
1 |
|
$this->applyScope(); |
274
|
|
|
|
275
|
1 |
|
$this->whereInputCriteria($input); |
276
|
|
|
|
277
|
1 |
|
$avg = $this->model->avg($field); |
278
|
|
|
|
279
|
1 |
|
$this->resetModel(); |
280
|
1 |
|
return $avg; |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* Order Up |
285
|
|
|
* |
286
|
|
|
* @param Model $model |
287
|
|
|
* @param string $field Field Order |
288
|
|
|
* @param array $input Array Where |
289
|
|
|
* |
290
|
|
|
* @return boolean |
291
|
|
|
*/ |
292
|
|
|
public function orderUp($model, $field, array $input = []) |
293
|
|
|
{ |
294
|
|
|
$input["{$field} <= ?"] = $model->{$field}; |
295
|
|
|
$input["id != ?"] = $model->id; |
296
|
|
|
return $this->reorder($model, $field, $input, 'DESC'); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
/** |
300
|
|
|
* Order Down |
301
|
|
|
* |
302
|
|
|
* @param Model $model |
303
|
|
|
* @param string $field Field Order |
304
|
|
|
* @param array $input Array Where |
305
|
|
|
* |
306
|
|
|
* @return boolean |
307
|
|
|
*/ |
308
|
|
|
public function orderDown($model, $field, array $input = []) |
309
|
|
|
{ |
310
|
|
|
$input["{$field} >= ?"] = $model->{$field}; |
311
|
|
|
$input["id != ?"] = $model->id; |
312
|
|
|
return $this->reorder($model, $field, $input, 'ASC'); |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* Reorder |
317
|
|
|
* |
318
|
|
|
* @param Model $model |
319
|
|
|
* @param string $field Field Order |
320
|
|
|
* @param array $input Array Where |
321
|
|
|
* @param string $sort Sort |
322
|
|
|
* |
323
|
|
|
* @return boolean |
324
|
|
|
*/ |
325
|
|
|
protected function reorder($model, $field, array $input, $sort) |
326
|
|
|
{ |
327
|
|
|
if (!$model->exists) { |
328
|
|
|
return false; |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
$order = $model->{$field}; |
332
|
|
|
|
333
|
|
|
$anterior = $this->whereInputCriteria($input)->orderBy($field, $sort)->first(); |
334
|
|
|
|
335
|
|
|
if ($anterior) { |
336
|
|
|
$model->{$field} = $anterior->{$field}; |
337
|
|
|
$model->save(); |
338
|
|
|
|
339
|
|
|
$anterior->{$field} = $order; |
340
|
|
|
$anterior->save(); |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
return true; |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* Where InputCriteria |
348
|
|
|
* |
349
|
|
|
* @param array $input Array Input |
350
|
|
|
* |
351
|
|
|
* @return RepositoryInterface |
352
|
|
|
*/ |
353
|
8 |
|
public function whereInputCriteria(array $input = array()) |
354
|
|
|
{ |
355
|
8 |
|
if (count($input)) { |
356
|
8 |
|
$criteria = app(InputCriteria::class, [$input]); |
357
|
8 |
|
$this->model = $criteria->apply($this->model, $this); |
358
|
8 |
|
} |
359
|
|
|
|
360
|
8 |
|
return $this; |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
/** |
364
|
|
|
* Validar |
365
|
|
|
* |
366
|
|
|
* @param array $attributes |
367
|
|
|
* @param string $action |
368
|
|
|
* @param string $id |
369
|
|
|
* |
370
|
|
|
* @return bool |
371
|
|
|
*/ |
372
|
2 |
|
public function validar(array $attributes, $action, $id = null) |
373
|
|
|
{ |
374
|
2 |
|
$return = false; |
375
|
|
|
|
376
|
2 |
|
if (!is_null($this->validator)) { |
377
|
|
|
// we should pass data that has been casts by the model |
378
|
|
|
// to make sure data type are same because validator may need to use |
379
|
|
|
// this data to compare with data that fetch from database. |
380
|
2 |
|
$model = $this->model->newInstance()->forceFill($attributes); |
381
|
2 |
|
$attributes = array_merge($attributes, $model->toArray()); |
382
|
|
|
|
383
|
2 |
|
$validator = $this->validator->with($attributes); |
384
|
|
|
|
385
|
2 |
|
if ($id) { |
|
|
|
|
386
|
1 |
|
$validator->setId($id); |
387
|
1 |
|
} |
388
|
|
|
|
389
|
2 |
|
$return = $validator->passesOrFail($action); |
390
|
2 |
|
} |
391
|
|
|
|
392
|
2 |
|
return $return; |
393
|
|
|
} |
394
|
|
|
|
395
|
|
|
/** |
396
|
|
|
* Save a new model in repository |
397
|
|
|
* |
398
|
|
|
* @throws ValidatorException |
399
|
|
|
* @param array $attributes Array Attributes |
400
|
|
|
* @return mixed |
401
|
|
|
*/ |
402
|
1 |
|
public function create(array $attributes) |
403
|
|
|
{ |
404
|
1 |
|
$this->validar($attributes, ValidatorInterface::RULE_CREATE); |
405
|
|
|
|
406
|
1 |
|
$model = $this->model->newInstance($attributes); |
407
|
1 |
|
$model->save(); |
408
|
1 |
|
$this->resetModel(); |
409
|
|
|
|
410
|
1 |
|
event(new RepositoryEntityCreated($this, $model)); |
411
|
|
|
|
412
|
1 |
|
return $this->parserResult($model); |
413
|
|
|
} |
414
|
|
|
|
415
|
|
|
/** |
416
|
|
|
* Update a model in repository by id |
417
|
|
|
* |
418
|
|
|
* @throws ValidatorException |
419
|
|
|
* @param array $attributes Array Attributes |
420
|
|
|
* @param int $id Integer Id |
421
|
|
|
* @return mixed |
422
|
|
|
*/ |
423
|
1 |
|
public function update(array $attributes, $id) |
424
|
|
|
{ |
425
|
1 |
|
$this->applyScope(); |
426
|
|
|
|
427
|
1 |
|
$this->validar($attributes, ValidatorInterface::RULE_UPDATE, $id); |
428
|
|
|
|
429
|
1 |
|
$temporarySkipPresenter = $this->skipPresenter; |
430
|
|
|
|
431
|
1 |
|
$this->skipPresenter(true); |
432
|
|
|
|
433
|
1 |
|
$model = $this->model->findOrFail($id); |
434
|
1 |
|
$model->fill($attributes); |
435
|
1 |
|
$model->save(); |
436
|
|
|
|
437
|
1 |
|
$this->skipPresenter($temporarySkipPresenter); |
438
|
1 |
|
$this->resetModel(); |
439
|
|
|
|
440
|
1 |
|
event(new RepositoryEntityUpdated($this, $model)); |
441
|
|
|
|
442
|
1 |
|
return $this->parserResult($model); |
443
|
|
|
} |
444
|
|
|
|
445
|
|
|
/** |
446
|
|
|
* Delete multiple entities by given criteria. |
447
|
|
|
* |
448
|
|
|
* @param array $where |
449
|
|
|
* |
450
|
|
|
* @return boolean|null |
451
|
|
|
*/ |
452
|
1 |
|
public function deleteWhere(array $where) |
453
|
|
|
{ |
454
|
1 |
|
$this->applyCriteria(); |
455
|
1 |
|
$this->applyScope(); |
456
|
|
|
|
457
|
1 |
|
$temporarySkipPresenter = $this->skipPresenter; |
458
|
1 |
|
$this->skipPresenter(true); |
459
|
|
|
|
460
|
1 |
|
$this->whereInputCriteria($where); |
461
|
|
|
|
462
|
1 |
|
$deleted = $this->model->delete(); |
463
|
|
|
|
464
|
1 |
|
event(new RepositoryEntityDeleted($this, $this->model)); |
465
|
|
|
|
466
|
1 |
|
$this->skipPresenter($temporarySkipPresenter); |
467
|
1 |
|
$this->resetModel(); |
468
|
|
|
|
469
|
1 |
|
return $deleted; |
470
|
|
|
} |
471
|
|
|
|
472
|
|
|
/** |
473
|
|
|
* Update multiple entities by given criteria. |
474
|
|
|
* |
475
|
|
|
* @param array $where |
476
|
|
|
* |
477
|
|
|
* @return boolean|null |
478
|
|
|
*/ |
479
|
1 |
|
public function updateWhere(array $attributes, array $where) |
480
|
|
|
{ |
481
|
1 |
|
$this->applyCriteria(); |
482
|
1 |
|
$this->applyScope(); |
483
|
|
|
|
484
|
1 |
|
$temporarySkipPresenter = $this->skipPresenter; |
485
|
1 |
|
$this->skipPresenter(true); |
486
|
|
|
|
487
|
1 |
|
$this->whereInputCriteria($where); |
488
|
|
|
|
489
|
1 |
|
$updated = $this->model->update($attributes); |
490
|
|
|
|
491
|
1 |
|
event(new RepositoryEntityUpdated($this, $this->model)); |
492
|
|
|
|
493
|
1 |
|
$this->skipPresenter($temporarySkipPresenter); |
494
|
1 |
|
$this->resetModel(); |
495
|
|
|
|
496
|
1 |
|
return $updated; |
497
|
|
|
} |
498
|
|
|
|
499
|
|
|
/** |
500
|
|
|
* Handle dynamic method calls into the method. |
501
|
|
|
* |
502
|
|
|
* @param string $method |
503
|
|
|
* @param array $parameters |
504
|
|
|
* |
505
|
|
|
* @return AbstractRepository |
506
|
|
|
* |
507
|
|
|
* @throws BadMethodCallException |
508
|
|
|
*/ |
509
|
7 |
|
public function __call($method, $parameters) |
510
|
|
|
{ |
511
|
7 |
|
$pattern = '/^(((where|orWhere).*)|limit|groupBy|join|leftJoin|rightJoin|crossJoin)$/'; |
512
|
7 |
|
if (preg_match($pattern, $method)) { |
513
|
6 |
|
$this->model = call_user_func_array([$this->model, $method], $parameters); |
514
|
6 |
|
return $this; |
515
|
|
|
} |
516
|
|
|
|
517
|
1 |
|
$className = static::class; |
518
|
1 |
|
throw new BadMethodCallException("Call to undefined method {$className}::{$method}()"); |
519
|
|
|
} |
520
|
|
|
} |
521
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.