Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
Options
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
3 / 3
6
100.00% covered (success)
100.00%
1 / 1
 options
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
1 / 1
4
 modifyOptionsQuery
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 afterOptions
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\Database\Query\Builder as QueryBuilder;
11use Illuminate\Http\JsonResponse;
12use Illuminate\Http\Request;
13use Illuminate\Support\Facades\DB;
14use Psr\Http\Message\ResponseInterface;
15
16/**
17 * Provides the options (GET /options) action for CRUD controllers.
18 *
19 * Returns a list of label-value pairs suitable for populating select dropdowns
20 * or autocomplete fields in frontend applications.
21 *
22 * Query parameters:
23 * - label (required) - The column name to use as the display label
24 * - value (optional) - The column name to use as the value (default: external_id)
25 *
26 * @method string modelClassName() Returns the Eloquent model class name
27 * @method JsonResponse|ResponseInterface answerRequired(string $field, HttpStatusCode $code) Required field error
28 * @method JsonResponse|ResponseInterface answerColumnNotFound(string $field, HttpStatusCode $code) Column not found
29 * @method JsonResponse|ResponseInterface answerSuccess(mixed $data, HttpStatusCode $code, array $meta = [])
30 *         Returns success response
31 */
32trait Options
33{
34    /**
35     * Returns a list of label-value pairs for select dropdowns.
36     *
37     * @param Request $request The HTTP request with label and optional value parameters
38     * @return JsonResponse|ResponseInterface JSON response with array of {label, value} objects
39     *
40     * @example
41     * ```
42     * GET /products/options?label=name
43     * GET /categories/options?label=title&value=slug
44     * ```
45     */
46    public function options(Request $request): JsonResponse|ResponseInterface
47    {
48        $httpStatus = HttpStatusCode::from(
49            config('devToolbelt.fast-crud.options.http_status', HttpStatusCode::OK->value)
50        );
51
52        $validationHttpStatus = HttpStatusCode::from(
53            config('devToolbelt.fast-crud.global.validation.http_status', HttpStatusCode::BAD_REQUEST->value)
54        );
55
56        $defaultValue = config('devToolbelt.fast-crud.options.default_value', 'id');
57        $deletedAtField = config('devToolbelt.fast-crud.soft_delete.deleted_at_field', 'deleted_at');
58
59        $value = $request->get('value', $defaultValue);
60        $label = $request->get('label');
61
62        if ($label === null) {
63            return $this->answerRequired('label', $validationHttpStatus);
64        }
65
66        $modelName = $this->modelClassName();
67
68        /** @var Model $model */
69        $model = new $modelName();
70
71        if (!$this->hasModelAttribute($model, $label)) {
72            return $this->answerColumnNotFound($label, $validationHttpStatus);
73        }
74
75        $table = $model->getTable();
76        $connection = $model->getConnectionName();
77
78        $query = DB::connection($connection)
79            ->table($table)
80            ->select([$value . ' as value', $label . ' as label'])
81            ->orderBy($label, 'ASC');
82
83        if ($this->hasModelAttribute($model, $deletedAtField)) {
84            $query->whereNull($deletedAtField);
85        }
86
87        $this->modifyOptionsQuery($query);
88
89        $rows = $query->get()
90            ->map(static fn(object $record): array => [
91                'label' => $record->label,
92                'value' => $record->value,
93            ])
94            ->all();
95
96        $this->afterOptions($rows);
97
98        return $this->answerSuccess(data: $rows, code: $httpStatus);
99    }
100
101    /**
102     * Hook to modify the options query before execution.
103     *
104     * Override this method to add conditions, such as filtering
105     * only active records or scoping to a specific user.
106     *
107     * @param Builder|QueryBuilder $query The query builder instance
108     *
109     * @example
110     * ```php
111     * protected function modifyOptionsQuery(Builder|QueryBuilder $query): void
112     * {
113     *     $query->where('is_active', true);
114     * }
115     * ```
116     */
117    protected function modifyOptionsQuery(Builder|QueryBuilder $query): void
118    {
119    }
120
121    /**
122     * Hook called after the options have been fetched and formatted.
123     *
124     * Override this method to perform post-fetch actions,
125     * such as caching or modifying the results.
126     *
127     * @param array<int, array{label: mixed, value: mixed}> $rows The formatted options array
128     */
129    protected function afterOptions(array $rows): void
130    {
131    }
132}