diff --git a/lib/Doctrine/Connection.php b/lib/Doctrine/Connection.php index 5014ed17d..1640550d3 100644 --- a/lib/Doctrine/Connection.php +++ b/lib/Doctrine/Connection.php @@ -535,18 +535,18 @@ abstract class Doctrine_Connection extends Doctrine_Configurable implements Coun $record->loadReference($alias); } - $r = Doctrine_Relation::getDeleteOperations($this->originals[$alias],$new); + $operations = Doctrine_Relation::getDeleteOperations($this->originals[$alias],$new); - foreach($r as $record) { + foreach($operations as $r) { $query = "DELETE FROM ".$asf->getTableName()." WHERE ".$fk->getForeign()." = ?" ." AND ".$fk->getLocal()." = ?"; - $this->table->getConnection()->execute($query, array($record->getIncremented(),$this->getIncremented())); + $this->table->getConnection()->execute($query, array($r->getIncremented(),$record->getIncremented())); } - $r = Doctrine_Relation::getInsertOperations($this->originals[$alias],$new); - foreach($r as $record) { + $operations = Doctrine_Relation::getInsertOperations($this->originals[$alias],$new); + foreach($operations as $r) { $reldao = $asf->create(); - $reldao->set($fk->getForeign(),$record); + $reldao->set($fk->getForeign(),$r); $reldao->set($fk->getLocal(),$this); $reldao->save(); @@ -569,15 +569,15 @@ abstract class Doctrine_Connection extends Doctrine_Configurable implements Coun $new = $this->references[$alias]; if( ! isset($this->originals[$alias])) - $this->loadReference($alias); + $record->loadReference($alias); - $r = Doctrine_Relation::getDeleteOperations($this->originals[$alias], $new); + $operations = Doctrine_Relation::getDeleteOperations($this->originals[$alias], $new); - foreach($r as $record) { - $record->delete(); + foreach($operations as $r) { + $r->delete(); } - $this->originals[$alias] = clone $this->references[$alias]; + $record->assignOriginals($alias, clone $this->references[$alias]); } break; endswitch; diff --git a/lib/Doctrine/Connection/Transaction.php b/lib/Doctrine/Connection/Transaction.php index b7ba11ceb..4c9f87e06 100644 --- a/lib/Doctrine/Connection/Transaction.php +++ b/lib/Doctrine/Connection/Transaction.php @@ -60,6 +60,10 @@ class Doctrine_Connection_Transaction implements Countable, IteratorAggregate { * @var Doctrine_Validator $validator transaction validator */ private $validator; + /** + * @var array $invalid an array containing all invalid records within this transaction + */ + protected $invalid = array(); /** * @var array $update two dimensional pending update list, the records in * this list will be updated when transaction is committed @@ -159,11 +163,13 @@ class Doctrine_Connection_Transaction implements Countable, IteratorAggregate { throw new Doctrine_Exception($e->__toString()); } - if($this->conn->getAttribute(Doctrine::ATTR_VLD)) { - if($this->validator->hasErrors()) { - $this->rollback(); - throw new Doctrine_Validator_Exception($this->validator); - } + if(count($this->invalid) > 0) { + $this->rollback(); + + $tmp = $this->invalid; + $this->invalid = array(); + + throw new Doctrine_Validator_Exception($tmp); } $this->conn->getDBH()->commit(); @@ -172,6 +178,7 @@ class Doctrine_Connection_Transaction implements Countable, IteratorAggregate { $this->state = Doctrine_Connection_Transaction::STATE_OPEN; $this->validator = null; + $this->invalid = array(); } elseif($this->transaction_level == 1) $this->state = Doctrine_Connection_Transaction::STATE_ACTIVE; @@ -361,6 +368,21 @@ class Doctrine_Connection_Transaction implements Countable, IteratorAggregate { public function addDelete(Doctrine_Record $record) { $name = $record->getTable()->getComponentName(); $this->delete[$name][] = $record; + } + /** + * addInvalid + * adds record into invalid records list + * + * @param Doctrine_Record $record + * @return boolean false if record already existed in invalid records list, + * otherwise true + */ + public function addInvalid(Doctrine_Record $record) { + if(in_array($record, $this->invalid)) + return false; + + $this->invalid[] = $record; + return true; } /** * returns the pending insert list @@ -387,6 +409,12 @@ class Doctrine_Connection_Transaction implements Countable, IteratorAggregate { return $this->delete; } public function getIterator() { } - - public function count() { } + /** + * an alias for getTransactionLevel + * + * @return integer returns the nesting level of this transaction + */ + public function count() { + return $this->transaction_level; + } } diff --git a/lib/Doctrine/Record.php b/lib/Doctrine/Record.php index b730be306..d3271f9fb 100644 --- a/lib/Doctrine/Record.php +++ b/lib/Doctrine/Record.php @@ -65,26 +65,6 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite * a Doctrine_Record turns into deleted state when it is deleted */ const STATE_DELETED = 6; - - /** - * CALLBACK CONSTANTS - */ - - /** - * RAW CALLBACK - * - * when using a raw callback and the property if a record is changed using this callback the - * record state remains untouched - */ - const CALLBACK_RAW = 1; - /** - * STATE-WISE CALLBACK - * - * state-wise callback means that when callback is used and the property is changed the - * record state is also updated - */ - const CALLBACK_STATEWISE = 2; - /** * @var object Doctrine_Table $table the factory that created this data access object */ @@ -119,9 +99,9 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite */ private $originals = array(); /** - * @var array $filters + * @var Doctrine_Validator_ErrorStack error stack object */ - private $filters = array(); + private $errorStack; /** * @var integer $index this index is used for creating object identifiers */ @@ -155,7 +135,7 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite } // Check if the current connection has the records table in its registry - // If not this is record is only used for creating table definition and setting up + // If not this record is only used for creating table definition and setting up // relations. if($this->table->getConnection()->hasTable($this->table->getComponentName())) { @@ -210,6 +190,8 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onLoad($this); } + $this->errorStack = new Doctrine_Validator_ErrorStack(); + $repository = $this->table->getRepository(); $repository->add($this); } @@ -218,6 +200,7 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite * initNullObject * * @param Doctrine_Null $null + * @return void */ public static function initNullObject(Doctrine_Null $null) { self::$null = $null; @@ -245,11 +228,37 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite public function getOID() { return $this->oid; } + /** + * isValid + * + * @return boolean whether or not this record passes all column validations + */ + public function isValid() { + if( ! $this->table->getAttribute(Doctrine::ATTR_VLD)) + return true; + + $validator = new Doctrine_Validator(); + + if($validator->validateRecord($this)) + return true; + + $this->errorStack->merge($validator->getErrorStack()); + + return false; + } + /** + * getErrorStack + * + * @return Doctrine_Validator_ErrorStack returns the errorStack associated with this record + */ + public function getErrorStack() { + return $this->errorStack; + } /** * setDefaultValues * sets the default values for records internal data * - * @param boolean $overwrite whether or not to overwrite the already set values + * @param boolean $overwrite whether or not to overwrite the already set values * @return boolean */ public function setDefaultValues($overwrite = false) { @@ -721,7 +730,7 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite $value = $this->table->invokeSet($this, $name, $value); $value = $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onSetProperty($this, $name, $value); - + if($value === null) $value = self::$null; @@ -825,7 +834,11 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite $saveLater = $conn->saveRelated($this); - $conn->save($this); + if( ! $this->isValid()) { + $conn->getTransaction()->addInvalid($this); + } else { + $conn->save($this); + } foreach($saveLater as $fk) { $table = $fk->getTable(); @@ -1105,6 +1118,16 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite $this->modified = array(); } } + /** + * assignOriginals + * + * @param string $alias + * @param Doctrine_Collection $coll + * @return void + */ + public function assignOriginals($alias, Doctrine_Collection $coll) { + $this->originals[$alias] = $coll; + } /** * returns the primary keys of this object * diff --git a/lib/Doctrine/Validator.php b/lib/Doctrine/Validator.php index 59630c701..12e57ec6a 100644 --- a/lib/Doctrine/Validator.php +++ b/lib/Doctrine/Validator.php @@ -26,62 +26,15 @@ * @url www.phpdoctrine.com * @license LGPL */ -class Doctrine_Validator { - /** - * ERROR CONSTANTS - */ - - /** - * constant for length validation error - */ - const ERR_LENGTH = 0; - /** - * constant for type validation error - */ - const ERR_TYPE = 1; - /** - * constant for general validation error - */ - const ERR_VALID = 2; - /** - * constant for unique validation error - */ - const ERR_UNIQUE = 3; - /** - * constant for blank validation error - */ - const ERR_NOTBLANK = 4; - /** - * constant for date validation error - */ - const ERR_DATE = 5; - /** - * constant for null validation error - */ - const ERR_NOTNULL = 6; - /** - * constant for enum validation error - */ - const ERR_ENUM = 7; - /** - * constant for range validation error - */ - const ERR_RANGE = 8; - /** - * constant for regexp validation error - */ - const ERR_REGEXP = 9; - - - +class Doctrine_Validator { /** * @var array $stack error stack */ - private $stack = array(); + private $stack = array(); /** * @var array $validators an array of validator objects */ - private static $validators = array(); + private static $validators = array(); /** * @var Doctrine_Null $null a Doctrine_Null object used for extremely fast * null value testing @@ -140,6 +93,8 @@ class Doctrine_Validator { foreach($data as $key => $value) { if($value === self::$null) $value = null; + elseif($value instanceof Doctrine_Record) + $value = $value->getIncremented(); $column = $columns[$key]; @@ -147,7 +102,7 @@ class Doctrine_Validator { $value = $record->getTable()->enumIndex($key, $value); if($value === false) { - $err[$key] = Doctrine_Validator::ERR_ENUM; + $err[$key] = 'enum'; continue; } } @@ -158,9 +113,10 @@ class Doctrine_Validator { $length = strlen($value); if($length > $column[1]) { - $err[$key] = Doctrine_Validator::ERR_LENGTH; + $err[$key] = 'length'; continue; } + if( ! is_array($column[2])) $e = explode("|",$column[2]); else @@ -187,25 +143,23 @@ class Doctrine_Validator { $validator = self::getValidator($name); if( ! $validator->validate($record, $key, $value, $args)) { - $constant = 'Doctrine_Validator::ERR_'.strtoupper($name); - if(defined($constant)) - $err[$key] = constant($constant); - else - $err[$key] = Doctrine_Validator::ERR_VALID; + $err[$key] = $name; + + //$err[$key] = 'not valid'; // errors found quit validation looping for this column break; } } if( ! self::isValidType($value, $column[0])) { - $err[$key] = Doctrine_Validator::ERR_TYPE; + $err[$key] = 'type'; continue; } } if( ! empty($err)) { - $this->stack[$component][] = $err; + $this->stack = $err; return false; } diff --git a/lib/Doctrine/Validator/ErrorStack.php b/lib/Doctrine/Validator/ErrorStack.php new file mode 100644 index 000000000..2cdf86dc8 --- /dev/null +++ b/lib/Doctrine/Validator/ErrorStack.php @@ -0,0 +1,49 @@ +. + */ +Doctrine::autoload('Doctrine_Access'); +/** + * Doctrine_Validator_ErrorStack + * + * @author Konsta Vesterinen + * @license LGPL + * @package Doctrine + */ +class Doctrine_Validator_ErrorStack extends Doctrine_Access { + + private $errors = array(); + + public function merge($stack) { + if(is_array($stack)) { + $this->errors = array_merge($this->errors, $stack); + } + } + + public function get($name) { + if(isset($this->errors[$name])) + return $this->errors[$name]; + + return null; + } + + public function set($name, $value) { + $this->errors[$name] = $value; + } +} diff --git a/lib/Doctrine/Validator/Exception.php b/lib/Doctrine/Validator/Exception.php index bcd7c8170..936dada7f 100644 --- a/lib/Doctrine/Validator/Exception.php +++ b/lib/Doctrine/Validator/Exception.php @@ -1,24 +1,53 @@ . + */ Doctrine::autoload('Doctrine_Exception'); - -class Doctrine_Validator_Exception extends Doctrine_Exception { +/** + * Doctrine_Validator_Exception + * + * @author Konsta Vesterinen + * @license LGPL + * @package Doctrine + */ +class Doctrine_Validator_Exception extends Doctrine_Exception implements Countable, IteratorAggregate { /** - * @var Doctrine_Validator $validator + * @var array $invalid */ - private $validator; + private $invalid = array(); /** * @param Doctrine_Validator $validator */ - public function __construct(Doctrine_Validator $validator) { - $this->validator = $validator; + public function __construct(array $invalid) { + $this->invalid = $invalid; } - /** - * returns the error stack - * - * @return array - */ - public function getErrorStack() { - return $this->validator->getErrorStack(); + + public function getInvalidRecords() { + return $this->invalid; + } + + public function getIterator() { + return new ArrayIterator($this->invalid); + } + + public function count() { + return count($this->invalid); } /** * __toString @@ -26,8 +55,8 @@ class Doctrine_Validator_Exception extends Doctrine_Exception { * @return string */ public function __toString() { - $string = "Error stack : ".print_r($this->validator->getErrorStack(), true); - return $string.parent::__toString(); + + return parent::__toString(); } } diff --git a/tests/ValidatorTestCase.php b/tests/ValidatorTestCase.php index be95d0daf..372f6d025 100644 --- a/tests/ValidatorTestCase.php +++ b/tests/ValidatorTestCase.php @@ -82,14 +82,11 @@ class Doctrine_ValidatorTestCase extends Doctrine_UnitTestCase { $stack = $validator->getErrorStack(); $this->assertTrue(is_array($stack)); - $this->assertTrue(isset($stack['ValidatorTest'][0])); - $stack = $stack['ValidatorTest'][0]; - - $this->assertEqual($stack['mystring'], Doctrine_Validator::ERR_NOTNULL); - $this->assertEqual($stack['myemail2'], Doctrine_Validator::ERR_NOTBLANK); - $this->assertEqual($stack['myrange'], Doctrine_Validator::ERR_RANGE); - $this->assertEqual($stack['myregexp'], Doctrine_Validator::ERR_REGEXP); + $this->assertEqual($stack['mystring'], 'notnull'); + $this->assertEqual($stack['myemail2'], 'notblank'); + $this->assertEqual($stack['myrange'], 'range'); + $this->assertEqual($stack['myregexp'], 'regexp'); $test->mystring = 'str'; @@ -113,22 +110,29 @@ class Doctrine_ValidatorTestCase extends Doctrine_UnitTestCase { $validator = new Doctrine_Validator(); $validator->validateRecord($user); - $validator->validateRecord($email); + $stack = $validator->getErrorStack(); $this->assertTrue(is_array($stack)); - $this->assertEqual($stack["User"][0]["loginname"], Doctrine_Validator::ERR_LENGTH); - $this->assertEqual($stack["User"][0]["password"], Doctrine_Validator::ERR_LENGTH); - $this->assertEqual($stack["User"][0]["created"], Doctrine_Validator::ERR_TYPE); + $this->assertEqual($stack["loginname"], 'length'); + $this->assertEqual($stack["password"], 'length'); + $this->assertEqual($stack["created"], 'type'); + - $this->assertEqual($stack["Email"][0]["address"], Doctrine_Validator::ERR_VALID); + $validator->validateRecord($email); + $stack = $validator->getErrorStack(); + $this->assertEqual($stack["address"], 'email'); $email->address = "arnold@example.com"; $validator->validateRecord($email); $stack = $validator->getErrorStack(); - $this->assertEqual($stack["Email"][1]["address"], Doctrine_Validator::ERR_UNIQUE); + $this->assertEqual($stack["address"], 'unique'); + + $email->isValid(); + + $this->assertTrue($email->getErrorStack() instanceof Doctrine_Validator_ErrorStack); } public function testIsValidEmail() { @@ -156,7 +160,7 @@ class Doctrine_ValidatorTestCase extends Doctrine_UnitTestCase { $user->name = "this is an example of too long name not very good example but an example nevertheless"; $user->save(); } catch(Doctrine_Validator_Exception $e) { - $this->assertEqual($e->getErrorStack(),array("User" => array(array("name" => 0)))); + $this->assertEqual($e->count(), 1); } try { @@ -164,12 +168,19 @@ class Doctrine_ValidatorTestCase extends Doctrine_UnitTestCase { $user->Email->address = "jackdaniels@drinkmore.info..."; $user->name = "this is an example of too long user name not very good example but an example nevertheles"; $user->save(); + $this->fail(); } catch(Doctrine_Validator_Exception $e) { - $a = $e->getErrorStack(); + $this->pass(); + $a = $e->getInvalidRecords(); } + $this->assertTrue(is_array($a)); - $this->assertEqual($a["Email"][0]["address"], Doctrine_Validator::ERR_VALID); - $this->assertEqual($a["User"][0]["name"], Doctrine_Validator::ERR_LENGTH); + + $emailStack = $a[array_search($user->Email, $a)]->getErrorStack(); + $userStack = $a[array_search($user, $a)]->getErrorStack(); + + $this->assertEqual($emailStack["address"], 'email'); + $this->assertEqual($userStack["name"], 'length'); $this->manager->setAttribute(Doctrine::ATTR_VLD, false); } diff --git a/tests/run.php b/tests/run.php index ecd43f696..6fbc2c877 100644 --- a/tests/run.php +++ b/tests/run.php @@ -48,9 +48,12 @@ require_once("DataDictSqliteTestCase.php"); require_once("CustomResultSetOrderTestCase.php"); error_reporting(E_ALL); +print "
";
 
 $test = new GroupTest("Doctrine Framework Unit Tests");
 
+$test->addTestCase(new Doctrine_ValidatorTestCase());
+
 $test->addTestCase(new Doctrine_Query_MultiJoin_TestCase());
 
 $test->addTestCase(new Doctrine_Relation_TestCase());
@@ -75,8 +78,6 @@ $test->addTestCase(new Doctrine_BatchIteratorTestCase());
 
 $test->addTestCase(new Doctrine_ConfigurableTestCase());
 
-$test->addTestCase(new Doctrine_ValidatorTestCase());
-
 $test->addTestCase(new Doctrine_Collection_OffsetTestCase());
 
 $test->addTestCase(new Doctrine_PessimisticLockingTestCase());
@@ -124,6 +125,7 @@ $test->addTestCase(new Doctrine_Query_ComponentAlias_TestCase());
 $test->addTestCase(new Doctrine_Query_Subquery_TestCase());
 
 $test->addTestCase(new Doctrine_Record_Filter_TestCase());
+
 //$test->addTestCase(new Doctrine_Cache_FileTestCase());
 //$test->addTestCase(new Doctrine_Cache_SqliteTestCase());