Completed
Push — master ( a80889...73ce07 )
by Michael
01:21
created

Repository::__call()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.6333
c 0
b 0
f 0
cc 3
nc 4
nop 2
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 ($this->canWrapInResource($name)) {
97
            return $this->wrapInResource(
98
                call_user_func_array([&$this, Str::before($name, 'Resource')], $arguments)
99
            );
100
        }
101
102
        if ($this->canReturnAsQuery($name)) {
103
            try {
104
                $this->only_query = true;
105
                return call_user_func_array([&$this, Str::before($name, 'Query')], $arguments);
106
            } finally {
107
                $this->only_query = false;
108
            }
109
        }
110
111
        throw new BadMethodCallException();
112
    }
113
114
    private function canReturnAsQuery($name)
115
    {
116
        return Str::endsWith($name, 'Query') &&
117
            in_array(
118
                Str::before($name, 'Query'),
119
                ['all', 'find', 'list']
120
            );
121
    }
122
123
    private function canWrapInResource($name)
124
    {
125
        return (Str::endsWith($name, 'Resource') ||
126
            Str::endsWith($name, 'Resources')) &&
127
            in_array(
128
                Str::before($name, 'Resource'),
129
                ['all', 'find', 'create', 'update', 'destroy']
130
            );
131
    }
132
133
    /**
134
     * Get the currenct resource context
135
     */
136
    public function getContext(): ResourceContext
137
    {
138
        return $this->resource_context;
139
    }
140
141
    /**
142
     * Set or replace the resource context
143
     */
144
    public function setContext(ResourceContext $resource_context)
145
    {
146
        $this->resource_context = $resource_context;
147
148
        return $this;
149
    }
150
151
    /**
152
     * Set the model
153
     */
154
    public function setModel(string $model)
155
    {
156
        $this->model = $model;
157
158
        return $this;
159
    }
160
161
    /**
162
     * Return all models based on resource context and query
163
     *
164
     * The ResourceContext determines if the result is a Collection or a
165
     * LengthAwarePaginator.
166
     *
167
     * @return LengthAwarePaginator|Collection|Builder
168
     */
169 View Code Duplication
    public function all($query = null)
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...
170
    {
171
        $query = $this->modelQuery($query);
172
173
        $this
174
            ->validateQurey($query)
175
            ->applyWith($query)
176
            ->applySort($query)
177
            ->applyFilters($query);
178
179
        if ($this->only_query) {
180
            return $query;
181
        }
182
183
        return $this->execute($query);
184
    }
185
186
    /**
187
     * Execute query
188
     *
189
     * @param Builder $query
190
     * @return LengthAwarePaginator|Collection
191
     */
192
    private function execute($query)
193
    {
194
        $page = $this->resource_context->page();
195
        $per_page = $this->resource_context->perPage();
196
        $should_paginate = $this->resource_context->paginate();
197
198
        return $should_paginate
199
            ? $query->paginate($per_page, ['*'], 'page', $page)
200
            : $query->get();
201
    }
202
203
    /**
204
     * Produces a result suitable for selects, lists, and autocomplete. All
205
     * entries that has a 'value' and a 'label' key.
206
     *
207
     * Note: if a callable is used the mapping is performed in memory, while a
208
     * string is done in the database layer.
209
     *
210
     * @param callable|string $column
211
     * @param Builder $query
212
     * @return Collection|Builder
213
     */
214
    public function list($column = null, $query = null)
215
    {
216
        $query = $this->modelQuery($query);
217
218
        $column = $column
219
            ?: $this->default_list_column
220
            ?: $this->default_sort_by;
221
222
        if (is_string($column)) {
223
            $query->select([$query->getModel()->getKeyName() . " AS value", "$column AS label"]);
224
            return $this->all($query);
225
        }
226
227
        $mapper = function (Model $model) use ($column) {
228
            return [
229
                'value' => $model->getKey(),
230
                'label' => $column($model)
231
            ];
232
        };
233
234
        if (is_callable($column)) {
235
            $all = $this->all($query);
236
237
            if ($all instanceof Builder) {
238
                return $all;
239
            }
240
            if ($all instanceof Collection) {
241
                return $all->map($mapper);
242
            }
243
            if ($all instanceof LengthAwarePaginator) {
244
                $items = collect($all->items())->map($mapper)->toArray();
245
                $all = new PaginationLengthAwarePaginator(
246
                    $items,
247
                    $all->total(),
248
                    $all->perPage(),
249
                    $all->currentPage()
250
                );
251
                return $all;
252
            }
253
        }
254
255
        throw new InvalidArgumentException("'column' should be a string or callable");
256
    }
257
258
    /**
259
     * @return Model|Builder
260
     */
261 View Code Duplication
    public function find($id, $query = null)
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...
262
    {
263
        $query = $this->modelQuery($query);
264
265
        $this
266
            ->validateQurey($query)
267
            ->applyWith($query);
268
269
        $query->whereId($id);
270
271
        if ($this->only_query) {
272
            return $query;
273
        }
274
275
        return $query->firstOrFail();
276
    }
277
278
    public function create(array $data): Model
279
    {
280
        return $this->model::create($data);
0 ignored issues
show
Bug introduced by
The method create cannot be called on $this->model (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
281
    }
282
283
    public function update(Model $model, array $data): Model
284
    {
285
        $model->update($data);
286
287
        return $model;
288
    }
289
290
    public function destroy(Model $model)
291
    {
292
        $model->delete();
293
    }
294
295
    protected function modelQuery($query = null)
296
    {
297
        return $query ?? $this->model::query();
0 ignored issues
show
Bug introduced by
The method query cannot be called on $this->model (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
298
    }
299
300
    protected function validateQurey(Builder $query)
301
    {
302
        $model = $this->model;
303
        $query_model = get_class($query->getModel());
304
305
        if ($model !== $query_model) {
306
            throw new InvalidArgumentException("The input query and model does not match");
307
        }
308
309
        return $this;
310
    }
311
}
312