Repository: codeigniter4/CodeIgniter4 (PHP)

Issue:
Bug: Validation: Closure rule in combination with rule `permit_empty` fails
### PHP Version

8.2

### CodeIgniter4 Version

4.3.4

### CodeIgniter4 Installation Method

Composer (as dependency to an existing project)

### Which operating systems have you tested for this bug?

Linux

### Which server did you use?

apache

### Database

_No response_

### What happened?

When using [`permit_empty`](https://www.codeigniter.com/user_guide/libraries/validation.html#rules-for-general-use) in combination with a [closure rule](https://www.codeigniter.com/user_guide/libraries/validation.html#using-closure-rule) the fatal error `Object of class Closure could not be converted to string` at [SYSTEMPATH/Validation/Validation.php at line 279](https://github.com/codeigniter4/CodeIgniter4/blob/42b174fc75d01098936514606f6147dffbaccb8b/system/Validation/Validation.php#L278) occurs.

### Steps to Reproduce

Use `'rules' => ['permit_empty', fn($value) => true]` as validation config and supply an empty value for the field.

### Expected Output

1. check if the key of the rule is one of `required_with` and `required_without` for closures too, as done for all other rules
2. closures should work in combination with `permit_empty`

### Anything else?

It is likely that `array_diff` has difficulties with non-string values; which would be an issue with [`if_exist`](https://github.com/codeigniter4/CodeIgniter4/blob/42b174fc75d01098936514606f6147dffbaccb8b/system/Validation/Validation.php#L242) too.


Discussion:
Hi @michalsn, I also worked on a bug fix in the meantime.
Mix bug fix is only for PHP 8.2, so there might be some changes needed.
All in all, I primarily introduced `processSingleRule` to get rid of all the duplicate code.

https://github.com/codeigniter4/CodeIgniter4/compare/develop...element-code:CodeIgniter4:fix-validation-closures
@element-code Sounds great!

I didn't want to mix things up, so I only focused on fixing the reported bug. But feel free to send a PR that will refactor the code to make it better / cleaner.
@michalsn sounds good. I don't have the time to write extensive tests tho, is this something you would participate in?
@element-code I'm not holding my breath even to write tests for my code 😅 but you can give your PR a try. We'll see what the coverage looks like and what other people think about the proposed changes.



Patch:
diff --git a/system/Validation/Validation.php b/system/Validation/Validation.php
index c22f38ae2413..07d619b59cd7 100644
--- a/system/Validation/Validation.php
+++ b/system/Validation/Validation.php
@@ -239,7 +239,7 @@ protected function processRules(
             }
 
             // Otherwise remove the if_exist rule and continue the process
-            $rules = array_diff($rules, ['if_exist']);
+            $rules = array_filter($rules, static fn ($rule) => $rule instanceof Closure || $rule !== 'if_exist');
         }
 
         if (in_array('permit_empty', $rules, true)) {
@@ -250,7 +250,7 @@ protected function processRules(
                 $passed = true;
 
                 foreach ($rules as $rule) {
-                    if (preg_match('/(.*?)\[(.*)\]/', $rule, $match)) {
+                    if (! $this->isClosure($rule) && preg_match('/(.*?)\[(.*)\]/', $rule, $match)) {
                         $rule  = $match[1];
                         $param = $match[2];
 
@@ -275,7 +275,7 @@ protected function processRules(
                 }
             }
 
-            $rules = array_diff($rules, ['permit_empty']);
+            $rules = array_filter($rules, static fn ($rule) => $rule instanceof Closure || $rule !== 'permit_empty');
         }
 
         foreach ($rules as $i => $rule) {
diff --git a/user_guide_src/source/changelogs/v4.3.5.rst b/user_guide_src/source/changelogs/v4.3.5.rst
index 5473f6b2c808..7ff5f2e4b5b9 100644
--- a/user_guide_src/source/changelogs/v4.3.5.rst
+++ b/user_guide_src/source/changelogs/v4.3.5.rst
@@ -24,6 +24,8 @@ Deprecations
 Bugs Fixed
 **********
 
+- **Validation:** Fixed a bug where a closure used in combination with ``permit_empty`` or ``if_exist`` rules was causing an error.
+
 See the repo's
 `CHANGELOG.md <https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHANGELOG.md>`_
 for a complete list of bugs fixed.


Test patch:
diff --git a/tests/system/Validation/RulesTest.php b/tests/system/Validation/RulesTest.php
index 3bf60aca8131..0ed3ac97e49c 100644
--- a/tests/system/Validation/RulesTest.php
+++ b/tests/system/Validation/RulesTest.php
@@ -117,6 +117,12 @@ public function ifExistProvider(): Generator
                 ['foo' => []],
                 true,
             ],
+            // Testing with closure
+            [
+                ['foo' => ['if_exist', static fn ($value) => true]],
+                ['foo' => []],
+                true,
+            ],
         ];
     }
 
@@ -275,6 +281,12 @@ public function providePermitEmptyCases(): Generator
                 ['foo' => '', 'bar' => 1],
                 true,
             ],
+            // Testing with closure
+            [
+                ['foo' => ['permit_empty', static fn ($value) => true]],
+                ['foo' => ''],
+                true,
+            ],
         ];
     }
 
diff --git a/tests/system/Validation/StrictRules/RulesTest.php b/tests/system/Validation/StrictRules/RulesTest.php
index 4c0e84274226..4389a48b0ff1 100644
--- a/tests/system/Validation/StrictRules/RulesTest.php
+++ b/tests/system/Validation/StrictRules/RulesTest.php
@@ -84,6 +84,12 @@ public function providePermitEmptyCasesStrict(): Generator
                 ['foo' => false],
                 true,
             ],
+            // Testing with closure
+            [
+                ['foo' => ['permit_empty', static fn ($value) => true]],
+                ['foo' => ''],
+                true,
+            ],
         ];
     }