1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Mblarsen\LaravelRepository; |
4
|
|
|
|
5
|
|
|
use BadMethodCallException; |
6
|
|
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator; |
7
|
|
|
use Illuminate\Database\Eloquent\Builder; |
8
|
|
|
use Illuminate\Database\Eloquent\Collection; |
9
|
|
|
use Illuminate\Database\Eloquent\Model; |
10
|
|
|
use Illuminate\Pagination\LengthAwarePaginator as PaginationLengthAwarePaginator; |
11
|
|
|
use Illuminate\Support\Str; |
12
|
|
|
use InvalidArgumentException; |
13
|
|
|
use Mblarsen\LaravelRepository\Traits\Filters; |
14
|
|
|
use Mblarsen\LaravelRepository\Traits\IncludesRelations; |
15
|
|
|
use Mblarsen\LaravelRepository\Traits\Sorts; |
16
|
|
|
use Mblarsen\LaravelRepository\Traits\WrapsInResource; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* @method allQuery($query = null): Builder |
20
|
|
|
* @method allResources($query = null): \Illuminate\Http\Resources\Json\ResourceCollection |
21
|
|
|
* @method createQuery(array $data): JsonBuilder |
22
|
|
|
* @method createResource(array $data): JsonResource |
23
|
|
|
* @method findQuery($id, $query = null): Builder |
24
|
|
|
* @method findResource($id, $query = null): JsonResource |
25
|
|
|
* @method listQuery($column = null, $query = null): Builder |
26
|
|
|
* @method updateQuery(Model $model, array $data): Builder |
27
|
|
|
* @method updateResource(Model $model, array $data): JsonResource |
28
|
|
|
*/ |
29
|
|
|
class Repository |
30
|
|
|
{ |
31
|
|
|
use Filters; |
32
|
|
|
use IncludesRelations; |
33
|
|
|
use Sorts; |
34
|
|
|
use WrapsInResource; |
35
|
|
|
|
36
|
|
|
const WITH_ALLOW_ALL = ['*']; |
37
|
|
|
const WITH_ALLOW_NONE = []; |
38
|
|
|
|
39
|
|
|
/** @var string */ |
40
|
|
|
protected $model; |
41
|
|
|
|
42
|
|
|
/** @var ResourceContext $resource_context */ |
43
|
|
|
protected $resource_context; |
44
|
|
|
|
45
|
|
|
/** @var array $allowed_with */ |
46
|
|
|
protected $allowed_with = self::WITH_ALLOW_NONE; |
47
|
|
|
|
48
|
|
|
/** @var array $default_with */ |
49
|
|
|
protected $default_with = []; |
50
|
|
|
|
51
|
|
|
/** @var string $default_sort_by */ |
52
|
|
|
protected $default_sort_by; |
53
|
|
|
|
54
|
|
|
/** @var string $default_sort_order */ |
55
|
|
|
protected $default_sort_order = 'asc'; |
56
|
|
|
|
57
|
|
|
/** @var string|function $list_column */ |
58
|
|
|
protected $default_list_column; |
59
|
|
|
|
60
|
|
|
/** @var bool $only_query */ |
61
|
|
|
protected $only_query = false; |
62
|
|
|
|
63
|
|
|
public function __construct(ResourceContext $resource_context) |
64
|
|
|
{ |
65
|
|
|
$this->resource_context = $resource_context; |
66
|
|
|
$this->register(); |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
protected function register() |
70
|
|
|
{ |
71
|
|
|
// Allows you to set default_list_column |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* Creates a new repository for a model. |
76
|
|
|
* |
77
|
|
|
* @param string $model model name |
78
|
|
|
* @param array|ResourceContext $context |
79
|
|
|
*/ |
80
|
|
|
public static function for(string $model, $context = null): self |
81
|
|
|
{ |
82
|
|
|
$repository = resolve(static::class); |
83
|
|
|
$repository->setModel($model); |
84
|
|
|
if ($context) { |
85
|
|
|
$repository->setContext( |
86
|
|
|
is_array($context) |
87
|
|
|
? ArrayResourceContext::create($context) |
88
|
|
|
: $context |
89
|
|
|
); |
90
|
|
|
} |
91
|
|
|
return $repository; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
public function __call($name, $arguments) |
95
|
|
|
{ |
96
|
|
|
if (Str::endsWith($name, 'Resource') || Str::endsWith($name, 'Resources')) { |
97
|
|
|
$short_name = Str::before($name, 'Resource'); |
98
|
|
|
$valid_names = ['all', 'find', 'create', 'update', 'destroy']; |
99
|
|
|
if (in_array($short_name, $valid_names)) { |
100
|
|
|
return $this->wrapInResource( |
101
|
|
|
call_user_func_array([&$this, $short_name], $arguments) |
102
|
|
|
); |
103
|
|
|
} |
104
|
|
|
} elseif (Str::endsWith($name, 'Query')) { |
105
|
|
|
$short_name = Str::before($name, 'Query'); |
106
|
|
|
$valid_names = ['all', 'find', 'list']; |
107
|
|
|
if (in_array($short_name, $valid_names)) { |
108
|
|
|
try { |
109
|
|
|
$this->only_query = true; |
110
|
|
|
return call_user_func_array([&$this, $short_name], $arguments); |
111
|
|
|
} finally { |
112
|
|
|
$this->only_query = false; |
113
|
|
|
} |
114
|
|
|
} |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
throw new BadMethodCallException(); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* Get the currenct resource context |
122
|
|
|
*/ |
123
|
|
|
public function getContext(): ResourceContext |
124
|
|
|
{ |
125
|
|
|
return $this->resource_context; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Set or replace the resource context |
130
|
|
|
*/ |
131
|
|
|
public function setContext(ResourceContext $resource_context) |
132
|
|
|
{ |
133
|
|
|
$this->resource_context = $resource_context; |
134
|
|
|
|
135
|
|
|
return $this; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* Set the model |
140
|
|
|
*/ |
141
|
|
|
public function setModel(string $model) |
142
|
|
|
{ |
143
|
|
|
$this->model = $model; |
144
|
|
|
|
145
|
|
|
return $this; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Return all models based on resource context and query |
150
|
|
|
* |
151
|
|
|
* The ResourceContext determines if the result is a Collection or a |
152
|
|
|
* LengthAwarePaginator. |
153
|
|
|
* |
154
|
|
|
* @return LengthAwarePaginator|Collection|Builder |
155
|
|
|
*/ |
156
|
|
View Code Duplication |
public function all($query = null) |
|
|
|
|
157
|
|
|
{ |
158
|
|
|
$query = $this->modelQuery($query); |
159
|
|
|
|
160
|
|
|
$this |
161
|
|
|
->validateQurey($query) |
162
|
|
|
->applyWith($query) |
163
|
|
|
->applySort($query) |
164
|
|
|
->applyFilters($query); |
165
|
|
|
|
166
|
|
|
if ($this->only_query) { |
167
|
|
|
return $query; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
return $this->execute($query); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* Execute query |
175
|
|
|
* |
176
|
|
|
* @param Builder $query |
177
|
|
|
* @return LengthAwarePaginator|Collection |
178
|
|
|
*/ |
179
|
|
|
private function execute($query) |
180
|
|
|
{ |
181
|
|
|
$page = $this->resource_context->page(); |
182
|
|
|
$per_page = $this->resource_context->perPage(); |
183
|
|
|
$should_paginate = $this->resource_context->paginate(); |
184
|
|
|
|
185
|
|
|
return $should_paginate |
186
|
|
|
? $query->paginate($per_page, ['*'], 'page', $page) |
187
|
|
|
: $query->get(); |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* Produces a result suitable for selects, lists, and autocomplete. All |
192
|
|
|
* entries that has a 'value' and a 'label' key. |
193
|
|
|
* |
194
|
|
|
* Note: if a callable is used the mapping is performed in memory, while a |
195
|
|
|
* string is done in the database layer. |
196
|
|
|
* |
197
|
|
|
* @param callable|string $column |
198
|
|
|
* @param Builder $query |
199
|
|
|
* @return Collection|Builder |
200
|
|
|
*/ |
201
|
|
|
public function list($column = null, $query = null) |
202
|
|
|
{ |
203
|
|
|
$query = $this->modelQuery($query); |
204
|
|
|
|
205
|
|
|
$column = $column |
206
|
|
|
?: $this->default_list_column |
207
|
|
|
?: $this->default_sort_by; |
208
|
|
|
|
209
|
|
|
if (is_string($column)) { |
210
|
|
|
$query->select([$query->getModel()->getKeyName() . " AS value", "$column AS label"]); |
211
|
|
|
return $this->all($query); |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
$mapper = function (Model $model) use ($column) { |
215
|
|
|
return [ |
216
|
|
|
'value' => $model->getKey(), |
217
|
|
|
'label' => $column($model) |
218
|
|
|
]; |
219
|
|
|
}; |
220
|
|
|
|
221
|
|
|
if (is_callable($column)) { |
222
|
|
|
$all = $this->all($query); |
223
|
|
|
|
224
|
|
|
if ($all instanceof Builder) { |
225
|
|
|
return $all; |
226
|
|
|
} |
227
|
|
|
if ($all instanceof Collection) { |
228
|
|
|
return $all->map($mapper); |
229
|
|
|
} |
230
|
|
|
if ($all instanceof LengthAwarePaginator) { |
231
|
|
|
$items = collect($all->items())->map($mapper)->toArray(); |
232
|
|
|
$all = new PaginationLengthAwarePaginator( |
233
|
|
|
$items, |
234
|
|
|
$all->total(), |
235
|
|
|
$all->perPage(), |
236
|
|
|
$all->currentPage() |
237
|
|
|
); |
238
|
|
|
return $all; |
239
|
|
|
} |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
throw new InvalidArgumentException("'column' should be a string or callable"); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* @return Model|Builder |
247
|
|
|
*/ |
248
|
|
View Code Duplication |
public function find($id, $query = null) |
|
|
|
|
249
|
|
|
{ |
250
|
|
|
$query = $this->modelQuery($query); |
251
|
|
|
|
252
|
|
|
$this |
253
|
|
|
->validateQurey($query) |
254
|
|
|
->applyWith($query); |
255
|
|
|
|
256
|
|
|
$query->whereId($id); |
257
|
|
|
|
258
|
|
|
if ($this->only_query) { |
259
|
|
|
return $query; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
return $query->firstOrFail(); |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
public function create(array $data): Model |
266
|
|
|
{ |
267
|
|
|
return $this->model::create($data); |
|
|
|
|
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
public function update(Model $model, array $data): Model |
271
|
|
|
{ |
272
|
|
|
$model->update($data); |
273
|
|
|
|
274
|
|
|
return $model; |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
public function destroy(Model $model) |
278
|
|
|
{ |
279
|
|
|
$model->delete(); |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
protected function modelQuery($query = null) |
283
|
|
|
{ |
284
|
|
|
return $query ?? $this->model::query(); |
|
|
|
|
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
protected function validateQurey(Builder $query) |
288
|
|
|
{ |
289
|
|
|
$model = $this->model; |
290
|
|
|
$query_model = get_class($query->getModel()); |
291
|
|
|
|
292
|
|
|
if ($model !== $query_model) { |
293
|
|
|
throw new InvalidArgumentException("The input query and model does not match"); |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
return $this; |
297
|
|
|
} |
298
|
|
|
} |
299
|
|
|
|
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.