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

conedevelopment / root / 13086943877

01 Feb 2025 08:35AM UTC coverage: 79.285% (+1.2%) from 78.037%
13086943877

push

github

web-flow
Merge pull request #234 from xHeaven/patch-1

Codebase health checkup

195 of 239 new or added lines in 45 files covered. (81.59%)

2 existing lines in 2 files now uncovered.

2572 of 3244 relevant lines covered (79.28%)

35.8 hits per line

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

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

3
namespace Cone\Root\Actions;
4

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

127
        return $this;
198✔
128
    }
129

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

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

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

151
        return $this;
1✔
152
    }
153

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

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

169
        return $this;
1✔
170
    }
171

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

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

187
        return $this;
1✔
188
    }
189

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

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

205
        $models = match (true) {
4✔
206
            $this->isStandalone() => new Collection([$model]),
4✔
207
            $request->boolean('all') => $this->resolveQuery($request)->get(),
4✔
208
            default => $this->resolveQuery($request)->findMany($request->input('models', [])),
4✔
209
        };
4✔
210

211
        $this->handle($request, $models);
4✔
212

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

223
    /**
224
     * Perform the action.
225
     */
226
    public function perform(Request $request): Response
227
    {
228
        try {
229
            DB::beginTransaction();
5✔
230

231
            $this->handleFormRequest($request, $this->resolveQuery($request)->getModel());
5✔
232

233
            DB::commit();
4✔
234

235
            return Redirect::back()->with(
4✔
236
                sprintf('alerts.action-%s', $this->getKey()),
4✔
237
                Alert::info(__(':action was successful!', ['action' => $this->getName()]))
4✔
238
            );
4✔
239
        } catch (Throwable $exception) {
1✔
240
            report($exception);
1✔
241

242
            DB::rollBack();
1✔
243

244
            throw new SaveFormDataException($exception->getMessage());
1✔
245
        }
246
    }
247

248
    /**
249
     * Register the routes using the given router.
250
     */
251
    public function registerRoutes(Request $request, Router $router): void
252
    {
253
        $this->__registerRoutes($request, $router);
198✔
254

255
        $router->prefix($this->getUriKey())->group(function (Router $router) use ($request): void {
198✔
256
            $this->resolveFields($request)->registerRoutes($request, $router);
198✔
257
        });
198✔
258
    }
259

260
    /**
261
     * Get the route middleware for the registered routes.
262
     */
263
    public function getRouteMiddleware(): array
264
    {
265
        return [
198✔
266
            Authorize::class.':action',
198✔
267
        ];
198✔
268
    }
269

270
    /**
271
     * The routes that should be registered.
272
     */
273
    public function routes(Router $router): void
274
    {
275
        $router->post('/', ActionController::class);
198✔
276
    }
277

278
    /**
279
     * Convert the element to a JSON serializable format.
280
     */
281
    public function jsonSerialize(): string|false
282
    {
283
        return json_encode($this->toArray());
×
284
    }
285

286
    /**
287
     * Convert the action to an array.
288
     */
289
    public function toArray(): array
290
    {
291
        return [
4✔
292
            'confirmable' => $this->isConfirmable(),
4✔
293
            'destructive' => $this->isDestructive(),
4✔
294
            'key' => $this->getKey(),
4✔
295
            'modalKey' => $this->getModalKey(),
4✔
296
            'name' => $this->getName(),
4✔
297
            'standalone' => $this->isStandalone(),
4✔
298
            'template' => $this->template,
4✔
299
        ];
4✔
300
    }
301

302
    /**
303
     * {@inheritdoc}
304
     */
305
    public function toForm(Request $request, Model $model): array
306
    {
307
        return array_merge($this->toArray(), [
3✔
308
            'url' => ! is_null($request->route()) ? $this->replaceRoutePlaceholders($request->route()) : null,
3✔
309
            'open' => $this->errors($request)->isNotEmpty(),
3✔
310
            'fields' => $this->resolveFields($request)->mapToInputs($request, $model),
3✔
311
        ]);
3✔
312
    }
313
}
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