Question

I have a base model that I extend from. In it, I have defined two validation filters. One checks if a record is unique, the other checks if a record exists. They work the exact same way except ones return value will be the opposite of the other.

So, it doesn't sound right to write the same code twice to only return a different value. I'd like to know how I can call one custom validator from another.

Here's my code for the unique validator:

<?php
Validator::add('unique', function($value, $rule, $options) {
    $model = $options['model'];
    $primary = $model::meta('key');

    foreach ($options['conditions'] as $field => $check) {
        if (!is_numeric($field)) {
            if (is_array($check)) {
                /**
                 * array(
                 *   'exists',
                 *   'message'    => 'You are too old.',
                 *   'conditions' => array(
                 *       
                 *       'Users.age' => array('>' => '18')
                 *   )
                 * )
                 */
                $conditions[$field] = $check;
            }
        } else {
            /**
             * Regular lithium conditions array:
             * array(
             *   'exists',
             *   'message'    => 'This email already exists.',
             *   'conditions' => array(
             *       'Users.email' //no key ($field) defined
             *   )
             * )
             */
            $conditions[$check] = $value;
        }
    }

    /**
     * Checking to see if the entity exists.
     * If it exists, record exists.
     * If record exists, we make sure the record is not checked
     * against itself by matching with the primary key.
     */
    if (isset($options['values'][$primary])) {
        //primary key value exists so it's probably an update
        $conditions[$primary] = array('!=' => $options['values'][$primary]);
    }

    $exists = $model::count($conditions);
    return ($exists) ? false : true;
});
?>

exists should work like this:

<?php
Validator::add('exists', function($value, $rule, $options) {
    $model = $options['model'];
    return !$model::unique($value, $rule, $options);
});
?>

But obviously, it can't be done that way. Would I have to define the validation function as an anonymous function, assign it to a variable and pass that in instead of the closure? Or is there a way I can call unique from within exists?

Was it helpful?

Solution

The anonymous function method would work. And then you could use that variable in another anonymous function you define for the 'exists' validator. Here's another idea that incorporates it into your base model class:

<?php

namespace app\data\Model;

use lithium\util\Validator;

class Model extends \lithium\data\Model {

    public static function __init() {
        static::_isBase(__CLASS__, true);
        Validator::add('unique', function($value, $rule, $options) {
            $model = $options['model'];
            return $model::unique(compact('value') + $options);
        });
        Validator::add('exists', function($value, $rule, $options) {
            $model = $options['model'];
            return !$model::unique(compact('value') + $options);
        });
        parent::__init();
    }

    // ... code ...

    public static function unique($options) {
        $primary = static::meta('key');

        foreach ($options['conditions'] as $field => $check) {
            if (!is_numeric($field)) {
                if (is_array($check)) {
                    /**
                     * array(
                     *   'exists',
                     *   'message'  => 'You are too old.',
                     *   'conditions' => array(
                     *
                     *     'Users.age' => array('>' => '18')
                     *   )
                     * )
                     */
                    $conditions[$field] = $check;
                }
            } else {
                /**
                 * Regular lithium conditions array:
                 * array(
                 *   'exists',
                 *   'message'  => 'This email already exists.',
                 *   'conditions' => array(
                 *     'Users.email' //no key ($field) defined
                 *   )
                 * )
                 */
                $conditions[$check] = $options['value'];
            }
        }

        /**
         * Checking to see if the entity exists.
         * If it exists, record exists.
         * If record exists, we make sure the record is not checked
         * against itself by matching with the primary key.
         */
        if (isset($options['values'][$primary])) {
            //primary key value exists so it's probably an update
            $conditions[$primary] = array('!=' => $options['values'][$primary]);
        }

        $exists = $model::count($conditions);
        return ($exists) ? false : true;
    }

}

?>

OTHER TIPS

I ended up creating a separate method that contains the functionality I need and then calling it from my validation filter. I've trimmed down my base model to hold only the relevant data in it. Hope it helps someone who has a similar problem.

<?php
namespace app\extensions\data;
class Model extends \lithium\data\Model {

    public static function __init() {
        parent::__init();

        Validator::add('unique', function($value, $rule, $options) {
            $model  = $options['model'];
            return ($model::exists($value, $rule, $options, $model)) ? false : true;
        });

        Validator::add('exists', function($value, $rule, $options) {
            $model  = $options['model'];
            return ($model::exists($value, $rule, $options, $model)) ? true : false;
        });
    }


    public static function exists($value, $rule, $options, $model) {
        $field   = $options['field'];
        $primary = $model::meta('key');

        if (isset($options['conditions']) && !empty($options['conditions'])) {
            //go here only of `conditions` are given
            foreach ($options['conditions'] as $field => $check) {
                if (!is_numeric($field)) {
                    if (is_array($check)) {
                        /**
                         *   'conditions' => array(
                         *       'Users.age' => array('>' => 18) //condition with custom operator
                         *   )
                         */
                        $conditions[$field] = $check;
                    }
                } else {
                    /**
                     * Regular lithium conditions array:
                     *   'conditions' => array(
                     *       'Users.email' //no key ($field) defined
                     *   )
                     */
                    $conditions[$check] = $value;
                }
            }
        } else {
            //since `conditions` is not set, we assume
            $modelName = $model::meta('name');
            $conditions["$modelName.$field"] = $value;
        }

        /**
         * Checking to see if the entity exists.
         * If it exists, record exists.
         * If record exists, we make sure the record is not checked
         * against itself by matching with the primary key.
         */
        if (isset($options['values'][$primary])) {
            //primary key value exists so it's probably an update
            $conditions[$primary] = array('!=' => $options['values'][$primary]);
        }

        return $model::count($conditions);
    }
}
?>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top