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

codeigniter4 / CodeIgniter4 / 25902734269

15 May 2026 05:51AM UTC coverage: 88.459% (+0.2%) from 88.299%
25902734269

Pull #10159

github

web-flow
Merge f0573f3e0 into 170b89a6e
Pull Request #10159: feat: Add support for callable TTLs in cache handlers

6 of 10 new or added lines in 3 files covered. (60.0%)

446 existing lines in 24 files now uncovered.

24114 of 27260 relevant lines covered (88.46%)

219.07 hits per line

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

95.56
/system/HTTP/FormRequest.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of CodeIgniter 4 framework.
7
 *
8
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace CodeIgniter\HTTP;
15

16
use CodeIgniter\Exceptions\RuntimeException;
17
use CodeIgniter\Validation\ValidatedInput;
18
use ReflectionNamedType;
19
use ReflectionParameter;
20

21
/**
22
 * @see \CodeIgniter\HTTP\FormRequestTest
23
 */
24
abstract class FormRequest
25
{
26
    /**
27
     * The underlying HTTP request instance.
28
     */
29
    protected IncomingRequest $request;
30

31
    /**
32
     * Data that passed validation (only the fields covered by rules()).
33
     *
34
     * @var array<string, mixed>
35
     */
36
    private array $validatedData = [];
37

38
    /**
39
     * When called by the framework, the current IncomingRequest is injected
40
     * explicitly. When instantiated manually (e.g. in tests), the constructor
41
     * falls back to service('request').
42
     */
43
    final public function __construct(?Request $request = null)
44
    {
45
        $request ??= service('request');
36✔
46

47
        if (! $request instanceof IncomingRequest) {
36✔
48
            throw new RuntimeException(
1✔
49
                sprintf('%s requires an IncomingRequest instance, got %s.', static::class, $request::class),
1✔
50
            );
1✔
51
        }
52

53
        $this->request = $request;
35✔
54
    }
55

56
    /**
57
     * Validation rules for this request.
58
     *
59
     * Return an array of field => rules pairs, identical to what you would
60
     * pass to $this->validate() in a controller:
61
     *
62
     *  return [
63
     *      'title' => 'required|min_length[5]',
64
     *      'body'  => ['required', 'max_length[10000]'],
65
     *  ];
66
     *
67
     * @return array<string, list<string>|string>
68
     */
69
    abstract public function rules(): array;
70

71
    /**
72
     * Custom error messages keyed by field.rule.
73
     *
74
     *  return [
75
     *      'title' => ['required' => 'Post title cannot be empty.'],
76
     *  ];
77
     *
78
     * @return array<string, array<string, string>>
79
     */
80
    public function messages(): array
81
    {
82
        return [];
28✔
83
    }
84

85
    /**
86
     * Determine if the current user is authorized to make this request.
87
     *
88
     * Override in subclasses to add authorization logic:
89
     *
90
     *  public function isAuthorized(): bool
91
     *  {
92
     *      return auth()->user()->can('create-posts');
93
     *  }
94
     */
95
    public function isAuthorized(): bool
96
    {
97
        return true;
28✔
98
    }
99

100
    /**
101
     * Returns the class name when the given reflection parameter is typed as a
102
     * FormRequest subclass, or null otherwise. Used by the dispatcher and
103
     * auto-router to distinguish injectable parameters from URI-segment parameters.
104
     *
105
     * @internal
106
     *
107
     * @return class-string<self>|null
108
     */
109
    final public static function getFormRequestClass(ReflectionParameter $param): ?string
110
    {
111
        $type = $param->getType();
49✔
112

113
        if (
114
            $type instanceof ReflectionNamedType
49✔
115
            && ! $type->isBuiltin()
49✔
116
            && is_subclass_of($type->getName(), self::class)
49✔
117
        ) {
118
            return $type->getName();
13✔
119
        }
120

121
        return null;
42✔
122
    }
123

124
    /**
125
     * Modify the request data before validation rules are applied.
126
     * Override to normalize or cast input values:
127
     *
128
     *  protected function prepareForValidation(array $data): array
129
     *  {
130
     *      $data['slug'] = url_title($data['title'] ?? '', '-', true);
131
     *      return $data;
132
     *  }
133
     *
134
     * The $data array is the same payload that will be passed to the
135
     * validator. Return the (possibly modified) array.
136
     *
137
     * @param array<string, mixed> $data
138
     *
139
     * @return array<string, mixed>
140
     */
141
    protected function prepareForValidation(array $data): array
142
    {
143
        return $data;
26✔
144
    }
145

146
    /**
147
     * Called when validation fails. Override to customize the failure response.
148
     *
149
     * The default implementation redirects back with input and flashes validation
150
     * errors via the standard ``_ci_validation_errors`` channel (the same channel
151
     * used by controller-level validation and readable by ``validation_errors()``
152
     * helpers). For JSON/AJAX requests it returns a 422 JSON response instead.
153
     *
154
     * @param array<string, string> $errors
155
     */
156
    protected function failedValidation(array $errors): ResponseInterface
157
    {
158
        if ($this->request->is('json') || $this->request->isAJAX()) {
8✔
159
            return service('response')->setStatusCode(422)->setJSON(['errors' => $errors]);
5✔
160
        }
161

162
        return redirect()->back()->withInput();
3✔
163
    }
164

165
    /**
166
     * Called when the isAuthorized() check returns false. Override to customize.
167
     */
168
    protected function failedAuthorization(): ResponseInterface
169
    {
170
        return service('response')->setStatusCode(403);
3✔
171
    }
172

173
    /**
174
     * Returns only the fields that passed validation (those covered by rules()).
175
     *
176
     * Prefer this over $this->request->getPost() in controllers to avoid
177
     * processing fields that were not declared in the rules.
178
     *
179
     * @return array<string, mixed>
180
     */
181
    public function validated(): array
182
    {
183
        return $this->validatedData;
12✔
184
    }
185

186
    /**
187
     * Returns the validated data as a typed input object.
188
     */
189
    public function validatedInput(): ValidatedInput
190
    {
191
        return service('inputdatafactory')->createValidated($this->validatedData);
1✔
192
    }
193

194
    /**
195
     * Returns a single validated field value by name, or the default value
196
     * if the field is not present in the validated data.
197
     *
198
     * Supports dot-array syntax for nested validated data.
199
     */
200
    public function getValidated(string $key, mixed $default = null): mixed
201
    {
202
        helper('array');
6✔
203

204
        if (! dot_array_has($key, $this->validatedData)) {
6✔
205
            return $default;
3✔
206
        }
207

208
        return dot_array_search($key, $this->validatedData);
3✔
209
    }
210

211
    /**
212
     * Returns true when the named field exists in the validated data, even if
213
     * its value is null.
214
     *
215
     * Supports dot-array syntax for nested validated data.
216
     */
217
    public function hasValidated(string $key): bool
218
    {
219
        helper('array');
4✔
220

221
        return dot_array_has($key, $this->validatedData);
4✔
222
    }
223

224
    /**
225
     * Returns the data to be validated.
226
     *
227
     * Override this method to provide custom data or to merge data from
228
     * multiple sources. By default, data is sourced from the appropriate
229
     * part of the request based on HTTP method and Content-Type:
230
     *
231
     * - JSON (any method)      - decoded JSON body
232
     * - PUT / PATCH / DELETE   - raw body (unless multipart/form-data)
233
     * - GET / HEAD             - query-string parameters
234
     * - Everything else (POST) - POST body
235
     *
236
     * @return array<string, mixed>
237
     */
238
    protected function validationData(): array
239
    {
240
        $contentType = $this->request->getHeaderLine('Content-Type');
26✔
241

242
        if (str_contains($contentType, 'application/json')) {
26✔
243
            return $this->request->getJSON(true) ?? [];
4✔
244
        }
245

246
        if (
247
            in_array($this->request->getMethod(), [Method::PUT, Method::PATCH, Method::DELETE], true)
22✔
248
            && ! str_contains($contentType, 'multipart/form-data')
22✔
249
        ) {
UNCOV
250
            return $this->request->getRawInput() ?? [];
×
251
        }
252

253
        if (in_array($this->request->getMethod(), [Method::GET, Method::HEAD], true)) {
22✔
UNCOV
254
            return $this->request->getGet() ?? [];
×
255
        }
256

257
        return $this->request->getPost() ?? [];
22✔
258
    }
259

260
    /**
261
     * Runs authorization and validation. Called by the framework before
262
     * injecting the FormRequest into the controller method.
263
     *
264
     * Returns null on success, or a ResponseInterface to short-circuit the
265
     * request when authorization or validation fails.
266
     *
267
     * Do not call this method directly unless you are inside a ``_remap()``
268
     * method, where automatic injection is not available.
269
     */
270
    final public function resolveRequest(): ?ResponseInterface
271
    {
272
        if (! $this->isAuthorized()) {
31✔
273
            return $this->failedAuthorization();
4✔
274
        }
275

276
        $data = $this->prepareForValidation($this->validationData());
27✔
277

278
        $validation = service('validation')
27✔
279
            ->setRules($this->rules(), $this->messages());
27✔
280

281
        if (! $validation->run($data)) {
27✔
282
            return $this->failedValidation($validation->getErrors());
9✔
283
        }
284

285
        $this->validatedData = $validation->getValidated();
18✔
286

287
        return null;
18✔
288
    }
289
}
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