Skip to content

Commit 7d7d967

Browse files
authored
Merge pull request #6384 from kenjis/feat-validation-errors
feat: add Form helpers for Validation Errors
2 parents 7108333 + a12b231 commit 7d7d967

File tree

13 files changed

+215
-41
lines changed

13 files changed

+215
-41
lines changed

phpstan-baseline.neon.dist

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -655,11 +655,6 @@ parameters:
655655
count: 1
656656
path: system/Throttle/Throttler.php
657657

658-
-
659-
message: "#^Property CodeIgniter\\\\Validation\\\\Validation\\:\\:\\$errors \\(array\\) on left side of \\?\\? is not nullable\\.$#"
660-
count: 1
661-
path: system/Validation/Validation.php
662-
663658
-
664659
message: "#^Variable \\$error on left side of \\?\\? always exists and is always null\\.$#"
665660
count: 1

system/HTTP/RedirectResponse.php

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ public function back(?int $code = null, string $method = 'auto')
7979
}
8080

8181
/**
82-
* Specifies that the current $_GET and $_POST arrays should be
83-
* packaged up with the response.
82+
* Sets the current $_GET and $_POST arrays in the session.
83+
* This also saves the validation errors.
8484
*
8585
* It will then be available via the 'old()' helper function.
8686
*
@@ -94,22 +94,18 @@ public function withInput()
9494
'post' => $_POST ?? [],
9595
]);
9696

97-
// @TODO Remove this in the future.
98-
// See https://github.com/codeigniter4/CodeIgniter4/issues/5839#issuecomment-1086624600
9997
$this->withErrors();
10098

10199
return $this;
102100
}
103101

104102
/**
105-
* Set validation errors in the session.
103+
* Sets validation errors in the session.
106104
*
107105
* If the validation has any errors, transmit those back
108106
* so they can be displayed when the validation is handled
109107
* within a method different than displaying the form.
110108
*
111-
* @TODO Make this method public when removing $this->withErrors() in withInput().
112-
*
113109
* @return $this
114110
*/
115111
private function withErrors(): self
@@ -118,7 +114,7 @@ private function withErrors(): self
118114

119115
if ($validation->getErrors()) {
120116
$session = Services::session();
121-
$session->setFlashdata('_ci_validation_errors', serialize($validation->getErrors()));
117+
$session->setFlashdata('_ci_validation_errors', $validation->getErrors());
122118
}
123119

124120
return $this;

system/Helpers/form_helper.php

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* the LICENSE file that was distributed with this source code.
1010
*/
1111

12+
use CodeIgniter\Validation\Exceptions\ValidationException;
1213
use Config\App;
1314
use Config\Services;
1415

@@ -680,12 +681,91 @@ function set_radio(string $field, string $value = '', bool $default = false): st
680681
}
681682
}
682683

