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

codeigniter4 / CodeIgniter4 / 12673986434

08 Jan 2025 03:42PM UTC coverage: 84.455% (+0.001%) from 84.454%
12673986434

Pull #9385

github

web-flow
Merge 06e47f0ee into e475fd8fa
Pull Request #9385: refactor: Fix phpstan expr.resultUnused

20699 of 24509 relevant lines covered (84.45%)

190.57 hits per line

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

87.07
/system/Validation/Rules.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\Validation;
15

16
use CodeIgniter\Database\BaseBuilder;
17
use CodeIgniter\Exceptions\InvalidArgumentException;
18
use CodeIgniter\Helpers\Array\ArrayHelper;
19
use Config\Database;
20

21
/**
22
 * Validation Rules.
23
 *
24
 * @see \CodeIgniter\Validation\RulesTest
25
 */
26
class Rules
27
{
28
    /**
29
     * The value does not match another field in $data.
30
     *
31
     * @param string|null $str
32
     * @param array       $data Other field/value pairs
33
     */
34
    public function differs($str, string $field, array $data): bool
35
    {
36
        if (str_contains($field, '.')) {
13✔
37
            return $str !== dot_array_search($field, $data);
2✔
38
        }
39

40
        return array_key_exists($field, $data) && $str !== $data[$field];
11✔
41
    }
42

43
    /**
44
     * Equals the static value provided.
45
     *
46
     * @param string|null $str
47
     */
48
    public function equals($str, string $val): bool
49
    {
50
        if (! is_string($str) && $str !== null) {
10✔
51
            $str = (string) $str;
×
52
        }
53

54
        return $str === $val;
10✔
55
    }
56

57
    /**
58
     * Returns true if $str is $val characters long.
59
     * $val = "5" (one) | "5,8,12" (multiple values)
60
     *
61
     * @param string|null $str
62
     */
63
    public function exact_length($str, string $val): bool
64
    {
65
        if (! is_string($str) && $str !== null) {
17✔
66
            $str = (string) $str;
3✔
67
        }
68

69
        $val = explode(',', $val);
17✔
70

71
        foreach ($val as $tmp) {
17✔
72
            if (is_numeric($tmp) && (int) $tmp === mb_strlen($str ?? '')) {
17✔
73
                return true;
6✔
74
            }
75
        }
76

77
        return false;
13✔
78
    }
79

80
    /**
81
     * Greater than
82
     *
83
     * @param string|null $str
84
     */
85
    public function greater_than($str, string $min): bool
86
    {
87
        if (! is_string($str) && $str !== null) {
24✔
88
            $str = (string) $str;
×
89
        }
90

91
        return is_numeric($str) && $str > $min;
24✔
92
    }
93

94
    /**
95
     * Equal to or Greater than
96
     *
97
     * @param string|null $str
98
     */
99
    public function greater_than_equal_to($str, string $min): bool
100
    {
101
        if (! is_string($str) && $str !== null) {
24✔
102
            $str = (string) $str;
×
103
        }
104

105
        return is_numeric($str) && $str >= $min;
24✔
106
    }
107

108
    /**
109
     * Checks the database to see if the given value exist.
110
     * Can ignore records by field/value to filter (currently
111
     * accept only one filter).
112
     *
113
     * Example:
114
     *    is_not_unique[dbGroup.table.field,where_field,where_value]
115
     *    is_not_unique[table.field,where_field,where_value]
116
     *    is_not_unique[menu.id,active,1]
117
     *
118
     * @param string|null $str
119
     */
120
    public function is_not_unique($str, string $field, array $data): bool
121
    {
122
        [$builder, $whereField, $whereValue] = $this->prepareUniqueQuery($str, $field, $data);
14✔
123

124
        if (
125
            $whereField !== null && $whereField !== ''
12✔
126
            && $whereValue !== null && $whereValue !== ''
12✔
127
            && preg_match('/^\{(\w+)\}$/', $whereValue) !== 1
12✔
128
        ) {
129
            $builder = $builder->where($whereField, $whereValue);
4✔
130
        }
131

132
        return $builder->get()->getRow() !== null;
12✔
133
    }
134

135
    /**
136
     * Value should be within an array of values
137
     *
138
     * @param string|null $value
139
     */
140
    public function in_list($value, string $list): bool
141
    {
142
        if (! is_string($value) && $value !== null) {
59✔
143
            $value = (string) $value;
×
144
        }
145

146
        $list = array_map(trim(...), explode(',', $list));
59✔
147

148
        return in_array($value, $list, true);
59✔
149
    }
150

151
    /**
152
     * Checks the database to see if the given value is unique. Can
153
     * ignore a single record by field/value to make it useful during
154
     * record updates.
155
     *
156
     * Example:
157
     *    is_unique[dbGroup.table.field,ignore_field,ignore_value]
158
     *    is_unique[table.field,ignore_field,ignore_value]
159
     *    is_unique[users.email,id,5]
160
     *
161
     * @param string|null $str
162
     */
163
    public function is_unique($str, string $field, array $data): bool
164
    {
165
        [$builder, $ignoreField, $ignoreValue] = $this->prepareUniqueQuery($str, $field, $data);
20✔
166

167
        if (
168
            $ignoreField !== null && $ignoreField !== ''
16✔
169
            && $ignoreValue !== null && $ignoreValue !== ''
16✔
170
            && preg_match('/^\{(\w+)\}$/', $ignoreValue) !== 1
16✔
171
        ) {
172
            $builder = $builder->where("{$ignoreField} !=", $ignoreValue);
4✔
173
        }
174

175
        return $builder->get()->getRow() === null;
16✔
176
    }
177

178
    /**
179
     * Prepares the database query for uniqueness checks.
180
     *
181
     * @param mixed                $value The value to check.
182
     * @param string               $field The field parameters.
183
     * @param array<string, mixed> $data  Additional data.
184
     *
185
     * @return array{0: BaseBuilder, 1: string|null, 2: string|null}
186
     */
187
    private function prepareUniqueQuery($value, string $field, array $data): array
188
    {
189
        if (! is_string($value) && $value !== null) {
34✔
190
            $value = (string) $value;
2✔
191
        }
192

193
        // Split the field parameters and pad the array to ensure three elements.
194
        [$field, $extraField, $extraValue] = array_pad(explode(',', $field), 3, null);
34✔
195

196
        // Parse the field string to extract dbGroup, table, and field.
197
        $parts    = explode('.', $field, 3);
34✔
198
        $numParts = count($parts);
34✔
199

200
        if ($numParts === 3) {
34✔
201
            [$dbGroup, $table, $field] = $parts;
4✔
202
        } elseif ($numParts === 2) {
30✔
203
            [$table, $field] = $parts;
26✔
204
        } else {
205
            throw new InvalidArgumentException('The field must be in the format "table.field" or "dbGroup.table.field".');
4✔
206
        }
207

208
        // Connect to the database.
209
        $dbGroup ??= $data['DBGroup'] ?? null;
30✔
210
        $builder = Database::connect($dbGroup)
30✔
211
            ->table($table)
30✔
212
            ->select('1')
30✔
213
            ->where($field, $value)
30✔
214
            ->limit(1);
30✔
215

216
        return [$builder, $extraField, $extraValue];
28✔
217
    }
218

219
    /**
220
     * Less than
221
     *
222
     * @param string|null $str
223
     */
224
    public function less_than($str, string $max): bool
225
    {
226
        if (! is_string($str) && $str !== null) {
27✔
227
            $str = (string) $str;
×
228
        }
229

230
        return is_numeric($str) && $str < $max;
27✔
231
    }
232

233
    /**
234
     * Equal to or Less than
235
     *
236
     * @param string|null $str
237
     */
238
    public function less_than_equal_to($str, string $max): bool
239
    {
240
        if (! is_string($str) && $str !== null) {
24✔
241
            $str = (string) $str;
×
242
        }
243

244
        return is_numeric($str) && $str <= $max;
24✔
245
    }
246

247
    /**
248
     * Matches the value of another field in $data.
249
     *
250
     * @param string|null $str
251
     * @param array       $data Other field/value pairs
252
     */
253
    public function matches($str, string $field, array $data): bool
254
    {
255
        if (str_contains($field, '.')) {
13✔
256
            return $str === dot_array_search($field, $data);
2✔
257
        }
258

259
        return isset($data[$field]) && $str === $data[$field];
11✔
260
    }
261

262
    /**
263
     * Returns true if $str is $val or fewer characters in length.
264
     *
265
     * @param string|null $str
266
     */
267
    public function max_length($str, string $val): bool
268
    {
269
        if (! is_string($str) && $str !== null) {
20✔
270
            $str = (string) $str;
×
271
        }
272

273
        return is_numeric($val) && $val >= mb_strlen($str ?? '');
20✔
274
    }
275

276
    /**
277
     * Returns true if $str is at least $val length.
278
     *
279
     * @param string|null $str
280
     */
281
    public function min_length($str, string $val): bool
282
    {
283
        if (! is_string($str) && $str !== null) {
82✔
284
            $str = (string) $str;
×
285
        }
286

287
        return is_numeric($val) && $val <= mb_strlen($str ?? '');
82✔
288
    }
289

290
    /**
291
     * Does not equal the static value provided.
292
     *
293
     * @param string|null $str
294
     */
295
    public function not_equals($str, string $val): bool
296
    {
297
        if (! is_string($str) && $str !== null) {
×
298
            $str = (string) $str;
×
299
        }
300

301
        return $str !== $val;
×
302
    }
303

304
    /**
305
     * Value should not be within an array of values.
306
     *
307
     * @param string|null $value
308
     */
309
    public function not_in_list($value, string $list): bool
310
    {
311
        if (! is_string($value) && $value !== null) {
17✔
312
            $value = (string) $value;
×
313
        }
314

315
        return ! $this->in_list($value, $list);
17✔
316
    }
317

318
    /**
319
     * @param array|bool|float|int|object|string|null $str
320
     */
321
    public function required($str = null): bool
322
    {
323
        if ($str === null) {
271✔
324
            return false;
44✔
325
        }
326

327
        if (is_object($str)) {
242✔
328
            return true;
4✔
329
        }
330

331
        if (is_array($str)) {
238✔
332
            return $str !== [];
12✔
333
        }
334

335
        return trim((string) $str) !== '';
228✔
336
    }
337

338
    /**
339
     * The field is required when any of the other required fields are present
340
     * in the data.
341
     *
342
     * Example (field is required when the password field is present):
343
     *
344
     *     required_with[password]
345
     *
346
     * @param string|null $str
347
     * @param string|null $fields List of fields that we should check if present
348
     * @param array       $data   Complete list of fields from the form
349
     */
350
    public function required_with($str = null, ?string $fields = null, array $data = []): bool
351
    {
352
        if ($fields === null || $data === []) {
62✔
353
            throw new InvalidArgumentException('You must supply the parameters: fields, data.');
×
354
        }
355

356
        // If the field is present we can safely assume that
357
        // the field is here, no matter whether the corresponding
358
        // search field is present or not.
359
        $present = $this->required($str ?? '');
62✔
360

361
        if ($present) {
62✔
362
            return true;
20✔
363
        }
364

365
        // Still here? Then we fail this test if
366
        // any of the fields are present in $data
367
        // as $fields is the list
368
        $requiredFields = [];
42✔
369

370
        foreach (explode(',', $fields) as $field) {
42✔
371
            if (
372
                (array_key_exists($field, $data) && ! empty($data[$field]))
42✔
373
                || (str_contains($field, '.') && ! empty(dot_array_search($field, $data)))
42✔
374
            ) {
375
                $requiredFields[] = $field;
22✔
376
            }
377
        }
378

379
        return $requiredFields === [];
42✔
380
    }
381

382
    /**
383
     * The field is required when all the other fields are present
384
     * in the data but not required.
385
     *
386
     * Example (field is required when the id or email field is missing):
387
     *
388
     *     required_without[id,email]
389
     *
390
     * @param string|null $str
391
     * @param string|null $otherFields The param fields of required_without[].
392
     * @param string|null $field       This rule param fields aren't present, this field is required.
393
     */
394
    public function required_without(
395
        $str = null,
396
        ?string $otherFields = null,
397
        array $data = [],
398
        ?string $error = null,
399
        ?string $field = null
400
    ): bool {
401
        if ($otherFields === null || $data === []) {
48✔
402
            throw new InvalidArgumentException('You must supply the parameters: otherFields, data.');
×
403
        }
404

405
        // If the field is present we can safely assume that
406
        // the field is here, no matter whether the corresponding
407
        // search field is present or not.
408
        $present = $this->required($str ?? '');
48✔
409

410
        if ($present) {
48✔
411
            return true;
20✔
412
        }
413

414
        // Still here? Then we fail this test if
415
        // any of the fields are not present in $data
416
        foreach (explode(',', $otherFields) as $otherField) {
32✔
417
            if (
418
                (! str_contains($otherField, '.'))
32✔
419
                && (! array_key_exists($otherField, $data)
32✔
420
                    || empty($data[$otherField]))
32✔
421
            ) {
422
                return false;
18✔
423
            }
424

425
            if (str_contains($otherField, '.')) {
18✔
426
                if ($field === null) {
8✔
427
                    throw new InvalidArgumentException('You must supply the parameters: field.');
×
428
                }
429

430
                $fieldData       = dot_array_search($otherField, $data);
8✔
431
                $fieldSplitArray = explode('.', $field);
8✔
432
                $fieldKey        = $fieldSplitArray[1];
8✔
433

434
                if (is_array($fieldData)) {
8✔
435
                    return ! empty(dot_array_search($otherField, $data)[$fieldKey]);
2✔
436
                }
437
                $nowField      = str_replace('*', $fieldKey, $otherField);
8✔
438
                $nowFieldVaule = dot_array_search($nowField, $data);
8✔
439

440
                return null !== $nowFieldVaule;
8✔
441
            }
442
        }
443

444
        return true;
6✔
445
    }
446

447
    /**
448
     * The field exists in $data.
449
     *
450
     * @param array|bool|float|int|object|string|null $value The field value.
451
     * @param string|null                             $param The rule's parameter.
452
     * @param array                                   $data  The data to be validated.
453
     * @param string|null                             $field The field name.
454
     */
455
    public function field_exists(
456
        $value = null,
457
        ?string $param = null,
458
        array $data = [],
459
        ?string $error = null,
460
        ?string $field = null
461
    ): bool {
462
        if (str_contains($field, '.')) {
10✔
463
            return ArrayHelper::dotKeyExists($field, $data);
5✔
464
        }
465

466
        return array_key_exists($field, $data);
5✔
467
    }
468
}
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