• 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

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

3
namespace Cone\Root\Fields;
4

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

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

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

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

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

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

46
        $this->hiddenOn(['index']);
198✔
47
    }
48

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

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

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

72
        return $this;
×
73
    }
74

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

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

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

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

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

116
        if ($field instanceof Relation) {
198✔
NEW
117
            $field->resolveRouteKeyNameUsing(fn (): string => Str::of($field->getRelationName())->singular()->ucfirst()->prepend($this->getModelAttribute())->value());
×
118
        }
119
    }
120

121
    /**
122
     * {@inheritdoc}
123
     */
124
    public function withFields(Closure $callback): static
125
    {
126
        $this->optionFieldsResolver = function (Request $request, Model $model, Model $tmpModel) use ($callback): Fields {
198✔
127
            $fields = new Fields([
1✔
128
                Hidden::make(__('Key'), '_key'),
1✔
129
            ]);
1✔
130

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

133
            $fields->each(function (Field $field) use ($tmpModel): void {
1✔
134
                $attribute = sprintf(
1✔
135
                    '%s.%s.%s',
1✔
136
                    $this->getModelAttribute(),
1✔
137
                    $tmpModel->getAttribute('_key'),
1✔
138
                    $key = $field->getModelAttribute()
1✔
139
                );
1✔
140

141
                $field->setModelAttribute($attribute)
1✔
142
                    ->name($attribute)
1✔
143
                    ->id($attribute)
1✔
144
                    ->value(fn (): mixed => $tmpModel->getAttribute($key));
1✔
145
            });
1✔
146

147
            return $fields;
1✔
148
        };
198✔
149

150
        return $this->__withFields($callback);
198✔
151
    }
152

153
    /**
154
     * Resolve the option fields.
155
     */
156
    public function resolveOptionFields(Request $request, Model $model, Model $tmpModel): Fields
157
    {
158
        return is_null($this->optionFieldsResolver)
1✔
159
            ? new Fields
×
160
            : call_user_func_array($this->optionFieldsResolver, [$request, $model, $tmpModel]);
1✔
161
    }
162

163
    /**
164
     * Make a new temporary model for the option.
165
     */
166
    public function newTemporaryModel(array $attributes = []): Model
167
    {
168
        $model = new class extends Model
1✔
169
        {
1✔
170
            //
171
        };
1✔
172

173
        return $model->forceFill(array_replace(
1✔
174
            ['_key' => Str::uuid()],
1✔
175
            $attributes
1✔
176
        ));
1✔
177
    }
178

179
    /**
180
     * Resolve the repeater options.
181
     */
182
    public function resolveOptions(Request $request, Model $model): array
183
    {
184
        $value = (array) $this->resolveValue($request, $model);
2✔
185

186
        return array_map(fn (array $option): array => $this->toOption($request, $model, $this->newTemporaryModel($option)), $value);
2✔
187
    }
188

189
    /**
190
     * Build a new option.
191
     */
192
    public function buildOption(Request $request, Model $model): array
193
    {
194
        $option = $this->toOption($request, $model, $this->newTemporaryModel([]));
1✔
195

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

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

200
        return $option;
1✔
201
    }
202

203
    /**
204
     * Get the model URL.
205
     */
206
    public function modelUrl(Model $model): string
207
    {
208
        return str_replace('{resourceModel}', $model->exists ? $model->getKey() : 'create', $this->getUri());
2✔
209
    }
210

211
    /**
212
     * {@inheritdoc}
213
     */
214
    public function resolveFormat(Request $request, Model $model): ?string
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
234
    {
235
        $this->__registerRoutes($request, $router);
198✔
236

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

242
    /**
243
     * The routes that should be registered.
244
     */
245
    public function routes(Router $router): void
246
    {
247
        $router->post('/', RepeaterController::class);
198✔
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
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
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->modelUrl($model),
2✔
279
        ]);
2✔
280
    }
281

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