|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace SleepingOwl\Admin\Traits; |
|
4
|
|
|
|
|
5
|
|
|
use Illuminate\Database\Eloquent\Model; |
|
6
|
|
|
use Illuminate\Database\Eloquent\Relations\HasOneOrMany; |
|
7
|
|
|
use Illuminate\Support\Arr; |
|
8
|
|
|
use Illuminate\Support\Collection; |
|
9
|
|
|
use SleepingOwl\Admin\Contracts\Repositories\RepositoryInterface; |
|
10
|
|
|
use SleepingOwl\Admin\Exceptions\Form\Element\SelectException; |
|
11
|
|
|
|
|
12
|
|
|
trait SelectOptionsFromModel |
|
13
|
|
|
{ |
|
14
|
|
|
/** |
|
15
|
|
|
* @var Model |
|
16
|
|
|
*/ |
|
17
|
|
|
protected $modelForOptions; |
|
18
|
|
|
|
|
19
|
|
|
/** |
|
20
|
|
|
* @var string |
|
21
|
|
|
*/ |
|
22
|
|
|
protected $display = 'title'; |
|
23
|
|
|
|
|
24
|
|
|
/** |
|
25
|
|
|
* @var string|null |
|
26
|
|
|
*/ |
|
27
|
|
|
protected $foreignKey = null; |
|
28
|
|
|
|
|
29
|
|
|
/** |
|
30
|
|
|
* @var string|null |
|
31
|
|
|
*/ |
|
32
|
|
|
protected $usageKey = null; |
|
33
|
|
|
|
|
34
|
|
|
/** |
|
35
|
|
|
* @var array |
|
36
|
|
|
*/ |
|
37
|
|
|
protected $fetchColumns = []; |
|
38
|
|
|
|
|
39
|
|
|
/** |
|
40
|
|
|
* @var \Closure|object callable |
|
41
|
|
|
*/ |
|
42
|
|
|
protected $loadOptionsQueryPreparer; |
|
43
|
|
|
|
|
44
|
|
|
/** |
|
45
|
|
|
* @var bool |
|
46
|
|
|
*/ |
|
47
|
|
|
protected $isEmptyRelation = false; |
|
48
|
|
|
|
|
49
|
|
|
/** |
|
50
|
|
|
* @return Model |
|
51
|
|
|
*/ |
|
52
|
|
|
public function getModelForOptions() |
|
53
|
|
|
{ |
|
54
|
|
|
return $this->modelForOptions; |
|
55
|
|
|
} |
|
56
|
|
|
|
|
57
|
|
|
/** |
|
58
|
|
|
* @param @param string|Model $modelForOptions |
|
59
|
|
|
* |
|
60
|
|
|
* @return $this |
|
61
|
|
|
* @throws SelectException |
|
62
|
|
|
*/ |
|
63
|
|
|
public function setModelForOptions($modelForOptions, $display = null) |
|
64
|
|
|
{ |
|
65
|
|
|
if ($display) { |
|
66
|
|
|
$this->display = $display; |
|
67
|
|
|
} |
|
68
|
|
|
|
|
69
|
|
|
if (is_string($modelForOptions)) { |
|
70
|
|
|
$modelForOptions = app($modelForOptions); |
|
71
|
|
|
} |
|
72
|
|
|
|
|
73
|
|
|
if (! ($modelForOptions instanceof Model)) { |
|
74
|
|
|
throw new SelectException('Class must be instanced of Illuminate\Database\Eloquent\Model'); |
|
75
|
|
|
} |
|
76
|
|
|
|
|
77
|
|
|
$this->modelForOptions = $modelForOptions; |
|
78
|
|
|
|
|
79
|
|
|
return $this; |
|
80
|
|
|
} |
|
81
|
|
|
|
|
82
|
|
|
/** |
|
83
|
|
|
* @param string $key |
|
84
|
|
|
* |
|
85
|
|
|
* @return $this |
|
86
|
|
|
*/ |
|
87
|
|
|
public function setUsageKey($key) |
|
88
|
|
|
{ |
|
89
|
|
|
$this->usageKey = $key; |
|
90
|
|
|
|
|
91
|
|
|
return $this; |
|
92
|
|
|
} |
|
93
|
|
|
|
|
94
|
|
|
/** |
|
95
|
|
|
* @return string |
|
96
|
|
|
*/ |
|
97
|
|
|
public function getDisplay() |
|
98
|
|
|
{ |
|
99
|
|
|
return $this->display; |
|
100
|
|
|
} |
|
101
|
|
|
|
|
102
|
|
|
/** |
|
103
|
|
|
* @param string|\Closure $display |
|
104
|
|
|
* |
|
105
|
|
|
* @return $this |
|
106
|
|
|
*/ |
|
107
|
|
|
public function setDisplay($display) |
|
108
|
|
|
{ |
|
109
|
|
|
$this->display = $display; |
|
|
|
|
|
|
110
|
|
|
|
|
111
|
|
|
return $this; |
|
112
|
|
|
} |
|
113
|
|
|
|
|
114
|
|
|
/** |
|
115
|
|
|
* Set Only fetch columns. |
|
116
|
|
|
* |
|
117
|
|
|
* If use {@link Select#setModelForOptions($model)}, on fetch |
|
118
|
|
|
* data from the $model table, only specified columns has be |
|
119
|
|
|
* feched. |
|
120
|
|
|
* |
|
121
|
|
|
* Examples: <code>setFetchColumns('title')</code> or |
|
122
|
|
|
* <code>setFetchColumns(['title'])</code> or |
|
123
|
|
|
* <code>setFetchColumns('title', 'position')</code> or |
|
124
|
|
|
* <code>setFetchColumns(['title', 'position'])</code>. |
|
125
|
|
|
* |
|
126
|
|
|
* @param string|array $columns |
|
127
|
|
|
* |
|
128
|
|
|
* @return $this |
|
129
|
|
|
*/ |
|
130
|
|
|
public function setFetchColumns($columns) |
|
131
|
|
|
{ |
|
132
|
|
|
if (! is_array($columns)) { |
|
133
|
|
|
$columns = func_get_args(); |
|
134
|
|
|
} |
|
135
|
|
|
|
|
136
|
|
|
$this->fetchColumns = $columns; |
|
137
|
|
|
|
|
138
|
|
|
return $this; |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
|
|
/** |
|
142
|
|
|
* Get the fetch columns. |
|
143
|
|
|
* |
|
144
|
|
|
* @return array |
|
145
|
|
|
*/ |
|
146
|
|
|
public function getFetchColumns() |
|
147
|
|
|
{ |
|
148
|
|
|
return $this->fetchColumns; |
|
149
|
|
|
} |
|
150
|
|
|
|
|
151
|
|
|
/** |
|
152
|
|
|
* Set Callback for prepare load options Query. |
|
153
|
|
|
* |
|
154
|
|
|
* Example: |
|
155
|
|
|
* <code> |
|
156
|
|
|
* <?php |
|
157
|
|
|
* AdminFormElement::select('column', 'Label') |
|
158
|
|
|
* ->modelForOptions(MyModel::class) |
|
159
|
|
|
* ->setLoadOptionsQueryPreparer(function($item, QueryBuilder $query) { |
|
160
|
|
|
* return $query |
|
161
|
|
|
* ->where('column', 'value') |
|
162
|
|
|
* ->were('owner_id', Auth::user()->id) |
|
163
|
|
|
* }); |
|
164
|
|
|
* ?> |
|
165
|
|
|
* </code> |
|
166
|
|
|
* |
|
167
|
|
|
* @param callable $callback The Callback with $item and $options args. |
|
168
|
|
|
* |
|
169
|
|
|
* @return $this |
|
170
|
|
|
*/ |
|
171
|
|
|
public function setLoadOptionsQueryPreparer($callback) |
|
172
|
|
|
{ |
|
173
|
|
|
$this->loadOptionsQueryPreparer = $callback; |
|
174
|
|
|
|
|
175
|
|
|
return $this; |
|
176
|
|
|
} |
|
177
|
|
|
|
|
178
|
|
|
/** |
|
179
|
|
|
* Get Callback for prepare load options Query. |
|
180
|
|
|
* |
|
181
|
|
|
* @return callable |
|
182
|
|
|
*/ |
|
183
|
|
|
public function getLoadOptionsQueryPreparer() |
|
184
|
|
|
{ |
|
185
|
|
|
return $this->loadOptionsQueryPreparer; |
|
|
|
|
|
|
186
|
|
|
} |
|
187
|
|
|
|
|
188
|
|
|
/** |
|
189
|
|
|
* @param null|string $foreignKey |
|
190
|
|
|
* |
|
191
|
|
|
* @return $this |
|
192
|
|
|
*/ |
|
193
|
|
|
public function setForeignKey($foreignKey) |
|
194
|
|
|
{ |
|
195
|
|
|
$this->foreignKey = $foreignKey; |
|
196
|
|
|
|
|
197
|
|
|
return $this; |
|
198
|
|
|
} |
|
199
|
|
|
|
|
200
|
|
|
/** |
|
201
|
|
|
* @return $this |
|
202
|
|
|
*/ |
|
203
|
|
|
public function onlyEmptyRelation() |
|
204
|
|
|
{ |
|
205
|
|
|
$this->isEmptyRelation = true; |
|
206
|
|
|
|
|
207
|
|
|
return $this; |
|
208
|
|
|
} |
|
209
|
|
|
|
|
210
|
|
|
/** |
|
211
|
|
|
* @return bool |
|
212
|
|
|
*/ |
|
213
|
|
|
public function isEmptyRelation() |
|
214
|
|
|
{ |
|
215
|
|
|
return $this->isEmptyRelation; |
|
216
|
|
|
} |
|
217
|
|
|
|
|
218
|
|
|
/** |
|
219
|
|
|
* @return array |
|
220
|
|
|
*/ |
|
221
|
|
|
protected function loadOptions() |
|
222
|
|
|
{ |
|
223
|
|
|
$repository = app(RepositoryInterface::class); |
|
224
|
|
|
$repository->setModel($this->getModelForOptions()); |
|
225
|
|
|
$key = ($this->usageKey) ? $this->usageKey : $repository->getModel()->getKeyName(); |
|
226
|
|
|
|
|
227
|
|
|
$options = $repository->getQuery(); |
|
228
|
|
|
|
|
229
|
|
|
if ($this->isEmptyRelation() && ! is_null($foreignKey = $this->getForeignKey())) { |
|
|
|
|
|
|
230
|
|
|
$relation = $this->getModelAttributeKey(); |
|
|
|
|
|
|
231
|
|
|
$model = $this->getModel(); |
|
|
|
|
|
|
232
|
|
|
|
|
233
|
|
|
if ($model->{$relation}() instanceof HasOneOrMany) { |
|
234
|
|
|
$options->where($foreignKey, 0)->orWhereNull($foreignKey); |
|
235
|
|
|
} |
|
236
|
|
|
} |
|
237
|
|
|
|
|
238
|
|
|
if (count($this->getFetchColumns()) > 0) { |
|
239
|
|
|
$options->select(array_merge([$key], $this->getFetchColumns())); |
|
240
|
|
|
} |
|
241
|
|
|
|
|
242
|
|
|
// call the pre load options query preparer if has be set |
|
243
|
|
|
if (! is_null($preparer = $this->getLoadOptionsQueryPreparer())) { |
|
|
|
|
|
|
244
|
|
|
$options = $preparer($this, $options); |
|
245
|
|
|
} |
|
246
|
|
|
$options = $options->get(); |
|
247
|
|
|
|
|
248
|
|
|
//some fix for setUsage |
|
249
|
|
|
$key = str_replace('->', '.', $key); |
|
250
|
|
|
|
|
251
|
|
|
if (is_callable($makeDisplay = $this->getDisplay())) { |
|
252
|
|
|
// make dynamic display text |
|
253
|
|
|
if ($options instanceof Collection) { |
|
254
|
|
|
$options = $options->all(); |
|
255
|
|
|
} |
|
256
|
|
|
|
|
257
|
|
|
// iterate for all options and redefine it as |
|
258
|
|
|
// list of KEY and TEXT pair |
|
259
|
|
|
$options = array_map(function ($opt) use ($key, $makeDisplay) { |
|
260
|
|
|
// get the KEY and make the display text |
|
261
|
|
|
return [data_get($opt, $key), $makeDisplay($opt)]; |
|
262
|
|
|
}, $options); |
|
263
|
|
|
|
|
264
|
|
|
// take options as array with KEY => VALUE pair |
|
265
|
|
|
$options = Arr::pluck($options, 1, 0); |
|
266
|
|
|
} elseif ($options instanceof Collection) { |
|
267
|
|
|
// take options as array with KEY => VALUE pair |
|
268
|
|
|
$options = Arr::pluck($options->all(), $this->getDisplay(), $key); |
|
269
|
|
|
} else { |
|
270
|
|
|
// take options as array with KEY => VALUE pair |
|
271
|
|
|
$options = Arr::pluck($options, $this->getDisplay(), $key); |
|
272
|
|
|
} |
|
273
|
|
|
|
|
274
|
|
|
return $options; |
|
275
|
|
|
} |
|
276
|
|
|
} |
|
277
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountIdthat can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theidproperty of an instance of theAccountclass. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.