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

conedevelopment / root / 18554953619

16 Oct 2025 08:19AM UTC coverage: 75.974% (-0.3%) from 76.265%
18554953619

push

github

iamgergo
version

3355 of 4416 relevant lines covered (75.97%)

34.23 hits per line

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

83.33
/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)
197✔
45
    {
46
        parent::__construct($label, $modelAttribute);
197✔
47

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

51
    /**
52
     * Get the URI key.
53
     */
54
    public function getUriKey(): string
197✔
55
    {
56
        return str_replace('.', '-', $this->getRequestKey());
197✔
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
197✔
113
    {
114
        $field->setModelAttribute(
197✔
115
            sprintf('%s.*.%s', $this->getModelAttribute(), $field->getModelAttribute())
197✔
116
        );
197✔
117

118
        if ($field instanceof Relation) {
197✔
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
197✔
133
    {
134
        $this->optionFieldsResolver = function (Request $request, Model $model, Model $tmpModel) use ($callback): Fields {
197✔
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
                    ->value(fn (): mixed => $tmpModel->getAttribute($key));
1✔
153
            });
1✔
154

155
            return $fields;
1✔
156
        };
197✔
157

158
        return $this->__withFields($callback);
197✔
159
    }
160

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

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

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

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

194
        return array_map(fn (array $option): array => $this->toOption($request, $model, $this->newTemporaryModel($option)), $value);
2✔
195
    }
196

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

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

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

208
        return $option;
1✔
209
    }
210

211
    /**
212
     * {@inheritdoc}
213
     */
214
    public function resolveFormat(Request $request, Model $model): ?string
3✔
215
    {
216
        if (is_null($this->formatResolver)) {
3✔
217
            $this->formatResolver = function (Request $request, Model $model, ?array $value = null): string {
3✔
218
                $values = array_map(fn (array $value): array => $this->resolveOptionFields($request, $model, $this->newTemporaryModel($value))
3✔
219
                    ->authorized($request, $model)
3✔
220
                    ->visible('show')
3✔
221
                    ->mapToDisplay($request, $model), (array) $value);
3✔
222

223
                return View::make('root::fields.repeater-table', ['values' => $values])->render();
3✔
224
            };
3✔
225
        }
226

227
        return parent::resolveFormat($request, $model);
3✔
228
    }
229

230
    /**
231
     * Register the routes using the given router.
232
     */
233
    public function registerRoutes(Request $request, Router $router): void
197✔
234
    {
235
        $this->__registerRoutes($request, $router);
197✔
236

237
        $router->prefix($this->getUriKey())->group(function (Router $router) use ($request): void {
197✔
238
            $this->resolveFields($request)->registerRoutes($request, $router);
197✔
239
        });
197✔
240
    }
241

242
    /**
243
     * The routes that should be registered.
244
     */
245
    public function routes(Router $router): void
197✔
246
    {
247
        $router->post('/', RepeaterController::class);
197✔
248
    }
249

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

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

274
                return array_merge($option, [
×
275
                    'html' => View::make('root::fields.repeater-option', $option)->render(),
×
276
                ]);
×
277
            }, $this->resolveOptions($request, $model)),
2✔
278
            'url' => $this->replaceRoutePlaceholders($request->route()),
2✔
279
        ]);
2✔
280
    }
281

282
    /**
283
     * {@inheritdoc}
284
     */
285
    public function toValidate(Request $request, Model $model): array
3✔
286
    {
287
        return array_merge(
3✔
288
            parent::toValidate($request, $model),
3✔
289
            $this->resolveFields($request)->mapToValidate($request, $model)
3✔
290
        );
3✔
291
    }
292

293
    /**
294
     * Clone the field.
295
     */
296
    public function __clone(): void
×
297
    {
298
        $this->fields = null;
×
299
    }
300
}
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

© 2025 Coveralls, Inc