Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
SoftDelete
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
4 / 4
9
100.00% covered (success)
100.00%
1 / 1
 softDelete
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
1 / 1
6
 modifySoftDeleteQuery
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 beforeSoftDelete
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 afterSoftDelete
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
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\Http\JsonResponse;
11use Illuminate\Support\Str;
12use Psr\Http\Message\ResponseInterface;
13
14/**
15 * Provides the soft delete action for CRUD controllers.
16 *
17 * Soft deletes a model instance by calling the model's delete() method.
18 * Requires the model to use Laravel's SoftDeletes trait.
19 *
20 * @method string modelClassName() Returns the Eloquent model class name
21 * @method bool hasModelAttribute(Model $model, string $attributeName) Checks if model has attribute
22 * @method JsonResponse|ResponseInterface answerInvalidUuid(HttpStatusCode $code) Returns invalid UUID error response
23 * @method JsonResponse|ResponseInterface answerRecordNotFound(HttpStatusCode $code = HttpStatusCode::NOT_FOUND)
24 *         Returns not found error response
25 * @method JsonResponse|ResponseInterface answerColumnNotFound(string $field, HttpStatusCode $code) Column not found
26 * @method JsonResponse|ResponseInterface answerNoContent(HttpStatusCode $code = HttpStatusCode::OK)
27 *         Returns No Content response
28 */
29trait SoftDelete
30{
31    /**
32     * Soft deletes a record by its identifier field.
33     *
34     * @param string $id The identifier value of the record to soft delete
35     * @return JsonResponse|ResponseInterface 204 No Content on success, or error response
36     */
37    public function softDelete(string $id): JsonResponse|ResponseInterface
38    {
39        $httpStatus = HttpStatusCode::from(
40            config('devToolbelt.fast-crud.soft_delete.http_status', HttpStatusCode::OK->value)
41        );
42
43        $validationHttpStatus = HttpStatusCode::from(
44            config('devToolbelt.fast-crud.global.validation.http_status', HttpStatusCode::BAD_REQUEST->value)
45        );
46
47        $findField = config('devToolbelt.fast-crud.soft_delete.find_field')
48            ?? config('devToolbelt.fast-crud.global.find_field', 'id');
49
50        $isUuid = config('devToolbelt.fast-crud.soft_delete.find_field_is_uuid')
51            ?? config('devToolbelt.fast-crud.global.find_field_is_uuid', false);
52
53        $deletedAtField = config('devToolbelt.fast-crud.soft_delete.deleted_at_field', 'deleted_at');
54        $deletedByField = config('devToolbelt.fast-crud.soft_delete.deleted_by_field', 'deleted_by');
55
56        if ($isUuid && !Str::isUuid($id)) {
57            return $this->answerInvalidUuid($validationHttpStatus);
58        }
59
60        $modelName = $this->modelClassName();
61
62        /** @var Model $model */
63        $model = new $modelName();
64
65        if (!$this->hasModelAttribute($model, $deletedAtField)) {
66            return $this->answerColumnNotFound($deletedAtField, $validationHttpStatus);
67        }
68
69        if (!$this->hasModelAttribute($model, $deletedByField)) {
70            return $this->answerColumnNotFound($deletedByField, $validationHttpStatus);
71        }
72
73        $query = $modelName::query()
74            ->where($findField, $id)
75            ->whereNull($deletedAtField)
76            ->whereNull($deletedByField);
77
78        $this->modifySoftDeleteQuery($query);
79
80        /** @var Model|null $record */
81        $record = $query->first();
82
83        if ($record === null) {
84            return $this->answerRecordNotFound();
85        }
86
87        $this->beforeSoftDelete($record);
88        $record->delete();
89        $this->afterSoftDelete($record);
90
91        return $this->answerNoContent(code: $httpStatus);
92    }
93
94    /**
95     * Hook to modify the soft delete query before fetching the record.
96     *
97     * Override this method to add additional conditions or scopes,
98     * such as ensuring the user owns the record.
99     *
100     * @param Builder $query The query builder instance
101     *
102     * @example
103     * ```php
104     * protected function modifySoftDeleteQuery(Builder $query): void
105     * {
106     *     $query->where('user_id', auth()->id());
107     * }
108     * ```
109     */
110    protected function modifySoftDeleteQuery(Builder $query): void
111    {
112    }
113
114    /**
115     * Hook called before the record is soft deleted.
116     *
117     * Override this method to perform actions before soft deletion,
118     * such as logging, validation, or cleanup of related data.
119     *
120     * @param Model $record The model instance about to be soft deleted
121     */
122    protected function beforeSoftDelete(Model $record): void
123    {
124    }
125
126    /**
127     * Hook called after the record has been soft deleted.
128     *
129     * Override this method to perform post-soft-deletion actions,
130     * such as clearing cache, dispatching events, or audit logging.
131     *
132     * @param Model $record The soft deleted model instance
133     */
134    protected function afterSoftDelete(Model $record): void
135    {
136    }
137}