Skip to content

Commit

Permalink
Merge pull request #8123 from kenjis/feat-validation-field_exists
Browse files Browse the repository at this point in the history
feat: [Validation] add `field_exists` rule
  • Loading branch information
kenjis authored Nov 19, 2023
2 parents 3846204 + cfa4fa1 commit 8b72997
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 12 deletions.
4 changes: 2 additions & 2 deletions system/Common.php
Original file line number Diff line number Diff line change
Expand Up @@ -750,14 +750,14 @@ function lang(string $line, array $args = [], ?string $locale = null)
$language->setLocale($locale);
}

$line = $language->getLine($line, $args);
$lines = $language->getLine($line, $args);

if ($locale && $locale !== $activeLocale) {
// Reset to active locale
$language->setLocale($activeLocale);
}

return $line;
return $lines;
}
}

Expand Down
1 change: 1 addition & 0 deletions system/Language/en/Validation.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
'differs' => 'The {field} field must differ from the {param} field.',
'equals' => 'The {field} field must be exactly: {param}.',
'exact_length' => 'The {field} field must be exactly {param} characters in length.',
'field_exists' => 'The {field} field must exist.',
'greater_than' => 'The {field} field must contain a number greater than {param}.',
'greater_than_equal_to' => 'The {field} field must contain a number greater than or equal to {param}.',
'hex' => 'The {field} field may only contain hexadecimal characters.',
Expand Down
23 changes: 23 additions & 0 deletions system/Validation/Rules.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace CodeIgniter\Validation;

use CodeIgniter\Helpers\Array\ArrayHelper;
use Config\Database;
use InvalidArgumentException;

Expand Down Expand Up @@ -427,4 +428,26 @@ public function required_without(

return true;
}

/**
* The field exists in $data.
*
* @param array|bool|float|int|object|string|null $value The field value.
* @param string|null $param The rule's parameter.
* @param array $data The data to be validated.
* @param string|null $field The field name.
*/
public function field_exists(
$value = null,
?string $param = null,
array $data = [],
?string $error = null,
?string $field = null
): bool {
if (strpos($field, '.') !== false) {
return ArrayHelper::dotKeyExists($field, $data);
}

return array_key_exists($field, $data);
}
}
23 changes: 23 additions & 0 deletions system/Validation/StrictRules/Rules.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace CodeIgniter\Validation\StrictRules;

use CodeIgniter\Helpers\Array\ArrayHelper;
use CodeIgniter\Validation\Rules as NonStrictRules;
use Config\Database;

Expand Down Expand Up @@ -403,4 +404,26 @@ public function required_without(
): bool {
return $this->nonStrictRules->required_without($str, $otherFields, $data, $error, $field);
}

/**
* The field exists in $data.
*
* @param array|bool|float|int|object|string|null $value The field value.
* @param string|null $param The rule's parameter.
* @param array $data The data to be validated.
* @param string|null $field The field name.
*/
public function field_exists(
$value = null,
?string $param = null,
array $data = [],
?string $error = null,
?string $field = null
): bool {
if (strpos($field, '.') !== false) {
return ArrayHelper::dotKeyExists($field, $data);
}

return array_key_exists($field, $data);
}
}
21 changes: 14 additions & 7 deletions system/Validation/Validation.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ public function run(?array $data = null, ?string $group = null, ?string $dbGroup

if ($values === []) {
// We'll process the values right away if an empty array
$this->processRules($field, $setup['label'] ?? $field, $values, $rules, $data);
$this->processRules($field, $setup['label'] ?? $field, $values, $rules, $data, $field);

continue;
}
Expand All @@ -196,7 +196,7 @@ public function run(?array $data = null, ?string $group = null, ?string $dbGroup
}
} else {
// Process single field
$this->processRules($field, $setup['label'] ?? $field, $values, $rules, $data);
$this->processRules($field, $setup['label'] ?? $field, $values, $rules, $data, $field);
}
}

Expand Down Expand Up @@ -323,10 +323,15 @@ protected function processRules(
continue;
}

$found = true;
$passed = $param === false
? $set->{$rule}($value, $error)
: $set->{$rule}($value, $param, $data, $error, $field);
$found = true;

if ($rule === 'field_exists') {
$passed = $set->{$rule}($value, $param, $data, $error, $originalField);
} else {
$passed = ($param === false)
? $set->{$rule}($value, $error)
: $set->{$rule}($value, $param, $data, $error, $field);
}

