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

conedevelopment / root / 17213554131

25 Aug 2025 03:39PM UTC coverage: 78.032% (-0.05%) from 78.079%
17213554131

push

github

iamgergo
wip

7 of 7 new or added lines in 2 files covered. (100.0%)

66 existing lines in 4 files now uncovered.

3307 of 4238 relevant lines covered (78.03%)

35.77 hits per line

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

93.75
/src/Actions/Action.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Cone\Root\Actions;
6

7
use Closure;
8
use Cone\Root\Exceptions\QueryResolutionException;
9
use Cone\Root\Exceptions\SaveFormDataException;
10
use Cone\Root\Fields\Field;
11
use Cone\Root\Fields\Relation;
12
use Cone\Root\Http\Controllers\ActionController;
13
use Cone\Root\Http\Middleware\Authorize;
14
use Cone\Root\Interfaces\Form;
15
use Cone\Root\Support\Alert;
16
use Cone\Root\Traits\AsForm;
17
use Cone\Root\Traits\Authorizable;
18
use Cone\Root\Traits\HasRootEvents;
19
use Cone\Root\Traits\Makeable;
20
use Cone\Root\Traits\RegistersRoutes;
21
use Cone\Root\Traits\ResolvesVisibility;
22
use Illuminate\Contracts\Support\Arrayable;
23
use Illuminate\Database\Eloquent\Builder;
24
use Illuminate\Database\Eloquent\Collection;
25
use Illuminate\Database\Eloquent\Concerns\HasAttributes;
26
use Illuminate\Database\Eloquent\Model;
27
use Illuminate\Http\Request;
28
use Illuminate\Routing\Router;
29
use Illuminate\Support\Facades\DB;
30
use Illuminate\Support\Facades\Redirect;
31
use Illuminate\Support\MessageBag;
32
use Illuminate\Support\Str;
33
use JsonSerializable;
34
use Symfony\Component\HttpFoundation\Response;
35
use Throwable;
36

