Browse Source

Add callable support to `Validator::requirePresence()`.

While `Validator::add()`, `Validator::allowEmpty()` and
`Validator::notEmpty()` do accept callables,
`Validator::requirePresence()` does not, making it unnecessarily
"complicated" to handle data, where, depending on conditions, some
fields may be absent.
ndm2 10 years ago
parent
commit
3c9abb56a0
2 changed files with 43 additions and 9 deletions
  1. 21 9
      src/Validation/Validator.php
  2. 22 0
      tests/TestCase/Validation/ValidatorTest.php

+ 21 - 9
src/Validation/Validator.php

@@ -106,7 +106,10 @@ class Validator implements ArrayAccess, IteratorAggregate, Countable
         foreach ($this->_fields as $name => $field) {
             $keyPresent = array_key_exists($name, $data);
 
-            if (!$keyPresent && !$this->_checkPresence($field, $newRecord)) {
+            $providers = $this->_providers;
+            $context = compact('data', 'newRecord', 'field', 'providers');
+
+            if (!$keyPresent && !$this->_checkPresence($field, $context)) {
                 $errors[$name]['_required'] = isset($this->_presenceMessages[$name])
                     ? $this->_presenceMessages[$name]
                     : $requiredMessage;
@@ -116,8 +119,6 @@ class Validator implements ArrayAccess, IteratorAggregate, Countable
                 continue;
             }
 
-            $providers = $this->_providers;
-            $context = compact('data', 'newRecord', 'field', 'providers');
             $canBeEmpty = $this->_canBeEmpty($field, $context);
             $isEmpty = $this->_fieldIsEmpty($data[$name]);
 
@@ -423,7 +424,9 @@ class Validator implements ArrayAccess, IteratorAggregate, Countable
      * Sets whether a field is required to be present in data array.
      *
      * @param string $field the name of the field
-     * @param bool|string $mode Valid values are true, false, 'create', 'update'
+     * @param bool|string|callable $mode Valid values are true, false, 'create', 'update'.
+     * If a callable is passed then the field will be required only when the callback
+     * returns true.
      * @param string|null $message The message to show if the field presence validation fails.
      * @return $this
      */
@@ -553,25 +556,34 @@ class Validator implements ArrayAccess, IteratorAggregate, Countable
      * record.
      *
      * @param string $field Field name.
-     * @param bool $newRecord whether the data to be validated is new or to be updated.
+     * @param bool $newRecord Whether the data to be validated is new or to be updated.
      * @return bool
      */
     public function isPresenceRequired($field, $newRecord)
     {
-        return !$this->_checkPresence($this->field($field), $newRecord);
+        $providers = $this->_providers;
+        $data = [];
+        $context = compact('data', 'newRecord', 'field', 'providers');
+        return !$this->_checkPresence($this->field($field), $context);
     }
 
     /**
      * Returns false if any validation for the passed rule set should be stopped
      * due to the field missing in the data array
      *
-     * @param ValidationSet $field the set of rules for a field
-     * @param bool $newRecord whether the data to be validated is new or to be updated.
+     * @param ValidationSet $field The set of rules for a field.
+     * @param array $context A key value list of data containing the validation context.
      * @return bool
      */
-    protected function _checkPresence($field, $newRecord)
+    protected function _checkPresence($field, $context)
     {
         $required = $field->isPresenceRequired();
+
+        if (!is_string($required) && is_callable($required)) {
+            return !$required($context);
+        }
+
+        $newRecord = $context['newRecord'];
         if (in_array($required, ['create', 'update'], true)) {
             return (
                 ($required === 'create' && !$newRecord) ||

+ 22 - 0
tests/TestCase/Validation/ValidatorTest.php

@@ -192,6 +192,28 @@ class ValidatorTest extends TestCase
     }
 
     /**
+     * Tests the requirePresence method when passing a callback
+     *
+     * @return void
+     */
+    public function testRequirePresenceCallback()
+    {
+        $validator = new Validator;
+        $require = true;
+        $validator->requirePresence('title', function ($context) use (&$require) {
+            $this->assertEquals([], $context['data']);
+            $this->assertEquals([], $context['providers']);
+            $this->assertEquals('title', $context['field']);
+            $this->assertTrue($context['newRecord']);
+            return $require;
+        });
+        $this->assertTrue($validator->isPresenceRequired('title', true));
+
+        $require = false;
+        $this->assertFalse($validator->isPresenceRequired('title', true));
+    }
+
+    /**
      * Tests the isPresenceRequired method
      *
      * @return void