Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [Validation] Callable Rules #7933

Merged
merged 3 commits into from
Oct 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions system/Validation/Validation.php
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,9 @@ protected function processRules(
}

foreach ($rules as $i => $rule) {
$isCallable = is_callable($rule);
$isCallable = is_callable($rule);
$stringCallable = $isCallable && is_string($rule);
$arrayCallable = $isCallable && is_array($rule);

$passed = false;
$param = false;
Expand All @@ -295,7 +297,7 @@ protected function processRules(
if ($this->isClosure($rule)) {
$passed = $rule($value, $data, $error, $field);
} elseif ($isCallable) {
$passed = $param === false ? $rule($value) : $rule($value, $param, $data);
$passed = $stringCallable ? $rule($value) : $rule($value, $data, $error, $field);
} else {
$found = false;

Expand Down Expand Up @@ -335,7 +337,7 @@ protected function processRules(

// @phpstan-ignore-next-line $error may be set by rule methods.
$this->errors[$field] = $error ?? $this->getErrorMessage(
$this->isClosure($rule) ? $i : $rule,
($this->isClosure($rule) || $arrayCallable) ? $i : $rule,
$field,
$label,
$param,
Expand Down
95 changes: 95 additions & 0 deletions tests/system/Validation/ValidationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,101 @@ public function testClosureRuleWithLabel(): void
);
}

/**
* Validation rule1
*
* @param mixed $value
*/
public function rule1($value)
{
return $value === 'abc';
}

public function testCallableRule(): void
{
$this->validation->setRules(
[
'foo' => ['required', [$this, 'rule1']],
],
[
// Errors
'foo' => [
// Specify the array key for the callable rule.
1 => 'The value is not "abc"',
],
],
);

$data = ['foo' => 'xyz'];
$result = $this->validation->run($data);

$this->assertFalse($result);
$this->assertSame(
['foo' => 'The value is not "abc"'],
$this->validation->getErrors()
);
$this->assertSame([], $this->validation->getValidated());
}

/**
* Validation rule1
*
* @param mixed $value
*/
public function rule2($value, array $data, ?string &$error, string $field)
{
if ($value !== 'abc') {
$error = 'The ' . $field . ' value is not "abc"';

return false;
}

return true;
}

public function testCallableRuleWithParamError(): void
{
$this->validation->setRules([
'foo' => [
'required',
[$this, 'rule2'],
],
]);

$data = ['foo' => 'xyz'];
$result = $this->validation->run($data);

$this->assertFalse($result);
$this->assertSame(
['foo' => 'The foo value is not "abc"'],
$this->validation->getErrors()
MGatner marked this conversation as resolved.
Show resolved Hide resolved
);
$this->assertSame([], $this->validation->getValidated());
}

public function testCallableRuleWithLabel(): void
{
$this->validation->setRules([
'secret' => [
'label' => 'シークレット',
'rules' => ['required', [$this, 'rule1']],
'errors' => [
// Specify the array key for the callable rule.
1 => 'The {field} is invalid',
],
],
]);

$data = ['secret' => 'xyz'];
$result = $this->validation->run($data);

$this->assertFalse($result);
$this->assertSame(
['secret' => 'The シークレット is invalid'],
MGatner marked this conversation as resolved.
Show resolved Hide resolved
$this->validation->getErrors()
);
}

/**
* @see https://github.com/codeigniter4/CodeIgniter4/issues/5368
*
Expand Down
13 changes: 8 additions & 5 deletions user_guide_src/source/installation/upgrade_validations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ Documentations of Library
What has been changed
=====================
- If you want to change validation error display, you have to set CI4 :ref:`validation View templates <validation-customizing-error-display>`.
- CI4 validation has no Callbacks nor Callable in CI3.
Use :ref:`Rule Classes <validation-using-rule-classes>` or
:ref:`Closure Rule <validation-using-closure-rule>`
instead.
- In CI3, Callbacks/Callable rules were prioritized, but in CI4, Closure Rules are
- CI4 validation has no `Callbacks <http://www.codeigniter.com/userguide3/libraries/form_validation.html#callbacks-your-own-validation-methods>`_
in CI3.
Use :ref:`Callable Rules <validation-using-callable-rule>` (since v4.5.0) or
:ref:`Closure Rules <validation-using-closure-rule>` (since v4.3.0) or
:ref:`Rule Classes <validation-using-rule-classes>` instead.
- In CI3, Callbacks/Callable rules were prioritized, but in CI4, Closure/Callable Rules are
not prioritized, and are checked in the order in which they are listed.
- Since v4.5.0, :ref:`Callable Rules <validation-using-callable-rule>` has been
introduced, but it is a bit different from CI3's `Callable <http://www.codeigniter.com/userguide3/libraries/form_validation.html#callable-use-anything-as-a-rule>`_.
- CI4 validation format rules do not permit empty string.
- CI4 validation never changes your data.
- Since v4.3.0, :php:func:`validation_errors()` has been introduced, but the API is different from CI3's.
Expand Down
23 changes: 23 additions & 0 deletions user_guide_src/source/libraries/validation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,29 @@ Or you can use the following parameters:
.. literalinclude:: validation/041.php
:lines: 2-

.. _validation-using-callable-rule:

Using Callable Rule
===================

.. versionadded:: 4.5.0

If you like to use an array callback as a rule, you may use it instead of a Closure Rule.

You need to use an array for validation rules:

.. literalinclude:: validation/046.php
:lines: 2-

You must set the error message for the callable rule.
When you specify the error message, set the array key for the callable rule.
In the above code, the ``required`` rule has the key ``0``, and the callable has ``1``.

Or you can use the following parameters:

.. literalinclude:: validation/047.php
:lines: 2-

***************
Available Rules
***************
Expand Down
43 changes: 43 additions & 0 deletions user_guide_src/source/libraries/validation/046.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace App\Controllers;

use Config\Services;

class Form extends BaseController
{
// Define a custom validation rule.
public function _ruleEven($value): bool
{
return (int) $value % 2 === 0;
}

public function process()
{
// ...

$validation = Services::validation();
$validation->setRules(
[
'foo' => [
'required',
// Specify the method in this controller as a rule.
[$this, '_ruleEven'],
],
],
[
// Errors
'foo' => [
// Specify the array key for the callable rule.
1 => 'The value is not even.',
],
],
);

if (! $validation->run($data)) {
// handle validation errors
}

// ...
}
}
22 changes: 22 additions & 0 deletions user_guide_src/source/libraries/validation/047.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace App\Controllers;

use Config\Services;

class Form extends BaseController
{
// Define a custom validation rule.
public function _ruleEven($value, $data, &$error, $field): bool
{
if ((int) $value % 2 === 0) {
return true;
}

$error = 'The value is not even.';

return false;
}

// ...
}
Loading