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

conedevelopment / root / 20665784697

02 Jan 2026 08:09PM UTC coverage: 75.022% (-0.05%) from 75.076%
20665784697

push

github

iamgergo
fix

9 of 13 new or added lines in 2 files covered. (69.23%)

1 existing line in 1 file now uncovered.

3472 of 4628 relevant lines covered (75.02%)

32.79 hits per line

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

80.0
/src/Fields/Repeater.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Cone\Root\Fields;
6

7
use Closure;
8
use Cone\Root\Http\Controllers\RepeaterController;
9
use Cone\Root\Traits\RegistersRoutes;
10
use Cone\Root\Traits\ResolvesFields;
11
use Illuminate\Database\Eloquent\Model;
12
use Illuminate\Http\Request;
13
use Illuminate\Routing\Router;
14
use Illuminate\Support\Facades\View;
15
use Illuminate\Support\Str;
16

17
class Repeater extends Field
18
{
19
    use RegistersRoutes {
20
        RegistersRoutes::registerRoutes as __registerRoutes;
21
    }
22
    use ResolvesFields {
23
        ResolvesFields::withFields as __withFields;
24
    }
25

26
    /**
27
     * The Blade template.
28
     */
29
    protected string $template = 'root::fields.repeater';
30

31
    /**
32
     * The option fields resolver.
33
     */
34
    protected ?Closure $optionFieldsResolver = null;
35

36
    /**
37
     * The maximum number of options.
38
     */
39
    protected ?int $max = null;
40

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

48
        $this->hiddenOn(['index']);
196✔
49
    }
50

51
    /**
52
     * Get the URI key.
53
     */
54
    public function getUriKey(): string
196✔
55
    {
56
        return str_replace('.', '-', $this->getRequestKey());
196✔
57
    }
58

59
    /**
60
     * Get the route parameter name.
61
     */
62
    public function getRouteParameterName(): string
1✔
63
    {
64
        return 'field';
1✔
65
    }
66

67
    /**
68
     * Set the maximum number of options.
69
     */
70
    public function max(int $value): static
×
71
    {
72
        $this->max = $value;
×
73

74
        return $this;
×
75
    }
76

77
    /**
78
     * Get the option name.
79
     */
80
    public function getOptionName(): string
3✔
81
    {
82
        return __(Str::singular($this->label));
3✔
83
    }
84

85
    /**
86
     * Get the add new option label.
87
     */
88
    public function getAddNewOptionLabel(): string
2✔
89
    {
90
        return __('Add :name', ['name' => $this->getOptionName()]);
2✔
91
    }
92

93
    /**
94
     * {@inheritdoc}
95
     */
96
    public function getValueForHydrate(Request $request): array
3✔
97
    {
98
        return array_values((array) parent::getValueForHydrate($request));
3✔
99
    }
100

101
    /**
102
     * {@inheritdoc}
103
     */
104
    public function getOldValue(Request $request): array
×
105
    {
106
        return array_values((array) parent::getOldValue($request));
×
107
    }
108

109
    /**
110
     * Handle the callback for the field resolution.
111
     */
112
    protected function resolveField(Request $request, Field $field): void
196✔
113
    {
114
        $field->setModelAttribute(
196✔
115
            sprintf('%s.*.%s', $this->getModelAttribute(), $field->getModelAttribute())
196✔
116
        );
196✔
117

118
        if ($field instanceof Relation) {
196✔
119
            $field->resolveRouteKeyNameUsing(function () use ($field): string {
×
120
                return Str::of($field->getRelationName())
×
121
                    ->singular()
×
122
                    ->ucfirst()
×
123
                    ->prepend($this->getModelAttribute())
×
124
                    ->value();
×
125
            });
×
126
        }
127
    }
128

129
    /**
130
     * {@inheritdoc}
131
     */
132
    public function withFields(Closure $callback): static
196✔
133
    {
134
        $this->optionFieldsResolver = function (Request $request, Model $model, Model $tmpModel) use ($callback): Fields {
196✔
135
            $fields = new Fields([
1✔
136
                Hidden::make(__('Key'), '_key'),
1✔
137
            ]);
1✔
138

139
            $fields->register(call_user_func_array($callback, [$request, $model, $tmpModel]));
1✔
140

141
            $fields->each(function (Field $field) use ($tmpModel): void {
1✔
142
                $attribute = sprintf(
1✔
143
                    '%s.%s.%s',
1✔
144
                    $this->getModelAttribute(),
1✔
145
                    $tmpModel->getAttribute('_key'),
1✔
146
                    $key = $field->getModelAttribute()
1✔
147
                );
1✔
148

149
                $field->setModelAttribute($attribute)
1✔
150
                    ->name($attribute)
1✔
151
                    ->id($attribute)
1✔
152
                    ->when($tmpModel->hasAttribute($key), function (Field $field) use ($tmpModel, $key): void {
1✔
153
                        $field->value(fn (): mixed => $tmpModel->getAttribute($key));
1✔
154
                    });
1✔
155
            });
1✔
156

157
            return $fields;
1✔
158
        };
196✔
159

160
        return $this->__withFields($callback);
196✔
161
    }
