Commit 171226d5 authored by romanb's avatar romanb

Continued work on the validation component.

Ticket: 150
parent d81a4245
...@@ -237,10 +237,12 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite ...@@ -237,10 +237,12 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
if( ! $this->table->getAttribute(Doctrine::ATTR_VLD)) if( ! $this->table->getAttribute(Doctrine::ATTR_VLD))
return true; return true;
// Clear the stack from any previous errors.
$this->errorStack->clear();
// Run validation process
$validator = new Doctrine_Validator(); $validator = new Doctrine_Validator();
// Run validators
$validator->validateRecord($this); $validator->validateRecord($this);
// Run custom validation
$this->validate(); $this->validate();
return $this->errorStack->count() == 0 ? true : false; return $this->errorStack->count() == 0 ? true : false;
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
* @license LGPL * @license LGPL
* @package Doctrine * @package Doctrine
*/ */
class Doctrine_Validator_ErrorStack implements ArrayAccess, Countable, IteratorAggregate { class Doctrine_Validator_ErrorStack extends Doctrine_Access implements Countable, IteratorAggregate {
/** /**
* The errors of the error stack. * The errors of the error stack.
...@@ -49,8 +49,8 @@ class Doctrine_Validator_ErrorStack implements ArrayAccess, Countable, IteratorA ...@@ -49,8 +49,8 @@ class Doctrine_Validator_ErrorStack implements ArrayAccess, Countable, IteratorA
* @param string $invalidFieldName * @param string $invalidFieldName
* @param string $errorType * @param string $errorType
*/ */
public function add($invalidFieldName, $errorType = 'general') { public function add($invalidFieldName, $errorCode = 'general') {
$this->errors[$invalidFieldName][] = array('type' => $errorType); $this->errors[$invalidFieldName][] = $errorCode;
} }
/** /**
...@@ -70,65 +70,34 @@ class Doctrine_Validator_ErrorStack implements ArrayAccess, Countable, IteratorA ...@@ -70,65 +70,34 @@ class Doctrine_Validator_ErrorStack implements ArrayAccess, Countable, IteratorA
* @param unknown_type $name * @param unknown_type $name
* @return unknown * @return unknown
*/ */
public function get($name) { public function get($fieldName) {
return $this[$name]; return isset($this->errors[$fieldName]) ? $this->errors[$fieldName] : null;
}
/** ArrayAccess implementation */
/**
* Gets all errors that occured for the specified field.
*
* @param string $offset
* @return The array containing the errors or NULL if no errors were found.
*/
public function offsetGet($offset) {
return isset($this->errors[$offset]) ? $this->errors[$offset] : null;
}
/**
* Enter description here...
*
* @param string $offset
* @param mixed $value
* @throws Doctrine_Validator_ErrorStack_Exception Always thrown since this operation is not allowed.
*/
public function offsetSet($offset, $value) {
throw new Doctrine_Validator_ErrorStack_Exception("Errors can only be added through
Doctrine_Validator_ErrorStack::add()");
} }
/** /**
* Enter description here... * Enter description here...
* *
* @param unknown_type $offset * @param unknown_type $name
*/ */
public function offsetExists($offset) { public function set($fieldName, $errorCode) {
return isset($this->errors[$offset]); $this->add($fieldName, $errorCode);
} }
/** /**
* Enter description here... * Enter description here...
* *
* @param unknown_type $offset * @return unknown
* @throws Doctrine_Validator_ErrorStack_Exception Always thrown since this operation is not allowed.
*/ */
public function offsetUnset($offset) { public function contains($fieldName) {
throw new Doctrine_Validator_ErrorStack_Exception("Errors can only be removed return array_key_exists($fieldName, $this->errors);
through Doctrine_Validator_ErrorStack::remove()");
} }
/** /**
* Enter description here... * Removes all errors from the stack.
*
* @param unknown_type $stack
*/ */
/* public function clear() {
public function merge($stack) { $this->errors = array();
if(is_array($stack)) {
$this->errors = array_merge($this->errors, $stack);
} }
}*/
/** IteratorAggregate implementation */ /** IteratorAggregate implementation */
......
<?php
class Doctrine_Validator_Required {
/**
* @param Doctrine_Record $record
* @param string $key
* @param mixed $value
* @return boolean
*/
public function validate(Doctrine_Record $record, $key, $value) {
return ($value === null);
}
}
<?php
try {
$user->name = "this is an example of too long name";
$user->Email->address = "drink@@notvalid..";
$user->save();
} catch(Doctrine_Validator_Exception $e) {
$stack = $e->getErrorStack();
foreach($stack as $component => $err) {
foreach($err as $field => $type) {
switch($type):
case Doctrine_Validator::ERR_TYPE:
print $field." is not right type";
break;
case Doctrine_Validator::ERR_UNIQUE:
print $field." is not unique";
break;
case Doctrine_Validator::ERR_VALID:
print $field." is not valid";
break;
case Doctrine_Validator::ERR_LENGTH:
print $field." is too long";
break;
endswitch;
}
}
}
?>
...@@ -13,30 +13,14 @@ class User extends Doctrine_Record { ...@@ -13,30 +13,14 @@ class User extends Doctrine_Record {
} }
class Email extends Doctrine_Record { class Email extends Doctrine_Record {
public function setTableDefinition() { public function setTableDefinition() {
// specialized validators 'email' and 'unique' used // validators 'email' and 'unique' used
$this->hasColumn("address","string",150,"email|unique"); $this->hasColumn("address","string",150, array("email", "unique" => true));
}
protected function validate() {
if ($this->address !== 'the-only-allowed-mail@address.com') {
// syntax: add(<fieldName>, <error identifier>)
$this->errorStack->add('address', 'myCustomValidationTypeError');
}
} }
} }
$conn = Doctrine_Manager::getInstance()->openConnection(new PDO("dsn","username","password"));
$user = new User();
$user->name = "this is an example of too long name";
$user->save(); // throws a Doctrine_Validator_Exception
$user->name = "valid name";
$user->created = "not valid"; // not valid type
$user->save(); // throws a Doctrine_Validator_Exception
$user->created = time();
$user->Email->address = "drink@.."; // not valid email address
$user->save(); // throws a Doctrine_Validator_Exception
$user->Email->address = "drink@drinkmore.info";
$user->save(); // saved
$user = $conn->create("User");
$user->Email->address = "drink@drinkmore.info"; // not unique!
$user->save(); // throws a Doctrine_Validator_Exception
?> ?>
<?php
try {
$user->name = "this is an example of too long name";
$user->Email->address = "drink@@notvalid..";
$user->save();
} catch(Doctrine_Validator_Exception $e) {
$userErrors = $user->getErrorStack();
$emailErrors = $user->Email->getErrorStack();
/* Inspect user errors */
foreach($userErrors as $fieldName => $errorCodes) {
switch ($fieldName) {
case 'name':
// $user->name is invalid. inspect the error codes if needed.
break;
}
}
/* Inspect email errors */
foreach($emailErrors as $fieldName => $errorCodes) {
switch ($fieldName) {
case 'address':
// $user->Email->address is invalid. inspect the error codes if needed.
break;
}
}
}
?>
Validation in Doctrine is a way to enforce your business rules in the model part of the MVC architecture.
You can think of this validation as a gateway that needs to be passed right before data gets into the
persistent data store. The definition of these business rules takes place at the record level, that means
in your active record model classes (classes derived from Doctrine_Record).
The first thing you need to do to be able to use this kind of validation is to enable it globally.
This is done through the Doctrine_Manager (see the code below).<br />
<br />
Once you enabled validation, you'll get a bunch of validations automatically:<br />
<br />
- Data type validations: All values assigned to columns are checked for the right type. That means
if you specified a column of your record as type 'integer', Doctrine will validate that
any values assigned to that column are of this type. This kind of type validation tries to
be as smart as possible since PHP is a loosely typed language. For example 2 as well as "7"
are both valid integers whilst "3f" is not. Type validations occur on every column (since every
column definition needs a type).<br /><br />
- Length validation: As the name implies, all values assigned to columns are validated to make
sure that the value does not exceed the maximum length.
With Doctrine validators you can validate a whole transaction and get info of everything
that went wrong. Some Doctrine validators also act as a database level constraints. For example
adding a unique validator to column 'name' also adds a database level unique constraint into that
column.
<br \><br \>
Validators are added as a 4 argument for hasColumn() method. Validators should be separated
by '|' mark. For example email|unique would validate a value using Doctrine_Validator_Email
and Doctrine_Validator_Unique.
<br \><br \>
Doctrine has many predefined validators (see chapter 13.3). If you wish to use
custom validators just write *Validator classes and doctrine will automatically use them.
The type and length validations are handy but most of the time they're not enough. Therefore
Doctrine provides some mechanisms that can be used to validate your data in more detail.<br />
<br />
Validators: Validators are an easy way to specify further validations. Doctrine has a lot of predefined
validators that are frequently needed such as email, country, ip, range and regexp validators. You
find a full list of available validators at the bottom of this page. You can specify which validators
apply to which column through the 4th argument of the hasColumn() method.
If that is still not enough and you need some specialized validation that is not yet available as
a predefined validator you have three options:<br />
<br />
- You can write the validator on your own.<br />
- You can propose your need for a new validator to a Doctrine developer.<br />
- You can use validation hooks.<br />
<br />
The first two options are advisable if it is likely that the validation is of general use
and is potentially applicable in many situations. In that case it is a good idea to implement
a new validator. However if the validation is special it is better to use hooks provided by Doctrine.
One of these hooks is the validate() method. If you need a special validation in your active record
you can simply override validate() in your active record class (a descendant of Doctrine_Record).
Within this method you can use all the power of PHP to validate your fields. When a field
doesnt pass your validation you can then add errors to the record's error stack.
The following code snippet shows an example of how to define validators together with custom
validation:<br />
Now that you know how to specify your business rules in your models, it is time to look at how to
deal with these rules in the rest of your application.<br />
<br />
Implicit validation:<br />
Whenever a record is going to be saved to the persistent data store (i.e. through calling $record->save())
the full validation procedure is executed. If errors occur during that process an exception of the type
Doctrine_Validator_Exception will be thrown. You can catch that exception and analyze the errors by
using the instance method Doctine_Validator_Exception::getInvalidRecords(). This method returns
an ordinary array with references to all records that did not pass validation. You can then
further explore the errors of each record by analyzing the error stack of each record.
The error stack of a record can be obtained with the instance method Doctrine_Record::getErrorStack().
Each error stack is an instance of the class Doctrine_Validator_ErrorStack. The error stack
provides an easy to use interface to inspect the errors.<br />
<br />
Explicit validation:<br />
You can explicitly trigger the validation for any record at any time. For this purpose Doctrine_Record
provides the instance method Doctrine_Record::isValid(). This method returns a boolean value indicating
the result of the validation. If the method returns FALSE, you can inspect the error stack in the same
way as seen above except that no exception is thrown, so you simply obtain
the error stack of the record that didnt pass validation through Doctrine_Record::getErrorStack().<br />
<br />
The following code snippet shows an example of handling implicit validation which caused a Doctrine_Validator_Exception.
When the validation attribute is set as true all transactions will be validated, so whenever Doctrine_Record::save(),
Doctrine_Connection::flush() or any other saving method is used all the properties of all records in that transaction will have their values
validated.
<br \><br \>
Validation errors are being stacked into Doctrine_Validator_Exception.
...@@ -270,9 +270,9 @@ $menu = array("Getting started" => ...@@ -270,9 +270,9 @@ $menu = array("Getting started" =>
"Creating a logger", "Creating a logger",
), ),
"Validators" => array( "Validators" => array(
"Intruduction", "Introduction",
"Validating transactions", "More Validation",
"Analyzing the ErrorStack", "Valid or Not Valid",
"List of predefined validators" "List of predefined validators"
), ),
"View" => array( "View" => array(
......
...@@ -94,10 +94,10 @@ class Doctrine_ValidatorTestCase extends Doctrine_UnitTestCase { ...@@ -94,10 +94,10 @@ class Doctrine_ValidatorTestCase extends Doctrine_UnitTestCase {
$this->assertTrue($stack instanceof Doctrine_Validator_ErrorStack); $this->assertTrue($stack instanceof Doctrine_Validator_ErrorStack);
$this->assertTrue(in_array(array('type' => 'notnull'), $stack['mystring'])); $this->assertTrue(in_array('notnull', $stack['mystring']));
$this->assertTrue(in_array(array('type' => 'notblank'), $stack['myemail2'])); $this->assertTrue(in_array('notblank', $stack['myemail2']));
$this->assertTrue(in_array(array('type' => 'range'), $stack['myrange'])); $this->assertTrue(in_array('range', $stack['myrange']));
$this->assertTrue(in_array(array('type' => 'regexp'), $stack['myregexp'])); $this->assertTrue(in_array('regexp', $stack['myregexp']));
$test->mystring = 'str'; $test->mystring = 'str';
...@@ -127,19 +127,19 @@ class Doctrine_ValidatorTestCase extends Doctrine_UnitTestCase { ...@@ -127,19 +127,19 @@ class Doctrine_ValidatorTestCase extends Doctrine_UnitTestCase {
$stack = $user->getErrorStack(); $stack = $user->getErrorStack();
$this->assertTrue($stack instanceof Doctrine_Validator_ErrorStack); $this->assertTrue($stack instanceof Doctrine_Validator_ErrorStack);
$this->assertTrue(in_array(array('type' => 'length'), $stack['loginname'])); $this->assertTrue(in_array('length', $stack['loginname']));
$this->assertTrue(in_array(array('type' => 'length'), $stack['password'])); $this->assertTrue(in_array('length', $stack['password']));
$this->assertTrue(in_array(array('type' => 'type'), $stack['created'])); $this->assertTrue(in_array('type', $stack['created']));
$validator->validateRecord($email); $validator->validateRecord($email);
$stack = $email->getErrorStack(); $stack = $email->getErrorStack();
$this->assertTrue(in_array(array('type' => 'email'), $stack['address'])); $this->assertTrue(in_array('email', $stack['address']));
$email->address = "arnold@example.com"; $email->address = "arnold@example.com";
$validator->validateRecord($email); $validator->validateRecord($email);
$stack = $email->getErrorStack(); $stack = $email->getErrorStack();
$this->assertTrue(in_array(array('type' => 'unique'), $stack['address'])); $this->assertTrue(in_array('unique', $stack['address']));
} }
/** /**
...@@ -177,7 +177,7 @@ class Doctrine_ValidatorTestCase extends Doctrine_UnitTestCase { ...@@ -177,7 +177,7 @@ class Doctrine_ValidatorTestCase extends Doctrine_UnitTestCase {
$invalidRecords = $e->getInvalidRecords(); $invalidRecords = $e->getInvalidRecords();
$this->assertEqual(count($invalidRecords), 1); $this->assertEqual(count($invalidRecords), 1);
$stack = $invalidRecords[0]->getErrorStack(); $stack = $invalidRecords[0]->getErrorStack();
$this->assertTrue(in_array(array('type' => 'length'), $stack['name'])); $this->assertTrue(in_array('length', $stack['name']));
} }
try { try {
...@@ -196,8 +196,8 @@ class Doctrine_ValidatorTestCase extends Doctrine_UnitTestCase { ...@@ -196,8 +196,8 @@ class Doctrine_ValidatorTestCase extends Doctrine_UnitTestCase {
$emailStack = $a[array_search($user->Email, $a)]->getErrorStack(); $emailStack = $a[array_search($user->Email, $a)]->getErrorStack();
$userStack = $a[array_search($user, $a)]->getErrorStack(); $userStack = $a[array_search($user, $a)]->getErrorStack();
$this->assertTrue(in_array(array('type' => 'email'), $emailStack['address'])); $this->assertTrue(in_array('email', $emailStack['address']));
$this->assertTrue(in_array(array('type' => 'length'), $userStack['name'])); $this->assertTrue(in_array('length', $userStack['name']));
$this->manager->setAttribute(Doctrine::ATTR_VLD, false); $this->manager->setAttribute(Doctrine::ATTR_VLD, false);
} }
...@@ -219,8 +219,16 @@ class Doctrine_ValidatorTestCase extends Doctrine_UnitTestCase { ...@@ -219,8 +219,16 @@ class Doctrine_ValidatorTestCase extends Doctrine_UnitTestCase {
$stack = $invalidRecords[0]->getErrorStack(); $stack = $invalidRecords[0]->getErrorStack();
$this->assertEqual($stack->count(), 1); $this->assertEqual($stack->count(), 1);
$this->assertTrue(in_array(array('type' => 'notTheSaint'), $stack['name'])); $this->assertTrue(in_array('notTheSaint', $stack['name']));
} }
try {
$user->name = "The Saint";
$user->save();
} catch(Doctrine_Validator_Exception $e) {
$this->fail();
}
$this->manager->setAttribute(Doctrine::ATTR_VLD, false); $this->manager->setAttribute(Doctrine::ATTR_VLD, false);
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment