• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

conedevelopment / root / 13076030548

31 Jan 2025 03:44PM UTC coverage: 79.373% (+1.3%) from 78.037%
13076030548

Pull #234

github

web-flow
Merge 9d7378884 into 04cb83a80
Pull Request #234: Codebase health checkup

178 of 217 new or added lines in 45 files covered. (82.03%)

2 existing lines in 2 files now uncovered.

2559 of 3224 relevant lines covered (79.37%)

35.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.19
/src/Fields/BelongsToMany.php
1
<?php
2

3
namespace Cone\Root\Fields;
4

5
use Closure;
6
use Cone\Root\Http\Controllers\BelongsToManyController;
7
use Illuminate\Database\Eloquent\Builder;
8
use Illuminate\Database\Eloquent\Model;
9
use Illuminate\Database\Eloquent\Relations\BelongsTo as BelongsToRelation;
10
use Illuminate\Database\Eloquent\Relations\BelongsToMany as EloquentRelation;
11
use Illuminate\Database\Eloquent\Relations\Pivot;
12
use Illuminate\Http\Request;
13
use Illuminate\Routing\Router;
14
use Illuminate\Support\Arr;
15
use Illuminate\Support\Str;
16

17
/**
18
 * @template TRelation of \Illuminate\Database\Eloquent\Relations\BelongsToMany
19
 *
20
 * @extends \Cone\Root\Fields\Relation<TRelation>
21
 */