162

163
    /**
164
     * Resolve the option fields.
165
     */
166
    public function resolveOptionFields(Request $request, Model $model, Model $tmpModel): Fields
1✔
167
    {
168
        return is_null($this->optionFieldsResolver)
1✔
169
            ? new Fields
×
170
            : call_user_func_array($this->optionFieldsResolver, [$request, $model, $tmpModel]);
1✔
171
    }
172

173
    /**
174
     * Make a new temporary model for the option.
175
     */
176
    public function newTemporaryModel(array $attributes = []): Model
1✔
177
    {
178
        $model = new class extends Model
1✔
179
        {
1✔
180
            //
181
        };
1✔
182

183
        return $model->forceFill(array_replace(
1✔
184
            ['_key' => Str::uuid()->toString()],
1✔
185
            $attributes
1✔
186
        ));
1✔
187
    }
188

189
    /**
190
     * Resolve the repeater options.
191
     */
192
    public function resolveOptions(Request $request, Model $model): array
2✔
193
    {
194
        $value = (array) $this->resolveValue($request, $model);
2✔
195

196
        return array_map(function (array $option) use ($request, $model): array {
2✔
197
            return $this->toOption($request, $model, $this->newTemporaryModel($option));
×
198
        }, $value);
2✔
199
    }
200

201
    /**
202
     * Build a new option.
203
     */
204
    public function buildOption(Request $request, Model $model): array
1✔
205
    {
206
        $option = $this->toOption($request, $model, $this->newTemporaryModel([]));
1✔
207

208
        $option['fields'] = $option['fields']->mapToInputs($request, $model);
1✔
209

210
        $option['html'] = View::make('root::fields.repeater-option', $option)->render();
1✔
211

212
        return $option;
1✔
213
    }
214

215
    /**
216
     * {@inheritdoc}
217
     */
218
    public function resolveFormat(Request $request, Model $model): ?string
3✔
219
    {
220
        if (is_null($this->formatResolver)) {
3✔
221
            $this->formatResolver = function (Request $request, Model $model, ?array $value = null): string {
3✔
222
                $values = array_map(function (array $value) use ($request, $model): array {
3✔
NEW
223
                    return $this->resolveOptionFields($request, $model, $this->newTemporaryModel($value))
×
NEW
224
                        ->authorized($request, $model)
×
NEW
225
                        ->visible('show')
×
NEW
226
                        ->mapToDisplay($request, $model);
×
227
                }, (array) $value);
3✔
228

229
                return View::make('root::fields.repeater-table', ['values' => $values])->render();
3✔
230
            };
3✔
231
        }
232

233
        return parent::resolveFormat($request, $model);
3✔
234
    }
235

236
    /**
237
     * Register the routes using the given router.
238
     */
239
    public function registerRoutes(Request $request, Router $router): void
196✔
240
    {
241
        $this->__registerRoutes($request, $router);
196✔
242

243
        $router->prefix($this->getUriKey())->group(function (Router $router) use ($request): void {
196✔
244
            $this->resolveFields($request)->registerRoutes($request, $router);
196✔
245
        });
196✔
246
    }
247

248
    /**
249
     * The routes that should be registered.
250
     */
251
    public function routes(Router $router): void
196✔
252
    {
253
        $router->post('/', RepeaterController::class);
196✔
254
    }
255

256
    /**
257
     * Get the option representation of the model and the temporary model.
258
     */
259
    public function toOption(Request $request, Model $model, Model $tmpModel): array
1✔
260
    {
261
        return [
1✔
262
            'open' => true,
1✔
263
            'value' => $tmpModel->getAttribute('_key'),
1✔
264
            'label' => $this->getOptionName(),
1✔
265
            'fields' => $this->resolveOptionFields($request, $model, $tmpModel),
1✔
266
        ];
1✔
267
    }
268

269
    /**
270
     * {@inheritdoc}
271
     */
272
    public function toInput(Request $request, Model $model): array
2✔
273
    {
274
        return array_merge(parent::toInput($request, $model), [
2✔
275
            'addNewLabel' => $this->getAddNewOptionLabel(),
2✔
276
            'max' => $this->max,
2✔
277
            'options' => array_map(static function (array $option) use ($request, $model): array {
2✔
278
                $option['fields'] = $option['fields']->mapToInputs($request, $model);
×
279

280
                return array_merge($option, [
×
281
                    'html' => View::make('root::fields.repeater-option', $option)->render(),
×
282
                ]);
×
283
            }, $this->resolveOptions($request, $model)),
2✔
284
            'url' => $this->replaceRoutePlaceholders($request->route()),
2✔
285
        ]);
2✔
286
    }
287

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

299
    /**
300
     * Clone the field.
301
     */
302
    public function __clone(): void
×
303
    {
304
        $this->fields = null;
×
305
    }
306
}
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