break;
}
Expand All @@ -351,8 +356,10 @@ protected function processRules(

$param = ($param === false) ? '' : $param;

$fieldForErrors = ($rule === 'field_exists') ? $originalField : $field;

// @phpstan-ignore-next-line $error may be set by rule methods.
$this->errors[$field] = $error ?? $this->getErrorMessage(
$this->errors[$fieldForErrors] = $error ?? $this->getErrorMessage(
($this->isClosure($rule) || $arrayCallable) ? (string) $i : $rule,
$field,
$label,
Expand Down
113 changes: 110 additions & 3 deletions tests/system/Validation/RulesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class RulesTest extends CIUnitTestCase
protected function setUp(): void
{
parent::setUp();

$this->validation = new Validation((object) $this->config, Services::renderer());
$this->validation->reset();
}
Expand All @@ -63,6 +64,7 @@ public function testRequired(array $data, bool $expected): void
public static function provideRequired(): iterable
{
yield from [
[[], false],
[['foo' => null], false],
[['foo' => 123], true],
[['foo' => null, 'bar' => 123], false],
Expand Down Expand Up @@ -138,6 +140,11 @@ public static function providePermitEmpty(): iterable
{
yield from [
// If the rule is only `permit_empty`, any value will pass.
[
['foo' => 'permit_empty|valid_email'],
[],
true,
],
[
['foo' => 'permit_empty|valid_email'],
['foo' => ''],
Expand Down Expand Up @@ -203,8 +210,8 @@ public static function providePermitEmpty(): iterable
['foo' => 'invalid'],
false,
],
// Required has more priority
[
// Required has more priority
['foo' => 'permit_empty|required|valid_email'],
['foo' => ''],
false,
Expand All @@ -224,8 +231,8 @@ public static function providePermitEmpty(): iterable
['foo' => false],
false,
],
// This tests will return true because the input data is trimmed
[
// This tests will return true because the input data is trimmed
['foo' => 'permit_empty|required'],
['foo' => '0'],
true,
Expand Down Expand Up @@ -280,8 +287,8 @@ public static function providePermitEmpty(): iterable
['foo' => '', 'bar' => 1],
true,
],
// Testing with closure
[
// Testing with closure
['foo' => ['permit_empty', static fn ($value) => true]],
['foo' => ''],
true,
Expand Down Expand Up @@ -845,4 +852,104 @@ public static function provideRequiredWithoutMultipleWithoutFields(): iterable
],
];
}

/**
* @dataProvider provideFieldExists
*/
public function testFieldExists(array $rules, array $data, bool $expected): void
{
$this->validation->setRules($rules);
$this->assertSame($expected, $this->validation->run($data));
}

public static function provideFieldExists(): iterable
{
// Do not use `foo`, because there is a lang file `Foo`, and
// the error message may be messed up.
yield from [
'empty string' => [
['fiz' => 'field_exists'],
['fiz' => ''],
true,
],
'null' => [
['fiz' => 'field_exists'],
['fiz' => null],
true,
],
'false' => [
['fiz' => 'field_exists'],
['fiz' => false],
true,
],
'empty array' => [
['fiz' => 'field_exists'],
['fiz' => []],
true,
],
'empty data' => [
['fiz' => 'field_exists'],
[],
false,
],
'dot array syntax: true' => [
['fiz.bar' => 'field_exists'],
[
'fiz' => ['bar' => null],
],
true,
],
'dot array syntax: false' => [
['fiz.bar' => 'field_exists'],
[],
false,
],
'dot array syntax asterisk: true' => [
['fiz.*.baz' => 'field_exists'],
[
'fiz' => [
'bar' => [
'baz' => null,
],
],
],
true,
],
'dot array syntax asterisk: false' => [
['fiz.*.baz' => 'field_exists'],
[
'fiz' => [
'bar' => [
'baz' => null,
],
'hoge' => [
// 'baz' is missing.
],
],
],
false,
],
];
}

public function testFieldExistsErrorMessage(): void
{
$this->validation->setRules(['fiz.*.baz' => 'field_exists']);
$data = [
'fiz' => [
'bar' => [
'baz' => null,
],
'hoge' => [
// 'baz' is missing.
],
],
];

$this->assertFalse($this->validation->run($data));
$this->assertSame(
['fiz.*.baz' => 'The fiz.*.baz field must exist.'],
$this->validation->getErrors()
);
}
}
5 changes: 5 additions & 0 deletions tests/system/Validation/StrictRules/RulesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ public function testPermitEmptyStrict(array $rules, array $data, bool $expected)
public static function providePermitEmptyStrict(): iterable
{
yield from [
[
['foo' => 'permit_empty'],
[],
true,
],
[
['foo' => 'permit_empty'],
['foo' => ''],
Expand Down
5 changes: 5 additions & 0 deletions user_guide_src/source/changelogs/v4.5.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,9 @@ Model
Libraries
=========

- **Validation:** Added the new rule ``field_exists`` that checks the filed
exists in the data to be validated.

Helpers and Functions
=====================

Expand All @@ -246,6 +249,8 @@ Others
Message Changes
***************

- Added ``Validation.field_exists`` error message.

Changes
*******

Expand Down
17 changes: 17 additions & 0 deletions user_guide_src/source/libraries/validation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,21 @@ for including multiple Rulesets, and collections of rules that can be easily reu
.. note:: You may never need to use this method, as both the :doc:`Controller </incoming/controllers>` and
the :doc:`Model </models/model>` provide methods to make validation even easier.

********************
How Validation Works
********************

- The validation never changes data to be validated.
- The validation checks each field in turn according to the Validation Rules you
set. If any rule returns false, the check for that field ends there.
- The Format Rules do not permit empty string. If you want to permit empty string,
add the ``permit_empty`` rule.
- If a field does not exist in the data to be validated, the value is interpreted
as ``null``. If you want to check that the field exists, add the ``field_exists``
rule.

.. note:: The ``field_exists`` rule can be used since v4.5.0.

************************
Setting Validation Rules
************************
Expand Down Expand Up @@ -898,6 +913,8 @@ differs Yes Fails if field does not differ from the one
in the parameter.
exact_length Yes Fails if field is not exactly the parameter ``exact_length[5]`` or ``exact_length[5,8,12]``
value. One or more comma-separated values.
field_exists Yes Fails if field does not exist. (This rule was
added in v4.5.0.)
greater_than Yes Fails if field is less than or equal to ``greater_than[8]``
the parameter value or not numeric.
greater_than_equal_to Yes Fails if field is less than the parameter ``greater_than_equal_to[5]``
Expand Down

0 comments on commit 8b72997

Please sign in to comment.