684+
if (! function_exists('validation_errors')) {
685+
/**
686+
* Returns the validation errors.
687+
*
688+
* First, checks the validation errors that are stored in the session.
689+
* To store the errors in the session, you need to use `withInput()` with `redirect()`.
690+
*
691+
* The returned array should be in the following format:
692+
* [
693+
* 'field1' => 'error message',
694+
* 'field2' => 'error message',
695+
* ]
696+
*
697+
* @return array<string, string>
698+
*/
699+
function validation_errors()
700+
{
701+
session();
702+
703+
// Check the session to see if any were
704+
// passed along from a redirect withErrors() request.
705+
if (isset($_SESSION['_ci_validation_errors']) && (ENVIRONMENT === 'testing' || ! is_cli())) {
706+
return $_SESSION['_ci_validation_errors'];
707+
}
708+
709+
$validation = Services::validation();
710+
711+
return $validation->getErrors();
712+
}
713+
}
714+
715+
if (! function_exists('validation_list_errors')) {
716+
/**
717+
* Returns the rendered HTML of the validation errors.
718+
*
719+
* See Validation::listErrors()
720+
*/
721+
function validation_list_errors(string $template = 'list'): string
722+
{
723+
$config = config('Validation');
724+
$view = Services::renderer();
725+
726+
if (! array_key_exists($template, $config->templates)) {
727+
throw ValidationException::forInvalidTemplate($template);
728+
}
729+
730+
return $view->setVar('errors', validation_errors())
731+
->render($config->templates[$template]);
732+
}
733+
}
734+
735+
if (! function_exists('validation_show_error')) {
736+
/**
737+
* Returns a single error for the specified field in formatted HTML.
738+
*
739+
* See Validation::showError()
740+
*/
741+
function validation_show_error(string $field, string $template = 'single'): string
742+
{
743+
$config = config('Validation');
744+
$view = Services::renderer();
745+
746+
$errors = validation_errors();
747+
748+
if (! array_key_exists($field, $errors)) {
749+
return '';
750+
}
751+
752+
if (! array_key_exists($template, $config->templates)) {
753+
throw ValidationException::forInvalidTemplate($template);
754+
}
755+
756+
return $view->setVar('error', $errors[$field])
757+
->render($config->templates[$template]);
758+
}
759+
}
760+
683761
if (! function_exists('parse_form_attributes')) {
684762
/**
685763
* Parse the form attributes
686764
*
687765
* Helper function used by some of the form helpers
688766
*
767+
* @internal
768+
*
689769
* @param array|string $attributes List of attributes
690770
* @param array $default Default values
691771
*/

system/Validation/Validation.php

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,6 @@ public function __construct($config, RendererInterface $view)
108108
*/
109109
public function run(?array $data = null, ?string $group = null, ?string $dbGroup = null): bool
110110
{
111-
// If there are still validation errors for redirect_with_input request, remove them.
112-
// See `getErrors()` method.
113-
if (isset($_SESSION, $_SESSION['_ci_validation_errors'])) {
114-
unset($_SESSION['_ci_validation_errors']);
115-
}
116-
117111
$data ??= $this->data;
118112

119113
// i.e. is_unique
@@ -506,6 +500,8 @@ public function setRuleGroup(string $group)
506500

507501
/**
508502
* Returns the rendered HTML of the errors as defined in $template.
503+
*
504+
* You can also use validation_list_errors() in Form helper.
509505
*/
510506
public function listErrors(string $template = 'list'): string
511507
{
@@ -520,6 +516,8 @@ public function listErrors(string $template = 'list'): string
520516

521517
/**
522518
* Displays a single error in formatted HTML as defined in the $template view.
519+
*
520+
* You can also use validation_show_error() in Form helper.
523521
*/
524522
public function showError(string $field, string $template = 'single'): string
525523
{
@@ -681,14 +679,7 @@ public function getError(?string $field = null): string
681679
*/
682680
public function getErrors(): array
683681
{
684-
// If we already have errors, we'll use those.
685-
// If we don't, check the session to see if any were
686-
// passed along from a redirect_with_input request.
687-
if (empty($this->errors) && ! is_cli() && isset($_SESSION, $_SESSION['_ci_validation_errors'])) {
688-
$this->errors = unserialize($_SESSION['_ci_validation_errors']);
689-
}
690-
691-
return $this->errors ?? [];
682+
return $this->errors;
692683
}
693684

694685
/**

tests/system/Helpers/FormHelperTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,43 @@ public function testSetRadioDefault()
926926
$this->assertSame('', set_radio('code', 'beta', false));
927927
}
928928

929+
public function testValidationErrorsFromSession()
930+
{
931+
$_SESSION = ['_ci_validation_errors' => ['foo' => 'bar']];
932+
933+
$this->assertSame(['foo' => 'bar'], validation_errors());
934+
935+
$_SESSION = [];
936+
}
937+
938+
public function testValidationErrorsFromValidation()
939+
{
940+
$validation = Services::validation();
941+
$validation->setRule('id', 'ID', 'required')->run([]);
942+
943+
$this->assertSame(['id' => 'The ID field is required.'], validation_errors());
944+
}
945+
946+
public function testValidationListErrors()
947+
{
948+
$validation = Services::validation();
949+
$validation->setRule('id', 'ID', 'required')->run([]);
950+
951+
$html = validation_list_errors();
952+
953+
$this->assertStringContainsString('<li>The ID field is required.</li>', $html);
954+
}
955+
956+
public function testValidationShowError()
957+
{
958+
$validation = Services::validation();
959+
$validation->setRule('id', 'ID', 'required')->run([]);
960+
961+
$html = validation_show_error('id');
962+
963+
$this->assertSame('<span class="help-block">The ID field is required.</span>' . "\n", $html);
964+
}
965+
929966
public function testFormParseFormAttributesTrue()
930967
{
931968
$expected = 'readonly ';

user_guide_src/source/changelogs/v4.3.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Others
6767
- Now ``spark routes`` command shows route names. See :ref:`URI Routing <routing-spark-routes>`.
6868
- Added new :ref:`entities-property-casting` class ``IntBoolCast`` for Entity.
6969
- Help information for a spark command can now be accessed using the ``--help`` option (e.g. ``php spark serve --help``)
70+
- Added new Form helper function :php:func:`validation_errors()`, :php:func:`validation_list_errors()` and :php:func:`validation_show_error()` to display Validation Errors.
7071

7172
Changes
7273
*******

user_guide_src/source/helpers/form_helper.rst

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,3 +510,58 @@ The following functions are available:
510510
<input type="radio" name="myradio" value="1" <?= set_radio('myradio', '1', true) ?> />
511511
<input type="radio" name="myradio" value="2" <?= set_radio('myradio', '2') ?> />
512512

513+
.. php:function:: validation_errors()
514+
515+
:returns: The validation errors
516+
:rtype: array
517+
518+
This function was introduced in v4.3.0.
519+
520+
Returns the validation errors. First, this function checks the validation errors
521+
that are stored in the session. To store the errors in the session, you need to use ``withInput()`` with :php:func:`redirect() <redirect>`.
522+
523+
The returned array is the same as ``Validation::getErrors()``.
524+
See :ref:`Validation <validation-getting-all-errors>` for details.
525+
526+
Example::
527+
528+
<?php $errors = validation_errors(); ?>
529+
530+
.. php:function:: validation_list_errors($template = 'list')
531+
532+
:param string $template: Validation template name
533+
:returns: Rendered HTML of the validation errors
534+
:rtype: string
535+
536+
This function was introduced in v4.3.0.
537+
538+
Returns the rendered HTML of the validation errors.
539+
540+
The parameter ``$template`` is a Validation template name.
541+
See :ref:`validation-customizing-error-display` for details.
542+
543+
This function uses :php:func:`validation_errors()` internally.
544+
545+
Example::
546+
547+
<?= validation_list_errors() ?>
548+
549+
.. php:function:: validation_show_error($field, $template = 'single')
550+
551+
:param string $field: Field name
552+
:param string $template: Validation template name
553+
:returns: Rendered HTML of the validation error
554+
:rtype: string
555+
556+
This function was introduced in v4.3.0.
557+
558+
Returns a single error for the specified field in formatted HTML.
559+
560+
The parameter ``$template`` is a Validation template name.
561+
See :ref:`validation-customizing-error-display` for details.
562+
563+
This function uses :php:func:`validation_errors()` internally.
564+
565+
Example::
566+
567+
<?= validation_show_error('username') ?>

user_guide_src/source/incoming/routing.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ Routes are registered in the routing table in the order in which they are define
455455

456456
.. note:: If a route (the URI path) is defined more than once with different handlers, only the first defined route is registered.
457457

458-
You can check registered routes in the routing table by running the :ref:`spark routes <spark-routes>` command.
458+
You can check registered routes in the routing table by running the :ref:`spark routes <routing-spark-routes>` command.
459459

460460
Changing Route Priority
461461
=======================

user_guide_src/source/installation/upgrade_430.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,28 @@ HTTP Status Code and Exit Code of Uncaught Exceptions
5252
- If you expect *Exit code* based on *Exception code*, the Exit code will be changed.
5353
In that case, you need to implement ``HasExitCodeInterface`` in the Exception. See :ref:`error-specify-exit-code`.
5454

55+
redirect()->withInput() and Validation Errors
56+
=============================================
57+
58+
``redirect()->withInput()`` and Validation errors had an undocumented behavior.
59+
If you redirect with ``withInput()``, CodeIgniter stores the validation errors
60+
in the session, and you can get the errors in the redirected page from
61+
a validation object *before a new validation is run*::
62+
63+
// In the controller
64+
if (! $this->validate($rules)) {
65+
return redirect()->back()->withInput();
66+
}
67+
68+
// In the view of the redirected page
69+
<?= service('Validation')->listErrors() ?>
70+
71+
This behavior was a bug and fixed in v4.3.0.
72+
73+
If you have code that depends on the bug, you need to change the code.
74+
Use new Form helpers, :php:func:`validation_errors()`, :php:func:`validation_list_errors()` and :php:func:`validation_show_error()` to display Validation Errors,
75+
instead of the Validation object.
76+
5577
Others
5678
======
5779

user_guide_src/source/installation/upgrade_validations.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ What has been changed
1717
- CI4 validation has no Callbacks nor Callable in CI3.
1818
- CI4 validation format rules do not permit empty string.
1919
- CI4 validation never changes your data.
20+
- Since v4.3.0, :php:func:`validation_errors()` has been introduced, but the API is different from CI3's.
2021

2122
Upgrade Guide
2223
=============
2324
1. Within the view which contains the form you have to change:
2425

25-
- ``<?php echo validation_errors(); ?>`` to ``<?= $validation->listErrors() ?>``
26+
- ``<?php echo validation_errors(); ?>`` to ``<?= validation_list_errors() ?>``
2627

2728
2. Within the controller you have to change the following:
2829

@@ -85,7 +86,7 @@ Path: **app/Views**::
8586
</head>
8687
<body>
8788

88-
<?= $validation->listErrors() ?>
89+
<?= validation_list_errors() ?>
8990

9091
<?= form_open('form') ?>
9192

user_guide_src/source/installation/upgrade_validations/002.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ public function index()
1313
if (! $this->validate([
1414
// Validation rules
1515
])) {
16-
echo view('myform', [
17-
'validation' => $this->validator,
18-
]);
16+
echo view('myform');
1917
} else {
2018
echo view('formsuccess');
2119
}

0 commit comments

Comments
 (0)