1
0
mirror of synced 2025-01-20 07:21:40 +03:00

Continued work on the validation component.

Ticket: 150
This commit is contained in:
romanb 2006-10-10 16:15:43 +00:00
parent d81a4245b7
commit 171226d532
17 changed files with 936 additions and 939 deletions

View File

@ -237,10 +237,12 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
if( ! $this->table->getAttribute(Doctrine::ATTR_VLD))
return true;
// Clear the stack from any previous errors.
$this->errorStack->clear();
// Run validation process
$validator = new Doctrine_Validator();
// Run validators
$validator->validateRecord($this);
// Run custom validation
$this->validate();
return $this->errorStack->count() == 0 ? true : false;

View File

@ -27,7 +27,7 @@
* @license LGPL
* @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.
@ -49,8 +49,8 @@ class Doctrine_Validator_ErrorStack implements ArrayAccess, Countable, IteratorA
* @param string $invalidFieldName
* @param string $errorType
*/
public function add($invalidFieldName, $errorType = 'general') {
$this->errors[$invalidFieldName][] = array('type' => $errorType);
public function add($invalidFieldName, $errorCode = 'general') {
$this->errors[$invalidFieldName][] = $errorCode;
}
/**
@ -70,66 +70,35 @@ class Doctrine_Validator_ErrorStack implements ArrayAccess, Countable, IteratorA
* @param unknown_type $name
* @return unknown
*/
public function get($name) {
return $this[$name];
}
/** 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;
public function get($fieldName) {
return isset($this->errors[$fieldName]) ? $this->errors[$fieldName] : null;
}
/**
* Enter description here...
*
* @param string $offset
* @param mixed $value
* @throws Doctrine_Validator_ErrorStack_Exception Always thrown since this operation is not allowed.
* @param unknown_type $name
*/
public function offsetSet($offset, $value) {
throw new Doctrine_Validator_ErrorStack_Exception("Errors can only be added through
Doctrine_Validator_ErrorStack::add()");
public function set($fieldName, $errorCode) {
$this->add($fieldName, $errorCode);
}
/**
* Enter description here...
*
* @param unknown_type $offset
* @return unknown
*/
public function offsetExists($offset) {
return isset($this->errors[$offset]);
public function contains($fieldName) {
return array_key_exists($fieldName, $this->errors);
}
/**
* Enter description here...
*
* @param unknown_type $offset
* @throws Doctrine_Validator_ErrorStack_Exception Always thrown since this operation is not allowed.
* Removes all errors from the stack.
*/
public function offsetUnset($offset) {
throw new Doctrine_Validator_ErrorStack_Exception("Errors can only be removed
through Doctrine_Validator_ErrorStack::remove()");
public function clear() {
$this->errors = array();
}
/**
* Enter description here...
*
* @param unknown_type $stack
*/
/*
public function merge($stack) {
if(is_array($stack)) {
$this->errors = array_merge($this->errors, $stack);
}
}*/
/** IteratorAggregate implementation */

View File

@ -1,13 +0,0 @@
<?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);
}
}

View File

@ -1,27 +0,0 @@
<?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;
}
}
}
?>

View File

@ -0,0 +1,26 @@
<?php
class User extends Doctrine_Record {
public function setUp() {
$this->ownsOne("Email","User.email_id");
}
public function setTableDefinition() {
// no special validators used only types
// and lengths will be validated
$this->hasColumn("name","string",15);
$this->hasColumn("email_id","integer");
$this->hasColumn("created","integer",11);
}
}
class Email extends Doctrine_Record {
public function setTableDefinition() {
// validators 'email' and 'unique' used
$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');
}
}
}
?>

View File

@ -0,0 +1,28 @@
<?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;
}
}
}
?>

View File

