Completed
Push — master ( 74f9e0...0e337e )
by Michael
01:13
created

Repository::setContext()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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