37
abstract class Action implements Arrayable, Form, JsonSerializable
38
{
39
    use AsForm;
40
    use Authorizable;
41
    use HasAttributes;
42
    use Makeable;
43
    use RegistersRoutes {
44
        RegistersRoutes::registerRoutes as __registerRoutes;
45
    }
46
    use ResolvesVisibility;
47

48
    /**
49
     * The Blade template.
50
     */
51
    protected string $template = 'root::actions.action';
52

53
    /**
54
     * Indicates if the action is destructive.
55
     */
56
    protected bool $destructive = false;
57

58
    /**
59
     * Indicates if the action is confirmable.
60
     */
61
    protected bool $confirmable = false;
62

63
    /**
64
     * Indicates if the action is standalone.
65
     */
66
    protected bool $standalone = false;
67

68
    /**
69
     * The query resolver.
70
     */
71
    protected ?Closure $queryResolver = null;
72

73
    /**
74
     * Handle the action.
75
     */
76
    abstract public function handle(Request $request, Collection $models): void;
77

78
    /**
79
     * Get the key.
80
     */
81
    public function getKey(): string
197✔
82
    {
83
        return Str::of(static::class)->classBasename()->kebab()->value();
197✔
84
    }
85

86
    /**
87
     * Get the URI key.
88
     */
89
    public function getUriKey(): string
197✔
90
    {
91
        return $this->getKey();
197✔
92
    }
93

94
    /**
95
     * Get the name.
96
     */
97
    public function getName(): string
9✔
98
    {
99
        return __(Str::of(static::class)->classBasename()->headline()->value());
9✔
100
    }
101

102
    /**
103
     * Get the modal key.
104
     */
105
    public function getModalKey(): string
4✔
106
    {
107
        return sprintf('action-%s', $this->getKey());
4✔
108
    }
109

110
    /**
111
     * Resolve the query.
112
     */
113
    public function resolveQuery(Request $request): Builder
5✔
114
    {
115
        if (is_null($this->queryResolver)) {
5✔
116
            throw new QueryResolutionException;
1✔
117
        }
118

119
        return call_user_func_array($this->queryResolver, [$request]);
4✔
120
    }
121

122
    /**
123
     * Set the query resolver callback.
124
     */
125
    public function withQuery(Closure $callback): static
197✔
126
    {
127
        $this->queryResolver = $callback;
197✔
128

129
        return $this;
197✔
130
    }
131

132
    /**
133
     * Handle the callback for the field resolution.
134
     */
135
    protected function resolveField(Request $request, Field $field): void
1✔
136
    {
137
        $field->setAttribute('form', $this->getKey());
1✔
138
        $field->id($this->getKey().'-'.$field->getAttribute('id'));
1✔
139
        $field->resolveErrorsUsing(fn (Request $request): MessageBag => $this->errors($request));
1✔
140

141
        if ($field instanceof Relation) {
1✔
142
            $field->resolveRouteKeyNameUsing(fn (): string => Str::of($field->getRelationName())->singular()->ucfirst()->prepend($this->getKey())->value());
×
143
        }
144
    }
145

146
    /**
147
     * Set the destructive property.
148
     */
149
    public function destructive(bool $value = true): static
1✔
150
    {
151
        $this->destructive = $value;
1✔
152

153
        return $this;
1✔
154
    }
155

156
    /**
157
     * Determine if the action is destructive.
158
     */
159
    public function isDestructive(): bool
5✔
160
    {
161
        return $this->destructive;
5✔
162
    }
163

164
    /**
165
     * Set the confirmable property.
166
     */
167
    public function confirmable(bool $value = true): static
1✔
168
    {
169
        $this->confirmable = $value;
1✔
170

171
        return $this;
1✔
172
    }
173

174
    /**
175
     * Determine if the action is confirmable.
176
     */
177
    public function isConfirmable(): bool
5✔
178
    {
179
        return $this->confirmable;
5✔
180
    }
181

182
    /**
183
     * Set the standalone property.
184
     */
185
    public function standalone(bool $value = true): static
1✔
186
    {
187
        $this->standalone = $value;
1✔
188

189
        return $this;
1✔
190
    }
191

192
    /**
193
     * Determine if the action is standalone.
194
     */
195
    public function isStandalone(): bool
9✔
196
    {
197
        return $this->standalone;
9✔
198
    }
199

200
    /**
201
     * Handle the request.
202
     */
203
    public function handleFormRequest(Request $request, Model $model): Response
4✔
204
    {
205
        $this->validateFormRequest($request, $model);
4✔
206

207
        try {
208
            return DB::transaction(function () use ($request, $model): Response {
4✔
209
                $models = match (true) {
4✔
210
                    $this->isStandalone() => new Collection([$model]),
4✔
211
                    $request->boolean('all') => $this->resolveQuery($request)->get(),
4✔
212
                    default => $this->resolveQuery($request)->findMany($request->input('models', [])),
4✔
213
                };
4✔
214

215
                $this->handle($request, $models);
4✔
216

217
                if (in_array(HasRootEvents::class, class_uses_recursive($model))) {
4✔
218
                    $models->each(static function (Model $model) use ($request): void {
4✔
219
                        $model->recordRootEvent(
3✔
220
                            Str::of(static::class)->classBasename()->headline()->value(),
3✔
221
                            $request->user()
3✔
222
                        );
3✔
223
                    });
4✔
224
                }
225

226
                return $this->formResponse($request, $model);
4✔
227
            });
4✔
UNCOV
228
        } catch (Throwable $exception) {
×
UNCOV
229
            report($exception);
×
230

UNCOV
231
            throw new SaveFormDataException($exception->getMessage());
×
232
        }
233
    }
234

235
    /**
236
     * Make a form response.
237
     */
238
    public function formResponse(Request $request, Model $model): Response
4✔
239
    {
240
        return Redirect::back()->with(
4✔
241
            sprintf('alerts.action-%s', $this->getKey()),
4✔
242
            Alert::info(__(':action was successful!', ['action' => $this->getName()]))
4✔
243
        );
4✔
244
    }
245

246
    /**
247
     * Perform the action.
248
     */
249
    public function perform(Request $request): Response
4✔
250
    {
251
        return $this->handleFormRequest(
4✔
252
            $request,
4✔
253
            $this->resolveQuery($request)->getModel()
4✔
254
        );
4✔
255
    }
256

257
    /**
258
     * Register the routes using the given router.
259
     */
260
    public function registerRoutes(Request $request, Router $router): void
197✔
261
    {
262
        $this->__registerRoutes($request, $router);
197✔
263

264
        $router->prefix($this->getUriKey())->group(function (Router $router) use ($request): void {
197✔
265
            $this->resolveFields($request)->registerRoutes($request, $router);
197✔
266
        });
197✔
267
    }
268

269
    /**
270
     * Get the route middleware for the registered routes.
271
     */
272
    public function getRouteMiddleware(): array
197✔
273
    {
274
        return [
197✔
275
            Authorize::class.':action',
197✔
276
        ];
197✔
277
    }
278

279
    /**
280
     * The routes that should be registered.
281
     */
282
    public function routes(Router $router): void
197✔
283
    {
284
        $router->post('/', ActionController::class);
197✔
285
    }
286

287
    /**
288
     * Convert the element to a JSON serializable format.
289
     */
UNCOV
290
    public function jsonSerialize(): string|false
×
291
    {
UNCOV
292
        return json_encode($this->toArray());
×
293
    }
294

295
    /**
296
     * Convert the action to an array.
297
     */
298
    public function toArray(): array
4✔
299
    {
300
        return [
4✔
301
            'confirmable' => $this->isConfirmable(),
4✔
302
            'destructive' => $this->isDestructive(),
4✔
303
            'key' => $this->getKey(),
4✔
304
            'modalKey' => $this->getModalKey(),
4✔
305
            'name' => $this->getName(),
4✔
306
            'standalone' => $this->isStandalone(),
4✔
307
            'template' => $this->template,
4✔
308
        ];
4✔
309
    }
310

311
    /**
312
     * {@inheritdoc}
313
     */
314
    public function toForm(Request $request, Model $model): array
3✔
315
    {
316
        return array_merge($this->toArray(), [
3✔
317
            'url' => ! is_null($request->route()) ? $this->replaceRoutePlaceholders($request->route()) : null,
3✔
318
            'open' => $this->errors($request)->isNotEmpty(),
3✔
319
            'fields' => $this->resolveFields($request)->mapToInputs($request, $model),
3✔
320
        ]);
3✔
321
    }
322
}
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