@ -1,42 +0,0 @@
<?php
class User extends Doctrine_Record {
public function setUp() {
$this->ownsOne("Email","User.email_id");
}
public function setTableDefinition() {
// no special validators used only types
// and lengths will be validated
$this->hasColumn("name","string",15);
$this->hasColumn("email_id","integer");
$this->hasColumn("created","integer",11);
}
}
class Email extends Doctrine_Record {
public function setTableDefinition() {
// specialized validators 'email' and 'unique' used
$this->hasColumn("address","string",150,"email|unique");
}
}
$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
?>

View File

@ -0,0 +1,17 @@
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.

View File

@ -1,11 +0,0 @@
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.

View File

@ -0,0 +1,23 @@
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 />

View File

@ -0,0 +1,22 @@
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.

View File

@ -1,5 +0,0 @@
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.

View File

@ -270,9 +270,9 @@ $menu = array("Getting started" =>
"Creating a logger",
),
"Validators" => array(
"Intruduction",
"Validating transactions",
"Analyzing the ErrorStack",
"Introduction",
"More Validation",
"Valid or Not Valid",
"List of predefined validators"
),
"View" => array(

View File

@ -94,10 +94,10 @@ class Doctrine_ValidatorTestCase extends Doctrine_UnitTestCase {
$this->assertTrue($stack instanceof Doctrine_Validator_ErrorStack);
$this->assertTrue(in_array(array('type' => 'notnull'), $stack['mystring']));
$this->assertTrue(in_array(array('type' => 'notblank'), $stack['myemail2']));
$this->assertTrue(in_array(array('type' => 'range'), $stack['myrange']));
$this->assertTrue(in_array(array('type' => 'regexp'), $stack['myregexp']));
$this->assertTrue(in_array('notnull', $stack['mystring']));
$this->assertTrue(in_array('notblank', $stack['myemail2']));
$this->assertTrue(in_array('range', $stack['myrange']));
$this->assertTrue(in_array('regexp', $stack['myregexp']));
$test->mystring = 'str';
@ -127,19 +127,19 @@ class Doctrine_ValidatorTestCase extends Doctrine_UnitTestCase {
$stack = $user->getErrorStack();
$this->assertTrue($stack instanceof Doctrine_Validator_ErrorStack);
$this->assertTrue(in_array(array('type' => 'length'), $stack['loginname']));
$this->assertTrue(in_array(array('type' => 'length'), $stack['password']));
$this->assertTrue(in_array(array('type' => 'type'), $stack['created']));
$this->assertTrue(in_array('length', $stack['loginname']));
$this->assertTrue(in_array('length', $stack['password']));
$this->assertTrue(in_array('type', $stack['created']));
$validator->validateRecord($email);
$stack = $email->getErrorStack();
$this->assertTrue(in_array(array('type' => 'email'), $stack['address']));
$this->assertTrue(in_array('email', $stack['address']));
$email->address = "arnold@example.com";
$validator->validateRecord($email);
$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 {
$invalidRecords = $e->getInvalidRecords();
$this->assertEqual(count($invalidRecords), 1);
$stack = $invalidRecords[0]->getErrorStack();
$this->assertTrue(in_array(array('type' => 'length'), $stack['name']));
$this->assertTrue(in_array('length', $stack['name']));
}
try {
@ -196,8 +196,8 @@ class Doctrine_ValidatorTestCase extends Doctrine_UnitTestCase {
$emailStack = $a[array_search($user->Email, $a)]->getErrorStack();
$userStack = $a[array_search($user, $a)]->getErrorStack();
$this->assertTrue(in_array(array('type' => 'email'), $emailStack['address']));
$this->assertTrue(in_array(array('type' => 'length'), $userStack['name']));
$this->assertTrue(in_array('email', $emailStack['address']));
$this->assertTrue(in_array('length', $userStack['name']));
$this->manager->setAttribute(Doctrine::ATTR_VLD, false);
}
@ -219,8 +219,16 @@ class Doctrine_ValidatorTestCase extends Doctrine_UnitTestCase {
$stack = $invalidRecords[0]->getErrorStack();
$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);
}
}