Passed
Push — master ( ebf009...132829 )
by Michael
05:55 queued 03:27
created

Repository::shouldAuthorize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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