yoyoma
yoyoma

Reputation: 3536

Conditional validator in cakephp modelless form

i need to do the following conditional validation in my modelless form:

class NewForm extends Form
{
    if (address_type == 'Street')
        required: street_name, street_type
        not required: po_box
    elseif (address_type == 'PO')
        required: po_box
        not required: street_name, street_type
}

I have tried the following validator:

$validator
        ->requirePresence('street_name', true, 'street_name is a required field')
        ->notEmpty('street_name', 'street_name is a required field', function ($context) {
            return ($context['address_type'] == 'Street Address');
        })
        ->requirePresence('street_type', true, 'street_type is a required field')
        ->notEmpty('street_type', 'street_type is a required field', function ($context) {
            return ($context['address_type'] == 'Street Address');
        });

    $validator
        ->requirePresence('po_box', true, 'po_box is a required field')
        ->notEmpty('po_box', 'po_box is a required field', function ($context) {
            return ($context['address_type'] == 'PO');
        });

When i call validate like such

$valid = $newform->validate($data);

It requires ALL street_name, street_type, and po_box to be in the input data.

How do i structure the conditional validator to accomplish the pseudocode ? Thanks

Edit

I've come up with the following, which works ! However, it does not allow me get the fields that are empty/invalid from the ->errors() Ie, i need to be able to know that, for eg, 'street_name' was empty and needs to be filled in.

$validator
        ->add('address_type', 'missing fields', [
            'rule' => function ($value, $context) {
                if ($value == 'Street') {
                    if ((!array_key_exists('street_name', $context['data']) && empty($context['data']['street_name'])) ||
                        (!array_key_exists('street_type', $context['data']) && empty($context['data']['street_type']))) {
                        return false;
                    }
                } elseif ($value == 'PO') {
                    if ((!array_key_exists('po_box', $context['data']) && empty($context['data']['po_box'])) ||
                        (!array_key_exists('po_no', $context['data']) && empty($context['data']['po_no'])) ||
                        (!array_key_exists('po_exchange', $context['data']) && empty($context['data']['po_exchange']))) {
                        return false;
                    }
                }
            }
        ]);

Upvotes: 0

Views: 414

Answers (1)

ndm
ndm

Reputation: 60453

It would come handy if Validator::requirePresence() would support callables for conditional evaluation, just like add(), allowEmpty() and noteEmpty() do.

Until something like this is added, there are various other ways to handle this, like for example modifying validators in a custom rule for your address_type field, in an overriden Form::validate() method, or even in the controller.

I'd probably go with the first variant, something like this, which sets the fields to be required depending on the required address_type value:

protected function _buildValidator(Validator $validator)
{
    return $validator
        ->requirePresence('address_type')
        ->add('address_type', 'valid', [
            'rule' => function($value) {
                switch ($value) {
                    case 'Street Address':
                        $this->validator()
                            ->requirePresence('street_name')
                            ->requirePresence('street_type');
                        break;

                    case 'PO':
                        $this->validator()
                            ->requirePresence('po_box');
                        break;

                    default:
                        return false;
                }
                return true;
            }
        ])
        ->notEmpty('street_name')
        ->notEmpty('street_type')
        ->notEmpty('po_box')
        // ...
        ;
}

Update

Support for callables in requirePresence() has been added for 3.1.1. With that functionality it's possible to conditionally require fields like

$validator
    // ...
    ->requirePresence('street_name', function ($context) {
        return $context['data']['address_type'] === 'Street Address';
    })
    // ...
    ;

To DRY up things in case of many fields one could for example make of use dynamic callable arguments like

$requiredFor = function ($value) {
    return function ($context) use ($value) {
        return $context['data']['address_type'] === $value;
    };
};

$validator
    // ...
    ->requirePresence('street_name', $requiredFor('Street Address'))
    ->requirePresence('street_type', $requiredFor('Street Address'))
    ->requirePresence('po_box', $requiredFor('PO'))
    // ...
    ;

See also Cookbook > Validation > Conditional Validation

Upvotes: 2

Related Questions