Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.00% covered (success)
90.00%
27 / 30
50.00% covered (danger)
50.00%
3 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Delete
90.00% covered (success)
90.00%
27 / 30
50.00% covered (danger)
50.00%
3 / 6
10.10
0.00% covered (danger)
0.00%
0 / 1
 delete
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 modelUsesSoftDeletes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 performHardDelete
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
4
 modifyDeleteQuery
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 beforeDelete
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 afterDelete
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace DevToolbelt\LaravelFastCrud\Actions;
6
7use DevToolbelt\Enums\Http\HttpStatusCode;
8use Illuminate\Database\Eloquent\Builder;
9use Illuminate\Database\Eloquent\Model;
10use Illuminate\Database\Eloquent\SoftDeletes;
11use Illuminate\Http\JsonResponse;
12use Illuminate\Support\Str;
13use Psr\Http\Message\ResponseInterface;
14
15/**
16 * Provides to delete (DELETE by ID) action for CRUD controllers.
17 *
18 * Deletes a model instance by its identifier field (configurable).
19 * If the model uses Laravel's SoftDeletes trait, delegates to softDelete() action.
20 * Otherwise, performs a hard delete using forceDelete().
21 *
22 * @method string modelClassName() Returns the Eloquent model class name
23 * @method JsonResponse|ResponseInterface answerInvalidUuid(HttpStatusCode $code) Returns invalid UUID error response
24 * @method JsonResponse|ResponseInterface answerRecordNotFound(HttpStatusCode $code = HttpStatusCode::NOT_FOUND)
25 *         Returns not found error response
26 * @method JsonResponse|ResponseInterface answerNoContent(HttpStatusCode $code = HttpStatusCode::OK)
27 *         Returns No Content response
28 * @method JsonResponse|ResponseInterface softDelete(string $id) Soft deletes a record (from SoftDelete trait)
29 */
30trait Delete
31{
32    /**
33     * Deletes a record by its identifier field.
34     *
35     * If the model uses Laravel's SoftDeletes trait, this method delegates
36     * to the softDelete() action. Otherwise, it performs a hard delete.
37     *
38     * @param string $id The identifier value of the record to delete
39     * @return JsonResponse|ResponseInterface 204 No Content on success, or error response
40     */
41    public function delete(string $id): JsonResponse|ResponseInterface
42    {
43        $modelName = $this->modelClassName();
44
45        // Check if model uses SoftDeletes trait - delegate to softDelete action
46        if ($this->modelUsesSoftDeletes($modelName)) {
47            return $this->softDelete($id);
48        }
49
50        return $this->performHardDelete($id);
51    }
52
53    /**
54     * Checks if the model uses Laravel's SoftDeletes trait.
55     *
56     * @param string $modelName The fully qualified model class name
57     * @return bool True if the model uses SoftDeletes trait
58     */
59    protected function modelUsesSoftDeletes(string $modelName): bool
60    {
61        return in_array(SoftDeletes::class, class_uses_recursive($modelName), true);
62    }
63
64    /**
65     * Performs a hard delete on the record.
66     *
67     * @param string $id The identifier value of the record to delete
68     * @return JsonResponse|ResponseInterface 204 No Content on success, or error response
69     */
70    protected function performHardDelete(string $id): JsonResponse|ResponseInterface
71    {
72        $httpStatus = HttpStatusCode::from(
73            config('devToolbelt.fast-crud.delete.http_status', HttpStatusCode::OK->value)
74        );
75
76        $validationHttpStatus = HttpStatusCode::from(
77            config('devToolbelt.fast-crud.global.validation.http_status', HttpStatusCode::BAD_REQUEST->value)
78        );
79
80        $findField = config('devToolbelt.fast-crud.delete.find_field')
81            ?? config('devToolbelt.fast-crud.global.find_field', 'id');
82
83        $isUuid = config('devToolbelt.fast-crud.delete.find_field_is_uuid')
84            ?? config('devToolbelt.fast-crud.global.find_field_is_uuid', false);
85
86        if ($isUuid && !Str::isUuid($id)) {
87            return $this->answerInvalidUuid($validationHttpStatus);
88        }
89
90        $modelName = $this->modelClassName();
91        $query = $modelName::query()->where($findField, $id);
92        $this->modifyDeleteQuery($query);
93
94        /** @var Model|null $record */
95        $record = $query->first();
96
97        if ($record === null) {
98            return $this->answerRecordNotFound();
99        }
100
101        $this->beforeDelete($record);
102        $record->delete();
103        $this->afterDelete($record);
104
105        return $this->answerNoContent(code: $httpStatus);
106    }
107
108    /**
109     * Hook to modify the delete query before fetching the record.
110     *
111     * Override this method to add additional conditions or scopes,
112     * such as ensuring the user owns the record.
113     *
114     * @param Builder $query The query builder instance
115     *
116     * @example
117     * ```php
118     * protected function modifyDeleteQuery(Builder $query): void
119     * {
120     *     $query->where('user_id', auth()->id());
121     * }
122     * ```
123     */
124    protected function modifyDeleteQuery(Builder $query): void
125    {
126    }
127
128    /**
129     * Hook called before the record is deleted.
130     *
131     * Override this method to perform actions before deletion,
132     * such as logging, validation, or cleanup of related data.
133     *
134     * @param Model $record The model instance about to be deleted
135     */
136    protected function beforeDelete(Model $record): void
137    {
138    }
139
140    /**
141     * Hook called after the record has been deleted.
142     *
143     * Override this method to perform post-deletion actions,
144     * such as clearing cache, dispatching events, or audit logging.
145     *
146     * @param Model $record The deleted model instance
147     */
148    protected function afterDelete(Model $record): void
149    {
150    }
151}