22
class BelongsToMany extends Relation
23
{
24
    /**
25
     * The pivot fields resolver callback.
26
     */
27
    protected ?Closure $pivotFieldsResolver = null;
28

29
    /**
30
     * The default pivot values that should be saved.
31
     */
32
    protected array $pivotValues = [];
33

34
    /**
35
     * Indicates if the field allows duplicate relations.
36
     */
37
    protected bool $allowDuplicateRelations = false;
38

39
    /**
40
     * Create a new relation field instance.
41
     */
42
    public function __construct(string $label, Closure|string|null $modelAttribute = null, Closure|string|null $relation = null)
43
    {
44
        parent::__construct($label, $modelAttribute, $relation);
198✔
45

46
        $this->setAttribute('multiple', true);
198✔
47
        $this->name(sprintf('%s[]', $this->getAttribute('name')));
198✔
48
    }
49

50
    /**
51
     * {@inheritdoc}
52
     */
53
    public function getRelation(Model $model): EloquentRelation
54
    {
55
        $relation = parent::getRelation($model);
13✔
56

57
        return $relation->withPivot([
13✔
58
            $relation->newPivot()->getKeyName(),
13✔
59
            $relation->getForeignPivotKeyName(),
13✔
60
            $relation->getRelatedPivotKeyName(),
13✔
61
        ]);
13✔
62
    }
63

64
    /**
65
     * {@inheritdoc}
66
     */
67
    public function fields(Request $request): array
68
    {
69
        return [
198✔
70
            BelongsTo::make($this->getRelatedName(), 'related', static fn (Pivot $model): BelongsToRelation => $model->belongsTo(
198✔
71
                $model->getRelation('related')::class,
198✔
72
                $model->getRelatedKey(),
198✔
73
                $model->getForeignKey(),
198✔
74
                'related'
198✔
75
            )->withDefault())->withRelatableQuery(fn (Request $request, Builder $query, Pivot $model): Builder => $this->resolveRelatableQuery($request, $model->pivotParent)
198✔
76
                ->unless($this->allowDuplicateRelations, fn (Builder $query): Builder => $query->whereNotIn(
198✔
77
                    $query->getModel()->getQualifiedKeyName(),
198✔
78
                    $this->getRelation($model->pivotParent)->select($query->getModel()->getQualifiedKeyName())
198✔
79
                )))->hydrate(function (Request $request, Pivot $model, mixed $value): void {
198✔
80
                    $model->setAttribute(
2✔
81
                        $this->getRelation($model->pivotParent)->getRelatedPivotKeyName(),
2✔
82
                        $value
2✔
83
                    );
2✔
84
                })->display(fn (Model $model): ?string => $this->resolveDisplay($model)),
198✔
85
        ];
198✔
86
    }
87

88
    /**
89
     * Allow duplicate relations.
90
     */
91
    public function allowDuplicateRelations(bool $value = true): static
92
    {
93
        $this->allowDuplicateRelations = $value;
×
94

95
        return $this;
×
96
    }
97

98
    /**
99
     * {@inheritdoc}
100
     */
101
    protected function resolveField(Request $request, Field $field): void
102
    {
103
        if (! $this->isSubResource()) {
198✔
104
            $field->setModelAttribute(
×
105
                sprintf('%s.*.%s', $this->getModelAttribute(), $field->getModelAttribute())
×
106
            );
×
107
        }
108

109
        if ($field instanceof Relation) {
198✔
110
            $field->resolveRouteKeyNameUsing(fn (): string => Str::of($field->getRelationName())->singular()->ucfirst()->prepend($this->getRouteKeyName())->value());
198✔
111
        }
112

113
        parent::resolveField($request, $field);
198✔
114
    }
115

116
    /**
117
     * Set the pivot field resolver.
118
     */
119
    public function withPivotFields(Closure $callback): static
120
    {
121
        $this->withFields($callback);
198✔
122

123
        $this->pivotFieldsResolver = function (Request $request, Model $model, Model $related) use ($callback): Fields {
198✔
124
            $fields = new Fields;
×
125

126
            $fields->register(Arr::wrap(call_user_func_array($callback, [$request])));
×
127

128
            $fields->each(function (Field $field) use ($model, $related): void {
×
129
                $attribute = sprintf(
×
130
                    '%s.%s.%s',
×
131
                    $this->getModelAttribute(),
×
132
                    $related->getKey(),
×
133
                    $key = $field->getModelAttribute()
×
134
                );
×
135

136
                $field->setModelAttribute($attribute)
×
137
                    ->name($attribute)
×
138
                    ->id($attribute)
×
NEW
139
                    ->value(fn (): mixed => $related->getRelation($this->getRelation($model)->getPivotAccessor())->getAttribute($key));
×
140
            });
×
141

142
            return $fields;
×
143
        };
198✔
144

145
        return $this;
198✔
146
    }
147

148
    /**
149
     * {@inheritdoc}
150
     */
151
    public function getValueForHydrate(Request $request): mixed
152
    {
153
        $value = (array) parent::getValueForHydrate($request);
5✔
154

155
        return $this->mergePivotValues($value);
5✔
156
    }
157

158
    /**
159
     * Merge the pivot values.
160
     */
161
    public function mergePivotValues(array $value): array
162
    {
163
        $value = array_is_list($value) ? array_fill_keys($value, []) : $value;
6✔
164

165
        return array_map(fn (array $pivot): array => array_merge($this->pivotValues, $pivot), $value);
6✔
166
    }
167

168
    /**
169
     * {@inheritdoc}
170
     */
171
    public function persist(Request $request, Model $model, mixed $value): void
172
    {
173
        if ($this->isSubResource()) {
2✔
174
            parent::persist($request, $model, $value);
2✔
175
        } else {
176
            $model->saved(function (Model $model) use ($request, $value): void {
×
177
                $this->resolveHydrate($request, $model, $value);
×
178

179
                $this->getRelation($model)->sync($value);
×
180
            });
×
181
        }
182
    }
183

184
    /**
185
     * {@inheritdoc}
186
     */
187
    public function resolveHydrate(Request $request, Model $model, mixed $value): void
188
    {
189
        if (is_null($this->hydrateResolver)) {
4✔
190
            $this->hydrateResolver = function (Request $request, Model $model, mixed $value): void {
4✔
191
                $relation = $this->getRelation($model);
4✔
192

193
                $results = $this->resolveRelatableQuery($request, $model)
4✔
194
                    ->findMany(array_keys($value))
4✔
195
                    ->each(static function (Model $related) use ($relation, $value): void {
4✔
196
                        $related->setRelation(
1✔
197
                            $relation->getPivotAccessor(),
1✔
198
                            $relation->newPivot($value[$related->getKey()])
1✔
199
                        );
1✔
200
                    });
4✔
201

202
                $model->setRelation($relation->getRelationName(), $results);
4✔
203
            };
4✔
204
        }
205

206
        parent::resolveHydrate($request, $model, $value);
4✔
207
    }
208

209
    /**
210
     * {@inheritdoc}
211
     */
212
    public function resolveRouteBinding(Request $request, string $id): Model
213
    {
214
        $relation = $this->getRelation($request->route()->parentOfParameter($this->getRouteKeyName()));
2✔
215

216
        $related = $relation->wherePivot($relation->newPivot()->getQualifiedKeyName(), $id)->firstOrFail();
2✔
217

218
        return tap($related, static function (Model $related) use ($relation, $id): void {
2✔
219
            $pivot = $related->getRelation($relation->getPivotAccessor());
2✔
220

221
            $pivot->setRelation('related', $related)->setAttribute($pivot->getKeyName(), $id);
2✔
222
        });
2✔
223
    }
224

225
    /**
226
     * {@inheritdoc}
227
     */
228
    public function mapRelated(Request $request, Model $model, Model $related): array
229
    {
230
        $relation = $this->getRelation($model);
1✔
231

232
        $pivot = $related->getRelation($relation->getPivotAccessor());
1✔
233

234
        $pivot->setRelation('related', $related);
1✔
235

236
        return parent::mapRelated($request, $model, $pivot);
1✔
237
    }
238

239
    /**
240
     * {@inheritdoc}
241
     */
242
    public function routes(Router $router): void
243
    {
244
        if ($this->isSubResource()) {
198✔
245
            $router->get('/', [BelongsToManyController::class, 'index']);
198✔
246
            $router->get('/create', [BelongsToManyController::class, 'create']);
198✔
247
            $router->get("/{{$this->getRouteKeyName()}}", [BelongsToManyController::class, 'show']);
198✔
248
            $router->post('/', [BelongsToManyController::class, 'store']);
198✔
249
            $router->get("/{{$this->getRouteKeyName()}}/edit", [BelongsToManyController::class, 'edit']);
198✔
250
            $router->patch("/{{$this->getRouteKeyName()}}", [BelongsToManyController::class, 'update']);
198✔
251
            $router->delete("/{{$this->getRouteKeyName()}}", [BelongsToManyController::class, 'destroy']);
198✔
252
        }
253
    }
254

255
    /**
256
     * {@inheritdoc}
257
     */
258
    public function toOption(Request $request, Model $model, Model $related): array
259
    {
260
        $relation = $this->getRelation($model);
2✔
261

262
        if (! $related->relationLoaded($relation->getPivotAccessor())) {
2✔
263
            $related->setRelation($relation->getPivotAccessor(), $relation->newPivot());
2✔
264
        }
265

266
        $option = parent::toOption($request, $model, $related);
2✔
267

268
        $option['attrs']['name'] = sprintf(
2✔
269
            '%s[%s][%s]',
2✔
270
            $this->getAttribute('name'),
2✔
271
            $related->getKey(),
2✔
272
            $this->getRelation($model)->getRelatedPivotKeyName()
2✔
273
        );
2✔
274

275
        $option['fields'] = is_null($this->pivotFieldsResolver)
2✔
276
            ? []
2✔
277
            : call_user_func_array($this->pivotFieldsResolver, [$request, $model, $related])->mapToInputs($request, $model);
×
278

279
        return $option;
2✔
280
    }
281

282
    /**
283
     * {@inheritdoc}
284
     */
285
    public function toArray(): array
286
    {
287
        return array_merge(parent::toArray(), [
5✔
288
            'relatedName' => $this->getRelatedName(),
5✔
289
        ]);
5✔
290
    }
291

292
    /**
293
     * {@inheritdoc}
294
     */
295
    public function toValidate(Request $request, Model $model): array
296
    {
297
        return array_merge(
3✔
298
            parent::toValidate($request, $model),
3✔
299
            $this->resolveFields($request)->mapToValidate($request, $model)
3✔
300
        );
3✔
301
    }
302

303
    /**
304
     * {@inheritdoc}
305
     */
306
    public function toCreate(Request $request, Model $model): array
307
    {
308
        $relation = $this->getRelation($model);
×
309

310
        $pivot = $relation->newPivot();
×
311

312
        $pivot->setRelation('related', $relation->make());
×
313

314
        return array_merge($this->toSubResource($request, $model), [
×
315
            'title' => __('Attach :model', ['model' => $this->getRelatedName()]),
×
316
            'model' => $pivot,
×
317
            'action' => $this->modelUrl($model),
×
318
            'method' => 'POST',
×
319
            'fields' => $this->resolveFields($request)
×
320
                ->subResource(false)
×
321
                ->authorized($request, $pivot)
×
322
                ->visible('relation.create')
×
323
                ->mapToInputs($request, $pivot),
×
324
        ]);
×
325
    }
326

327
    /**
328
     * {@inheritdoc}
329
     */
330
    public function toShow(Request $request, Model $model, Model $related): array
331
    {
332
        $relation = $this->getRelation($model);
×
333

334
        $pivot = $related->getRelation($relation->getPivotAccessor());
×
335

336
        $pivot->setRelation('related', $related);
×
337

338
        return parent::toShow($request, $model, $pivot);
×
339
    }
340

341
    /**
342
     * {@inheritdoc}
343
     */
344
    public function toEdit(Request $request, Model $model, Model $related): array
345
    {
346
        $relation = $this->getRelation($model);
×
347

348
        $pivot = $related->getRelation($relation->getPivotAccessor());
×
349

350
        $pivot->setRelation('related', $related);
×
351

352
        return parent::toEdit($request, $model, $pivot);
×
353
    }
354
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc