Completed
Push — master ( 9a57b9...04e2e3 )
by Michael
01:30
created

Repository::__call()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 8.8977
c 0
b 0
f 0
cc 6
nc 6
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 (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)
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...
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)
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...
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);
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...
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();
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...
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