Multiple Validation Sets in CakePHP

23rd July, 2008 @ 11:19 pm — on ,

Jonathon Snook wrote about multiple validation sets in CakePHP and it reminded me how often we require rather different sets of validation rules for a single model. I have always enjoyed learning how others approached similar problems so I think I shall share mine too.

A very relevant example is the User model where we usually have multiple forms associated. E.g. sign up, profile updates, password reset, etc. Each form have a different set of fields while some share the same fields. My approach is to select a different set of fields for each “action” and modify the rules if required. This means I’ll keep a master set of rules for all the User model fields and call setValidate() for each “action”.

/**
 * Set the default validation rules here.
 *
 * @var array
 */
	protected $_validate = array(
		'username' => array(
			'empty' => array(
				'required' => true,
				'rule' => VALID_NOT_EMPTY,
				'last' => true
			),
			'invalid' => array(
				'rule' => '/^[\w]{3,30}$/',
				'last' => true,
				'on' => 'create'
			),
			'exists' => array(
				'rule' => 'validateUniqueUsername',
				'last' => true,
				'on' => 'create'
			)
		),
		'password' => array(
			'empty' => array(
				'required' => true,
				'rule' => VALID_NOT_EMPTY,
				'last' => true
			),
			'invalid' => array(
				'required' => true,
				'rule' => '/^.{6,30}$/',
				'last' => true
			)
		),
		...
	);
/**
 * Sets just enough fields for validation.
 *
 * @param array $fields List of field names to validate against. If no param
 *                      passed, all fields will be included.
 * @author Derick
 */
	function setValidate($fields = null) {
		if ($fields === null) {
			$this->validate = $this->_validate;
		} else {
			$this->validate = array();
			foreach ($fields as $f) {
				if (isset($this->_validate[$f])) {
					$this->validate[$f] = $this->_validate[$f];
				}
			}
		}
	}

This is a simplified example in my User model where I have the register() function to validate only the fields required for sign up and the updateProfile() function which uses another set of fields with slight different requirement.

function register($data) {
	$fields = array('username', 'password', ...);
	$this->setValidate($fields);
	$this->save($data, true, $fields);
}

function updateProfile($data) {
	$fields = array('password', ...);
	$this->setValidate($fields);

	// derick: allow user to left password field empty
	unset($this->validate['password']['empty']);
	$this->validate['password']['invalid']['allowEmpty'] = true;
	$this->save($data, true, $fields);

	// derick: you can also call this again to "reset" to the default set of
	// rules but usually we don't need this
	$this->setValidate();
}

In other words, I only update the $validate array in the model when required. Some might think it is dangerous since there is a chance of saving data without validation but this works for me. What do the rest of you do?

Extracting PO file with CakePHP I18n Shell

6th July, 2008 @ 12:15 am — on ,

I have always been religiously wrapping __() and __n() around my texts knowing that it needs to be localized some day. Then it got to me that I never knew how to actually localize it! Out of curiosity, I went ahead to extract the PO file using the I18n shell.

What it does is basically look through your entire app folder for files and within each file extract all instances of __() and gang, then grab the tokens necessarily for building the PO file. Now, this is where it gets picky.

// error! invalid marker content!
__('Delete post #' . $id);
__n('comment', 'comments', $array[0]);
__n('comment', 'comments', $object->noOfComments());
__n('comment', 'comments', (int) $noOfComments);

// valid! it makes it easier to translate anyways
sprintf(__('Delete post #%d', true), $id);
// just $var will work, nothing funky
sprintf(__n('%d comment', '%d comments', $noOfComments, true), $noOfComments);

So if you are thinking of using the I18n shell, you should take note that the text to be translated should really be one single token. Also, the generated file has an extension of .pot but it should really be renamed to .po instead since Cake expects .po files in the locale folder.

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License.
(c) 2008 Derilicious | powered by WordPress with Barecity | Log in