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 Illuminate\Database\Query\Expression; |
14
|
|
|
use Illuminate\Database\Query\Grammars; |
15
|
|
|
use BadMethodCallException; |
16
|
|
|
use RuntimeException; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Class AbstractRepository |
20
|
|
|
* |
21
|
|
|
* @abstract |
22
|
|
|
*/ |
23
|
|
|
abstract class AbstractRepository extends BaseRepository implements RepositoryInterface |
24
|
|
|
{ |
25
|
|
|
/** |
26
|
|
|
* @var string |
27
|
|
|
*/ |
28
|
|
|
protected $orderBy; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var bool |
32
|
|
|
*/ |
33
|
|
|
protected $skipPresenter = true; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Reset Model |
37
|
|
|
* |
38
|
|
|
* @return AbstractRepository |
39
|
|
|
* @throws RepositoryException |
40
|
|
|
*/ |
41
|
9 |
|
public function resetModel() |
42
|
|
|
{ |
43
|
9 |
|
parent::resetModel(); |
44
|
9 |
|
return $this; |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Get Query |
49
|
|
|
* |
50
|
|
|
* @return Builder |
51
|
|
|
*/ |
52
|
2 |
|
public function getQuery() |
53
|
|
|
{ |
54
|
2 |
|
$this->applyCriteria(); |
55
|
2 |
|
$this->applyScope(); |
56
|
|
|
|
57
|
2 |
|
return ($this->model instanceof Builder) ? $this->model : $this->model->newQuery(); |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Search All |
62
|
|
|
* |
63
|
|
|
* @param array $input Array Imput |
64
|
|
|
* @param string $orderBy String Order By |
65
|
|
|
* @param int $limit Integer Limit |
66
|
|
|
* @param bool $skipPresenter Boolean Skip Presenter |
67
|
|
|
* |
68
|
|
|
* @return BuilderResultset |
69
|
|
|
*/ |
70
|
1 |
|
public function searchAll(array $input, $orderBy = '', $limit = null, $skipPresenter = true) |
71
|
|
|
{ |
72
|
1 |
|
$orderBy = $orderBy?:$this->orderBy; |
73
|
|
|
|
74
|
1 |
|
$query = $this |
75
|
1 |
|
->whereInputCriteria($input) |
76
|
1 |
|
->orderBy($orderBy) |
77
|
1 |
|
->skipPresenter($skipPresenter) |
78
|
1 |
|
->getQuery() |
79
|
1 |
|
->limit($limit); |
80
|
|
|
|
81
|
1 |
|
$this->resetModel(); |
82
|
1 |
|
return app(BuilderResultset::class, [$query]); |
|
|
|
|
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* Search Paginator |
87
|
|
|
* |
88
|
|
|
* @param array $input Array Input |
89
|
|
|
* @param string $orderBy String Order By |
90
|
|
|
* @param int|null $limit Integer Limit |
91
|
|
|
* @param bool $skipPresenter Boolean Skip Presenter |
92
|
|
|
* |
93
|
|
|
* @return Paginator |
94
|
|
|
*/ |
95
|
1 |
|
public function search(array $input, $orderBy = '', $limit = null, $skipPresenter = true) |
96
|
|
|
{ |
97
|
1 |
|
$orderBy = $orderBy?:$this->orderBy; |
98
|
|
|
|
99
|
1 |
|
return $this |
100
|
1 |
|
->whereInputCriteria($input) |
101
|
1 |
|
->orderBy($orderBy) |
102
|
1 |
|
->skipPresenter($skipPresenter) |
103
|
1 |
|
->paginate($limit); |
|
|
|
|
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Get an array with the values of a given column. |
108
|
|
|
* |
109
|
|
|
* @param string $column String Column |
110
|
|
|
* @param string $key String Key |
111
|
|
|
* |
112
|
|
|
* @return \Illuminate\Support\Collection |
113
|
|
|
*/ |
114
|
1 |
|
public function pluck($column, $key = null) |
115
|
|
|
{ |
116
|
1 |
|
$this->applyCriteria(); |
117
|
1 |
|
$this->applyScope(); |
118
|
|
|
|
119
|
1 |
|
$lists = $this->model->pluck($column, $key); |
120
|
|
|
|
121
|
1 |
|
$this->resetModel(); |
122
|
1 |
|
return $lists; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* Add an "order by" clause to the query. |
127
|
|
|
* |
128
|
|
|
* @param string $columns String Columns |
129
|
|
|
* @param string $direction String Direction |
130
|
|
|
* |
131
|
|
|
* @return RepositoryInterface |
132
|
|
|
*/ |
133
|
3 |
|
public function orderBy($columns, $direction = 'asc') |
134
|
|
|
{ |
135
|
3 |
|
if (!empty($columns)) { |
136
|
3 |
|
$columns = explode(',', $columns); |
137
|
3 |
|
foreach ($columns as $key => $column) { |
138
|
3 |
|
$column = explode(' ', $column); |
139
|
3 |
|
$column = array_filter($column); |
140
|
3 |
|
$column = array_pad($column, 2, ''); |
141
|
3 |
|
list($field, $sort) = array_values($column); |
142
|
3 |
|
if (!empty($sort)) { |
143
|
2 |
|
$direction = $sort; |
144
|
2 |
|
} |
145
|
3 |
|
$direction = strtoupper($direction); |
146
|
3 |
|
$direction = in_array($direction, ['ASC', 'DESC']) ? $direction : 'ASC'; |
147
|
3 |
|
$this->model = $this->model->orderBy($field, $direction); |
148
|
3 |
|
} |
149
|
3 |
|
} |
150
|
|
|
|
151
|
3 |
|
return $this; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Random |
156
|
|
|
* |
157
|
|
|
* @return RepositoryInterface |
158
|
|
|
*/ |
159
|
4 |
|
public function random() |
160
|
|
|
{ |
161
|
4 |
|
$grammar = $this->model->getConnection()->getQueryGrammar(); |
162
|
|
|
|
163
|
4 |
|
switch (true) { |
164
|
4 |
|
case $grammar instanceof Grammars\MySqlGrammar: |
165
|
4 |
|
case $grammar instanceof Grammars\SqlServerGrammar: |
166
|
2 |
|
$random = 'RAND()'; |
167
|
2 |
|
break; |
168
|
2 |
|
case $grammar instanceof Grammars\PostgresGrammar: |
169
|
2 |
|
case $grammar instanceof Grammars\SQLiteGrammar: |
170
|
2 |
|
$random = 'RANDOM()'; |
171
|
2 |
|
break; |
172
|
|
|
} |
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
|
|
|
* Reorder |
285
|
|
|
* |
286
|
|
|
* @param string $field Field Order |
287
|
|
|
* |
288
|
|
|
* @return boolean |
289
|
|
|
*/ |
290
|
4 |
|
public function reorder($field, $input = null) |
291
|
|
|
{ |
292
|
4 |
|
$self = $this; |
293
|
4 |
|
$conn = $this->model->getConnection(); |
294
|
|
|
|
295
|
4 |
|
$reorder = function ($statement, $value) use ($self, $conn, $input, $field) { |
296
|
3 |
|
$conn->statement($statement); |
297
|
3 |
|
$data = [$field => $conn->raw($value)]; |
298
|
|
|
|
299
|
3 |
|
return $self->whereInputCriteria($input) |
300
|
3 |
|
->orderBy($field) |
301
|
3 |
|
->getQuery() |
302
|
3 |
|
->update($data); |
303
|
4 |
|
}; |
304
|
|
|
|
305
|
4 |
|
$return = false; |
306
|
|
|
|
307
|
4 |
|
switch (true) { |
308
|
4 |
|
case $conn instanceof \Illuminate\Database\MySqlConnection: |
309
|
1 |
|
$statement = "SET @rownum := 0"; |
310
|
1 |
|
$value = "(@rownum := @rownum+1)"; |
311
|
1 |
|
$return = $reorder($statement, $value); |
312
|
1 |
|
break; |
313
|
|
|
|
314
|
3 |
|
case $conn instanceof \Illuminate\Database\PostgresConnection: |
315
|
1 |
|
$statement = "CREATE TEMPORARY SEQUENCE rownum_seq"; |
316
|
1 |
|
$value = "NETVAL('rownum_seq')"; |
317
|
1 |
|
$return = $reorder($statement, $value); |
318
|
1 |
|
break; |
319
|
|
|
|
320
|
2 |
|
case $conn instanceof \Illuminate\Database\SqlServerConnection: |
321
|
1 |
|
$statement = "DECLARE @rownum int; SET @rownum = 0"; |
322
|
1 |
|
$value = "(@rownum = @rownum+1)"; |
323
|
1 |
|
$return = $reorder($statement, $value); |
324
|
1 |
|
break; |
325
|
|
|
} |
326
|
|
|
|
327
|
4 |
|
if ($return) { |
328
|
3 |
|
return $return; |
329
|
|
|
} |
330
|
|
|
|
331
|
1 |
|
throw new RuntimeException(sprintf("Reorder not valid for connection (%s)", get_class($conn))); |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
/** |
335
|
|
|
* Where InputCriteria |
336
|
|
|
* |
337
|
|
|
* @param array $input Array Input |
338
|
|
|
* |
339
|
|
|
* @return RepositoryInterface |
340
|
|
|
*/ |
341
|
8 |
|
public function whereInputCriteria(array $input = array()) |
342
|
|
|
{ |
343
|
8 |
|
if (count($input)) { |
344
|
8 |
|
$criteria = app(InputCriteria::class, [$input]); |
|
|
|
|
345
|
8 |
|
$this->model = $criteria->apply($this->model, $this); |
346
|
8 |
|
} |
347
|
|
|
|
348
|
8 |
|
return $this; |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
/** |
352
|
|
|
* Save a new model in repository |
353
|
|
|
* |
354
|
|
|
* @throws ValidatorException |
355
|
|
|
* @param array $attributes Array Attributes |
356
|
|
|
* @return mixed |
357
|
|
|
*/ |
358
|
1 |
|
public function create(array $attributes) |
359
|
|
|
{ |
360
|
1 |
|
if (!is_null($this->validator)) { |
361
|
|
|
// we should pass data that has been casts by the model |
362
|
|
|
// to make sure data type are same because validator may need to use |
363
|
|
|
// this data to compare with data that fetch from database. |
364
|
1 |
|
$model = $this->model->newInstance()->forceFill($attributes); |
365
|
1 |
|
$attributes = array_merge($attributes, $model->toArray()); |
366
|
|
|
|
367
|
1 |
|
$this->validator->with($attributes)->passesOrFail(ValidatorInterface::RULE_CREATE); |
368
|
1 |
|
} |
369
|
|
|
|
370
|
1 |
|
$model = $this->model->newInstance($attributes); |
371
|
1 |
|
$model->save(); |
372
|
1 |
|
$this->resetModel(); |
373
|
|
|
|
374
|
1 |
|
event(new RepositoryEntityCreated($this, $model)); |
375
|
|
|
|
376
|
|
|
return $this->parserResult($model); |
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
/** |
380
|
|
|
* Update a model in repository by id |
381
|
|
|
* |
382
|
|
|
* @throws ValidatorException |
383
|
|
|
* @param array $attributes Array Attributes |
384
|
|
|
* @param int $id Integer Id |
385
|
|
|
* @return mixed |
386
|
|
|
*/ |
387
|
1 |
|
public function update(array $attributes, $id) |
388
|
|
|
{ |
389
|
1 |
|
$this->applyScope(); |
390
|
|
|
|
391
|
1 |
|
if (!is_null($this->validator)) { |
392
|
|
|
// we should pass data that has been casts by the model |
393
|
|
|
// to make sure data type are same because validator may need to use |
394
|
|
|
// this data to compare with data that fetch from database. |
395
|
1 |
|
$model = $this->model->newInstance()->forceFill($attributes); |
396
|
1 |
|
$attributes = array_merge($attributes, $model->toArray()); |
397
|
|
|
|
398
|
1 |
|
$this->validator->with($attributes)->setId($id)->passesOrFail(ValidatorInterface::RULE_UPDATE); |
399
|
1 |
|
} |
400
|
|
|
|
401
|
1 |
|
$temporarySkipPresenter = $this->skipPresenter; |
402
|
|
|
|
403
|
1 |
|
$this->skipPresenter(true); |
404
|
|
|
|
405
|
1 |
|
$model = $this->model->findOrFail($id); |
406
|
1 |
|
$model->fill($attributes); |
407
|
1 |
|
$model->save(); |
408
|
|
|
|
409
|
1 |
|
$this->skipPresenter($temporarySkipPresenter); |
410
|
1 |
|
$this->resetModel(); |
411
|
|
|
|
412
|
1 |
|
event(new RepositoryEntityUpdated($this, $model)); |
413
|
|
|
|
414
|
|
|
return $this->parserResult($model); |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
/** |
418
|
|
|
* Handle dynamic method calls into the method. |
419
|
|
|
* |
420
|
|
|
* @param string $method |
421
|
|
|
* @param array $parameters |
422
|
|
|
* @return mixed |
423
|
|
|
* |
424
|
|
|
* @throws BadMethodCallException |
425
|
|
|
*/ |
426
|
7 |
|
public function __call($method, $parameters) |
427
|
|
|
{ |
428
|
7 |
|
$pattern = '/^(((where|orWhere).*)|limit|groupBy|join|leftJoin|rightJoin|crossJoin)$/'; |
429
|
7 |
|
if (preg_match($pattern, $method)) { |
430
|
6 |
|
$this->model = call_user_func_array([$this->model, $method], $parameters); |
431
|
6 |
|
return $this; |
432
|
|
|
} |
433
|
|
|
|
434
|
1 |
|
$className = static::class; |
435
|
1 |
|
throw new BadMethodCallException("Call to undefined method {$className}::{$method}()"); |
436
|
|
|
} |
437
|
|
|
} |
438
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.