Completed
Push — master ( e791a2...805c07 )
by Michael
01:05
created

Repository::list()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 36
rs 9.0328
c 0
b 0
f 0
cc 5
nc 5
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
class Repository
19
{
20
    use Filters;
21
    use IncludesRelations;
22
    use Sorts;
23
    use WrapsInResource;
24
25
    const WITH_ALLOW_ALL = ['*'];
26
    const WITH_ALLOW_NONE = [];
27
28
    /** @var string */
29
    protected $model;
30
31
    /** @var string */
32
    protected $resource;
33
34
    /** @var string */
35
    protected $resource_collection;
36
37
    /** @var ResourceContext $resource_context */
38
    protected $resource_context;
39
40
    /** @var array $allowed_with */
41
    protected $allowed_with = self::WITH_ALLOW_NONE;
42
43
    /** @var array $default_with */
44
    protected $default_with = [];
45
46
    /** @var string $default_sort_by */
47
    protected $default_sort_by;
48
49
    /** @var string $default_sort_order */
50
    protected $default_sort_order = 'asc';
51
52
    public function __construct(ResourceContext $resource_context)
53
    {
54
        $this->resource_context = $resource_context;
55
    }
56
57
    /**
58
     * Creates a new repository for a model.
59
     */
60
    public static function for(string $model, ResourceContext $context = null): self
61
    {
62
        $repository = resolve(static::class);
63
        $repository->setModel($model);
64
        if ($context) {
65
            $repository->setContext($context);
66
        }
67
        return $repository;
68
    }
69
70
    public function __call($name, $arguments)
71
    {
72
        if (Str::endsWith($name, 'Resource') || Str::endsWith($name, 'Resources')) {
73
            $name_without_resource = Str::before($name, 'Resource');
74
            $valid_names = ['all', 'find', 'create', 'update', 'destroy'];
75
            if (in_array($name_without_resource, $valid_names)) {
76
                return $this->wrapInResource(
77
                    call_user_func_array([&$this, $name_without_resource], $arguments)
78
                );
79
            }
80
        }
81
82
        throw new BadMethodCallException();
83
    }
84
85
    /**
86
     * Get the currenct resource context
87
     */
88
    public function getContext(): ResourceContext
89
    {
90
        return $this->resource_context;
91
    }
92
93
    /**
94
     * Set or replace the resource context
95
     */
96
    public function setContext(ResourceContext $resource_context)
97
    {
98
        $this->resource_context = $resource_context;
99
100
        return $this;
101
    }
102
103
    /**
104
     * Set the model
105
     */
106
    public function setModel(string $model)
107
    {
108
        $this->model = $model;
109
110
        return $this;
111
    }
112
113
    /**
114
     * Return all models based on resource context and query
115
     *
116
     * The ResourceContext determines if the result is a Collection or a
117
     * LengthAwarePaginator.
118
     *
119
     * @return LengthAwarePaginator|Collection
120
     */
121
    public function all($query = null)
122
    {
123
        $query = $this->modelQuery($query);
124
125
        return $this
126
            ->validateQurey($query)
127
            ->applyWith($query)
128
            ->applySort($query)
129
            ->applyFilters($query)
130
            ->execute($query);
131
    }
132
133
    /**
134
     * Produces a result suitable for selects, lists, and autocomplete. All
135
     * entries that has a 'value' and a 'label' key.
136
     *
137
     * Note: if a callable is used the mapping is performed in memory, while a
138
     * string is done in the database layer.
139
     *
140
     * @param callable|string $column
141
     * @param Builder $query
142
     * @return Collection
143
     */
144
    public function list($column = null, $query = null)
145
    {
146
        $query = $this->modelQuery($query);
147
148
        if (is_string($column)) {
149
            $query->select([$query->getModel()->getKeyName() . " AS value", "$column AS label"]);
150
            return $this->all($query);
151
        }
152
153
        $mapper = function (Model $model) use ($column) {
154
            return [
155
                'value' => $model->getKey(),
156
                'label' => $column($model)
157
            ];
158
        };
159
160
        if (is_callable($column)) {
161
            $all = $this->all($query);
162
163
            if ($all instanceof Collection) {
164
                $all = $all->map($mapper);
165
            } elseif ($all instanceof LengthAwarePaginator) {
166
                $items = collect($all->items())->map($mapper)->toArray();
167
                $all = new PaginationLengthAwarePaginator(
168
                    $items,
169
                    $all->total(),
170
                    $all->perPage(),
171
                    $all->currentPage()
172
                );
173
            }
174
175
            return $all;
176
        }
177
178
        throw new InvalidArgumentException("'column' should be a string or callable");
179
    }
180
181
    public function find($id, $query = null): Model
182
    {
183
        $query = $this->modelQuery($query);
184
185
        $this
186
            ->validateQurey($query)
187
            ->applyWith($query);
188
189
        $query->whereId($id);
190
191
        return $query->firstOrFail();
192
    }
193
194
    public function create(array $data): Model
195
    {
196
        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...
197
    }
198
199
    public function update(Model $model, array $data): Model
200
    {
201
        $model->update($data);
202
203
        return $model;
204
    }
205
206
    public function destroy(Model $model)
207
    {
208
        $model->delete();
209
    }
210
211
    protected function modelQuery($query = null)
212
    {
213
        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...
214
    }
215
216
    protected function validateQurey(Builder $query)
217
    {
218
        $model = $this->model;
219
        $query_model = get_class($query->getModel());
220
221
        if ($model !== $query_model) {
222
            throw new InvalidArgumentException("The input query and model does not match");
223
        }
224
225
        return $this;
226
    }
227
228
    /**
229
     * Execute query
230
     *
231
     * @param Builder $query
232
     * @return LengthAwarePaginator|Collection
233
     */
234
    private function execute($query)
235
    {
236
        $page = $this->resource_context->page();
237
        $per_page = $this->resource_context->perPage();
238
        $should_paginate = $this->resource_context->paginate();
239
240
        return $should_paginate
241
            ? $query->paginate($per_page, ['*'], 'page', $page)
242
            : $query->get();
243
    }
244
}
245