diff --git a/lib/Doctrine.php b/lib/Doctrine.php index 1272dd810..ad175755c 100644 --- a/lib/Doctrine.php +++ b/lib/Doctrine.php @@ -36,70 +36,6 @@ */ final class Doctrine { - /** - * DEPRECATED ATTRIBUTE CONSTANT NAMES AND VALUES - * - * REMOVE BEFORE 1.0 - * These were deprecated either because they are not used anymore or because they didn't follow the naming pattern required - * by Doctrine attributes - * - * Attribute names must be ATTR_{ATTR_NAME} and the values must be {ATTR_NAME}_NAME_OF_VALUE - */ - const LIMIT_ROWS = 1; - const LIMIT_RECORDS = 2; - const FETCH_IMMEDIATE = 0; - const FETCH_BATCH = 1; - const FETCH_OFFSET = 3; - const FETCH_LAZY_OFFSET = 4; - const FETCH_VHOLDER = 1; - const FETCH_RECORD = 2; - const FETCH_ARRAY = 3; - - /** - * VERSION - */ - const VERSION = '2.0.0'; - - /** - * ERROR CONSTANTS - */ - const ERR = -1; - const ERR_SYNTAX = -2; - const ERR_CONSTRAINT = -3; - const ERR_NOT_FOUND = -4; - const ERR_ALREADY_EXISTS = -5; - const ERR_UNSUPPORTED = -6; - const ERR_MISMATCH = -7; - const ERR_INVALID = -8; - const ERR_NOT_CAPABLE = -9; - const ERR_TRUNCATED = -10; - const ERR_INVALID_NUMBER = -11; - const ERR_INVALID_DATE = -12; - const ERR_DIVZERO = -13; - const ERR_NODBSELECTED = -14; - const ERR_CANNOT_CREATE = -15; - const ERR_CANNOT_DELETE = -16; - const ERR_CANNOT_DROP = -17; - const ERR_NOSUCHTABLE = -18; - const ERR_NOSUCHFIELD = -19; - const ERR_NEED_MORE_DATA = -20; - const ERR_NOT_LOCKED = -21; - const ERR_VALUE_COUNT_ON_ROW = -22; - const ERR_INVALID_DSN = -23; - const ERR_CONNECT_FAILED = -24; - const ERR_EXTENSION_NOT_FOUND = -25; - const ERR_NOSUCHDB = -26; - const ERR_ACCESS_VIOLATION = -27; - const ERR_CANNOT_REPLACE = -28; - const ERR_CONSTRAINT_NOT_NULL = -29; - const ERR_DEADLOCK = -30; - const ERR_CANNOT_ALTER = -31; - const ERR_MANAGER = -32; - const ERR_MANAGER_PARSE = -33; - const ERR_LOADMODULE = -34; - const ERR_INSUFFICIENT_DATA = -35; - const ERR_CLASS_NAME = -36; - /** * PDO derived constants */ @@ -142,14 +78,6 @@ final class Doctrine const PARAM_NULL = 0; const PARAM_STMT = 4; const PARAM_STR = 2; - - /** - * ATTRIBUTE CONSTANTS - */ - - /** - * PDO derived attributes - */ const ATTR_AUTOCOMMIT = 0; const ATTR_PREFETCH = 1; const ATTR_TIMEOUT = 2; @@ -170,387 +98,6 @@ final class Doctrine const ATTR_STRINGIFY_FETCHES = 17; const ATTR_MAX_COLUMN_LEN = 18; - /** - * Doctrine constants - */ - const ATTR_LISTENER = 100; - const ATTR_QUOTE_IDENTIFIER = 101; // manager/session attribute - const ATTR_FIELD_CASE = 102; // manager/session attribute - const ATTR_IDXNAME_FORMAT = 103; // manager/session attribute - const ATTR_SEQNAME_FORMAT = 104; // manager/session attribute - const ATTR_SEQCOL_NAME = 105; // class attribute - const ATTR_CMPNAME_FORMAT = 118; // ?? - const ATTR_DBNAME_FORMAT = 117; // manager/session attribute - const ATTR_TBLCLASS_FORMAT = 119; // manager/session attribute - const ATTR_TBLNAME_FORMAT = 120; // manager/session attribute - const ATTR_EXPORT = 140; // manager/session attribute - const ATTR_DECIMAL_PLACES = 141; // manager/session attribute - const ATTR_PORTABILITY = 106; // manager/session attribute - const ATTR_COLL_KEY = 108; // class attribute - const ATTR_QUERY_LIMIT = 109; // manager/session attribute - const ATTR_DEFAULT_TABLE_TYPE = 112; // manager/session attribute - const ATTR_DEF_TEXT_LENGTH = 113; // manager/session attribute - const ATTR_DEF_VARCHAR_LENGTH = 114; // manager/session attribute - const ATTR_DEF_TABLESPACE = 115; // manager/session attribute - const ATTR_EMULATE_DATABASE = 116; // manager/session attribute - const ATTR_USE_NATIVE_ENUM = 117; // manager/session attribute - const ATTR_DEFAULT_SEQUENCE = 133; // ?? - const ATTR_FETCHMODE = 118; // deprecated? might use them again for associations - const ATTR_NAME_PREFIX = 121; // ?? - const ATTR_CREATE_TABLES = 122; // manager/session attribute - const ATTR_COLL_LIMIT = 123; // manager/session attribute - const ATTR_RESULT_CACHE = 150; // manager/session attribute - const ATTR_RESULT_CACHE_LIFESPAN = 151; // manager/session attribute - const ATTR_LOAD_REFERENCES = 153; // class attribute - const ATTR_RECORD_LISTENER = 154; - const ATTR_THROW_EXCEPTIONS = 155; // manager/session attribute - const ATTR_DEFAULT_PARAM_NAMESPACE = 156; // ?? - const ATTR_QUERY_CACHE = 157; // manager/session attribute - const ATTR_QUERY_CACHE_LIFESPAN = 158; // manager/session attribute - const ATTR_MODEL_LOADING = 161; // manager/session attribute - const ATTR_HYDRATE = 163; // ?? - const ATTR_IDENTIFIER = 164; // ?? - const ATTR_METADATA_CACHE = 165; // manager/session attribute - const ATTR_METADATA_CACHE_LIFESPAN = 166; // manager/session attribute - - /** - * QUERY_LIMIT CONSTANTS - */ - - /** - * QUERY_LIMIT_ROWS - * - * constant for row limiting - * - * @see self::ATTR_QUERY_LIMIT - */ - const QUERY_LIMIT_ROWS = 1; - - /** - * constant for record limiting - * @see self::ATTR_QUERY_LIMIT - */ - const QUERY_LIMIT_RECORDS = 2; - - /** - * FETCHMODE CONSTANTS - */ - - /** - * IMMEDIATE FETCHING - * mode for immediate fetching - * @see self::ATTR_FETCHMODE - * @deprecated??? - */ - const FETCHMODE_IMMEDIATE = 0; - - /** - * FETCHMODE_BATCH - * - * mode for batch fetching - * - * @see self::ATTR_FETCHMODE - * @deprecated??? - */ - const FETCHMODE_BATCH = 1; - - /** - * FETCHMODE_OFFSET - * - * mode for offset fetching - * - * @see self::ATTR_FETCHMODE - * @deprecated??? - */ - const FETCHMODE_OFFSET = 3; - - /** - * FETCHMODE_LAZY_OFFSET - * - * mode for lazy offset fetching - * - * @see self::ATTR_FETCHMODE - * @deprecated??? - */ - const FETCHMODE_LAZY_OFFSET = 4; - - /** - * FETCHMODE CONSTANTS - */ - - /** - * FETCHMODE_VHOLDER - * - * @see self::ATTR_FETCHMODE - * @deprecated??? - */ - const FETCHMODE_VHOLDER = 1; - - /** - * FETCHMODE_RECORD - * - * Specifies that the fetch method shall return Doctrine_Entity - * objects as the elements of the result set. - * - * This is the default fetchmode. - * - * @see self::ATTR_FETCHMODE - * @deprecated??? - */ - const FETCHMODE_RECORD = 2; - - /** - * FETCHMODE_ARRAY - * - * @see self::ATTR_FETCHMODE - * @deprecated??? - */ - const FETCHMODE_ARRAY = 3; - - /** - * PORTABILITY CONSTANTS - */ - - /** - * PORTABILITY_NONE - * - * Portability: turn off all portability features. - * - * @see self::ATTR_PORTABILITY - */ - const PORTABILITY_NONE = 0; - - /** - * PORTABILITY_FIX_CASE - * - * Portability: convert names of tables and fields to case defined in the - * "field_case" option when using the query*(), fetch*() methods. - * - * @see self::ATTR_PORTABILITY - */ - const PORTABILITY_FIX_CASE = 1; - - /** - * PORTABILITY_RTRIM - * - * Portability: right trim the data output by query*() and fetch*(). - * - * @see self::ATTR_PORTABILITY - */ - const PORTABILITY_RTRIM = 2; - - /** - * PORTABILITY_DELETE_COUNT - * - * Portability: force reporting the number of rows deleted. - * - * @see self::ATTR_PORTABILITY - */ - const PORTABILITY_DELETE_COUNT = 4; - - /** - * PORTABILITY_EMPTY_TO_NULL - * - * Portability: convert empty values to null strings in data output by - * query*() and fetch*(). - * - * @see self::ATTR_PORTABILITY - */ - const PORTABILITY_EMPTY_TO_NULL = 8; - - /** - * PORTABILITY_FIX_ASSOC_FIELD_NAMES - * - * Portability: removes database/table qualifiers from associative indexes - * - * @see self::ATTR_PORTABILITY - */ - const PORTABILITY_FIX_ASSOC_FIELD_NAMES = 16; - - /** - * PORTABILITY_EXPR - * - * Portability: makes Doctrine_Expression throw exception for unportable RDBMS expressions - * - * @see self::ATTR_PORTABILITY - */ - const PORTABILITY_EXPR = 32; - - /** - * PORTABILITY_ALL - * - * Portability: turn on all portability features. - * - * @see self::ATTR_PORTABILITY - */ - const PORTABILITY_ALL = 63; - - /** - * LOCK CONSTANTS - */ - - /** - * LOCK_OPTIMISTIC - * - * mode for optimistic locking - * @see self::ATTR_LOCK - * @deprecated??? - */ - const LOCK_OPTIMISTIC = 0; - - /** - * LOCK_PESSIMISTIC - * - * mode for pessimistic locking - * - * @see self::ATTR_LOCK - * @deprecated??? - */ - const LOCK_PESSIMISTIC = 1; - - /** - * EXPORT CONSTANTS - */ - - /** - * EXPORT_NONE - * - * @see self::ATTR_EXPORT - */ - const EXPORT_NONE = 0; - - /** - * EXPORT_TABLES - * - * @see self::ATTR_EXPORT - */ - const EXPORT_TABLES = 1; - - /** - * EXPORT_CONSTRAINTS - * - * @see self::ATTR_EXPORT - */ - const EXPORT_CONSTRAINTS = 2; - - /** - * EXPORT_PLUGINS - * - * @see self::ATTR_EXPORT - */ - const EXPORT_PLUGINS = 4; - - /** - * EXPORT_ALL - * - * @see self::ATTR_EXPORT - */ - const EXPORT_ALL = 7; - - /** - * HYDRATE CONSTANTS - */ - - /** - * HYDRATE_RECORD - * - * @see self::ATTR_HYDRATE - */ - const HYDRATE_RECORD = 2; - - /** - * HYDRATE_ARRAY - * - * @see self::ATTR_HYDRATE - */ - const HYDRATE_ARRAY = 3; - - /** - * HYDRATE_NONE - * - * @see self::ATTR_HYDRATE - */ - const HYDRATE_NONE = 4; - - /* new hydration modes. move to Query class when it's time. */ - const HYDRATE_IDENTITY_OBJECT = 2; // default, auto-adds PKs, produces object graphs - const HYDRATE_IDENTITY_ARRAY = 3; // auto-adds PKs, produces array graphs - const HYDRATE_SCALAR = 5; // produces flat result list with scalar values - const HYDRATE_SINGLE_SCALAR = 6; // produces a single scalar value - //const HYDRATE_NONE = 4; // produces a result set as it's returned by the db - - - /** - * VALIDATE CONSTANTS - * - * @see self::ATTR_VALIDATE - */ - const VALIDATE_NONE = 0; - - /** - * VALIDATE_LENGTHS - * - * @see self::ATTR_VALIDATE - */ - const VALIDATE_LENGTHS = 1; - - /** - * VALIDATE_TYPES - * - * @see self::ATTR_VALIDATE - */ - const VALIDATE_TYPES = 2; - - /** - * VALIDATE_CONSTRAINTS - * - * @see self::ATTR_VALIDATE - * Not used? Purpose? - */ - const VALIDATE_CONSTRAINTS = 4; - - /** - * VALIDATE_ALL - * - * @see self::ATTR_VALIDATE - */ - const VALIDATE_ALL = 7; - - /** - * IDENTIFIER_AUTOINC - * - * constant for auto_increment identifier - * - * @see self::ATTR_IDENTIFIER - */ - const IDENTIFIER_AUTOINC = 1; - - /** - * IDENTIFIER_SEQUENCE - * - * constant for sequence identifier - * - * @see self::ATTR_IDENTIFIER - */ - const IDENTIFIER_SEQUENCE = 2; - - /** - * IDENTIFIER_NATURAL - * - * constant for normal identifier - * - * @see self::ATTR_IDENTIFIER - */ - const IDENTIFIER_NATURAL = 3; - - /** - * IDENTIFIER_COMPOSITE - * - * constant for composite identifier - * @see self::ATTR_IDENTIFIER - */ - const IDENTIFIER_COMPOSITE = 4; - /** * MODEL_LOADING_AGGRESSIVE * @@ -571,33 +118,6 @@ final class Doctrine * @see self::ATTR_MODEL_LOADING */ const MODEL_LOADING_CONSERVATIVE = 2; - - /** - * INHERITANCE TYPE CONSTANTS. - */ - const INHERITANCE_TYPE_NONE = 0; - - /** - * Constant for Single Table Inheritance. - * - * @see http://martinfowler.com/eaaCatalog/singleTableInheritance.html - */ - const INHERITANCE_TYPE_SINGLE_TABLE = 1; - - /** - * Constant for Class Table Inheritance. - * - * @see http://martinfowler.com/eaaCatalog/classTableInheritance.html - */ - const INHERITANCE_TYPE_JOINED = 2; - - /** - * Constant for Concrete Table Inheritance. - * - * @see http://martinfowler.com/eaaCatalog/concreteTableInheritance.html - */ - const INHERITANCE_TYPE_TABLE_PER_CLASS = 3; - /** * Path @@ -614,7 +134,6 @@ final class Doctrine * @var array */ private static $_loadedModelFiles = array(); - private static $_pathModels = array(); /** @@ -1115,44 +634,6 @@ final class Doctrine } } - /** - * dump - * - * dumps a given variable - * - * @param mixed $var a variable of any type - * @param boolean $output whether to output the content - * @param string $indent indention string - * @return void|string - */ - public static function dump($var, $output = true, $indent = "") - { - $ret = array(); - switch (gettype($var)) { - case 'array': - $ret[] = 'Array('; - $indent .= " "; - foreach ($var as $k => $v) { - - $ret[] = $indent . $k . ' : ' . self::dump($v, false, $indent); - } - $indent = substr($indent,0, -4); - $ret[] = $indent . ")"; - break; - case 'object': - $ret[] = 'Object(' . get_class($var) . ')'; - break; - default: - $ret[] = var_export($var, true); - } - - if ($output) { - print implode("\n", $ret); - } - - return implode("\n", $ret); - } - /** * tableize * diff --git a/lib/Doctrine/Common/ClassLoader.php b/lib/Doctrine/Common/ClassLoader.php index e6184c45f..cf460fc51 100644 --- a/lib/Doctrine/Common/ClassLoader.php +++ b/lib/Doctrine/Common/ClassLoader.php @@ -6,9 +6,10 @@ * Usage recommendation: * 1) Use only 1 class loader instance. * 2) Prepend the base paths to your class libraries (including Doctrine's) to your include path. - * 3) DO NOT setCheckFileExists(true). Doing so is expensive. + * 3) DO NOT setCheckFileExists(true). Doing so is expensive in terms of performance. * * @since 2.0 + * @author romanb */ class Doctrine_Common_ClassLoader { @@ -79,12 +80,13 @@ class Doctrine_Common_ClassLoader /** * Registers this class loader using spl_autoload_register(). + * + * @return void */ public function register() { spl_autoload_register(array($this, 'loadClass')); } - } diff --git a/lib/Doctrine/Common/ClassMetadata.php b/lib/Doctrine/Common/ClassMetadata.php new file mode 100644 index 000000000..11fd5bae2 --- /dev/null +++ b/lib/Doctrine/Common/ClassMetadata.php @@ -0,0 +1,139 @@ + $className + */ + public function __construct($className) + { + $this->_entityName = $className; + } + + /** + * Adds metadata to a property of the class. + * + * @param string $fieldName + * @param array $fieldMetadata + */ + public function addFieldMetadata($fieldName, array $fieldMetadata) + { + $this->_fieldMetadata[$fieldName] = array_merge( + isset($this->_fieldMetadata[$fieldName]) ? $this->_fieldMetadata[$fieldName] : array(), + $fieldMetadata); + } + + /** + * + * + * @param $fieldName + * @param $metadata + */ + public function setFieldMetadata($fieldName, array $metadata) + { + $this->_fieldMetadata[$fieldName] = $metadata; + } + + /** + * + * @param $fieldName + * @param $metadataKey + * @param $value + */ + public function setFieldMetadataEntry($fieldName, $metadataKey, $value) + { + $this->_fieldMetadata[$fieldName][$metadataKey] = $value; + } + + /** + * Gets metadata of a property of the class. + * + * @param string $fieldName + * @param string $metadataKey + * @return mixed + */ + public function getFieldMetadata($fieldName) + { + return $this->_fieldMetadata[$fieldName]; + } + + /** + * + * @param $fieldName + * @param $metadataKey + * @return + */ + public function getFieldMetadataEntry($fieldName, $metadataKey) + { + return isset($this->_fieldMetadata[$fieldName][$metadataKey]) ? + $this->_fieldMetadata[$fieldName][$metadataKey] : null; + } + + /** + * Gets metadata of the class. + * + * @param string $metadataKey + * @return mixed + */ + public function getClassMetadata() + { + return $this->_classMetadata; + } + + /** + * + * + * @param $metadataKey + */ + public function getClassMetadataEntry($metadataKey) + { + return isset($this->_classMetadata[$metadataKey]) ? + $this->_classMetadata[$metadataKey] : null; + } + + /** + * Adds metadata to the class. + * + * @param array $classMetadata + */ + public function addClassMetadata(array $classMetadata) + { + $this->_classMetadata = array_merge($this->_classMetadata, $classMetadata); + } + + /** + * + * + * @param $metadata + */ + public function setClassMetadata(array $metadata) + { + $this->_classMetadata = $metadata; + } + + /** + * + * @param $metadataKey + * @param $value + */ + public function setClassMetadataEntry($metadataKey, $value) + { + $this->_classMetadata[$metadataKey] = $value; + } +} +?> diff --git a/lib/Doctrine/Common/ClassMetadataFactory.php b/lib/Doctrine/Common/ClassMetadataFactory.php new file mode 100644 index 000000000..b714042e4 --- /dev/null +++ b/lib/Doctrine/Common/ClassMetadataFactory.php @@ -0,0 +1,59 @@ +_driver = $driver; + } + + /** + * Returns the metadata object for a class. + * + * @param string $className The name of the class. + * @return Doctrine_Metadata + */ + public function getMetadataFor($className) + { + if ( ! isset($this->_loadedMetadata[$className])) { + $this->_loadMetadata($className); + $this->_validateAndCompleteMetadata($className); + } + return $this->_loadedMetadata[$className]; + } + + /** + * Loads the metadata for the given class. + * + * @param $className + * @return + */ + protected function _loadMetadata($className) + { + $classMetadata = new Doctrine_Common_ClassMetadata(); + $this->_driver->loadMetadataForClass($className, $classMetadata); + return $classMetadata; + } + + /** Template method for subclasses */ + protected function _validateAndCompleteMetadata($className) + { /*empty*/ } +} +?> diff --git a/lib/Doctrine/Common/Collections/Collection.php b/lib/Doctrine/Common/Collections/Collection.php new file mode 100644 index 000000000..0a6a2003a --- /dev/null +++ b/lib/Doctrine/Common/Collections/Collection.php @@ -0,0 +1,372 @@ +_data; + } + + /** + * returns the first record in the collection + * + * @return mixed + */ + public function getFirst() + { + return reset($this->_data); + } + + /** + * returns the last record in the collection + * + * @return mixed + */ + public function getLast() + { + return end($this->_data); + } + + /** + * returns the last record in the collection + * + * @return mixed + */ + public function end() + { + return end($this->_data); + } + + /** + * returns the current key + * + * @return mixed + */ + public function key() + { + return key($this->_data); + } + + /** + * Removes an entry from the collection. + * + * @param mixed $key + * @return boolean + */ + public function remove($key) + { + $removed = $this->_data[$key]; + unset($this->_data[$key]); + return $removed; + } + + /** + * __isset() + * + * @param string $name + * @return boolean whether or not this object contains $name + */ + public function __isset($key) + { + return $this->containsKey($key); + } + + /** + * __unset() + * + * @param string $name + * @since 1.0 + * @return mixed + */ + public function __unset($key) + { + return $this->remove($key); + } + + /** + * Check if an offsetExists. + * + * Part of the ArrayAccess implementation. + * + * @param mixed $offset + * @return boolean whether or not this object contains $offset + */ + public function offsetExists($offset) + { + return $this->containsKey($offset); + } + + /** + * offsetGet an alias of get() + * + * Part of the ArrayAccess implementation. + * + * @see get, __get + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->get($offset); + } + + /** + * Part of the ArrayAccess implementation. + * + * sets $offset to $value + * @see set, __set + * @param mixed $offset + * @param mixed $value + * @return void + */ + public function offsetSet($offset, $value) + { + if ( ! isset($offset)) { + return $this->add($value); + } + return $this->set($offset, $value); + } + + /** + * Part of the ArrayAccess implementation. + * + * unset a given offset + * @see set, offsetSet, __set + * @param mixed $offset + */ + public function offsetUnset($offset) + { + return $this->remove($offset); + } + + /** + * Checks whether the collection contains an entity. + * + * @param mixed $key the key of the element + * @return boolean + */ + public function containsKey($key) + { + return isset($this->_data[$key]); + } + + /** + * Enter description here... + * + * @param unknown_type $entity + * @return unknown + */ + public function contains($entity) + { + return in_array($entity, $this->_data, true); + } + + /** + * Enter description here... + * + * @param unknown_type $otherColl + * @todo Impl + */ + public function containsAll($otherColl) + { + //... + } + + /** + * + */ + public function search(Doctrine_ORM_Entity $record) + { + return array_search($record, $this->_data, true); + } + + /** + * returns a record for given key + * + * Collection also maps referential information to newly created records + * + * @param mixed $key the key of the element + * @return Doctrine_Entity return a specified record + */ + public function get($key) + { + if (isset($this->_data[$key])) { + return $this->_data[$key]; + } + return null; + } + + /** + * Gets all keys. + * (Map method) + * + * @return array + */ + public function getKeys() + { + return array_keys($this->_data); + } + + /** + * Gets all values. + * (Map method) + * + * @return array + */ + public function getValues() + { + return array_values($this->_data); + } + + /** + * Returns the number of records in this collection. + * + * Implementation of the Countable interface. + * + * @return integer The number of records in the collection. + */ + public function count() + { + return count($this->_data); + } + + /** + * When the collection is a Map this is like put(key,value)/add(key,value). + * When the collection is a List this is like add(position,value). + * + * @param integer $key + * @param mixed $value + * @return void + */ + public function set($key, $value) + { + if ( ! $value instanceof Doctrine_ORM_Entity) { + throw new Doctrine_Collection_Exception('Value variable in set is not an instance of Doctrine_Entity'); + } + $this->_data[$key] = $value; + //TODO: Register collection as dirty with the UoW if necessary + $this->_changed(); + } + + /** + * Adds an entry to the collection. + * + * @param mixed $value + * @param string $key + * @return boolean + */ + public function add($value, $key = null) + { + //TODO: really only allow entities? + if ( ! $value instanceof Doctrine_ORM_Entity) { + throw new Doctrine_Record_Exception('Value variable in collection is not an instance of Doctrine_Entity.'); + } + + // TODO: Really prohibit duplicates? + if (in_array($value, $this->_data, true)) { + return false; + } + + if (isset($key)) { + if (isset($this->_data[$key])) { + return false; + } + $this->_data[$key] = $value; + } else { + $this->_data[] = $value; + } + + if ($this->_hydrationFlag) { + if ($this->_backRefFieldName) { + // set back reference to owner + $value->_internalSetReference($this->_backRefFieldName, $this->_owner); + } + } else { + //TODO: Register collection as dirty with the UoW if necessary + $this->_changed(); + } + + return true; + } + + /** + * Adds all entities of the other collection to this collection. + * + * @param unknown_type $otherCollection + * @todo Impl + */ + public function addAll($otherCollection) + { + } + + /** + * Checks whether the collection is empty. + * + * @return boolean TRUE if the collection is empty, FALSE otherwise. + */ + public function isEmpty() + { + // Note: Little "trick". Empty arrays evaluate to FALSE. No need to count(). + return ! (bool)$this->_data; + } + + /** + * getIterator + * + * @return object ArrayIterator + */ + public function getIterator() + { + $data = $this->_data; + return new ArrayIterator($data); + } + + /** + * returns a string representation of this object + */ + public function __toString() + { + return Doctrine_Lib::getCollectionAsString($this); + } + + /** + * Clears the collection. + * + * @return void + */ + public function clear() + { + $this->_data = array(); + } +} +?> diff --git a/lib/Doctrine/Common/Configuration.php b/lib/Doctrine/Common/Configuration.php index 111305c9c..9d211eb7e 100644 --- a/lib/Doctrine/Common/Configuration.php +++ b/lib/Doctrine/Common/Configuration.php @@ -140,7 +140,7 @@ class Doctrine_Common_Configuration public function setTypeOverrides(array $overrides) { foreach ($override as $name => $typeClassName) { - Doctrine_DataType::overrideType($name, $typeClassName); + Doctrine_DBAL_Types_Type::overrideType($name, $typeClassName); } } } \ No newline at end of file diff --git a/lib/Doctrine/Common/EventManager.php b/lib/Doctrine/Common/EventManager.php index b679df1d9..c148f5bc0 100644 --- a/lib/Doctrine/Common/EventManager.php +++ b/lib/Doctrine/Common/EventManager.php @@ -92,11 +92,7 @@ class Doctrine_Common_EventManager public function addEventListener($events, $listener) { // TODO: maybe check for duplicate registrations? - if ( ! is_array($events)) { - $events = array($events); - } - - foreach ($events as $event) { + foreach ((array)$events as $event) { $this->_listeners[$event] = $listener; } } diff --git a/lib/Doctrine/Common/EventSubscriber.php b/lib/Doctrine/Common/EventSubscriber.php index 7333fcaf5..1dffe7ee8 100644 --- a/lib/Doctrine/Common/EventSubscriber.php +++ b/lib/Doctrine/Common/EventSubscriber.php @@ -18,13 +18,13 @@ * and is licensed under the LGPL. For more information, see * . */ - -#namespace Doctrine::Common; + +#namespace Doctrine::Common; /** - * An EventSubscriber knows itself what events it is interested in. - * If an EventSubscriber is added to an EventManager, the manager invokes - * getSubscribedEvents() and registers the subscriber as a listener for all + * An EventSubscriber knows himself what events he is interested in. + * If an EventSubscriber is added to an EventManager, the manager invokes + * getSubscribedEvents() and registers the subscriber as a listener for all * returned events. * * @author Roman Borschel diff --git a/lib/Doctrine/Common/Events/Event.php b/lib/Doctrine/Common/Events/Event.php index 37d56a3c3..22a33ffa7 100644 --- a/lib/Doctrine/Common/Events/Event.php +++ b/lib/Doctrine/Common/Events/Event.php @@ -18,8 +18,8 @@ * and is licensed under the LGPL. For more information, see * . */ - -#namespace Doctrine::Common::Events; + +#namespace Doctrine::Common::Events; /** * Doctrine_Event @@ -32,47 +32,46 @@ * @since 2.0 * @version $Revision$ */ -class Doctrine_Common_Events_Event -{ - /* Event callback constants */ - const preDelete = 'preDelete'; - const postDelete = 'postDelete'; - //...more - - - protected $_type; - protected $_target; - protected $_defaultPrevented; - - - public function __construct($type, $target = null) - { - $this->_type = $type; - $this->_target = $target; - $this->_defaultPrevented = false; - } - - - public function getType() - { - return $this->_type; - } - - - public function preventDefault() - { - $this->_defaultPrevented = true; - } - - - public function getDefaultPrevented() - { - return $this->_defaultPrevented; - } - - - public function getTarget() - { - return $this->_target; - } +class Doctrine_Common_Events_Event +{ + /* Event callback constants */ + const preDelete = 'preDelete'; + const postDelete = 'postDelete'; + //...more + + protected $_type; + protected $_target; + protected $_defaultPrevented; + + + public function __construct($type, $target = null) + { + $this->_type = $type; + $this->_target = $target; + $this->_defaultPrevented = false; + } + + + public function getType() + { + return $this->_type; + } + + + public function preventDefault() + { + $this->_defaultPrevented = true; + } + + + public function getDefaultPrevented() + { + return $this->_defaultPrevented; + } + + + public function getTarget() + { + return $this->_target; + } } diff --git a/lib/Doctrine/Common/Exceptions/DoctrineException.php b/lib/Doctrine/Common/Exceptions/DoctrineException.php index 4a331dbff..428890a94 100644 --- a/lib/Doctrine/Common/Exceptions/DoctrineException.php +++ b/lib/Doctrine/Common/Exceptions/DoctrineException.php @@ -17,7 +17,7 @@ class Doctrine_Common_Exceptions_DoctrineException extends Exception public static function notImplemented($method, $class) { - return new self("The method '$method' is not implemented in the class '$class'."); + return new self("The method '$method' is not implemented in class '$class'."); } } diff --git a/lib/Doctrine/Common/VirtualPropertyObject.php b/lib/Doctrine/Common/VirtualPropertyObject.php new file mode 100644 index 000000000..da5e07ef0 --- /dev/null +++ b/lib/Doctrine/Common/VirtualPropertyObject.php @@ -0,0 +1,260 @@ +_entityName = get_class($this); + if ( ! Doctrine_Common_VirtualPropertySystem::isInitialized($this->_entityName)) { + Doctrine_Common_VirtualPropertySystem::initialize($this->_entityName); + } + } + + /** + * Generic getter for virtual properties. + * + * @param string $fieldName Name of the field. + * @return mixed + */ + final public function get($fieldName) + { + if ( ! Doctrine_Common_VirtualPropertySystem::hasProperty($this->_entityName, $fieldName)) { + throw new Doctrine_Exception("Access of undefined property '$fieldName'."); + } + $getter = $this->_getCustomAccessor($fieldName); + if ($getter) { + return $this->$getter(); + } + return $this->_get($fieldName); + } + + /** + * Generic setter for virtual properties. + * + * @param string $name The name of the field to set. + * @param mixed $value The value of the field. + */ + final public function set($fieldName, $value) + { + if ( ! Doctrine_Common_VirtualPropertySystem::hasProperty($this->_entityName, $fieldName)) { + throw new Doctrine_Exception("Access of undefined property '$fieldName'."); + } + if (Doctrine_Common_VirtualPropertySystem::isTypeCheckEnabled()) { + $this->_checkType($fieldName, $value); + } + $setter = $this->_getCustomMutator($fieldName); + if ($setter) { + return $this->$setter($value); + } + $this->_set($fieldName, $value); + } + + /** + * Checks the type of a virtual property. + * + * @param $fieldName + * @param $value + */ + protected function _checkType($fieldName, $value) + { + $type = Doctrine_Common_VirtualPropertySystem::getType($this->_entityName, $fieldName); + if (Doctrine_Common_VirtualPropertySystem::isSimplePHPType($type)) { + $is_type = "is_$type"; + if ( ! $is_type($value)) { + throw new Doctrine_Exception("'$value' is of an invalid type. Expected: $type."); + } + } else if ($type == 'array') { + if ( ! is_array($value)) { + throw new Doctrine_Exception("'$value' is of an invalid type. Expected: array."); + } + } else { + if ( ! $value instanceof $type) { + throw new Doctrine_Exception("'$value' is of an invalid type. Expected: $type."); + } + } + } + + protected function _get($fieldName) + { + return isset($this->_data[$fieldName]) ? $this->_data[$fieldName] : null; + } + + protected function _set($fieldName, $value) + { + $this->_data[$fieldName] = $value; + } + + /** + * Gets the custom mutator method for a virtual property, if it exists. + * + * @param string $fieldName The field name. + * @return mixed The name of the custom mutator or FALSE, if the field does + * not have a custom mutator. + */ + private function _getCustomMutator($fieldName) + { + if (Doctrine_Common_VirtualPropertySystem::getMutator($this->_entityName, $fieldName) === null) { + if (Doctrine_Common_VirtualPropertySystem::isAutoAccessorOverride()) { + $setterMethod = 'set' . Doctrine::classify($fieldName); + if ( ! method_exists($this, $setterMethod)) { + $setterMethod = false; + } + Doctrine_Common_VirtualPropertySystem::setMutator( + $this->_entityName, $fieldName, $setterMethod); + } else { + Doctrine_Common_VirtualPropertySystem::setMutator( + $this->_entityName, $fieldName, false); + } + } + return Doctrine_Common_VirtualPropertySystem::getMutator($this->_entityName, $fieldName); + } + + /** + * Gets the custom accessor method of a virtual property, if it exists. + * + * @param string $fieldName The field name. + * @return mixed The name of the custom accessor method, or FALSE if the + * field does not have a custom accessor. + */ + private function _getCustomAccessor($fieldName) + { + if (Doctrine_Common_VirtualPropertySystem::getAccessor($this->_entityName, $fieldName) === null) { + if (Doctrine_Common_VirtualPropertySystem::isAutoAccessorOverride()) { + $getterMethod = 'get' . Doctrine::classify($fieldName); + if ( ! method_exists($this, $getterMethod)) { + $getterMethod = false; + } + Doctrine_Common_VirtualPropertySystem::setAccessor( + $this->_entityName, $fieldName, $getterMethod); + } else { + Doctrine_Common_VirtualPropertySystem::setAccessor( + $this->_entityName, $fieldName, false); + } + } + + return Doctrine_Common_VirtualPropertySystem::getAccessor($this->_entityName, $fieldName); + } + + protected function _contains($fieldName) + { + return isset($this->_data[$fieldName]); + } + + protected function _unset($fieldName) + { + unset($this->_data[$fieldName]); + } + + /** + * Intercepts mutating calls for virtual properties. + * + * @see set, offsetSet + * @param $name + * @param $value + * @since 1.0 + * @return void + */ + public function __set($name, $value) + { + $this->set($name, $value); + } + + /** + * Intercepts accessing calls for virtual properties. + * + * @see get, offsetGet + * @param mixed $name + * @return mixed + */ + public function __get($name) + { + return $this->get($name); + } + + /** + * Intercepts isset() calls for virtual properties. + * + * @param string $name + * @return boolean whether or not this object contains $name + */ + public function __isset($name) + { + return $this->_contains($name); + } + + /** + * Intercepts unset() calls for virtual properties. + * + * @param string $name + * @return void + */ + public function __unset($name) + { + return $this->_unset($name); + } + + /* ArrayAccess implementation */ + + /** + * Check if an offsetExists. + * + * @param mixed $offset + * @return boolean whether or not this object contains $offset + */ + public function offsetExists($offset) + { + return $this->_contains($offset); + } + + /** + * offsetGet an alias of get() + * + * @see get, __get + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->get($offset); + } + + /** + * sets $offset to $value + * @see set, __set + * @param mixed $offset + * @param mixed $value + * @return void + */ + public function offsetSet($offset, $value) + { + return $this->set($offset, $value); + } + + /** + * unset a given offset + * @see set, offsetSet, __set + * @param mixed $offset + */ + public function offsetUnset($offset) + { + return $this->_unset($offset); + } + + /* END of ArrayAccess implementation */ +} +?> diff --git a/lib/Doctrine/Common/VirtualPropertySystem.php b/lib/Doctrine/Common/VirtualPropertySystem.php new file mode 100644 index 000000000..1a4a48713 --- /dev/null +++ b/lib/Doctrine/Common/VirtualPropertySystem.php @@ -0,0 +1,241 @@ + true, + 'string' => true, + 'bool' => true, + 'double' => true + ); + + /** Private constructor. This class cannot be instantiated. */ + private function __construct() {} + + /** + * Gets all properties of a class that are registered with the VirtualPropertySystem. + * + * @param string $class + * @return array + */ + public static function getProperties($class) + { + if ( ! self::isInitialized($class)) { + self::initialize($class); + } + return self::$_properties[$class]; + } + + /** + * Gets whether automatic accessor overrides are enabled. + * + * @return boolean + */ + public static function isAutoAccessorOverride() + { + return self::$_useAutoAccessorOverride; + } + + /** + * Sets whether automatic accessor overrides are enabled. + * + * @param boolean $bool + */ + public static function setAutoAccessorOverride($bool) + { + self::$_useAutoAccessorOverride = (bool)$bool; + } + + /** + * Prepopulates the property system. + * + * @param array $properties + */ + public static function populate(array $properties) + { + self::$_properties = $properties; + } + + /** + * Checks whether the given type is a simple PHP type. + * Simple php types are: int, string, bool, double. + * + * @param string $type The type to check. + * @return boolean + */ + public static function isSimplePHPType($type) + { + return isset(self::$_simplePHPTypes[$type]); + } + + /** + * Gets whether type checks are enabled. + * + * @return boolean + */ + public static function isTypeCheckEnabled() + { + return self::$_checkTypes; + } + + /** + * Sets whether type checks are enabled. + * + * @param boolean $bool + */ + public static function setTypeCheckEnabled($bool) + { + self::$_checkTypes = (bool)$bool; + } + + /** + * Sets the name of the callback method to use for initializing the virtual + * properties of a class. The callback must be static and public. + * + * @param string $callback + */ + public static function setCallback($callback) + { + self::$_callback = $callback; + } + + /** + * Registers a virtual property for a class. + * + * @param string $class + * @param string $propName + * @param string $type + * @param string $accessor + * @param string $mutator + */ + public static function register($class, $propName, $type, $accessor = null, $mutator = null) + { + self::$_properties[$class][$propName] = array( + 'type' => $type, 'accessor' => $accessor, 'mutator' => $mutator + ); + } + + /** + * Gets whether a class has already been initialized by the virtual property system. + * + * @param string $class + * @return boolean + */ + public static function isInitialized($class) + { + return isset(self::$_properties[$class]); + } + + /** + * Initializes a class with the virtual property system. + * + * @param $class + */ + public static function initialize($class) + { + if (method_exists($class, self::$_callback)) { + call_user_func(array($class, self::$_callback)); + } else { + self::$_properties[$class] = false; + } + } + + /** + * Gets whether a class has a virtual property with a certain name. + * + * @param string $class + * @param string $propName + * @return boolean + */ + public static function hasProperty($class, $propName) + { + return isset(self::$_properties[$class][$propName]); + } + + /** + * Gets the accessor for a virtual property. + * + * @param string $class + * @param string $propName + * @return string|null + */ + public static function getAccessor($class, $propName) + { + return isset(self::$_properties[$class][$propName]['accessor']) ? + self::$_properties[$class][$propName]['accessor'] : null; + } + + /** + * Sets the accessor method for a virtual property. + * + * @param $class + * @param $propName + * @param $accessor + */ + public static function setAccessor($class, $propName, $accessor) + { + self::$_properties[$class][$propName]['accessor'] = $accessor; + } + + /** + * Gets the mutator method for a virtual property. + * + * @param $class + * @param $propName + * @return + */ + public static function getMutator($class, $propName) + { + return isset(self::$_properties[$class][$propName]['mutator']) ? + self::$_properties[$class][$propName]['mutator'] : null; + } + + /** + * Sets the mutator method for a virtual property. + * + * @param $class + * @param $propName + * @param $mutator + */ + public static function setMutator($class, $propName, $mutator) + { + self::$_properties[$class][$propName]['mutator'] = $mutator; + } + + /** + * Gets the type of a virtual property. + * + * @param $class + * @param $propName + * @return + */ + public static function getType($class, $propName) + { + return isset(self::$_properties[$class][$propName]['type']) ? + self::$_properties[$class][$propName]['type'] : null; + } + + /** + * Sets the type of a virtual property. + * + * @param $class + * @param $propName + * @param $type + */ + public static function setType($class, $propName, $type) + { + self::$_properties[$class][$propName]['type'] = $type; + } +} +?> diff --git a/lib/Doctrine/DBAL/Connection.php b/lib/Doctrine/DBAL/Connection.php index fbbed1334..dd32ad3df 100644 --- a/lib/Doctrine/DBAL/Connection.php +++ b/lib/Doctrine/DBAL/Connection.php @@ -19,14 +19,14 @@ * . */ -#namespace Doctrine::DBAL; +#namespace Doctrine\DBAL; -#use Doctrine::Common::Configuration; -#use Doctrine::Common::EventManager; -#use Doctrine::DBAL::Exceptions::ConnectionException; +#use Doctrine\Common\Configuration; +#use Doctrine\Common\EventManager; +#use Doctrine\DBAL\Exceptions\ConnectionException; /** - * A wrapper around a Doctrine::DBAL::Driver::Connection that adds features like + * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like * events, transaction isolation levels, configuration, emulated transaction nesting, * lazy connecting and more. * @@ -51,7 +51,7 @@ * 'masters' => array(...), * 'masterConnectionResolver' => new MyMasterConnectionResolver() * - * Doctrine::DBAL could ship with a simple standard broker that uses a primitive + * Doctrine\DBAL could ship with a simple standard broker that uses a primitive * round-robin approach to distribution. User can provide its own brokers. */ class Doctrine_DBAL_Connection @@ -76,21 +76,21 @@ class Doctrine_DBAL_Connection /** * The wrapped driver connection. * - * @var Doctrine::DBAL::Driver::Connection + * @var Doctrine\DBAL\Driver\Connection */ protected $_conn; /** * The Configuration. * - * @var Doctrine::Common::Configuration + * @var Doctrine\Common\Configuration */ protected $_config; /** * The EventManager. * - * @var Doctrine::Commom::EventManager + * @var Doctrine\Common\EventManager */ protected $_eventManager; @@ -116,7 +116,7 @@ class Doctrine_DBAL_Connection protected $_transactionIsolationLevel; /** - * The parameters used during creation of the Connection. + * The parameters used during creation of the Connection instance. * * @var array */ @@ -133,32 +133,32 @@ class Doctrine_DBAL_Connection * The DatabasePlatform object that provides information about the * database platform used by the connection. * - * @var Doctrine::DBAL::Platforms::DatabasePlatform + * @var Doctrine\DBAL\Platforms\AbstractPlatform */ protected $_platform; /** * The schema manager. * - * @var Doctrine::DBAL::Schema::SchemaManager + * @var Doctrine\DBAL\Schema\SchemaManager */ protected $_schemaManager; /** * The used DBAL driver. * - * @var Doctrine::DBAL::Driver + * @var Doctrine\DBAL\Driver */ protected $_driver; /** - * Constructor. - * Creates a new Connection. + * Initializes a new instance of the Connection class. * * @param array $params The connection parameters. */ public function __construct(array $params, Doctrine_DBAL_Driver $driver, - Doctrine_Common_Configuration $config = null, Doctrine_Common_EventManager $eventManager = null) + Doctrine_Common_Configuration $config = null, + Doctrine_Common_EventManager $eventManager = null) { $this->_driver = $driver; $this->_params = $params; @@ -193,7 +193,7 @@ class Doctrine_DBAL_Connection /** * Gets the EventManager used by the Connection. * - * @return Doctrine::Common::EventManager + * @return Doctrine\Common\EventManager */ public function getEventManager() { @@ -203,7 +203,7 @@ class Doctrine_DBAL_Connection /** * Gets the DatabasePlatform for the connection. * - * @return Doctrine::DBAL::Platforms::DatabasePlatform + * @return Doctrine\DBAL\Platforms\AbstractPlatform */ public function getDatabasePlatform() { @@ -842,7 +842,7 @@ class Doctrine_DBAL_Connection /** * Gets the wrapped driver connection. * - * @return Doctrine::DBAL::Driver::Connection + * @return Doctrine\DBAL\Driver\Connection */ public function getWrappedConnection() { @@ -853,7 +853,7 @@ class Doctrine_DBAL_Connection * Gets the SchemaManager that can be used to inspect or change the * database schema through the connection. * - * @return Doctrine::DBAL::Schema::SchemaManager + * @return Doctrine\DBAL\Schema\SchemaManager */ public function getSchemaManager() { diff --git a/lib/Doctrine/DBAL/DriverManager.php b/lib/Doctrine/DBAL/DriverManager.php index 9e3c0edc2..af88780b8 100644 --- a/lib/Doctrine/DBAL/DriverManager.php +++ b/lib/Doctrine/DBAL/DriverManager.php @@ -90,7 +90,8 @@ final class Doctrine_DBAL_DriverManager * @param Doctrine::Common::EventManager The event manager to use. * @return Doctrine::DBAL::Connection */ - public static function getConnection(array $params, Doctrine_Common_Configuration $config = null, + public static function getConnection(array $params, + Doctrine_Common_Configuration $config = null, Doctrine_Common_EventManager $eventManager = null) { // create default config and event manager, if not set diff --git a/lib/Doctrine/DBAL/Types/Type.php b/lib/Doctrine/DBAL/Types/Type.php index 714476a9a..538296b69 100644 --- a/lib/Doctrine/DBAL/Types/Type.php +++ b/lib/Doctrine/DBAL/Types/Type.php @@ -1,6 +1,8 @@ 'Doctrine_DataType_DoubleType' ); - public function convertToDatabaseValue($value, Doctrine_DatabasePlatform $platform) + public function convertToDatabaseValue($value, Doctrine_DBAL_Platforms_AbstractDatabasePlatform $platform) { return $value; } @@ -24,8 +26,8 @@ abstract class Doctrine_DBAL_Types_Type return $value; } - abstract public function getDefaultLength(Doctrine_DatabasePlatform $platform); - abstract public function getSqlDeclaration(array $fieldDeclaration, Doctrine_DatabasePlatform $platform); + abstract public function getDefaultLength(Doctrine_DBAL_Platforms_AbstractDatabasePlatform $platform); + abstract public function getSqlDeclaration(array $fieldDeclaration, Doctrine_DBAL_Platforms_AbstractDatabasePlatform $platform); abstract public function getName(); /** diff --git a/lib/Doctrine/ORM/ActiveEntity.php b/lib/Doctrine/ORM/ActiveEntity.php index 87ba87254..5be8ce82d 100644 --- a/lib/Doctrine/ORM/ActiveEntity.php +++ b/lib/Doctrine/ORM/ActiveEntity.php @@ -10,8 +10,45 @@ * * @since 2.0 */ -class Doctrine_ORM_ActiveEntity extends Doctrine_ORM_Entity +class Doctrine_ORM_ActiveEntity extends Doctrine_Common_VirtualPropertyObject implements Doctrine_ORM_Entity { + /** + * The class descriptor. + * + * @var Doctrine::ORM::ClassMetadata + */ + private $_class; + + /** + * The changes that happened to fields of a managed entity. + * Keys are field names, values oldValue => newValue tuples. + * + * @var array + */ + private $_dataChangeSet = array(); + + /** + * The EntityManager that is responsible for the persistent state of the entity. + * Only managed entities have an associated EntityManager. + * + * @var Doctrine\ORM\EntityManager + */ + private $_em; + + /** + * Initializes a new instance of a class derived from ActiveEntity. + */ + public function __construct() { + parent::__construct(); + $this->_oid = self::$_index++; + $this->_em = Doctrine_ORM_EntityManager::getActiveEntityManager(); + if (is_null($this->_em)) { + throw new Doctrine_Exception("No EntityManager found. ActiveEntity instances " + . "can only be instantiated within the context of an active EntityManager."); + } + $this->_class = $this->_em->getClassMetadata($this->_entityName); + } + /** * Saves the current state of the entity into the database. * @@ -419,6 +456,270 @@ class Doctrine_ORM_ActiveEntity extends Doctrine_ORM_Entity $this->_data = array_merge($this->_data, $data); $this->_extractIdentifier(); } + + + /** + * Helps freeing the memory occupied by the entity. + * Cuts all references the entity has to other entities and removes the entity + * from the instance pool. + * Note: The entity is no longer useable after free() has been called. Any operations + * done with the entity afterwards can lead to unpredictable results. + * + * @param boolean $deep Whether to cascade the free() call to (loaded) associated entities. + */ + public function free($deep = false) + { + if ($this->_state != self::STATE_LOCKED) { + if ($this->_state == self::STATE_MANAGED) { + $this->_em->detach($this); + } + if ($deep) { + foreach ($this->_data as $name => $value) { + if ($value instanceof Doctrine_ORM_Entity || $value instanceof Doctrine_ORM_Collection) { + $value->free($deep); + } + } + } + $this->_data = array(); + } + } + + /** + * Returns a string representation of this object. + */ + public function __toString() + { + return (string)$this->_oid; + } + + /** + * Checks whether the entity is new. + * + * @return boolean TRUE if the entity is new, FALSE otherwise. + */ + final public function isNew() + { + return $this->_state == self::STATE_NEW; + } + + /** + * Checks whether the entity has been modified since it was last synchronized + * with the database. + * + * @return boolean TRUE if the object has been modified, FALSE otherwise. + */ + final public function isModified() + { + return count($this->_dataChangeSet) > 0; + } + + /** + * Gets the ClassMetadata object that describes the entity class. + * + * @return Doctrine::ORM::Mapping::ClassMetadata + */ + final public function getClass() + { + return $this->_class; + } + + /** + * Gets the EntityManager that is responsible for the persistence of + * this entity. + * + * @return Doctrine::ORM::EntityManager + */ + final public function getEntityManager() + { + return $this->_em; + } + + /** + * Gets the EntityRepository of the Entity. + * + * @return Doctrine::ORM::EntityRepository + */ + final public function getRepository() + { + return $this->_em->getRepository($this->_entityName); + } + + + /** + * Checks whether a field is set (not null). + * + * @param string $name + * @return boolean + * @override + */ + final protected function _contains($fieldName) + { + if (isset($this->_data[$fieldName])) { + if ($this->_data[$fieldName] === Doctrine_ORM_Internal_Null::$INSTANCE) { + return false; + } + return true; + } + return false; + } + + /** + * Clears the value of a field. + * + * @param string $name + * @return void + * @override + */ + final protected function _unset($fieldName) + { + if (isset($this->_data[$fieldName])) { + if ($this->_state == self::STATE_MANAGED && $this->_class->hasAssociation($fieldName)) { + $assoc = $this->_class->getAssociationMapping($fieldName); + if ($assoc->isOneToOne() && $assoc->shouldDeleteOrphans()) { + $this->_em->delete($this->_references[$fieldName]); + } else if ($assoc->isOneToMany() && $assoc->shouldDeleteOrphans()) { + foreach ($this->_references[$fieldName] as $entity) { + $this->_em->delete($entity); + } + } + } + $this->_data[$fieldName] = null; + } + } + + + /** + * Registers the entity as dirty with the UnitOfWork. + * Note: The Entity is only registered dirty if it is MANAGED and not yet + * registered as dirty. + */ + private function _registerDirty() + { + if ($this->_state == self::STATE_MANAGED && + ! $this->_em->getUnitOfWork()->isRegisteredDirty($this)) { + $this->_em->getUnitOfWork()->registerDirty($this); + } + } + + /** + * Gets the entity class name. + * + * @return string + */ + final public function getClassName() + { + return $this->_entityName; + } + + /** + * Gets the data of the Entity. + * + * @return array The fields and their values. + */ + final public function getData() + { + return $this->_data; + } + + /** + * Gets the value of a field (regular field or reference). + * + * @param $name Name of the field. + * @return mixed Value of the field. + * @throws Doctrine::ORM::Exceptions::EntityException If trying to get an unknown field. + * @override + */ + final protected function _get($fieldName) + { + $nullObj = Doctrine_ORM_Internal_Null::$INSTANCE; + if (isset($this->_data[$fieldName])) { + return $this->_data[$fieldName] !== $nullObj ? + $this->_data[$fieldName] : null; + } else { + if ($this->_state == self::STATE_MANAGED && $this->_class->hasAssociation($fieldName)) { + $rel = $this->_class->getAssociationMapping($fieldName); + if ($rel->isLazilyFetched()) { + $this->_data[$fieldName] = $rel->lazyLoadFor($this); + return $this->_data[$fieldName] !== $nullObj ? + $this->_data[$fieldName] : null; + } else { + return null; + } + } else { + return null; + } + } + } + + /** + * Sets the value of a field (regular field or reference). + * + * @param $fieldName The name of the field. + * @param $value The value of the field. + * @return void + * @throws Doctrine::ORM::Exceptions::EntityException + * @override + */ + final protected function _set($fieldName, $value) + { + $old = isset($this->_data[$fieldName]) ? $this->_data[$fieldName] : null; + if ( ! is_object($value)) { + // NOTE: Common case: $old != $value. Special case: null == 0 (TRUE), which + // is addressed by xor. + if ($old != $value || (is_null($old) xor is_null($value))) { + $this->_data[$fieldName] = $value; + $this->_dataChangeSet[$fieldName] = array($old => $value); + $this->_registerDirty(); + } + } else { + if ($old !== $value) { + $this->_internalSetReference($fieldName, $value); + $this->_dataChangeSet[$fieldName] = array($old => $value); + $this->_registerDirty(); + if ($this->_state == self::STATE_MANAGED) { + //TODO: Allow arrays in $value. Wrap them in a Collection transparently. + if ($old instanceof Doctrine_ORM_Collection) { + $this->_em->getUnitOfWork()->scheduleCollectionDeletion($old); + } + if ($value instanceof Doctrine_ORM_Collection) { + $this->_em->getUnitOfWork()->scheduleCollectionRecreation($value); + } + } + } + } + } + + /* Serializable implementation */ + + /** + * Serializes the entity. + * This method is automatically called when the entity is serialized. + * + * Part of the implementation of the Serializable interface. + * + * @return string + * @todo Reimplement + */ + public function serialize() + { + return ""; + } + + /** + * Reconstructs the entity from it's serialized form. + * This method is automatically called everytime the entity is unserialized. + * + * @param string $serialized Doctrine_Entity as serialized string + * @throws Doctrine_Record_Exception if the cleanData operation fails somehow + * @return void + * @todo Reimplement. + */ + public function unserialize($serialized) + { + ; + } + + /* END of Serializable implementation */ } ?> \ No newline at end of file diff --git a/lib/Doctrine/ORM/Collection.php b/lib/Doctrine/ORM/Collection.php index 5dfb70abc..2cd6314a3 100644 --- a/lib/Doctrine/ORM/Collection.php +++ b/lib/Doctrine/ORM/Collection.php @@ -45,6 +45,7 @@ * @author Konsta Vesterinen * @author Roman Borschel * @todo Add more typical Collection methods. + * @todo Rename to PersistentCollection */ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializable, ArrayAccess { @@ -74,7 +75,7 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa /** * This entity that owns this collection. * - * @var Doctrine::ORM::Entity + * @var Doctrine\ORM\Entity */ protected $_owner; @@ -82,7 +83,7 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa * The association mapping the collection belongs to. * This is currently either a OneToManyMapping or a ManyToManyMapping. * - * @var Doctrine::ORM::Mapping::AssociationMapping + * @var Doctrine\ORM\Mapping\AssociationMapping */ protected $_association; @@ -92,18 +93,11 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa * @var string */ protected $_keyField; - - /** - * Helper variable. Used for fast null value testing. - * - * @var Doctrine_Null - */ - //protected static $null; /** * The EntityManager that manages the persistence of the collection. * - * @var Doctrine::ORM::EntityManager + * @var Doctrine\ORM\EntityManager */ protected $_em; @@ -124,7 +118,6 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa protected $_hydrationFlag; /** - * Constructor. * Creates a new persistent collection. */ public function __construct($entityBaseType, $keyField = null) @@ -174,9 +167,9 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa } /** - * returns all the records as an array + * Unwraps the array contained in the Collection instance. * - * @return array + * @return array The wrapped array. */ public function unwrap() { @@ -229,7 +222,7 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa * * @return void */ - public function _setOwner(Doctrine_ORM_Entity $entity, Doctrine_ORM_Mapping_AssociationMapping $relation) + public function _setOwner($entity, Doctrine_ORM_Mapping_AssociationMapping $relation) { $this->_owner = $entity; $this->_association = $relation; @@ -248,9 +241,9 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa /** * INTERNAL: - * getReference + * Gets the collection owner. * - * @return mixed + * @return Doctrine\ORM\Entity */ public function _getOwner() { @@ -474,8 +467,7 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa */ public function add($value, $key = null) { - //TODO: really only allow entities? - if ( ! $value instanceof Doctrine_ORM_Entity) { + if ( ! $value instanceof $this->_entityBaseType) { throw new Doctrine_Record_Exception('Value variable in collection is not an instance of Doctrine_Entity.'); } @@ -496,7 +488,8 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa if ($this->_hydrationFlag) { if ($this->_backRefFieldName) { // set back reference to owner - $value->_internalSetReference($this->_backRefFieldName, $this->_owner); + $this->_em->getClassMetadata($this->_entityBaseType)->getReflectionProperty( + $this->_backRefFieldName)->setValue($value, $this->_owner); } } else { //TODO: Register collection as dirty with the UoW if necessary @@ -721,7 +714,8 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa */ public function isEmpty() { - return $this->count() == 0; + // Note: Little "trick". Empty arrays evaluate to FALSE. No need to count(). + return ! (bool)$this->_data; } /** @@ -765,40 +759,6 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa } } - /** - * Export a Doctrine_Collection to one of the supported Doctrine_Parser formats - * - * @param string $type - * @param string $deep - * @return void - * @todo Move elsewhere. - */ - /*public function exportTo($type, $deep = false) - { - if ($type == 'array') { - return $this->toArray($deep); - } else { - return Doctrine_Parser::dump($this->toArray($deep, true), $type); - } - }*/ - - /** - * Import data to a Doctrine_Collection from one of the supported Doctrine_Parser formats - * - * @param string $type - * @param string $data - * @return void - * @todo Move elsewhere. - */ - /*public function importFrom($type, $data) - { - if ($type == 'array') { - return $this->fromArray($data); - } else { - return $this->fromArray(Doctrine_Parser::load($data, $type)); - } - }*/ - /** * INTERNAL: getDeleteDiff * @@ -833,62 +793,9 @@ class Doctrine_ORM_Collection implements Countable, IteratorAggregate, Serializa } /** - * Saves all records of this collection and processes the - * difference of the last snapshot and the current data. * - * @param Doctrine_Connection $conn optional connection parameter - * @return Doctrine_Collection + * @param $deep */ - /*public function save() - { - $conn = $this->_mapper->getConnection(); - - try { - $conn->beginInternalTransaction(); - - $conn->transaction->addCollection($this); - $this->processDiff(); - foreach ($this->getData() as $key => $record) { - $record->save($conn); - } - - $conn->commit(); - } catch (Exception $e) { - $conn->rollback(); - throw $e; - } - - return $this; - }*/ - - /** - * Deletes all records from the collection. - * Shorthand for calling delete() for all entities in the collection. - * - * @return void - */ - /*public function delete() - { - $conn = $this->_mapper->getConnection(); - - try { - $conn->beginInternalTransaction(); - - $conn->transaction->addCollection($this); - foreach ($this as $key => $record) { - $record->delete($conn); - } - - $conn->commit(); - } catch (Exception $e) { - $conn->rollback(); - throw $e; - } - - $this->clear(); - }*/ - - public function free($deep = false) { foreach ($this->getData() as $key => $record) { diff --git a/lib/Doctrine/ORM/Entity.php b/lib/Doctrine/ORM/Entity.php index d9b6612d9..96bf9475b 100644 --- a/lib/Doctrine/ORM/Entity.php +++ b/lib/Doctrine/ORM/Entity.php @@ -1,6 +1,6 @@ . */ -#namespace Doctrine::ORM; +#namespace Doctrine\ORM; + +#use \Serializable; /** - * Base class for all Entities (objects with persistent state in a RDBMS that are - * managed by Doctrine). Kind of a Layer Supertype. - * - * NOTE: Methods that are intended for internal use only but must be public - * are marked INTERNAL: and begin with an underscore "_" to indicate that they - * ideally would not be public and to minimize naming collisions. - * - * The "final" modifiers on most methods prevent accidental overrides. - * It is not desirable that subclasses can override these methods. - * The persistence layer should stay in the background as much as possible. + * Entity marker interface. * - * @author Konsta Vesterinen * @author Roman Borschel * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.phpdoctrine.org + * @link www.doctrine-project.org * @since 2.0 * @version $Revision: 4342 $ */ -abstract class Doctrine_ORM_Entity implements ArrayAccess, Serializable -{ - /** - * MANAGED - * An Entity is in managed state when it has a primary key/identifier and is - * managed by an EntityManager (registered in the identity map). - */ - const STATE_MANAGED = 1; - - /** - * NEW - * An Entity is new if it does not yet have an identifier/primary key - * and is not (yet) managed by an EntityManager. - */ - const STATE_NEW = 2; - - /** - * LOCKED STATE - * An Entity is temporarily locked during deletes and saves. - * - * This state is used internally to ensure that circular deletes - * and saves will not cause infinite loops. - * @todo Not sure this is a good idea. It is a problematic solution because - * it hides the original state while the locked state is active. - */ - const STATE_LOCKED = 6; - - /** - * A detached Entity is an instance with a persistent identity that is not - * (or no longer) associated with an EntityManager (and a UnitOfWork). - * This means its no longer in the identity map. - */ - const STATE_DETACHED = 3; - - /** - * A removed Entity instance is an instance with a persistent identity, - * associated with an EntityManager, whose persistent state has been - * deleted (or is scheduled for deletion). - */ - const STATE_DELETED = 4; - - /** - * Index used for creating object identifiers (oid's). - * - * @var integer - */ - private static $_index = 1; - - /** - * Boolean flag that indicates whether automatic accessor overriding is enabled. - * - * @var boolean - */ - private static $_useAutoAccessorOverride; - - /** - * The accessor cache is used as a memory for the existance of custom accessors. - * - * @var array - */ - private static $_accessorCache = array(); - - /** - * The mutator cache is used as a memory for the existance of custom mutators. - * - * @var array - */ - private static $_mutatorCache = array(); - - /** - * The class descriptor. - * - * @var Doctrine::ORM::ClassMetadata - */ - private $_class; - - /** - * The name of the Entity. - * - * @var string - */ - private $_entityName; - - /** - * The values that make up the ID/primary key of the entity. - * - * @var array - */ - private $_id = array(); - - /** - * The entity data. - * - * @var array - */ - private $_data = array(); - - /** - * The state of the object. - * - * @var integer - */ - private $_state; - - /** - * Name => Value map of join columns. - * - * @var array - * @todo Not yet clear if needed. - */ - //private $_joinColumns = array(); - - /** - * The changes that happened to fields of the entity. - * Keys are field names, values oldValue => newValue tuples. - * - * @var array - */ - private $_dataChangeSet = array(); - - /** - * The changes that happened to references of the entity to other entities. - * Keys are field names, values oldReference => newReference tuples. - * - * With one-one associations, a reference change means the reference has been - * swapped out / replaced by another one. - * - * With one-many, many-many associations, a reference change means the complete - * collection has been sweapped out / replaced by another one. - * - * @var array - */ - private $_referenceChangeSet = array(); - - /** - * The references for all associations of the entity to other entities. - * Keys are field names, values object references. - * - * @var array - */ - private $_references = array(); - - /** - * The EntityManager that is responsible for the persistent state of the entity. - * - * @var Doctrine::ORM::EntityManager - */ - private $_em; - - /** - * The object identifier of the object. Each object has a unique identifier - * during script execution. - * - * @var integer - */ - private $_oid; - - /** - * Constructor. - * Creates a new Entity instance. - */ - public function __construct() - { - $this->_entityName = get_class($this); - $this->_em = Doctrine_ORM_EntityManager::getActiveEntityManager(); - $this->_class = $this->_em->getClassMetadata($this->_entityName); - $this->_oid = self::$_index++; - $this->_data = $this->_em->_getTmpEntityData(); - if ($this->_data) { - $this->_extractIdentifier(); - $this->_state = self::STATE_MANAGED; - } else { - $this->_state = self::STATE_NEW; - } - - // @todo read from attribute the first time and move this initialization elsewhere. - self::$_useAutoAccessorOverride = true; - } - - /** - * Returns the object identifier. - * - * @return integer - */ - final public function getOid() - { - return $this->_oid; - } - - /** - * Copies the identifier names and values from _data into _id. - */ - private function _extractIdentifier() - { - if ( ! $this->_class->isIdentifierComposite()) { - // Single field identifier - $name = $this->_class->getIdentifier(); - $name = $name[0]; - if (isset($this->_data[$name]) && $this->_data[$name] !== Doctrine_ORM_Internal_Null::$INSTANCE) { - $this->_id[$name] = $this->_data[$name]; - } - } else { - // Composite identifier - $names = $this->_class->getIdentifier(); - foreach ($names as $name) { - if ($this->_data[$name] === Doctrine_Null::$INSTANCE) { - $this->_id[$name] = null; - } else { - $this->_id[$name] = $this->_data[$name]; - } - } - } - } - - /** - * Serializes the entity. - * This method is automatically called when the entity is serialized. - * - * Part of the implementation of the Serializable interface. - * - * @return string - */ - public function serialize() - { - //$this->_em->getEventManager()->dispatchEvent(Event::preSerialize); - //$this->_class->dispatchLifecycleEvent(Event::preSerialize, $this); - - $vars = get_object_vars($this); - - unset($vars['_references']); - unset($vars['_em']); - - //$name = (array)$this->_table->getIdentifier(); - $this->_data = array_merge($this->_data, $this->_id); - - foreach ($this->_data as $k => $v) { - if ($v instanceof Doctrine_ORM_Entity && $this->_class->getTypeOfField($k) != 'object') { - unset($vars['_data'][$k]); - } else if ($v === Doctrine_Null::$INSTANCE) { - unset($vars['_data'][$k]); - } else { - switch ($this->_class->getTypeOfField($k)) { - case 'array': - case 'object': - $vars['_data'][$k] = serialize($vars['_data'][$k]); - break; - case 'gzip': - $vars['_data'][$k] = gzcompress($vars['_data'][$k]); - break; - case 'enum': - $vars['_data'][$k] = $this->_class->enumIndex($k, $vars['_data'][$k]); - break; - } - } - } - - $str = serialize($vars); - - //$this->postSerialize($event); - - return $str; - } - - /** - * Reconstructs the entity from it's serialized form. - * This method is automatically called everytime the entity is unserialized. - * - * @param string $serialized Doctrine_Entity as serialized string - * @throws Doctrine_Record_Exception if the cleanData operation fails somehow - * @return void - */ - public function unserialize($serialized) - { - //$event = new Doctrine_Event($this, Doctrine_Event::RECORD_UNSERIALIZE); - //$this->preUnserialize($event); - - $this->_entityName = get_class($this); - $manager = Doctrine_EntityManagerFactory::getManager($this->_entityName); - $connection = $manager->getConnection(); - - $this->_oid = self::$_index; - self::$_index++; - - $this->_em = $manager; - - $array = unserialize($serialized); - - foreach($array as $k => $v) { - $this->$k = $v; - } - - $this->_class = $this->_em->getClassMetadata($this->_entityName); - - foreach ($this->_data as $k => $v) { - switch ($this->_class->getTypeOfField($k)) { - case 'array': - case 'object': - $this->_data[$k] = unserialize($this->_data[$k]); - break; - case 'gzip': - $this->_data[$k] = gzuncompress($this->_data[$k]); - break; - case 'enum': - $this->_data[$k] = $this->_class->enumValue($k, $this->_data[$k]); - break; - - } - } - - $this->_extractIdentifier(!$this->isNew()); - - //$this->postUnserialize($event); - } - - /** - * INTERNAL: - * Gets or sets the state of this Entity. - * - * @param integer|string $state if set, this method tries to set the record state to $state - * @see Doctrine_Entity::STATE_* constants - * - * @throws Doctrine_Record_State_Exception if trying to set an unknown state - * @return null|integer - */ - final public function _state($state = null) - { - if ($state == null) { - return $this->_state; - } - $this->_state = $state; - } - - /** - * Gets the current field values. - * - * @return array The fields and their values. - */ - final public function getData() - { - return $this->_data; - } - - /** - * Gets the value of a field (regular field or reference). - * - * @param $name Name of the field. - * @return mixed Value of the field. - * @throws Doctrine::ORM::Exceptions::EntityException If trying to get an unknown field. - */ - final protected function _get($fieldName) - { - $nullObj = Doctrine_ORM_Internal_Null::$INSTANCE; - if (isset($this->_data[$fieldName])) { - return $this->_data[$fieldName] !== $nullObj ? - $this->_data[$fieldName] : null; - } else if (isset($this->_references[$fieldName])) { - return $this->_references[$fieldName] !== $nullObj ? - $this->_references[$fieldName] : null; - } else { - if ($this->_class->hasField($fieldName)) { - return null; - } else if ($this->_class->hasAssociation($fieldName)) { - $rel = $this->_class->getAssociationMapping($fieldName); - if ($rel->isLazilyFetched()) { - $this->_references[$fieldName] = $rel->lazyLoadFor($this); - return $this->_references[$fieldName] !== $nullObj ? - $this->_references[$fieldName] : null; - } else { - return null; - } - } else { - throw Doctrine_Entity_Exception::invalidField($fieldName); - } - } - } - - /** - * Sets the value of a field (regular field or reference). - * - * @param $fieldName The name of the field. - * @param $value The value of the field. - * @return void - * @throws Doctrine::ORM::Exceptions::EntityException - */ - final protected function _set($fieldName, $value) - { - if ($this->_class->hasField($fieldName)) { - $old = isset($this->_data[$fieldName]) ? $this->_data[$fieldName] : null; - // NOTE: Common case: $old != $value. Special case: null == 0 (TRUE), which - // is addressed by xor. - if ($old != $value || (is_null($old) xor is_null($value))) { - $this->_data[$fieldName] = $value; - $this->_dataChangeSet[$fieldName] = array($old => $value); - if ($this->isNew() && $this->_class->isIdentifier($fieldName)) { - $this->_id[$fieldName] = $value; - } - $this->_registerDirty(); - } - } else if ($this->_class->hasAssociation($fieldName)) { - $old = isset($this->_references[$fieldName]) ? $this->_references[$fieldName] : null; - if ($old !== $value) { - $this->_internalSetReference($fieldName, $value); - $this->_referenceChangeSet[$fieldName] = array($old => $value); - $this->_registerDirty(); - //TODO: Allow arrays in $value. Wrap them in a collection transparently. - if ($old instanceof Doctrine_Collection) { - $this->_em->getUnitOfWork()->scheduleCollectionDeletion($old); - } - if ($value instanceof Doctrine_Collection) { - $this->_em->getUnitOfWork()->scheduleCollectionRecreation($value); - } - } - } else { - throw Doctrine_ORM_Exceptions_EntityException::invalidField($fieldName); - } - } - - /** - * Registers the entity as dirty with the UnitOfWork. - */ - private function _registerDirty() - { - if ($this->_state == self::STATE_MANAGED && - ! $this->_em->getUnitOfWork()->isRegisteredDirty($this)) { - $this->_em->getUnitOfWork()->registerDirty($this); - } - } - - /** - * INTERNAL: - * Gets the value of a field. - * - * NOTE: Use of this method in userland code is strongly discouraged. - * This method does NOT check whether the field exists. - * _get() in extending classes should be preferred. - * - * @param string $fieldName - * @return mixed - */ - final public function _internalGetField($fieldName) - { - if ($this->_data[$fieldName] === Doctrine_ORM_Internal_Null::$INSTANCE) { - return null; - } - return $this->_data[$fieldName]; - } - - /** - * INTERNAL: - * Sets the value of a field. - * - * NOTE: Use of this method in userland code is strongly discouraged. - * This method does NOT check whether the field exists. - * _set() in extending classes should be preferred. - * - * @param string $fieldName - * @param mixed $value - */ - final public function _internalSetField($fieldName, $value) - { - $this->_data[$fieldName] = $value; - } - - /** - * INTERNAL: - * Gets a reference to another Entity. - * - * NOTE: Use of this method in userland code is strongly discouraged. - * This method does NOT check whether the reference exists. - * - * @param string $fieldName - */ - final public function _internalGetReference($fieldName) - { - if ($this->_references[$fieldName] === Doctrine_ORM_Internal_Null::$INSTANCE) { - return null; - } - return $this->_references[$fieldName]; - } - - /** - * INTERNAL: - * Sets a reference to another entity or a collection of entities. - * - * NOTE: Use of this method in userland code is strongly discouraged. - * - * @param string $fieldName - * @param mixed $value - * @param boolean $completeBidirectional Whether to complete bidirectional associations - * (creating the back-reference). Should only - * be used by hydration. - */ - final public function _internalSetReference($name, $value, $completeBidirectional = false) - { - if (is_null($value) || $value === Doctrine_ORM_Internal_Null::$INSTANCE) { - $this->_references[$name] = $value; - return; // early exit! - } - - $rel = $this->_class->getAssociationMapping($name); - - if ($rel->isOneToOne() && ! $value instanceof Doctrine_ORM_Entity) { - throw Doctrine_Entity_Exception::invalidValueForOneToOneReference(); - } else if (($rel->isOneToMany() || $rel->isManyToMany()) && ! $value instanceof Doctrine_ORM_Collection) { - throw Doctrine_Entity_Exception::invalidValueForOneToManyReference(); - } - - $this->_references[$name] = $value; - - if ($completeBidirectional && $rel->isOneToOne()) { - if ($rel->isOwningSide()) { - // If there is an inverse mapping on the target class its bidirectional - $targetClass = $this->_em->getClassMetadata($rel->getTargetEntityName()); - if ($targetClass->hasInverseAssociationMapping($name)) { - $value->_internalSetReference( - $targetClass->getInverseAssociationMapping($name)->getSourceFieldName(), - $this - ); - } - } else { - // for sure bidirectional, as there is no inverse side in unidirectional - $value->_internalSetReference($rel->getMappedByFieldName(), $this); - } - } - } - - /** - * Generic getter for all (persistent) fields of the entity. - * - * Invoked by Doctrine::ORM::Access#__get(). - * - * @param string $fieldName Name of the field. - * @return mixed - * @override - */ - final public function get($fieldName) - { - if ($getter = $this->_getCustomAccessor($fieldName)) { - return $this->$getter(); - } - return $this->_get($fieldName); - } - - /** - * Gets the custom mutator method for a field, if it exists. - * - * @param string $fieldName The field name. - * @return mixed The name of the custom mutator or FALSE, if the field does - * not have a custom mutator. - */ - private function _getCustomMutator($fieldName) - { - if ( ! isset(self::$_mutatorCache[$this->_entityName][$fieldName])) { - if (self::$_useAutoAccessorOverride) { - $setterMethod = 'set' . Doctrine::classify($fieldName); - if (method_exists($this, $setterMethod)) { - self::$_mutatorCache[$this->_entityName][$fieldName] = $setterMethod; - } else { - self::$_mutatorCache[$this->_entityName][$fieldName] = false; - } - } - - if ($setter = $this->_class->getCustomMutator($fieldName)) { - self::$_mutatorCache[$this->_entityName][$fieldName] = $setter; - } else if ( ! isset(self::$_mutatorCache[$this->_entityName][$fieldName])) { - self::$_mutatorCache[$this->_entityName][$fieldName] = false; - } - } - - return self::$_mutatorCache[$this->_entityName][$fieldName]; - } - - /** - * Gets the custom accessor method of a field, if it exists. - * - * @param string $fieldName The field name. - * @return mixed The name of the custom accessor method, or FALSE if the - * field does not have a custom accessor. - */ - private function _getCustomAccessor($fieldName) - { - if ( ! isset(self::$_accessorCache[$this->_entityName][$fieldName])) { - if (self::$_useAutoAccessorOverride) { - $getterMethod = 'get' . Doctrine::classify($fieldName); - if (method_exists($this, $getterMethod)) { - self::$_accessorCache[$this->_entityName][$fieldName] = $getterMethod; - } else { - self::$_accessorCache[$this->_entityName][$fieldName] = false; - } - } - if ($getter = $this->_class->getCustomAccessor($fieldName)) { - self::$_accessorCache[$this->_entityName][$fieldName] = $getter; - } else if ( ! isset(self::$_accessorCache[$this->_entityName][$fieldName])) { - self::$_accessorCache[$this->_entityName][$fieldName] = false; - } - } - - return self::$_accessorCache[$this->_entityName][$fieldName]; - } - - /** - * Gets the entity class name. - * - * @return string - */ - final public function getClassName() - { - return $this->_entityName; - } - - /** - * Generic setter for (persistent) fields of the entity. - * - * Invoked by Doctrine::ORM::Access#__set(). - * - * @param string $name The name of the field to set. - * @param mixed $value The value of the field. - * @override - */ - final public function set($fieldName, $value) - { - if ($setter = $this->_getCustomMutator($fieldName)) { - return $this->$setter($value); - } - $this->_set($fieldName, $value); - } - - /** - * Checks whether a field is set (not null). - * - * NOTE: Invoked by Doctrine::ORM::Access#__isset(). - * - * @param string $name - * @return boolean - */ - private function _contains($fieldName) - { - if (isset($this->_data[$fieldName])) { - if ($this->_data[$fieldName] === Doctrine_ORM_Internal_Null::$INSTANCE) { - return false; - } - return true; - } - if (isset($this->_id[$fieldName])) { - return true; - } - if (isset($this->_references[$fieldName]) && - $this->_references[$fieldName] !== Doctrine_ORM_Internal_Null::$INSTANCE) { - return true; - } - return false; - } - - /** - * Clears the value of a field. - * - * @param string $name - * @return void - */ - private function _unset($fieldName) - { - if (isset($this->_data[$fieldName])) { - $this->_data[$fieldName] = null; - } else if (isset($this->_references[$fieldName])) { - $assoc = $this->_class->getAssociationMapping($fieldName); - if ($assoc->isOneToOne() && $assoc->shouldDeleteOrphans()) { - $this->_em->delete($this->_references[$fieldName]); - } else if ($assoc->isOneToMany() && $assoc->shouldDeleteOrphans()) { - foreach ($this->_references[$fieldName] as $entity) { - $this->_em->delete($entity); - } - } - $this->_references[$fieldName] = null; - } - } - - /** - * INTERNAL: - * Gets the changeset of the entities data. - * - * @return array - */ - final public function _getDataChangeSet() - { - return $this->_dataChangeSet; - } - - /** - * INTERNAL: - * Gets the changeset of the entities references to other entities. - * - * @return array - */ - final public function _getReferenceChangeSet() - { - return $this->_referenceChangeSet; - } - - /** - * Checks whether the entity already has a persistent state. - * - * @return boolean TRUE if the object is new, FALSE otherwise. - */ - final public function isNew() - { - return $this->_state == self::STATE_NEW; - } - - /** - * Checks whether the entity has been modified since it was last synchronized - * with the database. - * - * @return boolean TRUE if the object has been modified, FALSE otherwise. - */ - final public function isModified() - { - return count($this->_fieldChangeSet) > 0; - } - - /** - * INTERNAL: - * Assigns an identifier to the entity. This is only intended for use by - * the EntityPersisters or the UnitOfWork. - * - * @param mixed $id - */ - final public function _assignIdentifier($id) - { - if (is_array($id)) { - foreach ($id as $fieldName => $value) { - $this->_id[$fieldName] = $value; - $this->_data[$fieldName] = $value; - } - } else { - $name = $this->_class->getSingleIdentifierFieldName(); - $this->_id[$name] = $id; - $this->_data[$name] = $id; - } - $this->_dataChangeSet = array(); - $this->_referenceChangeSet = array(); - } - - /** - * @todo Not yet clear if needed. - */ - /*final public function _setJoinColumn($columnName, $value) - { - $this->_joinColumns[$columnName] = $value; - }*/ - - /** - * @todo Not yet clear if needed. - */ - /*final public function _getJoinColumn($columnName) - { - return $this->_joinColumns[$columnName]; - }*/ - - /** - * INTERNAL: - * Returns the primary keys of the entity (field => value pairs). - * - * @return array - */ - final public function _identifier() - { - return $this->_id; - } - - /** - * INTERNAL: - * - * getReferences - * @return array all references - */ - final public function _getReferences() - { - return $this->_references; - } - - /** - * Gets the ClassMetadata object that describes the entity class. - * - * @return Doctrine::ORM::Mapping::ClassMetadata - */ - final public function getClass() - { - return $this->_class; - } - - /** - * Gets the EntityManager that is responsible for the persistence of - * the entity. - * - * @return Doctrine::ORM::EntityManager - */ - final public function getEntityManager() - { - return $this->_em; - } - - /** - * Gets the EntityRepository of the Entity. - * - * @return Doctrine::ORM::EntityRepository - */ - final public function getRepository() - { - return $this->_em->getRepository($this->_entityName); - } - - /** - * Helps freeing the memory occupied by the entity. - * Cuts all references the entity has to other entities and removes the entity - * from the instance pool. - * Note: The entity is no longer useable after free() has been called. Any operations - * done with the entity afterwards can lead to unpredictable results. - * - * @param boolean $deep Whether to cascade the free() call to (loaded) associated entities. - */ - public function free($deep = false) - { - if ($this->_state != self::STATE_LOCKED) { - $this->_em->detach($this); - $this->_data = array(); - $this->_id = array(); - - if ($deep) { - foreach ($this->_references as $name => $reference) { - if ( ! ($reference instanceof Doctrine_Null)) { - $reference->free($deep); - } - } - } - - $this->_references = array(); - } - } - - /** - * Check if an offsetExists. - * - * Part of the ArrayAccess implementation. - * - * @param mixed $offset - * @return boolean whether or not this object contains $offset - */ - public function offsetExists($offset) - { - return $this->_contains($offset); - } - - /** - * offsetGet an alias of get() - * - * Part of the ArrayAccess implementation. - * - * @see get, __get - * @param mixed $offset - * @return mixed - */ - public function offsetGet($offset) - { - return $this->get($offset); - } - - /** - * Part of the ArrayAccess implementation. - * - * sets $offset to $value - * @see set, __set - * @param mixed $offset - * @param mixed $value - * @return void - */ - public function offsetSet($offset, $value) - { - return $this->set($offset, $value); - } - - /** - * Part of the ArrayAccess implementation. - * - * unset a given offset - * @see set, offsetSet, __set - * @param mixed $offset - */ - public function offsetUnset($offset) - { - return $this->_unset($offset); - } - - /** - * __set - * - * @see set, offsetSet - * @param $name - * @param $value - * @since 1.0 - * @return void - */ - public function __set($name, $value) - { - $this->set($name, $value); - } - - /** - * __get - * - * @see get, offsetGet - * @param mixed $name - * @return mixed - */ - public function __get($name) - { - return $this->get($name); - } - - /** - * __isset() - * - * @param string $name - * @since 1.0 - * @return boolean whether or not this object contains $name - */ - public function __isset($name) - { - return $this->_contains($name); - } - - /** - * __unset() - * - * @param string $name - * @since 1.0 - * @return void - */ - public function __unset($name) - { - return $this->_unset($name); - } - - /** - * returns a string representation of this object - */ - public function __toString() - { - return (string)$this->_oid; - } -} +interface Doctrine_ORM_Entity +{} diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index 22333f4b3..2b2e0475f 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -19,14 +19,14 @@ * . */ -#namespace Doctrine::ORM; +#namespace Doctrine\ORM; -#use Doctrine::Common::Configuration; -#use Doctrine::Common::EventManager; -#use Doctrine::DBAL::Connection; -#use Doctrine::ORM::Exceptions::EntityManagerException; -#use Doctrine::ORM::Internal::UnitOfWork; -#use Doctrine::ORM::Mapping::ClassMetadata; +#use Doctrine\Common\Configuration; +#use Doctrine\Common\EventManager; +#use Doctrine\DBAL\Connection; +#use Doctrine\ORM\Exceptions\EntityManagerException; +#use Doctrine\ORM\Internal\UnitOfWork; +#use Doctrine\ORM\Mapping\ClassMetadata; /** * The EntityManager is the central access point to ORM functionality. @@ -138,8 +138,12 @@ class Doctrine_ORM_EntityManager * @var array */ private $_tmpEntityData = array(); + + private $_idGenerators = array(); private $_closed = false; + + private $_originalEntityData = array(); /** * Creates a new EntityManager that operates on the given database connection. @@ -155,7 +159,8 @@ class Doctrine_ORM_EntityManager $this->_config = $config; $this->_eventManager = $eventManager; $this->_metadataFactory = new Doctrine_ORM_Mapping_ClassMetadataFactory( - $this, new Doctrine_ORM_Mapping_Driver_CodeDriver()); + new Doctrine_ORM_Mapping_Driver_CodeDriver(), + $this->_conn->getDatabasePlatform()); $this->_unitOfWork = new Doctrine_ORM_UnitOfWork($this); $this->_nullObject = Doctrine_ORM_Internal_Null::$INSTANCE; } @@ -169,6 +174,14 @@ class Doctrine_ORM_EntityManager { return $this->_conn; } + + /** + * Gets the metadata factory used to gather the metadata of classes. + */ + public function getMetadataFactory() + { + return $this->_metadataFactory; + } /** * Gets the name of the EntityManager. @@ -180,16 +193,6 @@ class Doctrine_ORM_EntityManager return $this->_name; } - /** - * Gets the metadata for a class. Alias for getClassMetadata(). - * - * @return Doctrine_Metadata - */ - public function getMetadata($className) - { - return $this->getClassMetadata($className); - } - /** * Starts a database transaction. */ @@ -222,20 +225,41 @@ class Doctrine_ORM_EntityManager { return $this->_metadataFactory->getMetadataFor($className); } - + /** - * Sets the driver that is used to obtain metadata mapping information - * about Entities. - * - * @param $driver The driver to use. + * Gets an IdGenerator that can be used to generate identifiers for the specified + * class. */ - public function setClassMetadataDriver($driver) + public function getIdGenerator($className) { - $this->_metadataFactory->setDriver($driver); + if (!isset($this->_idGenerators[$className])) { + $this->_idGenerators[$className] = $this->_createIdGenerator( + $this->getClassMetadata($className)->getIdGeneratorType()); + } + return $this->_idGenerators[$className]; + } + + /** + * Used to lazily create the id generator. + * + * @param string $generatorType + * @return void + */ + protected function _createIdGenerator($generatorType) + { + if ($generatorType == Doctrine_ORM_Mapping_ClassMetadata::GENERATOR_TYPE_IDENTITY) { + return new Doctrine_ORM_Id_IdentityGenerator($this); + } else if ($generatorType == Doctrine_ORM_Mapping_ClassMetadata::GENERATOR_TYPE_SEQUENCE) { + return new Doctrine_ORM_Id_SequenceGenerator($this); + } else if ($generatorType == Doctrine_ORM_Mapping_ClassMetadata::GENERATOR_TYPE_TABLE) { + return new Doctrine_ORM_Id_TableGenerator($this); + } else { + return new Doctrine_ORM_Id_Assigned($this); + } } /** - * Creates a new Doctrine_Query object that operates on this connection. + * Creates a new Query object. * * @param string The DQL string. * @return Doctrine::ORM::Query @@ -405,7 +429,7 @@ class Doctrine_ORM_EntityManager /** * Saves the given entity, persisting it's state. * - * @param Doctrine::ORM::Entity $entity + * @param Doctrine\ORM\Entity $entity * @return void */ public function save(Doctrine_ORM_Entity $entity) @@ -487,12 +511,12 @@ class Doctrine_ORM_EntityManager * * @param string $className The name of the entity class. * @param array $data The data for the entity. - * @return Doctrine::ORM::Entity + * @return Doctrine\ORM\Entity */ - public function createEntity($className, array $data) + public function createEntity($className, array $data, Doctrine_Query $query = null) { $this->_errorIfNotActiveOrClosed(); - + $this->_tmpEntityData = $data; $className = $this->_inferCorrectClassName($data, $className); $classMetadata = $this->getClassMetadata($className); @@ -512,9 +536,9 @@ class Doctrine_ORM_EntityManager $entity = new $className; } else { $idHash = $this->_unitOfWork->getIdentifierHash($id); - if ($entity = $this->_unitOfWork->tryGetByIdHash($idHash, - $classMetadata->getRootClassName())) { - $this->_mergeData($entity, $data); + $entity = $this->_unitOfWork->tryGetByIdHash($idHash, $classMetadata->getRootClassName()); + if ($entity) { + $this->_mergeData($entity, $data/*, $classMetadata, $query->getHint('doctrine.refresh')*/); return $entity; } else { $entity = new $className; @@ -525,6 +549,8 @@ class Doctrine_ORM_EntityManager $entity = new $className; } + //$this->_originalEntityData[$entity->getOid()] = $data; + return $entity; } @@ -532,12 +558,12 @@ class Doctrine_ORM_EntityManager * Merges the given data into the given entity, optionally overriding * local changes. * - * @param Doctrine::ORM::Entity $entity + * @param Doctrine\ORM\Entity $entity * @param array $data * @param boolean $overrideLocalChanges * @return void */ - private function _mergeData(Doctrine_ORM_Entity $entity, array $data, $overrideLocalChanges = false) { + private function _mergeData(Doctrine_ORM_Entity $entity, /*$class,*/ array $data, $overrideLocalChanges = false) { if ($overrideLocalChanges) { foreach ($data as $field => $value) { $entity->_internalSetField($field, $value); @@ -550,6 +576,21 @@ class Doctrine_ORM_EntityManager } } } + + // NEW + /*if ($overrideLocalChanges) { + foreach ($data as $field => $value) { + $class->getReflectionProperty($field)->setValue($entity, $value); + } + } else { + foreach ($data as $field => $value) { + $currentValue = $class->getReflectionProperty($field)->getValue($entity); + if ( ! isset($this->_originalEntityData[$entity->getOid()]) || + $currentValue == $this->_originalEntityData[$entity->getOid()]) { + $class->getReflectionProperty($field)->setValue($entity, $value); + } + } + }*/ } /** @@ -628,6 +669,8 @@ class Doctrine_ORM_EntityManager /** * Throws an exception if the EntityManager is closed or currently not active. + * + * @throws EntityManagerException If the EntityManager is closed or not active. */ private function _errorIfNotActiveOrClosed() { @@ -668,15 +711,16 @@ class Doctrine_ORM_EntityManager /** * Factory method to create EntityManager instances. + * * A newly created EntityManager is immediately activated, making it the * currently active EntityManager. * * @param mixed $conn An array with the connection parameters or an existing - * Doctrine::DBAL::Connection instance. - * @param string $name - * @param Doctrine::Common::Configuration $config The Configuration instance to use. - * @param Doctrine::Common::EventManager $eventManager The EventManager instance to use. - * @return Doctrine::ORM::EntityManager The created EntityManager. + * Connection instance. + * @param string $name The name of the EntityManager. + * @param Configuration $config The Configuration instance to use. + * @param EventManager $eventManager The EventManager instance to use. + * @return EntityManager The created EntityManager. */ public static function create($conn, $name, Doctrine_Common_Configuration $config = null, Doctrine_Common_EventManager $eventManager = null) @@ -702,8 +746,6 @@ class Doctrine_ORM_EntityManager /** * Static lookup to get the currently active EntityManager. - * This is used in the Entity constructor as well as unserialize() to connect - * the Entity with an EntityManager. * * @return Doctrine::ORM::EntityManager */ diff --git a/lib/Doctrine/ORM/Id/AbstractIdGenerator.php b/lib/Doctrine/ORM/Id/AbstractIdGenerator.php index 37eae1b34..fea0f8089 100644 --- a/lib/Doctrine/ORM/Id/AbstractIdGenerator.php +++ b/lib/Doctrine/ORM/Id/AbstractIdGenerator.php @@ -18,7 +18,7 @@ abstract class Doctrine_ORM_Id_AbstractIdGenerator $this->_em = $em; } - abstract public function generate(Doctrine_ORM_Entity $entity); + abstract public function generate($entity); } ?> \ No newline at end of file diff --git a/lib/Doctrine/ORM/Id/Assigned.php b/lib/Doctrine/ORM/Id/Assigned.php index 2af5847a8..5f9ce9067 100644 --- a/lib/Doctrine/ORM/Id/Assigned.php +++ b/lib/Doctrine/ORM/Id/Assigned.php @@ -14,11 +14,12 @@ class Doctrine_ORM_Id_Assigned extends Doctrine_ORM_Id_AbstractIdGenerator * @return unknown * @override */ - public function generate(Doctrine_ORM_Entity $entity) + public function generate($entity) { if ( ! $entity->_identifier()) { - throw Doctrine_IdException::missingAssignedId($entity); + throw new Doctrine_Exception("Entity '$entity' is missing an assigned Id"); } + return $entity->_identifier(); } } diff --git a/lib/Doctrine/ORM/Id/IdentityGenerator.php b/lib/Doctrine/ORM/Id/IdentityGenerator.php index 0b2892f70..52b343f85 100644 --- a/lib/Doctrine/ORM/Id/IdentityGenerator.php +++ b/lib/Doctrine/ORM/Id/IdentityGenerator.php @@ -9,7 +9,7 @@ class Doctrine_ORM_Id_IdentityGenerator extends Doctrine_ORM_Id_AbstractIdGenera * @return unknown * @override */ - public function generate(Doctrine_ORM_Entity $entity) + public function generate($entity) { return self::POST_INSERT_INDICATOR; } diff --git a/lib/Doctrine/ORM/Id/SequenceGenerator.php b/lib/Doctrine/ORM/Id/SequenceGenerator.php index ec0894e57..d585c8bde 100644 --- a/lib/Doctrine/ORM/Id/SequenceGenerator.php +++ b/lib/Doctrine/ORM/Id/SequenceGenerator.php @@ -15,7 +15,7 @@ class Doctrine_ORM_Id_SequenceGenerator extends Doctrine_ORM_Id_AbstractIdGenera * @param Doctrine_ORM_Entity $entity * @override */ - public function generate(Doctrine_ORM_Entity $entity) + public function generate($entity) { $conn = $this->_em->getConnection(); $sql = $conn->getDatabasePlatform()->getSequenceNextValSql($this->_sequenceName); diff --git a/lib/Doctrine/ORM/Id/TableGenerator.php b/lib/Doctrine/ORM/Id/TableGenerator.php index f87c59f65..2d62d69b2 100644 --- a/lib/Doctrine/ORM/Id/TableGenerator.php +++ b/lib/Doctrine/ORM/Id/TableGenerator.php @@ -8,7 +8,7 @@ class Doctrine_ORM_Id_TableGenerator extends Doctrine_ORM_Id_AbstractIdGenerator { - public function generate(Doctrine_ORM_Entity $entity) + public function generate($entity) { throw new Exception("Not implemented"); } diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayDriver.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayDriver.php index 1443a57fa..afbfd1df1 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ArrayDriver.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayDriver.php @@ -129,6 +129,38 @@ class Doctrine_ORM_Internal_Hydration_ArrayDriver return key($data); } + /** + * Updates the result pointer for an Entity. The result pointers point to the + * last seen instance of each Entity type. This is used for graph construction. + * + * @param array $resultPointers The result pointers. + * @param array|Collection $coll The element. + * @param boolean|integer $index Index of the element in the collection. + * @param string $dqlAlias + * @param boolean $oneToOne Whether it is a single-valued association or not. + */ + public function updateResultPointer(&$resultPointers, &$coll, $index, $dqlAlias, $oneToOne) + { + if ($coll === null) { + unset($resultPointers[$dqlAlias]); // Ticket #1228 + return; + } + + if ($index !== false) { + $resultPointers[$dqlAlias] =& $coll[$index]; + return; + } + + if ($coll) { + if ($oneToOne) { + $resultPointers[$dqlAlias] =& $coll; + } else { + end($coll); + $resultPointers[$dqlAlias] =& $coll[key($coll)]; + } + } + } + /** * */ diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectDriver.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectDriver.php index fba4f8762..3b1d1836f 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectDriver.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectDriver.php @@ -19,7 +19,7 @@ * . */ -#namespace Doctrine::ORM::Internal::Hydration; +#namespace Doctrine\ORM\Internal\Hydration; /** * Hydration strategy used for creating graphs of entities. @@ -41,6 +41,7 @@ class Doctrine_ORM_Internal_Hydration_ObjectDriver private $_nullObject; /** The EntityManager */ private $_em; + private $_metadataMap = array(); public function __construct(Doctrine_ORM_EntityManager $em) { @@ -52,32 +53,36 @@ class Doctrine_ORM_Internal_Hydration_ObjectDriver { $coll = new Doctrine_ORM_Collection($component); $this->_collections[] = $coll; - return $coll; } public function getLastKey($coll) { - // check needed because of mixed results - if (is_array($coll)) { + // check needed because of mixed results. + // is_object instead of is_array because is_array is slow. + if (is_object($coll)) { + $coll->end(); + return $coll->key(); + } else { end($coll); return key($coll); - } else { - $coll->end(); - return $coll->key(); } } - public function initRelatedCollection(Doctrine_ORM_Entity $entity, $name) + public function initRelatedCollection($entity, $name) { - if ( ! isset($this->_initializedRelations[$entity->getOid()][$name])) { - $relation = $entity->getClass()->getAssociationMapping($name); + //$class = get_class($entity); + $oid = spl_object_id($entity); + $classMetadata = $this->_metadataMap[$oid]; + //$classMetadata = $this->_em->getClassMetadata(get_class($entity)); + if ( ! isset($this->_initializedRelations[$oid][$name])) { + $relation = $classMetadata->getAssociationMapping($name); $relatedClass = $this->_em->getClassMetadata($relation->getTargetEntityName()); $coll = $this->getElementCollection($relatedClass->getClassName()); $coll->_setOwner($entity, $relation); $coll->_setHydrationFlag(true); - $entity->_internalSetReference($name, $coll, true); - $this->_initializedRelations[$entity->getOid()][$name] = true; + $classMetadata->getReflectionProperty($name)->setValue($entity, $coll); + $this->_initializedRelations[$oid][$name] = true; } } @@ -93,49 +98,91 @@ class Doctrine_ORM_Internal_Hydration_ObjectDriver public function getElement(array $data, $className) { - return $this->_em->createEntity($className, $data); + $entity = $this->_em->getUnitOfWork()->createEntity($className, $data); + + $this->_metadataMap[spl_object_id($entity)] = $this->_em->getClassMetadata($className); + + return $entity; } - public function addRelatedIndexedElement(Doctrine_ORM_Entity $entity1, $property, - Doctrine_ORM_Entity $entity2, $indexField) + public function addRelatedIndexedElement($entity1, $property, $entity2, $indexField) { - $entity1->_internalGetReference($property)->add($entity2, $entity2->_internalGetField($indexField)); + $classMetadata1 = $this->_metadataMap[spl_object_id($entity1)]; + $classMetadata2 = $this->_metadataMap[spl_object_id($entity2)]; + //$classMetadata1 = $this->_em->getClassMetadata(get_class($entity1)); + //$classMetadata2 = $this->_em->getClassMetadata(get_class($entity2)); + $indexValue = $classMetadata2->getReflectionProperty($indexField)->getValue($entity2); + $classMetadata1->getReflectionProperty($property)->getValue($entity1)->add($entity2, $indexValue); } - public function addRelatedElement(Doctrine_ORM_Entity $entity1, $property, - Doctrine_ORM_Entity $entity2) + public function addRelatedElement($entity1, $property, $entity2) { - $entity1->_internalGetReference($property)->add($entity2); + $classMetadata1 = $this->_metadataMap[spl_object_id($entity1)]; + //$classMetadata1 = $this->_em->getClassMetadata(get_class($entity1)); + $classMetadata1->getReflectionProperty($property) + ->getValue($entity1)->add($entity2); } - public function setRelatedElement(Doctrine_ORM_Entity $entity1, $property, $entity2) + public function setRelatedElement($entity1, $property, $entity2) { - $entity1->_internalSetReference($property, $entity2, true); + $classMetadata1 = $this->_metadataMap[spl_object_id($entity1)]; + //$classMetadata1 = $this->_em->getClassMetadata(get_class($entity1)); + $classMetadata1->getReflectionProperty($property) + ->setValue($entity1, $entity2); + $relation = $classMetadata1->getAssociationMapping($property); + if ($relation->isOneToOne()) { + $targetClass = $this->_em->getClassMetadata($relation->getTargetEntityName()); + if ($relation->isOwningSide()) { + // If there is an inverse mapping on the target class its bidirectional + if ($targetClass->hasInverseAssociationMapping($property)) { + $refProp = $targetClass->getReflectionProperty( + $targetClass->getInverseAssociationMapping($fieldName) + ->getSourceFieldName()); + $refProp->setValue($entity2, $entity1); + } + } else { + // for sure bidirectional, as there is no inverse side in unidirectional + $targetClass->getReflectionProperty($relation->getMappedByFieldName()) + ->setValue($entity2, $entity1); + } + } } - public function isIndexKeyInUse(Doctrine_ORM_Entity $entity, $assocField, $indexField) + public function isIndexKeyInUse($entity, $assocField, $indexField) { - return $entity->_internalGetReference($assocField)->contains($indexField); + return $this->_metadataMap[spl_object_id($entity)]->getReflectionProperty($assocField) + ->getValue($entity)->containsKey($indexField); + /*return $this->_em->getClassMetadata(get_class($entity))->getReflectionProperty($assocField) + ->getValue($entity)->containsKey($indexField);*/ } - public function isFieldSet(Doctrine_ORM_Entity $entity, $field) + public function isFieldSet($entity, $field) { - return $entity->contains($field); + return $this->_metadataMap[spl_object_id($entity)]->getReflectionProperty($field) + ->getValue($entity) !== null; + /*return $this->_em->getClassMetadata(get_class($entity))->getReflectionProperty($field) + ->getValue($entity) !== null;*/ } - public function getFieldValue(Doctrine_ORM_Entity $entity, $field) + public function getFieldValue($entity, $field) { - return $entity->_internalGetField($field); + return $this->_metadataMap[spl_object_id($entity)]->getReflectionProperty($field) + ->getValue($entity); + /*return $this->_em->getClassMetadata(get_class($entity))->getReflectionProperty($field) + ->getValue($entity);*/ } - public function getReferenceValue(Doctrine_ORM_Entity $entity, $field) + public function getReferenceValue($entity, $field) { - return $entity->_internalGetReference($field); + return $this->_metadataMap[spl_object_id($entity)]->getReflectionProperty($field) + ->getValue($entity); + /*return $this->_em->getClassMetadata(get_class($entity))->getReflectionProperty($field) + ->getValue($entity);*/ } public function addElementToIndexedCollection($coll, $entity, $keyField) { - $coll->add($entity, $entity->_internalGetField($keyField)); + $coll->add($entity, $this->getFieldValue($entity, $keyField)); } public function addElementToCollection($coll, $entity) @@ -143,6 +190,40 @@ class Doctrine_ORM_Internal_Hydration_ObjectDriver $coll->add($entity); } + /** + * Updates the result pointer for an Entity. The result pointers point to the + * last seen instance of each Entity type. This is used for graph construction. + * + * @param array $resultPointers The result pointers. + * @param array|Collection $coll The element. + * @param boolean|integer $index Index of the element in the collection. + * @param string $dqlAlias + * @param boolean $oneToOne Whether it is a single-valued association or not. + */ + public function updateResultPointer(&$resultPointers, &$coll, $index, $dqlAlias, $oneToOne) + { + if ($coll === $this->_nullObject) { + unset($resultPointers[$dqlAlias]); // Ticket #1228 + return; + } + + if ($index !== false) { + $resultPointers[$dqlAlias] = $coll[$index]; + return; + } + + if ( ! is_object($coll)) { + end($coll); + $resultPointers[$dqlAlias] =& $coll[key($coll)]; + } else if ($coll instanceof Doctrine_ORM_Collection) { + if (count($coll) > 0) { + $resultPointers[$dqlAlias] = $coll->getLast(); + } + } else { + $resultPointers[$dqlAlias] = $coll; + } + } + public function flush() { // take snapshots from all initialized collections @@ -152,6 +233,7 @@ class Doctrine_ORM_Internal_Hydration_ObjectDriver } $this->_collections = array(); $this->_initializedRelations = array(); + $this->_metadataMap = array(); } } diff --git a/lib/Doctrine/ORM/Internal/Hydration/StandardHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/StandardHydrator.php index 32f8d4638..87d945f55 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/StandardHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/StandardHydrator.php @@ -19,7 +19,7 @@ * . */ -#namespace Doctrine::ORM::Internal::Hydration; +#namespace Doctrine\ORM\Internal\Hydration; /** * The hydrator has the tedious to process result sets returned by the database @@ -104,7 +104,6 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte $s = microtime(true); - // Used variables during hydration reset($this->_queryComponents); $rootAlias = key($this->_queryComponents); $rootEntityName = $this->_queryComponents[$rootAlias]['metadata']->getClassName(); @@ -136,8 +135,7 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte // Initialize foreach ($this->_queryComponents as $dqlAlias => $component) { // disable lazy-loading of related elements during hydration - $component['metadata']->setAttribute('loadReferences', false); - $entityName = $component['metadata']->getClassName(); + //$component['metadata']->setAttribute('loadReferences', false); $identifierMap[$dqlAlias] = array(); $resultPointers[$dqlAlias] = array(); $idTemplate[$dqlAlias] = ''; @@ -147,6 +145,7 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte // Evaluate HYDRATE_SINGLE_SCALAR if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_SINGLE_SCALAR) { $result = $stmt->fetchAll(PDO::FETCH_ASSOC); + //TODO: Let this exception be raised by Query as QueryException if (count($result) > 1 || count($result[0]) > 1) { throw Doctrine_ORM_Exceptions_HydrationException::nonUniqueResult(); } @@ -193,7 +192,7 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte } else { $index = $identifierMap[$rootAlias][$id[$rootAlias]]; } - $this->_updateResultPointer($resultPointers, $result, $index, $rootAlias, false); + $driver->updateResultPointer($resultPointers, $result, $index, $rootAlias, false); unset($rowData[$rootAlias]); // end hydrate data of the root component for the current row @@ -211,10 +210,11 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte $entityName = $map['metadata']->getClassName(); $parent = $map['parent']; $relation = $map['relation']; - $relationAlias = $relation->getSourceFieldName();//$relation->getAlias(); + $relationAlias = $relation->getSourceFieldName(); $path = $parent . '.' . $dqlAlias; - // pick the right element that will get the associated element attached + // Get a reference to the right element in the result tree. + // This element will get the associated element attached. if ($parserResult->isMixedQuery() && $parent == $rootAlias) { $key = key(reset($resultPointers)); // TODO: Exception if $key === null ? @@ -228,13 +228,13 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte // check the type of the relation (many or single-valued) if ( ! $relation->isOneToOne()) { - // x-many relation + // x-to-many relation $oneToOne = false; if (isset($nonemptyComponents[$dqlAlias])) { $driver->initRelatedCollection($baseElement, $relationAlias); $indexExists = isset($identifierMap[$path][$id[$parent]][$id[$dqlAlias]]); $index = $indexExists ? $identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false; - $indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false; + $indexIsValid = $index !== false ? $driver->isIndexKeyInUse($baseElement, $relationAlias, $index) : false; if ( ! $indexExists || ! $indexIsValid) { $element = $driver->getElement($data, $entityName); if ($field = $this->_getCustomIndexField($dqlAlias)) { @@ -245,7 +245,7 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte $identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = $driver->getLastKey( $driver->getReferenceValue($baseElement, $relationAlias)); } - } else if ( ! isset($baseElement[$relationAlias])) { + } else if ( ! $driver->isFieldSet($baseElement, $relationAlias)) { if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_ARRAY) { $baseElement[$relationAlias] = array(); } else { @@ -254,7 +254,7 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte } } } else { - // x-1 relation + // x-to-one relation $oneToOne = true; if ( ! isset($nonemptyComponents[$dqlAlias]) && ! $driver->isFieldSet($baseElement, $relationAlias)) { @@ -269,15 +269,17 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_ARRAY) { $coll =& $baseElement[$relationAlias]; } else { - $coll = $baseElement->_internalGetReference($relationAlias); + $coll = $driver->getReferenceValue($baseElement, $relationAlias); + //$baseElement->_internalGetReference($relationAlias); } if ($coll !== null) { - $this->_updateResultPointer($resultPointers, $coll, $index, $dqlAlias, $oneToOne); + $driver->updateResultPointer($resultPointers, $coll, $index, $dqlAlias, $oneToOne); } } - // append scalar values to mixed result sets + // Append scalar values to mixed result sets + //TODO: we dont need to count every time here, instead count with the loop if (isset($scalars)) { $rowNumber = count($result) - 1; foreach ($scalars as $name => $value) { @@ -289,52 +291,16 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte $stmt->closeCursor(); $driver->flush(); - // re-enable lazy loading + /*// re-enable lazy loading foreach ($this->_queryComponents as $dqlAlias => $data) { $data['metadata']->setAttribute('loadReferences', true); - } + }*/ $e = microtime(true); - echo 'Hydration took: ' . ($e - $s) . ' for '.count($result).' records' . PHP_EOL; + echo 'Hydration took: ' . ($e - $s) . PHP_EOL; return $result; } - - /** - * Updates the result pointer for an Entity. The result pointers point to the - * last seen instance of each Entity type. This is used for graph construction. - * - * @param array $resultPointers The result pointers. - * @param array|Collection $coll The element. - * @param boolean|integer $index Index of the element in the collection. - * @param string $dqlAlias - * @param boolean $oneToOne Whether it is a single-valued association or not. - */ - protected function _updateResultPointer(&$resultPointers, &$coll, $index, $dqlAlias, $oneToOne) - { - if ($coll === $this->_nullObject || $coll === null) { - unset($resultPointers[$dqlAlias]); // Ticket #1228 - return; - } - - if ($index !== false) { - $resultPointers[$dqlAlias] =& $coll[$index]; - return; - } - - if (is_array($coll) && $coll) { - if ($oneToOne) { - $resultPointers[$dqlAlias] =& $coll; - } else { - end($coll); - $resultPointers[$dqlAlias] =& $coll[key($coll)]; - } - } else if ($coll instanceof Doctrine_ORM_Entity) { - $resultPointers[$dqlAlias] = $coll; - } else if (count($coll) > 0) { - $resultPointers[$dqlAlias] = $coll->getLast(); - } - } /** * Processes a row of the result set. @@ -370,7 +336,7 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte $fieldName = $this->_queryComponents[$cache[$key]['dqlAlias']]['agg'][$columnName]; $cache[$key]['isScalar'] = true; } else { - $fieldName = $classMetadata->lookupFieldName($columnName); + $fieldName = $this->_lookupFieldName($classMetadata, $columnName); $cache[$key]['isScalar'] = false; } @@ -455,7 +421,7 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte $fieldName = $this->_queryComponents[$cache[$key]['dqlAlias']]['agg'][$columnName]; $cache[$key]['isScalar'] = true; } else { - $fieldName = $classMetadata->lookupFieldName($columnName); + $fieldName = $this->_lookupFieldName($classMetadata, $columnName); $cache[$key]['isScalar'] = false; } @@ -510,6 +476,42 @@ class Doctrine_ORM_Internal_Hydration_StandardHydrator extends Doctrine_ORM_Inte { return $name == 'doctrine_rownum'; } + + /** + * Looks up the field name for a (lowercased) column name. + * + * This is mostly used during hydration, because we want to make the + * conversion to field names while iterating over the result set for best + * performance. By doing this at that point, we can avoid re-iterating over + * the data just to convert the column names to field names. + * + * However, when this is happening, we don't know the real + * class name to instantiate yet (the row data may target a sub-type), hence + * this method looks up the field name in the subclass mappings if it's not + * found on this class mapping. + * This lookup on subclasses is costly but happens only *once* for a column + * during hydration because the hydrator caches effectively. + * + * @return string The field name. + * @throws Doctrine::ORM::Exceptions::ClassMetadataException If the field name could + * not be found. + */ + private function _lookupFieldName($class, $lcColumnName) + { + if ($class->hasLowerColumn($lcColumnName)) { + return $class->getFieldNameForLowerColumnName($lcColumnName); + } + + foreach ($class->getSubclasses() as $subClass) { + $subClassMetadata = Doctrine_ORM_Mapping_ClassMetadataFactory::getInstance() + ->getMetadataFor($subClass); + if ($subClassMetadata->hasLowerColumn($lcColumnName)) { + return $subClassMetadata->getFieldNameForLowerColumnName($lcColumnName); + } + } + + throw new Doctrine_Exception("No field name found for column name '$lcColumnName' during hydration."); + } /** * prepareValue diff --git a/lib/Doctrine/ORM/Mapping/AssociationMapping.php b/lib/Doctrine/ORM/Mapping/AssociationMapping.php index dff47e352..0b6431e6b 100644 --- a/lib/Doctrine/ORM/Mapping/AssociationMapping.php +++ b/lib/Doctrine/ORM/Mapping/AssociationMapping.php @@ -19,7 +19,7 @@ * . */ -#namespace Doctrine::ORM::Mapping; +#namespace Doctrine\ORM\Mapping; /** * Base class for association mappings. diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadata.php b/lib/Doctrine/ORM/Mapping/ClassMetadata.php index 1b4791e9b..2170e3938 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadata.php @@ -19,9 +19,10 @@ * . */ -#namespace Doctrine::ORM::Mapping; +#namespace Doctrine\ORM\Mapping; -#use Doctrine::ORM::EntityManager; +#use \Serializable; +#use Doctrine\Common\ClassMetadata; /** * A ClassMetadata instance holds all the information (metadata) of an entity and @@ -32,7 +33,8 @@ * @since 2.0 * @todo Rename to ClassDescriptor. */ -class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable, Serializable +class Doctrine_ORM_Mapping_ClassMetadata extends Doctrine_Common_ClassMetadata + implements Doctrine_Common_Configurable, Serializable { /* The inheritance mapping types */ /** @@ -100,13 +102,6 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable * field & association mappings are inherited by subclasses. */ const ENTITY_TYPE_MAPPED_SUPERCLASS = 'mappedSuperclass'; - - /** - * The name of the entity class. - * - * @var string - */ - protected $_entityName; /** * The name of the entity class that is at the root of the entity inheritance @@ -118,20 +113,13 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable protected $_rootEntityName; /** - * The name of the custom mapper class used for the entity class. + * The name of the custom repository class used for the entity class. * (Optional). * * @var string */ protected $_customRepositoryClassName; - /** - * The EntityManager. - * - * @var Doctrine::ORM::EntityManager - */ - protected $_em; - /** * The names of the parent classes (ancestors). * @@ -168,13 +156,6 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable */ protected $_generatorType = self::GENERATOR_TYPE_NONE; - /** - * The Id generator. - * - * @var Doctrine::ORM::Id::IdGenerator - */ - protected $_idGenerator; - /** * The field mappings of the class. * Keys are field names and values are mapping definitions. @@ -233,21 +214,6 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable */ protected $_fieldMappings = array(); - /** - * The mapped embedded values (value objects). - * - * @var array - * @TODO Implementation (Value Object support) - */ - //protected $_embeddedValueMappings = array(); - - /** - * Enter description here... - * - * @var array - */ - protected $_attributes = array('loadReferences' => true); - /** * An array of field names. used to look up field names from column names. * Keys are column names and values are field names. @@ -298,10 +264,10 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable * -- collate collation attribute */ protected $_tableOptions = array( - 'tableName' => null, - 'type' => null, - 'charset' => null, - 'collate' => null + 'tableName' => null, + 'type' => null, + 'charset' => null, + 'collate' => null ); /** @@ -346,31 +312,41 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable * @var boolean */ protected $_isIdentifierComposite = false; - - protected $_customAssociationAccessors = array(); - protected $_customAssociationMutators = array(); + + protected $_reflectionClass; + protected $_reflectionProperties; /** * Constructs a new ClassMetadata instance. * * @param string $entityName Name of the entity class the metadata info is used for. - * @param Doctrine::ORM::Entitymanager $em */ - public function __construct($entityName, Doctrine_ORM_EntityManager $em) + public function __construct($entityName) { - $this->_entityName = $entityName; + parent::__construct($entityName); $this->_rootEntityName = $entityName; - $this->_em = $em; + $this->_reflectionClass = new ReflectionClass($entityName); + $reflectionProps = $this->_reflectionClass->getProperties(); + foreach ($reflectionProps as $prop) { + $prop->setAccessible(true); + $this->_reflectionProperties[$prop->getName()] = $prop; + } + //$this->_isVirtualPropertyObject = is_subclass_of($entityName, 'Doctrine\Common\VirtualPropertyObject'); } - - /** - * Gets the EntityManager that holds this ClassMetadata. - * - * @return Doctrine::ORM::EntityManager - */ - public function getEntityManager() + + public function getReflectionClass() { - return $this->_em; + return $this->_reflectionClass; + } + + public function getReflectionProperties() + { + return $this->_reflectionProperties; + } + + public function getReflectionProperty($name) + { + return $this->_reflectionProperties[$name]; } /** @@ -447,7 +423,6 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable public function isNotNull($fieldName) { $mapping = $this->getFieldMapping($fieldName); - if ($mapping !== false) { return isset($mapping['notnull']) && $mapping['notnull'] == true; } @@ -455,34 +430,6 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable return false; } - /** - * adds an index to this table - * - * @return void - * @deprecated - * @todo Should be done through setTableOption(). - */ - public function addIndex($index, array $definition) - { - $this->_tableOptions['indexes'][$index] = $definition; - } - - /** - * getIndex - * - * @return array|boolean array on success, FALSE on failure - * @todo Should be done through getTableOption(). - * @deprecated - */ - public function getIndex($index) - { - if (isset($this->_tableOptions['indexes'][$index])) { - return $this->_tableOptions['indexes'][$index]; - } - - return false; - } - /** * Sets a table option. */ @@ -547,11 +494,6 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable return $this->_fieldMappings[$fieldName]; } - public function addFieldMapping($fieldName, array $mapping) - { - $this->_fieldMappings[$fieldName] = $mapping; - } - /** * Gets the mapping of an association. * @@ -562,7 +504,7 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable public function getAssociationMapping($fieldName) { if ( ! isset($this->_associationMappings[$fieldName])) { - throw Doctrine_MappingException::mappingNotFound($fieldName); + throw new Doctrine_Exception("Mapping not found: $fieldName"); } return $this->_associationMappings[$fieldName]; @@ -579,7 +521,6 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable if ( ! isset($this->_associationMappings[$fieldName])) { throw Doctrine_MappingException::mappingNotFound($fieldName); } - return $this->_inverseMappings[$mappedByFieldName]; } @@ -594,11 +535,6 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable return isset($this->_inverseMappings[$mappedByFieldName]); } - public function addAssociationMapping($fieldName, Doctrine_Association $assoc) - { - $this->_associationMappings[$fieldName] = $assoc; - } - /** * Gets all association mappings of the class. * @@ -646,52 +582,17 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable return isset($this->_lcColumnToFieldNames[$lcColumnName]) ? $this->_lcColumnToFieldNames[$lcColumnName] : $lcColumnName; } - + + /** + * + * @param $lcColumnName + * @return + */ public function hasLowerColumn($lcColumnName) { return isset($this->_lcColumnToFieldNames[$lcColumnName]); } - /** - * Looks up the field name for a (lowercased) column name. - * - * This is mostly used during hydration, because we want to make the - * conversion to field names while iterating over the result set for best - * performance. By doing this at that point, we can avoid re-iterating over - * the data just to convert the column names to field names. - * - * However, when this is happening, we don't know the real - * class name to instantiate yet (the row data may target a sub-type), hence - * this method looks up the field name in the subclass mappings if it's not - * found on this class mapping. - * This lookup on subclasses is costly but happens only *once* for a column - * during hydration because the hydrator caches effectively. - * - * @return string The field name. - * @throws Doctrine::ORM::Exceptions::ClassMetadataException If the field name could - * not be found. - */ - public function lookupFieldName($lcColumnName) - { - if (isset($this->_lcColumnToFieldNames[$lcColumnName])) { - return $this->_lcColumnToFieldNames[$lcColumnName]; - }/* else if (isset($this->_subclassFieldNames[$lcColumnName])) { - return $this->_subclassFieldNames[$lcColumnName]; - }*/ - - foreach ($this->getSubclasses() as $subClass) { - $subClassMetadata = $this->_em->getClassMetadata($subClass); - if ($subClassMetadata->hasLowerColumn($lcColumnName)) { - /*$this->_subclassFieldNames[$lcColumnName] = $subClassMetadata-> - getFieldNameForLowerColumnName($lcColumnName); - return $this->_subclassFieldNames[$lcColumnName];*/ - return $subClassMetadata->getFieldNameForLowerColumnName($lcColumnName); - } - } - - throw new Doctrine_ClassMetadata_Exception("No field name found for column name '$lcColumnName' during lookup."); - } - /** * Adds a field mapping. * @@ -732,11 +633,6 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable $this->_fieldNames[$mapping['columnName']] = $mapping['fieldName']; $this->_lcColumnToFieldNames[$lcColumnName] = $mapping['fieldName']; - // Complete length mapping - if ( ! isset($mapping['length'])) { - $mapping['length'] = $this->_getDefaultLength($mapping['type']); - } - // Complete id mapping if (isset($mapping['id']) && $mapping['id'] === true) { if ( ! in_array($mapping['fieldName'], $this->_identifier)) { @@ -783,58 +679,6 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable //... } - /** - * Used to lazily create the id generator. - * - * @param string $generatorType - * @return void - */ - protected function _createIdGenerator() - { - if ($this->_generatorType == self::GENERATOR_TYPE_IDENTITY) { - $this->_idGenerator = new Doctrine_ORM_Id_IdentityGenerator($this->_em); - } else if ($this->_generatorType == self::GENERATOR_TYPE_SEQUENCE) { - $this->_idGenerator = new Doctrine_ORM_Id_SequenceGenerator($this->_em); - } else if ($this->_generatorType == self::GENERATOR_TYPE_TABLE) { - $this->_idGenerator = new Doctrine_ORM_Id_TableGenerator($this->_em); - } else { - $this->_idGenerator = new Doctrine_ORM_Id_Assigned($this->_em); - } - } - - /** - * Gets the default length for a column type. - * - * @param string $type - * @return mixed - */ - private function _getDefaultLength($type) - { - switch ($type) { - case 'string': - case 'clob': - case 'float': - case 'integer': - case 'array': - case 'object': - case 'blob': - case 'gzip': - // use php int max - return 2147483647; - case 'boolean': - return 1; - case 'date': - // YYYY-MM-DD ISO 8601 - return 10; - case 'time': - // HH:NN:SS+00:00 ISO 8601 - return 14; - case 'timestamp': - // YYYY-MM-DDTHH:MM:SS+00:00 ISO 8601 - return 25; - } - } - /** * Maps an embedded value object. * @@ -845,6 +689,59 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable //... } + private $_entityIdentifiers = array(); + /** + * Gets the identifier of an entity. + * + * @param object $entity + * @return array Map of identifier field names to values. + */ + public function getEntityIdentifier($entity) + { + $oid = spl_object_id($entity); + if ( ! isset($this->_entityIdentifiers[$oid])) { + if ( ! $this->isIdentifierComposite()) { + $idField = $this->_identifier[0]; + $idValue = $this->_reflectionProperties[$idField]->getValue($entity); + if (isset($idValue)) { + //return array($idField => $idValue); + $this->_entityIdentifiers[$oid] = array($idField => $idValue); + } else { + return false; + } + //$this->_entityIdentifiers[$oid] = false; + } else { + $id = array(); + foreach ($this->getIdentifierFieldNames() as $idFieldName) { + $idValue = $this->_reflectionProperties[$idFieldName]->getValue($entity); + if (isset($idValue)) { + $id[$idFieldName] = $idValue; + } + } + //return $id; + $this->_entityIdentifiers[$oid] = $id; + } + } + return $this->_entityIdentifiers[$oid]; + } + + /** + * + * + * @param $entity + * @param $identifier + */ + public function setEntityIdentifier($entity, $identifier) + { + if (is_array($identifier)) { + foreach ($identifier as $fieldName => $value) { + $this->_reflectionProperties[$fieldName]->setValue($entity, $value); + } + } else { + $this->_reflectionProperties[$this->_identifier[0]]->setValue($entity, $identifier); + } + } + /** * Gets the identifier (primary key) field names of the class. * @@ -895,38 +792,6 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable { return isset($this->_columnNames[$fieldName]); } - - /** - * Gets the custom accessor of a field. - * - * @return string The name of the accessor (getter) method or NULL if the field does - * not have a custom accessor. - */ - public function getCustomAccessor($fieldName) - { - if (isset($this->_fieldMappings[$fieldName]['accessor'])) { - return $this->_fieldMappings[$fieldName]['accessor']; - } else if (isset($this->_customAssociationAccessors[$fieldName])) { - return $this->_customAssociationAccessors[$fieldName]; - } - return null; - } - - /** - * Gets the custom mutator of a field. - * - * @return string The name of the mutator (setter) method or NULL if the field does - * not have a custom mutator. - */ - public function getCustomMutator($fieldName) - { - if (isset($this->_fieldMappings[$fieldName]['mutator'])) { - return $this->_fieldMappings[$fieldName]['mutator']; - } else if (isset($this->_customAssociationMutators[$fieldName])) { - return $this->_customAssociationMutators[$fieldName]; - } - return null; - } /** * Gets all field mappings. @@ -985,6 +850,14 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable { return $this->_generatorType; } + + /** + * Sets the type of Id generator to use for this class. + */ + public function setIdGeneratorType($generatorType) + { + $this->_generatorType = $generatorType; + } /** * Checks whether the class uses an Id generator. @@ -995,17 +868,44 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable { return $this->_generatorType != self::GENERATOR_TYPE_NONE; } + + /** + * + * @return + */ + public function isInheritanceTypeNone() + { + return $this->_inheritanceType == self::INHERITANCE_TYPE_NONE; + } + /** + * Checks whether the mapped class uses the JOINED inheritance mapping strategy. + * + * @return boolean TRUE if the class participates in a JOINED inheritance mapping, + * FALSE otherwise. + */ public function isInheritanceTypeJoined() { return $this->_inheritanceType == self::INHERITANCE_TYPE_JOINED; } + /** + * Checks whether the mapped class uses the SINGLE_TABLE inheritance mapping strategy. + * + * @return boolean TRUE if the class participates in a SINGLE_TABLE inheritance mapping, + * FALSE otherwise. + */ public function isInheritanceTypeSingleTable() { return $this->_inheritanceType == self::INHERITANCE_TYPE_SINGLE_TABLE; } + /** + * Checks whether the mapped class uses the TABLE_PER_CLASS inheritance mapping strategy. + * + * @return boolean TRUE if the class participates in a TABLE_PER_CLASS inheritance mapping, + * FALSE otherwise. + */ public function isInheritanceTypeTablePerClass() { return $this->_inheritanceType == self::INHERITANCE_TYPE_TABLE_PER_CLASS; @@ -1110,9 +1010,9 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable } /** - * Gets the inheritance type used by the class. + * Gets the inheritance mapping type used by the class. * - * @return integer + * @return string */ public function getInheritanceType() { @@ -1140,29 +1040,6 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable { return $this->_subClasses; } - - /** - * Gets the name of the class in the entity hierarchy that owns the field with - * the given name. The owning class is the one that defines the field. - * - * @param string $fieldName - * @return string - * @todo Consider using 'inherited' => 'ClassName' to make the lookup simpler. - */ - public function getOwningClass($fieldName) - { - if ($this->_inheritanceType == self::INHERITANCE_TYPE_NONE) { - return $this; - } else { - foreach ($this->_parentClasses as $parentClass) { - if ( ! $this->_em->getClassMetadata($parentClass)->isInheritedField($fieldName)) { - return $parentClass; - } - } - } - - throw new Doctrine_ClassMetadata_Exception("Unable to find defining class of field '$fieldName'."); - } /** * Checks whether the class has any persistent subclasses. @@ -1259,9 +1136,9 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable */ public function getInheritanceOption($name) { - if ( ! array_key_exists($name, $this->_inheritanceOptions)) { + /*if ( ! array_key_exists($name, $this->_inheritanceOptions)) { throw new Doctrine_ClassMetadata_Exception("Unknown inheritance option: '$name'."); - } + }*/ return $this->_inheritanceOptions[$name]; } @@ -1317,7 +1194,6 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable */ public function export() { - //$this->_em->export->exportTable($this); } /** @@ -1329,7 +1205,7 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable */ public function getExportableFormat($parseForeignKeys = true) { - $columns = array(); + /*$columns = array(); $primary = array(); $allColumns = $this->getColumns(); @@ -1338,13 +1214,13 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable if ($this->_inheritanceType == self::INHERITANCE_TYPE_SINGLE_TABLE) { $parents = $this->getParentClasses(); if ($parents) { - $rootClass = $this->_em->getClassMetadata(array_pop($parents)); + $rootClass = $this->_classFactory->getClassMetadata(array_pop($parents)); } else { $rootClass = $this; } $subClasses = $rootClass->getSubclasses(); foreach ($subClasses as $subClass) { - $subClassMetadata = $this->_em->getClassMetadata($subClass); + $subClassMetadata = $this->_classFactory->getClassMetadata($subClass); $allColumns = array_merge($allColumns, $subClassMetadata->getColumns()); } } else if ($this->_inheritanceType == self::INHERITANCE_TYPE_JOINED) { @@ -1440,6 +1316,8 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable return array('tableName' => $this->getTableOption('tableName'), 'columns' => $columns, 'options' => array_merge($options, $this->getTableOptions())); + + */ } /** @@ -1545,23 +1423,6 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable return $mapping; } - /** - * Registers any custom accessors/mutators in the given association mapping in - * an internal cache for fast lookup. - * - * @param Doctrine_Association $assoc - * @param unknown_type $fieldName - */ - private function _registerCustomAssociationAccessors(Doctrine_ORM_Mapping_AssociationMapping $assoc, $fieldName) - { - if ($acc = $assoc->getCustomAccessor()) { - $this->_customAssociationAccessors[$fieldName] = $acc; - } - if ($mut = $assoc->getCustomMutator()) { - $this->_customAssociationMutators[$fieldName] = $mut; - } - } - /** * Adds a one-to-one mapping. * @@ -1572,17 +1433,15 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable $mapping = $this->_completeAssociationMapping($mapping); $oneToOneMapping = new Doctrine_ORM_Mapping_OneToOneMapping($mapping); $this->_storeAssociationMapping($oneToOneMapping); - - /*if ($oneToOneMapping->isInverseSide()) { - //FIXME: infinite recursion possible? - // Alternative: Store inverse side mappings indexed by mappedBy fieldname - // ($this->_inverseMappings). Then look it up. - $owningClass = $this->_em->getClassMetadata($oneToOneMapping->getTargetEntityName()); - $owningClass->getAssociationMapping($oneToOneMapping->getMappedByFieldName()) - ->setBidirectional($oneToOneMapping->getSourceFieldName()); - }*/ } - + + /** + * Registers the mapping as an inverse mapping, if it is a mapping on the + * inverse side of an association mapping. + * + * @param AssociationMapping The mapping to register as inverse if it is a mapping + * for the inverse side of an association. + */ private function _registerMappingIfInverse(Doctrine_ORM_Mapping_AssociationMapping $assoc) { if ($assoc->isInverseSide()) { @@ -1637,7 +1496,6 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable throw Doctrine_MappingException::duplicateFieldMapping(); } $this->_associationMappings[$sourceFieldName] = $assocMapping; - $this->_registerCustomAssociationAccessors($assocMapping, $sourceFieldName); $this->_registerMappingIfInverse($assocMapping); } @@ -1665,19 +1523,6 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable { return $this->_customRepositoryClassName; } - - /** - * Gets the Id generator used by the class. - * - * @return Doctrine::ORM::Id::AbstractIdGenerator - */ - public function getIdGenerator() - { - if (is_null($this->_idGenerator)) { - $this->_createIdGenerator(); - } - return $this->_idGenerator; - } /** * @todo Thoughts & Implementation. @@ -1746,7 +1591,7 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable } /** - * Adds a lifecycle listener for Entities this class. + * Adds a lifecycle listener for Entities of this class. * * Note: If the same listener class is registered more than once, the old * one will be overridden. @@ -1780,33 +1625,6 @@ class Doctrine_ORM_Mapping_ClassMetadata implements Doctrine_Common_Configurable $this->_lifecycleCallbacks[$event][$callback] = $callback; } } - - /** - * INTERNAL: Completes the identifier mapping of the class. - * NOTE: Should only be called by the ClassMetadataFactory! - * - * @return void - */ - public function completeIdentifierMapping() - { - if ($this->_generatorType == self::GENERATOR_TYPE_AUTO) { - $platform = $this->_em->getConnection()->getDatabasePlatform(); - if ($platform === null) { - try { - throw new Exception(); - } catch (Exception $e) { - echo $e->getTraceAsString(); - } - } - if ($platform->prefersSequences()) { - $this->_generatorType = self::GENERATOR_TYPE_SEQUENCE; - } else if ($platform->prefersIdentityColumns()) { - $this->_generatorType = self::GENERATOR_TYPE_IDENTITY; - } else { - $this->_generatorType = self::GENERATOR_TYPE_TABLE; - } - } - } /** * @todo Implementation. Immutable entities can not be updated or deleted once diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index 82223ea1c..2532d155b 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -19,11 +19,14 @@ * . */ -#namespace Doctrine::ORM::Internal; +#namespace Doctrine\ORM\Mapping; + +#use Doctrine\DBAL\Platforms\AbstractPlatform; /** * The metadata factory is used to create ClassMetadata objects that contain all the - * metadata mapping informations of a class. + * metadata mapping informations of a class which describes how a class should be mapped + * to a relational database. * * @author Konsta Vesterinen * @author Roman Borschel @@ -33,44 +36,21 @@ * @since 2.0 * @todo Rename to ClassDescriptorFactory. */ -class Doctrine_ORM_Mapping_ClassMetadataFactory +class Doctrine_ORM_Mapping_ClassMetadataFactory extends Doctrine_Common_ClassMetadataFactory { - protected $_em; - protected $_driver; - - /** - * The already loaded metadata objects. - */ - protected $_loadedMetadata = array(); + /** The targeted database platform. */ + private $_targetPlatform; /** * Constructor. - * Creates a new factory instance that uses the given EntityManager and metadata driver - * implementations. + * Creates a new factory instance that uses the given metadata driver implementation. * - * @param $conn The connection to use. * @param $driver The metadata driver to use. */ - public function __construct(Doctrine_ORM_EntityManager $em, $driver) + public function __construct($driver, Doctrine_DBAL_Platforms_AbstractPlatform $targetPlatform) { - $this->_em = $em; - $this->_driver = $driver; - } - - /** - * Returns the metadata object for a class. - * - * @param string $className The name of the class. - * @return Doctrine_Metadata - */ - public function getMetadataFor($className) - { - if (isset($this->_loadedMetadata[$className])) { - return $this->_loadedMetadata[$className]; - } - $this->_loadClasses($className, $this->_loadedMetadata); - - return $this->_loadedMetadata[$className]; + parent::__construct($driver); + $this->_targetPlatform = $targetPlatform; } /** @@ -79,17 +59,16 @@ class Doctrine_ORM_Mapping_ClassMetadataFactory * * @param string $name The name of the class for which the metadata should get loaded. * @param array $tables The metadata collection to which the loaded metadata is added. + * @override */ - protected function _loadClasses($name, array &$classes) + protected function _loadMetadata($name) { + $parentClass = $name; $parentClasses = array(); $loadedParentClass = false; while ($parentClass = get_parent_class($parentClass)) { - if ($parentClass == 'Doctrine_ORM_Entity') { - break; - } - if (isset($classes[$parentClass])) { + if (isset($this->_loadedMetadata[$parentClass])) { $loadedParentClass = $parentClass; break; } @@ -99,12 +78,12 @@ class Doctrine_ORM_Mapping_ClassMetadataFactory $parentClasses[] = $name; if ($loadedParentClass) { - $class = $classes[$loadedParentClass]; + $class = $this->_loadedMetadata[$loadedParentClass]; } else { $rootClassOfHierarchy = count($parentClasses) > 0 ? array_shift($parentClasses) : $name; - $class = new Doctrine_ORM_Mapping_ClassMetadata($rootClassOfHierarchy, $this->_em); - $this->_loadMetadata($class, $rootClassOfHierarchy); - $classes[$rootClassOfHierarchy] = $class; + $class = new Doctrine_ORM_Mapping_ClassMetadata($rootClassOfHierarchy); + $this->_loadClassMetadata($class, $rootClassOfHierarchy); + $this->_loadedMetadata[$rootClassOfHierarchy] = $class; } if (count($parentClasses) == 0) { @@ -117,15 +96,15 @@ class Doctrine_ORM_Mapping_ClassMetadataFactory // Move down the hierarchy of parent classes, starting from the topmost class $parent = $class; foreach ($parentClasses as $subclassName) { - $subClass = new Doctrine_ORM_Mapping_ClassMetadata($subclassName, $this->_em); + $subClass = new Doctrine_ORM_Mapping_ClassMetadata($subclassName); $subClass->setInheritanceType($parent->getInheritanceType(), $parent->getInheritanceOptions()); $this->_addInheritedFields($subClass, $parent); $this->_addInheritedRelations($subClass, $parent); - $this->_loadMetadata($subClass, $subclassName); + $this->_loadClassMetadata($subClass, $subclassName); if ($parent->isInheritanceTypeSingleTable()) { $subClass->setTableName($parent->getTableName()); } - $classes[$subclassName] = $subClass; + $this->_loadedMetadata[$subclassName] = $subClass; $parent = $subClass; } } @@ -137,7 +116,7 @@ class Doctrine_ORM_Mapping_ClassMetadataFactory * @param Doctrine::ORM::Mapping::ClassMetadata $parentClass * @return void */ - protected function _addInheritedFields($subClass, $parentClass) + private function _addInheritedFields($subClass, $parentClass) { foreach ($parentClass->getFieldMappings() as $fieldName => $mapping) { if ( ! isset($mapping['inherited'])) { @@ -153,7 +132,7 @@ class Doctrine_ORM_Mapping_ClassMetadataFactory * @param unknown_type $subClass * @param unknown_type $parentClass */ - protected function _addInheritedRelations($subClass, $parentClass) + private function _addInheritedRelations($subClass, $parentClass) { foreach ($parentClass->getAssociationMappings() as $fieldName => $mapping) { $subClass->addAssociationMapping($name, $mapping); @@ -166,7 +145,7 @@ class Doctrine_ORM_Mapping_ClassMetadataFactory * @param Doctrine_ClassMetadata $class The container for the metadata. * @param string $name The name of the class for which the metadata will be loaded. */ - protected function _loadMetadata(Doctrine_ORM_Mapping_ClassMetadata $class, $name) + private function _loadClassMetadata(Doctrine_ORM_Mapping_ClassMetadata $class, $name) { if ( ! class_exists($name) || empty($name)) { throw new Doctrine_Exception("Couldn't find class " . $name . "."); @@ -177,17 +156,15 @@ class Doctrine_ORM_Mapping_ClassMetadataFactory // get parent classes //TODO: Skip Entity types MappedSuperclass/Transient do { - if ($className === 'Doctrine_ORM_Entity') { - break; - } else if ($className == $name) { + if ($className == $name) { continue; } $names[] = $className; } while ($className = get_parent_class($className)); - if ($className === false) { - throw new Doctrine_ClassMetadata_Factory_Exception("Unknown component '$className'."); - } + /*if ($className === false) { + throw new Doctrine_Exception("Unknown component '$className'."); + }*/ // save parents $class->setParentClasses($names); @@ -200,12 +177,21 @@ class Doctrine_ORM_Mapping_ClassMetadataFactory if ( ! isset($tableName)) { $class->setTableName(Doctrine::tableize($class->getClassName())); } - - $class->completeIdentifierMapping(); + + // Complete Id generator mapping. If AUTO is specified we choose the generator + // most appropriate for the target platform. + if ($class->getIdGeneratorType() == Doctrine_ORM_Mapping_ClassMetadata::GENERATOR_TYPE_AUTO) { + if ($this->_targetPlatform->prefersSequences()) { + $class->setIdGeneratorType(Doctrine_ORM_Mapping_ClassMetadata::GENERATOR_TYPE_SEQUENCE); + } else if ($this->_targetPlatform->prefersIdentityColumns()) { + $class->setIdGeneratorType(Doctrine_ORM_Mapping_ClassMetadata::GENERATOR_TYPE_IDENTITY); + } else { + $class->setIdGeneratorType(Doctrine_ORM_Mapping_ClassMetadata::GENERATOR_TYPE_TABLE); + } + } return $class; } - } diff --git a/lib/Doctrine/ORM/Mapping/Driver/CodeDriver.php b/lib/Doctrine/ORM/Mapping/Driver/CodeDriver.php index 53989c9f8..a57654955 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/CodeDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/CodeDriver.php @@ -19,7 +19,7 @@ * . */ -#namespace Doctrine::ORM::Internal; +#namespace Doctrine\ORM\Mapping\Driver; /** * The code metadata driver loads the metadata of the classes through invoking @@ -39,17 +39,27 @@ class Doctrine_ORM_Mapping_Driver_CodeDriver * * @todo We could make the name of the callback methods customizable for users. */ - const CALLBACK_METHOD = 'initMetadata'; - + private $_callback = 'initMetadata'; + + public function setCallback($callback) + { + $this->_callback = $callback; + } + + public function getCallback() + { + return $this->_callback; + } + /** * Loads the metadata for the specified class into the provided container. */ public function loadMetadataForClass($className, Doctrine_ORM_Mapping_ClassMetadata $metadata) { - if ( ! method_exists($className, self::CALLBACK_METHOD)) { - throw new Doctrine_ClassMetadata_Exception("Unable to load metadata for class" + if ( ! method_exists($className, $this->_callback)) { + throw new Doctrine_Exception("Unable to load metadata for class" . " '$className'. Callback method 'initMetadata' not found."); } - call_user_func_array(array($className, 'initMetadata'), array($metadata)); + call_user_func_array(array($className, $this->_callback), array($metadata)); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Persisters/AbstractEntityPersister.php b/lib/Doctrine/ORM/Persisters/AbstractEntityPersister.php index a7d48d595..a5fee9d94 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractEntityPersister.php @@ -19,7 +19,7 @@ * . */ -#namespace Doctrine::ORM::Persisters; +#namespace Doctrine\ORM\Persisters; /** * Base class for all EntityPersisters. @@ -89,60 +89,13 @@ abstract class Doctrine_ORM_Persisters_AbstractEntityPersister * @param Doctrine::ORM::Entity $entity The entity to insert. * @return void */ - public function insert(Doctrine_ORM_Entity $entity) + public function insert($entity) { $insertData = array(); - $class = $entity->getClass(); - - $referenceChangeSet = $entity->_getReferenceChangeSet(); - foreach ($referenceChangeSet as $field => $change) { - list($old, $new) = each($change); - $assocMapping = $class->getAssociationMapping($field); - if ( ! $assocMapping->isOneToOne() || $assocMapping->isInverseSide()) { - //echo "NOT TO-ONE OR INVERSE!"; - continue; - } - - foreach ($assocMapping->getSourceToTargetKeyColumns() as $sourceColumn => $targetColumn) { - //TODO: What if both join columns (local/foreign) are just db-only - // columns (no fields in models) ? Currently we assume the foreign column - // is mapped to a field in the foreign entity. - //TODO: throw exc if field not set - $insertData[$sourceColumn] = $new->_internalGetField( - $new->getClass()->getFieldName($targetColumn) - ); - } - //... - } - $this->_prepareData($entity, $insertData, true); - - //TODO: perform insert - $this->_conn->insert($class->getTableName(), $insertData); + $this->_conn->insert($this->_classMetadata->getTableName(), $insertData); } - /*protected function _fillJoinColumns($entity, array &$data) - { - $referenceChangeSet = $entity->_getReferenceChangeSet(); - foreach ($referenceChangeSet as $field => $change) { - list($old, $new) = each($change); - $assocMapping = $entity->getClass()->getAssociationMapping($field); - if ( ! $assocMapping->isOneToOne() || $assocMapping->isInverseSide()) { - //echo "NOT TO-ONE OR INVERSE!"; - continue; - } - - foreach ($assocMapping->getSourceToTargetKeyColumns() as $sourceColumn => $targetColumn) { - //TODO: What if both join columns (local/foreign) are just db-only - // columns (no fields in models) ? Currently we assume the foreign column - // is mapped to a field in the foreign entity. - $insertData[$sourceColumn] = $new->_internalGetField( - $new->getClass()->getFieldName($targetColumn) - ); - } - } - }*/ - /** * Updates an entity. * @@ -229,13 +182,29 @@ abstract class Doctrine_ORM_Persisters_AbstractEntityPersister $this->_fieldNames = $this->_classMetadata->getFieldNames(); return $this->_fieldNames; } - + /** - * @todo Move to ClassMetadata? + * Gets the name of the class in the entity hierarchy that owns the field with + * the given name. The owning class is the one that defines the field. + * + * @param string $fieldName + * @return string + * @todo Consider using 'inherited' => 'ClassName' to make the lookup simpler. */ public function getOwningClass($fieldName) { - return $this->_classMetadata; + if ($this->_classMetadata->isInheritanceTypeNone()) { + return $this->_classMetadata; + } else { + foreach ($this->_classMetadata->getParentClasses() as $parentClass) { + $parentClassMetadata = Doctrine_ORM_Mapping_ClassMetadataFactory::getInstance() + ->getMetadataFor($parentClass); + if ( ! $parentClassMetadata->isInheritedField($fieldName)) { + return $parentClassMetadata; + } + } + } + throw new Doctrine_Exception("Unable to find defining class of field '$fieldName'."); } /** @@ -278,44 +247,57 @@ abstract class Doctrine_ORM_Persisters_AbstractEntityPersister * * @param array $array * @return void - * @todo Move to EntityPersister. There call _getChangeSet() and apply this logic. */ protected function _prepareData($entity, array &$result, $isInsert = false) { - foreach ($entity->_getDataChangeSet() as $field => $change) { + foreach ($this->_em->getUnitOfWork()->getDataChangeSet($entity) as $field => $change) { list ($oldVal, $newVal) = each($change); - $type = $entity->getClass()->getTypeOfField($field); - $columnName = $entity->getClass()->getColumnName($field); + $type = $this->_classMetadata->getTypeOfField($field); + $columnName = $this->_classMetadata->getColumnName($field); if ($newVal === Doctrine_ORM_Internal_Null::$INSTANCE) { $result[$columnName] = null; - continue; - } + } else if (is_object($newVal)) { + $assocMapping = $this->_classMetadata->getAssociationMapping($field); + if ( ! $assocMapping->isOneToOne() || $assocMapping->isInverseSide()) { + //echo "NOT TO-ONE OR INVERSE!"; + continue; + } - switch ($type) { - case 'array': - case 'object': - $result[$columnName] = serialize($newVal); + foreach ($assocMapping->getSourceToTargetKeyColumns() as $sourceColumn => $targetColumn) { + //TODO: What if both join columns (local/foreign) are just db-only + // columns (no fields in models) ? Currently we assume the foreign column + // is mapped to a field in the foreign entity. + //TODO: throw exc if field not set + $otherClass = $this->_em->getClassMetadata($assocMapping->getTargetEntityName()); + $result[$sourceColumn] = $otherClass->getReflectionProperty( + $otherClass->getFieldName($targetColumn))->getValue($newVal); + } + } else { + switch ($type) { + case 'array': + case 'object': + $result[$columnName] = serialize($newVal); + break; + case 'gzip': + $result[$columnName] = gzcompress($newVal, 5); + break; + case 'boolean': + $result[$columnName] = $this->_em->getConnection()->convertBooleans($newVal); break; - case 'gzip': - $result[$columnName] = gzcompress($newVal, 5); - break; - case 'boolean': - $result[$columnName] = $this->_em->getConnection()->convertBooleans($newVal); - break; - default: - $result[$columnName] = $newVal; + default: + $result[$columnName] = $newVal; + } } /*$result[$columnName] = $type->convertToDatabaseValue( $newVal, $this->_em->getConnection()->getDatabasePlatform());*/ } - // @todo Cleanup // populates the discriminator column on insert in Single & Class Table Inheritance - if ($isInsert && ($entity->getClass()->isInheritanceTypeJoined() || - $entity->getClass()->isInheritanceTypeSingleTable())) { - $discColumn = $entity->getClass()->getInheritanceOption('discriminatorColumn'); - $discMap = $entity->getClass()->getInheritanceOption('discriminatorMap'); + if ($isInsert && ($this->_classMetadata->isInheritanceTypeJoined() || + $this->_classMetadata->isInheritanceTypeSingleTable())) { + $discColumn = $this->_classMetadata->getInheritanceOption('discriminatorColumn'); + $discMap = $this->_classMetadata->getInheritanceOption('discriminatorMap'); $result[$discColumn] = array_search($this->_entityName, $discMap); } } @@ -597,14 +579,14 @@ abstract class Doctrine_ORM_Persisters_AbstractEntityPersister */ protected function _insert(Doctrine_ORM_Entity $record) { - $record->preInsert(); - $this->notifyEntityListeners($record, 'preInsert', Doctrine_Event::RECORD_INSERT); + //$record->preInsert(); + //$this->notifyEntityListeners($record, 'preInsert', Doctrine_Event::RECORD_INSERT); $this->_doInsert($record); $this->addRecord($record); - $record->postInsert(); - $this->notifyEntityListeners($record, 'postInsert', Doctrine_Event::RECORD_INSERT); + //$record->postInsert(); + //$this->notifyEntityListeners($record, 'postInsert', Doctrine_Event::RECORD_INSERT); return true; } diff --git a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php index a7c30e9d7..b7e08060c 100644 --- a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php @@ -219,7 +219,7 @@ class Doctrine_ORM_Persisters_JoinedSubclassPersister extends Doctrine_ORM_Persi * * @todo Looks like this better belongs into the ClassMetadata class. */ - public function getOwningClass($fieldName) + /*public function getOwningClass($fieldName) { $conn = $this->_conn; $classMetadata = $this->_classMetadata; @@ -242,7 +242,7 @@ class Doctrine_ORM_Persisters_JoinedSubclassPersister extends Doctrine_ORM_Persi } throw new Doctrine_Mapper_Exception("Unable to find defining class of field '$fieldName'."); - } + }*/ /** * Analyzes the fields of the entity and creates a map in which the field names diff --git a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php index f6817175a..1e9776a22 100644 --- a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php @@ -19,6 +19,10 @@ * . */ +#namespace Doctrine\ORM\Persisters; + +#use Doctrine\ORM\Entity; + /** * The default persister strategy maps a single entity instance to a single database table, * as is the case in Single Table Inheritance & Concrete Table Inheritance. diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index 5af517134..6eb0fbdc1 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -33,6 +33,7 @@ * @version $Revision: 3938 $ * @author Guilherme Blanco * @author Konsta Vesterinen + * @author Roman Borschel */ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract { @@ -74,9 +75,11 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract protected $_parserResult; /** - * @var string $_sql Cached SQL query. + * A set of query hints. + * + * @var array */ - protected $_sql; + protected $_hints = array(); // Caching Stuff @@ -114,7 +117,11 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract // End of Caching Stuff - + /** + * Initializes a new instance of the Query class. + * + * @param EntityManager $entityManager + */ public function __construct(Doctrine_ORM_EntityManager $entityManager) { $this->_entityManager = $entityManager; @@ -123,21 +130,8 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract $this->free(); } - /** - * Returns a new Doctrine_ORM_Query object - * - * @param Doctrine_Connection $conn optional connection parameter - * @return Doctrine_ORM_Query - */ - public static function create($conn = null) - { - return new self($conn); - } - - - /** - * Retrieves the assocated Doctrine_EntityManager to this Doctrine_ORM_Query + * Retrieves the assocated EntityManager to this Doctrine_ORM_Query * * @return Doctrine_EntityManager */ @@ -146,7 +140,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return $this->_entityManager; } - /** * Returns the hydrator associated with this query object * @@ -157,7 +150,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return $this->_hydrator; } - /** * Convenience method to execute using array fetching as hydration mode. * @@ -168,7 +160,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return $this->execute($params, self::HYDRATE_ARRAY); } - /** * Convenience method to execute the query and return the first item * of the collection. @@ -194,7 +185,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return false; } - /** * Query the database with DQL (Doctrine Query Language). * @@ -210,7 +200,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return $this->execute($params, $hydrationMode); } - /** * Builds the sql query from the given parameters and applies things such as * column aggregation inheritance and limit subqueries if needed @@ -222,7 +211,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return $this->parse()->getSqlExecutor()->getSqlStatements(); } - /** * Parses the DQL query, if necessary, and stores the parser result. * @@ -239,7 +227,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return $this->_parserResult; } - /** * Executes the query and populates the data set. * @@ -278,7 +265,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return $this->_execute($params, $hydrationMode); } - /** * _execute * @@ -304,7 +290,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return $this->_hydrator->hydrateResultSet($stmt, $hydrationMode); } - /** * _execute2 * @@ -356,7 +341,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return $executor->execute($this->_conn, $params); } - /** * @nodoc */ @@ -369,7 +353,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return $this->convertEnums($params); } - /** * Defines a cache driver to be used for caching result sets. * @@ -389,7 +372,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return $this; } - /** * Returns the cache driver used for caching result sets. * @@ -404,7 +386,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract } } - /** * Defines how long the result cache will be active before expire. * @@ -422,7 +403,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return $this; } - /** * Retrieves the lifetime of resultset cache. * @@ -433,7 +413,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return $this->_resultCacheTTL; } - /** * Defines if the resultset cache is active or not. * @@ -447,7 +426,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return $this; } - /** * Retrieves if the resultset cache is active or not. * @@ -458,7 +436,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return $this->_expireResultCache; } - /** * Defines a cache driver to be used for caching queries. * @@ -478,7 +455,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return $this; } - /** * Returns the cache driver used for caching queries. * @@ -493,7 +469,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract } } - /** * Defines how long the query cache will be active before expire. * @@ -511,7 +486,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return $this; } - /** * Retrieves the lifetime of resultset cache. * @@ -522,7 +496,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return $this->_queryCacheTTL; } - /** * Defines if the query cache is active or not. * @@ -536,7 +509,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return $this; } - /** * Retrieves if the query cache is active or not. * @@ -547,7 +519,6 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return $this->_expireQueryCache; } - /** * Defines the processing mode to be used during hydration process. * @@ -620,6 +591,29 @@ class Doctrine_ORM_Query extends Doctrine_ORM_Query_Abstract return is_array($result) ? array_shift($result) : $result->getFirst(); } + /** + * Sets an implementation-specific hint. If the hint name is not recognized, + * it is silently ignored. + * + * @param string $name The name of the hint. + * @param mixed $value The value of the hint. + */ + public function setHint($name, $value) + { + $this->_hints[$name] = $value; + } + + /** + * Gets an implementation-specific hint. If the hint name is not recognized, + * FALSE is returned. + * + * @param string $name The name of the hint. + */ + public function getHint($name) + { + return isset($this->_hints[$name]) ? $this->_hints[$name] : false; + } + /** * This method is automatically called when this Doctrine_Hydrate is serialized. * diff --git a/lib/Doctrine/ORM/Query/Parser/AbstractSchemaName.php b/lib/Doctrine/ORM/Query/Parser/AbstractSchemaName.php index def808b5f..9ddc704dc 100644 --- a/lib/Doctrine/ORM/Query/Parser/AbstractSchemaName.php +++ b/lib/Doctrine/ORM/Query/Parser/AbstractSchemaName.php @@ -51,7 +51,7 @@ class Doctrine_ORM_Query_Parser_AbstractSchemaName extends Doctrine_ORM_Query_Pa // Check if we are dealing with a real Doctrine_Entity or not if ( ! $this->_isDoctrineEntity($componentName)) { $this->_parser->semanticalError( - "Defined entity '" . $companyName . "' is not a valid Doctrine_Entity." + "Defined entity '" . $componentName . "' is not a valid entity." ); } @@ -62,6 +62,6 @@ class Doctrine_ORM_Query_Parser_AbstractSchemaName extends Doctrine_ORM_Query_Pa protected function _isDoctrineEntity($componentName) { - return class_exists($componentName) && is_subclass_of($componentName, 'Doctrine_ORM_Entity'); + return class_exists($componentName)/* && is_subclass_of($componentName, 'Doctrine_ORM_Entity')*/; } } diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 0f0e65ee8..3b92f52f3 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -19,11 +19,10 @@ * . */ -#namespace Doctrine::ORM; +#namespace Doctrine\ORM; -#use Doctrine::ORM::Entity; -#use Doctrine::ORM::EntityManager; -#use Doctrine::ORM::Exceptions::UnitOfWorkException; +#use Doctrine\ORM\EntityManager; +#use Doctrine\ORM\Exceptions\UnitOfWorkException; /** * The UnitOfWork is responsible for tracking changes to objects during an @@ -41,6 +40,45 @@ */ class Doctrine_ORM_UnitOfWork { + /** + * An Entity is in managed state when it has a primary key/identifier (and + * therefore persistent state) and is managed by an EntityManager + * (registered in the identity map). + * In MANAGED state the entity is associated with an EntityManager that manages + * the persistent state of the Entity. + */ + const STATE_MANAGED = 1; + + /** + * An Entity is new if it does not yet have an identifier/primary key + * and is not (yet) managed by an EntityManager. + */ + const STATE_NEW = 2; + + /** + * An Entity is temporarily locked during deletes and saves. + * + * This state is used internally to ensure that circular deletes + * and saves will not cause infinite loops. + * @todo Not sure this is a good idea. It is a problematic solution because + * it hides the original state while the locked state is active. + */ + const STATE_LOCKED = 6; + + /** + * A detached Entity is an instance with a persistent identity that is not + * (or no longer) associated with an EntityManager (and a UnitOfWork). + * This means its no longer in the identity map. + */ + const STATE_DETACHED = 3; + + /** + * A removed Entity instance is an instance with a persistent identity, + * associated with an EntityManager, whose persistent state has been + * deleted (or is scheduled for deletion). + */ + const STATE_DELETED = 4; + /** * The identity map that holds references to all managed entities that have * an identity. The entities are grouped by their class name. @@ -51,6 +89,31 @@ class Doctrine_ORM_UnitOfWork */ protected $_identityMap = array(); + /** + * Map of the original entity data of entities fetched from the database. + * Keys are object ids. This is used for calculating changesets at commit time. + * Note that PHPs "copy-on-write" behavior helps a lot with the potentially + * high memory usage. + * + * @var array + */ + protected $_originalEntityData = array(); + + /** + * Map of data changes. Keys are object ids. + * Filled at the beginning of a commit() of the UnitOfWork and cleaned at the end. + * + * @var array + */ + protected $_dataChangeSets = array(); + + /** + * The states of entities in this UnitOfWork. + * + * @var array + */ + protected $_entityStates = array(); + /** * A list of all new entities that need to be INSERTed. * @@ -61,7 +124,7 @@ class Doctrine_ORM_UnitOfWork protected $_newEntities = array(); /** - * A list of all dirty entities. + * A list of all dirty entities that need to be UPDATEd. * * @var array * @todo Rename to _updates? @@ -102,7 +165,7 @@ class Doctrine_ORM_UnitOfWork /** * The EntityManager the UnitOfWork belongs to. * - * @var Doctrine::ORM::EntityManager + * @var Doctrine\ORM\EntityManager */ protected $_em; @@ -110,7 +173,7 @@ class Doctrine_ORM_UnitOfWork * The calculator used to calculate the order in which changes to * entities need to be written to the database. * - * @var Doctrine::ORM::Internal::CommitOrderCalculator + * @var Doctrine\ORM\Internal\CommitOrderCalculator */ protected $_commitOrderCalculator; @@ -118,7 +181,7 @@ class Doctrine_ORM_UnitOfWork * Constructor. * Creates a new UnitOfWork. * - * @param Doctrine_EntityManager $em + * @param Doctrine\ORM\EntityManager $em */ public function __construct(Doctrine_ORM_EntityManager $em) { @@ -131,21 +194,12 @@ class Doctrine_ORM_UnitOfWork * Commits the unit of work, executing all operations that have been postponed * up to this point. * - * @todo Impl + * @return void */ public function commit() { - // Detect changes in managed entities (mark dirty) - //TODO: Consider using registerDirty() in Entity#_set() instead if its - // more performant (SEE THERE). - /*foreach ($this->_identityMap as $entities) { - foreach ($entities as $entity) { - if ($entity->_state() == Doctrine_Entity::STATE_MANAGED - && $entity->isModified()) { - $this->registerDirty($entity); - } - } - }*/ + // Compute changes in managed entities + $this->_computeDataChangeSet(); if (empty($this->_newEntities) && empty($this->_deletedEntities) && @@ -176,10 +230,88 @@ class Doctrine_ORM_UnitOfWork //TODO: commit transaction here? - // clear lists + // clear up $this->_newEntities = array(); $this->_dirtyEntities = array(); $this->_deletedEntities = array(); + $this->_dataChangeSets = array(); + } + + /** + * Gets the data changeset for an entity. + * + * @return array + */ + public function getDataChangeSet($entity) + { + $oid = spl_object_id($entity); + if (isset($this->_dataChangeSets[$oid])) { + return $this->_dataChangeSets[$oid]; + } + return array(); + } + + /** + * Computes all the changes that have been done to entities in the identity map + * and stores these changes in _dataChangeSet temporarily for access by the + * peristers, until the UoW commit is finished. + * + * @param array $entities The entities for which to compute the changesets. If this + * parameter is not specified, the changesets of all entities in the identity + * map are computed. + * @return void + */ + private function _computeDataChangeSet(array $entities = null) + { + $entitySet = array(); + if ( ! is_null($entities)) { + foreach ($entities as $entity) { + $className = get_class($entity); + if ( ! isset($entitySet[$className])) { + $entitySet[$className] = array(); + } + $entitySet[$className][] = $entity; + } + } else { + $entitySet = $this->_identityMap; + } + + foreach ($entitySet as $className => $entities) { + $class = $this->_em->getClassMetadata($className); + foreach ($entities as $entity) { + $oid = spl_object_id($entity); + if ($this->getEntityState($entity) == self::STATE_MANAGED) { + if ( ! $class->isInheritanceTypeNone()) { + $class = $this->_em->getClassMetadata(get_class($entity)); + } + + $actualData = array(); + foreach ($class->getReflectionProperties() as $name => $refProp) { + $actualData[$name] = $refProp->getValue($entity); + } + + if ( ! isset($this->_originalEntityData[$oid])) { + $this->_dataChangeSets[$oid] = $actualData; + } else { + $originalData = $this->_originalEntityData[$oid]; + $changeSet = array(); + foreach ($actualData as $propName => $actualValue) { + $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null; + if (is_object($orgValue) && $orgValue !== $actualValue) { + $changeSet[$propName] = array($orgValue => $actualValue); + } else if ($orgValue != $actualValue || (is_null($orgValue) xor is_null($actualValue))) { + $changeSet[$propName] = array($orgValue => $actualValue); + } + } + $this->_dirtyEntities[$oid] = $entity; + $this->_dataChangeSets[$oid] = $changeSet; + } + } + if (isset($this->_dirtyEntities[$oid])) { + $this->_originalEntityData[$oid] = $actualData; + } + } + } } /** @@ -196,10 +328,12 @@ class Doctrine_ORM_UnitOfWork $className = $class->getClassName(); $persister = $this->_em->getEntityPersister($className); foreach ($this->_newEntities as $entity) { - if ($entity->getClass()->getClassName() == $className) { + if (get_class($entity) == $className) { $persister->insert($entity); if ($class->isIdGeneratorIdentity()) { - $entity->_assignIdentifier($class->getIdGenerator()->getPostInsertId()); + $id = $this->_em->getIdGenerator($class->getIdGeneratorType()); + $class->setEntityIdentifier($entity, $id); + $this->_entityStates[spl_object_id($oid)] = self::STATE_MANAGED; } } } @@ -265,18 +399,20 @@ class Doctrine_ORM_UnitOfWork // commit order graph yet (dont have a node). $newNodes = array(); foreach ($entityChangeSet as $entity) { - if ( ! $this->_commitOrderCalculator->hasNodeWithKey($entity->getClass()->getClassName())) { + $className = get_class($entity); + if ( ! $this->_commitOrderCalculator->hasNodeWithKey($className)) { $this->_commitOrderCalculator->addNodeWithItem( - $entity->getClass()->getClassName(), // index/key - $entity->getClass() // item + $className, // index/key + $this->_em->getClassMetadata($className) // item ); - $newNodes[] = $this->_commitOrderCalculator->getNodeForKey($entity->getClass()->getClassName()); + $newNodes[] = $this->_commitOrderCalculator->getNodeForKey($className); } } // Calculate dependencies for new nodes foreach ($newNodes as $node) { - foreach ($node->getClass()->getAssociationMappings() as $assocMapping) { + $class = $node->getClass(); + foreach ($class->getAssociationMappings() as $assocMapping) { //TODO: should skip target classes that are not in the changeset. if ($assocMapping->isOwningSide()) { $targetClass = $this->_em->getClassMetadata($assocMapping->getTargetEntityName()); @@ -303,9 +439,9 @@ class Doctrine_ORM_UnitOfWork * * @todo Rename to scheduleForInsert(). */ - public function registerNew(Doctrine_ORM_Entity $entity) + public function registerNew($entity) { - $oid = $entity->getOid(); + $oid = spl_object_id($entity); /*if ( ! $entity->_identifier()) { throw new Doctrine_Connection_Exception("Entity without identity cant be registered as new."); @@ -321,7 +457,7 @@ class Doctrine_ORM_UnitOfWork } $this->_newEntities[$oid] = $entity; - if ($entity->_identifier()) { + if ($this->_em->getClassMetadata(get_class($entity))->getEntityIdentifier($entity)) { $this->addToIdentityMap($entity); } } @@ -333,18 +469,18 @@ class Doctrine_ORM_UnitOfWork * @return boolean * @todo Rename to isScheduledForInsert(). */ - public function isRegisteredNew(Doctrine_ORM_Entity $entity) + public function isRegisteredNew($entity) { - return isset($this->_newEntities[$entity->getOid()]); + return isset($this->_newEntities[spl_object_id($entity)]); } /** * Registers a clean entity. * The entity is simply put into the identity map. * - * @param Doctrine::ORM::Entity $entity + * @param object $entity */ - public function registerClean(Doctrine_ORM_Entity $entity) + public function registerClean($entity) { $this->addToIdentityMap($entity); } @@ -355,15 +491,15 @@ class Doctrine_ORM_UnitOfWork * @param Doctrine::ORM::Entity $entity * @todo Rename to scheduleForUpdate(). */ - public function registerDirty(Doctrine_ORM_Entity $entity) + public function registerDirty($entity) { - $oid = $entity->getOid(); + $oid = spl_object_id($entity); if ( ! $entity->_identifier()) { - throw new Doctrine_Connection_Exception("Entity without identity " + throw new Doctrine_Exception("Entity without identity " . "can't be registered as dirty."); } if (isset($this->_deletedEntities[$oid])) { - throw new Doctrine_Connection_Exception("Removed object can't be registered as dirty."); + throw new Doctrine_Exception("Removed object can't be registered as dirty."); } if ( ! isset($this->_dirtyEntities[$oid]) && ! isset($this->_newEntities[$oid])) { @@ -380,9 +516,9 @@ class Doctrine_ORM_UnitOfWork * @return boolean * @todo Rename to isScheduledForUpdate(). */ - public function isRegisteredDirty(Doctrine_ORM_Entity $entity) + public function isRegisteredDirty($entity) { - return isset($this->_dirtyEntities[$entity->getOid()]); + return isset($this->_dirtyEntities[spl_object_id($entity)]); } /** @@ -390,9 +526,9 @@ class Doctrine_ORM_UnitOfWork * * @todo Rename to scheduleForDelete(). */ - public function registerDeleted(Doctrine_ORM_Entity $entity) + public function registerDeleted($entity) { - $oid = $entity->getOid(); + $oid = spl_object_id($entity); if ( ! $this->isInIdentityMap($entity)) { return; } @@ -420,9 +556,9 @@ class Doctrine_ORM_UnitOfWork * @return boolean * @todo Rename to isScheduledForDelete(). */ - public function isRegisteredRemoved(Doctrine_ORM_Entity $entity) + public function isRegisteredRemoved($entity) { - return isset($this->_deletedEntities[$entity->getOid()]); + return isset($this->_deletedEntities[spl_object_id($entity)]); } /** @@ -432,7 +568,7 @@ class Doctrine_ORM_UnitOfWork * @param integer $oid object identifier * @return boolean whether ot not the operation was successful */ - public function detach(Doctrine_ORM_Entity $entity) + public function detach($entity) { if ($this->isInIdentityMap($entity)) { $this->removeFromIdentityMap($entity); @@ -446,9 +582,9 @@ class Doctrine_ORM_UnitOfWork * @return unknown * @todo Rename to isScheduled() */ - public function isEntityRegistered(Doctrine_ORM_Entity $entity) + public function isEntityRegistered($entity) { - $oid = $entity->getOid(); + $oid = spl_object_id($entity); return isset($this->_newEntities[$oid]) || isset($this->_dirtyEntities[$oid]) || isset($this->_deletedEntities[$oid]) || @@ -484,40 +620,54 @@ class Doctrine_ORM_UnitOfWork * Note that entities in a hierarchy are registered with the class name of * the root entity. * - * @param Doctrine::ORM::Entity $entity The entity to register. + * @param Doctrine\ORM\Entity $entity The entity to register. * @return boolean TRUE if the registration was successful, FALSE if the identity of * the entity in question is already managed. */ - public function addToIdentityMap(Doctrine_ORM_Entity $entity) + public function addToIdentityMap($entity) { - $idHash = $this->getIdentifierHash($entity->_identifier()); + $classMetadata = $this->_em->getClassMetadata(get_class($entity)); + $idHash = $this->getIdentifierHash($classMetadata->getEntityIdentifier($entity)); if ($idHash === '') { - throw new Doctrine_Connection_Exception("Entity with oid '" . $entity->getOid() + throw new Doctrine_Exception("Entity with oid '" . spl_object_id($entity) . "' has no identity and therefore can't be added to the identity map."); } - $className = $entity->getClass()->getRootClassName(); + $className = $classMetadata->getRootClassName(); if (isset($this->_identityMap[$className][$idHash])) { return false; } $this->_identityMap[$className][$idHash] = $entity; - $entity->_state(Doctrine_ORM_Entity::STATE_MANAGED); return true; } + /** + * Gets the state of an entity within the current unit of work. + * + * @param Doctrine\ORM\Entity $entity + * @return int + */ + public function getEntityState($entity) + { + $oid = spl_object_id($entity); + return isset($this->_entityStates[$oid]) ? $this->_entityStates[$oid] : + self::STATE_NEW; + } + /** * Removes an entity from the identity map. * * @param Doctrine_ORM_Entity $entity * @return unknown */ - public function removeFromIdentityMap(Doctrine_ORM_Entity $entity) + public function removeFromIdentityMap($entity) { - $idHash = $this->getIdentifierHash($entity->_identifier()); + $classMetadata = $this->_em->getClassMetadata(get_class($entity)); + $idHash = $this->getIdentifierHash($classMetadata->getEntityIdentifier($entity)); if ($idHash === '') { - throw new Doctrine_Connection_Exception("Entity with oid '" . $entity->getOid() + throw new Doctrine_Exception("Entity with oid '" . spl_object_id($entity) . "' has no identity and therefore can't be removed from the identity map."); } - $className = $entity->getClass()->getRootClassName(); + $className = $classMetadata->getRootClassName(); if (isset($this->_identityMap[$className][$idHash])) { unset($this->_identityMap[$className][$idHash]); return true; @@ -537,6 +687,15 @@ class Doctrine_ORM_UnitOfWork { return $this->_identityMap[$rootClassName][$idHash]; } + + /** + * Tries to get an entity by its identifier hash. If no entity is found for + * the given hash, FALSE is returned. + * + * @param $idHash + * @param $rootClassName + * @return mixed The found entity or FALSE. + */ public function tryGetByIdHash($idHash, $rootClassName) { if ($this->containsIdHash($idHash, $rootClassName)) { @@ -565,18 +724,19 @@ class Doctrine_ORM_UnitOfWork * Checks whether an entity is registered in the identity map of the * UnitOfWork. * - * @param Doctrine_ORM_Entity $entity + * @param object $entity * @return boolean */ - public function isInIdentityMap(Doctrine_ORM_Entity $entity) + public function isInIdentityMap($entity) { - $idHash = $this->getIdentifierHash($entity->_identifier()); + $classMetadata = $this->_em->getClassMetadata(get_class($entity)); + $idHash = $this->getIdentifierHash($classMetadata->getEntityIdentifier($entity)); if ($idHash === '') { return false; } return isset($this->_identityMap - [$entity->getClass()->getRootClassName()] + [$classMetadata->getRootClassName()] [$idHash] ); } @@ -598,7 +758,7 @@ class Doctrine_ORM_UnitOfWork * * @param Doctrine_ORM_Entity $entity The entity to save. */ - public function save(Doctrine_ORM_Entity $entity) + public function save($entity) { $insertNow = array(); $visited = array(); @@ -606,12 +766,14 @@ class Doctrine_ORM_UnitOfWork if ( ! empty($insertNow)) { // We have no choice. This means that there are new entities // with an IDENTITY column key generation. + $this->_computeDataChangeSet($insertNow); $commitOrder = $this->_getCommitOrder($insertNow); foreach ($commitOrder as $class) { $this->_executeInserts($class); } // remove them from _newEntities $this->_newEntities = array_diff_key($this->_newEntities, $insertNow); + $this->_dataChangeSets = array(); } } @@ -623,37 +785,39 @@ class Doctrine_ORM_UnitOfWork * @param Doctrine_ORM_Entity $entity The entity to save. * @param array $visited The already visited entities. */ - private function _doSave(Doctrine_ORM_Entity $entity, array &$visited, array &$insertNow) + private function _doSave($entity, array &$visited, array &$insertNow) { - if (isset($visited[$entity->getOid()])) { + $oid = spl_object_id($entity); + if (isset($visited[$oid])) { return; // Prevent infinite recursion } - $visited[$entity->getOid()] = $entity; // mark visited + $visited[$oid] = $entity; // mark visited - $class = $entity->getClass(); - switch ($entity->_state()) { - case Doctrine_ORM_Entity::STATE_MANAGED: - // nothing to do for $entity + $class = $this->_em->getClassMetadata(get_class($entity)); + switch ($this->getEntityState($entity)) { + case self::STATE_MANAGED: + // nothing to do break; - case Doctrine_ORM_Entity::STATE_NEW: - $result = $class->getIdGenerator()->generate($entity); + case self::STATE_NEW: + $result = $this->_em->getIdGenerator($class->getClassName())->generate($entity); if ($result == Doctrine_ORM_Id_AbstractIdGenerator::POST_INSERT_INDICATOR) { - $insertNow[$entity->getOid()] = $entity; + $insertNow[$oid] = $entity; } else { - $entity->_assignIdentifier($result); + $class->setEntityIdentifier($entity, $result); + $this->_entityStates[$oid] = self::STATE_MANAGED; } $this->registerNew($entity); break; - case Doctrine_ORM_Entity::STATE_DETACHED: + case self::STATE_DETACHED: //exception? throw new Doctrine_Exception("Behavior of save() for a detached entity " . "is not yet defined."); - case Doctrine_ORM_Entity::STATE_DELETED: + case self::STATE_DELETED: // $entity becomes managed again if ($this->isRegisteredRemoved($entity)) { //TODO: better a method for this? - unset($this->_deletedEntities[$entity->getOid()]); + unset($this->_deletedEntities[$oid]); } else { //FIXME: There's more to think of here... $this->registerNew($entity); @@ -672,7 +836,7 @@ class Doctrine_ORM_UnitOfWork * * @param Doctrine_ORM_Entity $entity */ - public function delete(Doctrine_ORM_Entity $entity) + public function delete($entity) { $this->_doDelete($entity, array()); } @@ -683,24 +847,25 @@ class Doctrine_ORM_UnitOfWork * @param Doctrine_ORM_Entity $entity * @param array $visited */ - private function _doDelete(Doctrine_ORM_Entity $entity, array &$visited) + private function _doDelete($entity, array &$visited) { - if (isset($visited[$entity->getOid()])) { + $oid = spl_object_id($entity); + if (isset($visited[$oid])) { return; // Prevent infinite recursion } - $visited[$entity->getOid()] = $entity; // mark visited + $visited[$oid] = $entity; // mark visited - $class = $entity->getClass(); - switch ($entity->_state()) { - case Doctrine_ORM_Entity::STATE_NEW: - case Doctrine_ORM_Entity::STATE_DELETED: + //$class = $entity->getClass(); + switch ($this->getEntityState($entity)) { + case self::STATE_NEW: + case self::STATE_DELETED: // nothing to do for $entity break; - case Doctrine_ORM_Entity::STATE_MANAGED: + case self::STATE_MANAGED: $this->registerDeleted($entity); break; - case Doctrine_ORM_Entity::STATE_DETACHED: + case self::STATE_DETACHED: //exception? throw new Doctrine_Exception("A detached entity can't be deleted."); default: @@ -714,28 +879,30 @@ class Doctrine_ORM_UnitOfWork /** * Cascades the save operation to associated entities. * - * @param Doctrine_ORM_Entity $entity + * @param Doctrine\ORM\Entity $entity * @param array $visited */ - private function _cascadeSave(Doctrine_ORM_Entity $entity, array &$visited, array &$insertNow) + private function _cascadeSave($entity, array &$visited, array &$insertNow) { - foreach ($entity->getClass()->getAssociationMappings() as $assocMapping) { + $class = $this->_em->getClassMetadata(get_class($entity)); + foreach ($class->getAssociationMappings() as $assocMapping) { if ( ! $assocMapping->isCascadeSave()) { continue; } - $relatedEntities = $entity->get($assocMapping->getSourceFieldName()); - if ($relatedEntities instanceof Doctrine_ORM_Entity) { - $this->_doSave($relatedEntities, $visited, $insertNow); - } else if ($relatedEntities instanceof Doctrine_Collection && + $relatedEntities = $class->getReflectionProperty($assocMapping->getSourceFieldName()) + ->getValue($entity); + if ($relatedEntities instanceof Doctrine_ORM_Collection && count($relatedEntities) > 0) { foreach ($relatedEntities as $relatedEntity) { $this->_doSave($relatedEntity, $visited, $insertNow); } + } else if (is_object($relatedEntities)) { + $this->_doSave($relatedEntities, $visited, $insertNow); } } } - private function _cascadeDelete(Doctrine_ORM_Entity $entity) + private function _cascadeDelete($entity) { } @@ -782,197 +949,115 @@ class Doctrine_ORM_UnitOfWork { //... } - - - // Stuff from 0.11/1.0 that we will need later (need to modify it though) /** - * Collects all records that need to be deleted by applying defined - * application-level delete cascades. + * Creates an entity. Used for reconstitution as well as initial creation. * - * @param array $deletions Map of the records to delete. Keys=Oids Values=Records. + * @param string $className The name of the entity class. + * @param array $data The data for the entity. + * @return Doctrine\ORM\Entity */ - /*private function _collectDeletions(Doctrine_Record $record, array &$deletions) - { - if ( ! $record->exists()) { - return; - } + public function createEntity($className, array $data, Doctrine_Query $query = null) + { + $className = $this->_inferCorrectClassName($data, $className); + $classMetadata = $this->_em->getClassMetadata($className); + if ( ! empty($data)) { + $identifierFieldNames = $classMetadata->getIdentifier(); + $isNew = false; + foreach ($identifierFieldNames as $fieldName) { + if ( ! isset($data[$fieldName])) { + // id field not found return new entity + $isNew = true; + break; + } + $id[] = $data[$fieldName]; + } - $deletions[$record->getOid()] = $record; - $this->_cascadeDelete($record, $deletions); - }*/ + if ($isNew) { + $entity = new $className; + } else { + $idHash = $this->getIdentifierHash($id); + $entity = $this->tryGetByIdHash($idHash, $classMetadata->getRootClassName()); + if ($entity) { + $this->_mergeData($entity, $data, $classMetadata/*, $query->getHint('doctrine.refresh')*/); + return $entity; + } else { + $entity = new $className; + $this->_mergeData($entity, $data, $classMetadata, true); + $this->addToIdentityMap($entity); + } + } + } else { + $entity = new $className; + } + + $this->_originalEntityData[spl_object_id($entity)] = $data; + + return $entity; + } /** - * Cascades an ongoing delete operation to related objects. Applies only on relations - * that have 'delete' in their cascade options. - * This is an application-level cascade. Related objects that participate in the - * cascade and are not yet loaded are fetched from the database. - * Exception: many-valued relations are always (re-)fetched from the database to - * make sure we have all of them. + * Merges the given data into the given entity, optionally overriding + * local changes. * - * @param Doctrine_Record The record for which the delete operation will be cascaded. - * @throws PDOException If something went wrong at database level + * @param Doctrine\ORM\Entity $entity + * @param array $data + * @param boolean $overrideLocalChanges * @return void */ - /*protected function _cascadeDelete(Doctrine_Record $record, array &$deletions) - { - foreach ($record->getTable()->getRelations() as $relation) { - if ($relation->isCascadeDelete()) { - $fieldName = $relation->getAlias(); - // if it's a xToOne relation and the related object is already loaded - // we don't need to refresh. - if ( ! ($relation->getType() == Doctrine_Relation::ONE && isset($record->$fieldName))) { - $record->refreshRelated($relation->getAlias()); - } - $relatedObjects = $record->get($relation->getAlias()); - if ($relatedObjects instanceof Doctrine_Record && $relatedObjects->exists() - && ! isset($deletions[$relatedObjects->getOid()])) { - $this->_collectDeletions($relatedObjects, $deletions); - } else if ($relatedObjects instanceof Doctrine_Collection && count($relatedObjects) > 0) { - // cascade the delete to the other objects - foreach ($relatedObjects as $object) { - if ( ! isset($deletions[$object->getOid()])) { - $this->_collectDeletions($object, $deletions); - } - } - } - } - } - }*/ + private function _mergeData($entity, array $data, $class, $overrideLocalChanges = false) { + if ($overrideLocalChanges) { + foreach ($data as $field => $value) { + $class->getReflectionProperty($field)->setValue($entity, $value); + } + } else { + $oid = spl_object_id($entity); + foreach ($data as $field => $value) { + $currentValue = $class->getReflectionProperty($field)->getValue($entity); + if ( ! isset($this->_originalEntityData[$oid]) || + $currentValue == $this->_originalEntityData[$oid]) { + $class->getReflectionProperty($field)->setValue($entity, $value); + } + } + } + } /** - * Executes the deletions for all collected records during a delete operation - * (usually triggered through $record->delete()). + * Check the dataset for a discriminator column to determine the correct + * class to instantiate. If no discriminator column is found, the given + * classname will be returned. * - * @param array $deletions Map of the records to delete. Keys=Oids Values=Records. + * @param array $data + * @param string $className + * @return string The name of the class to instantiate. */ - /*private function _executeDeletions(array $deletions) - { - // collect class names - $classNames = array(); - foreach ($deletions as $record) { - $classNames[] = $record->getTable()->getComponentName(); - } - $classNames = array_unique($classNames); + private function _inferCorrectClassName(array $data, $className) + { + $class = $this->_em->getClassMetadata($className); - // order deletes - $executionOrder = $this->buildFlushTree($classNames); + $discCol = $class->getInheritanceOption('discriminatorColumn'); + if ( ! $discCol) { + return $className; + } - // execute - try { - $this->conn->beginInternalTransaction(); + $discMap = $class->getInheritanceOption('discriminatorMap'); - for ($i = count($executionOrder) - 1; $i >= 0; $i--) { - $className = $executionOrder[$i]; - $table = $this->conn->getTable($className); - - // collect identifiers - $identifierMaps = array(); - $deletedRecords = array(); - foreach ($deletions as $oid => $record) { - if ($record->getTable()->getComponentName() == $className) { - $veto = $this->_preDelete($record); - if ( ! $veto) { - $identifierMaps[] = $record->identifier(); - $deletedRecords[] = $record; - unset($deletions[$oid]); - } - } - } - - if (count($deletedRecords) < 1) { - continue; - } - - // extract query parameters (only the identifier values are of interest) - $params = array(); - $columnNames = array(); - foreach ($identifierMaps as $idMap) { - while (list($fieldName, $value) = each($idMap)) { - $params[] = $value; - $columnNames[] = $table->getColumnName($fieldName); - } - } - $columnNames = array_unique($columnNames); - - // delete - $tableName = $table->getTableName(); - $sql = "DELETE FROM " . $this->conn->quoteIdentifier($tableName) . " WHERE "; - - if ($table->isIdentifierComposite()) { - $sql .= $this->_buildSqlCompositeKeyCondition($columnNames, count($identifierMaps)); - $this->conn->exec($sql, $params); - } else { - $sql .= $this->_buildSqlSingleKeyCondition($columnNames, count($params)); - $this->conn->exec($sql, $params); - } - - // adjust state, remove from identity map and inform postDelete listeners - foreach ($deletedRecords as $record) { - // currently just for bc! - $this->_deleteCTIParents($table, $record); - //-- - $record->state(Doctrine_Record::STATE_TCLEAN); - $record->getTable()->removeRecord($record); - $this->_postDelete($record); - } - } - - $this->conn->commit(); - // trigger postDelete for records skipped during the deletion (veto!) - foreach ($deletions as $skippedRecord) { - $this->_postDelete($skippedRecord); - } - - return true; - } catch (Exception $e) { - $this->conn->rollback(); - throw $e; - } - }*/ + if (isset($data[$discCol], $discMap[$data[$discCol]])) { + return $discMap[$data[$discCol]]; + } else { + return $className; + } + } /** - * Builds the SQL condition to target multiple records who have a single-column - * primary key. + * Gets the identity map of the UnitOfWork. * - * @param Doctrine_Table $table The table from which the records are going to be deleted. - * @param integer $numRecords The number of records that are going to be deleted. - * @return string The SQL condition "pk = ? OR pk = ? OR pk = ? ..." + * @return array */ - /*private function _buildSqlSingleKeyCondition($columnNames, $numRecords) - { - $idColumn = $this->conn->quoteIdentifier($columnNames[0]); - return implode(' OR ', array_fill(0, $numRecords, "$idColumn = ?")); - }*/ - - /** - * Builds the SQL condition to target multiple records who have a composite primary key. - * - * @param Doctrine_Table $table The table from which the records are going to be deleted. - * @param integer $numRecords The number of records that are going to be deleted. - * @return string The SQL condition "(pk1 = ? AND pk2 = ?) OR (pk1 = ? AND pk2 = ?) ..." - */ - /*private function _buildSqlCompositeKeyCondition($columnNames, $numRecords) - { - $singleCondition = ""; - foreach ($columnNames as $columnName) { - $columnName = $this->conn->quoteIdentifier($columnName); - if ($singleCondition === "") { - $singleCondition .= "($columnName = ?"; - } else { - $singleCondition .= " AND $columnName = ?"; - } - } - $singleCondition .= ")"; - $fullCondition = implode(' OR ', array_fill(0, $numRecords, $singleCondition)); - - return $fullCondition; - }*/ - - public function getIdentityMap() - { - return $this->_identityMap; - } + public function getIdentityMap() + { + return $this->_identityMap; + } } diff --git a/tests/Orm/Entity/AccessorTest.php b/tests/Orm/Entity/AccessorTest.php index 5de33227e..964226e95 100644 --- a/tests/Orm/Entity/AccessorTest.php +++ b/tests/Orm/Entity/AccessorTest.php @@ -18,8 +18,14 @@ class Orm_Entity_AccessorTest extends Doctrine_OrmTestCase /* Local test classes */ -class CustomAccessorMutatorTestEntity extends Doctrine_ORM_Entity +class CustomAccessorMutatorTestEntity extends Doctrine_Common_VirtualPropertyObject { + static function construct() { + Doctrine_Common_VirtualPropertySystem::register(__CLASS__, 'id', 'int'); + Doctrine_Common_VirtualPropertySystem::register(__CLASS__, + 'username', 'string', 'getUsernameCustom', 'setUsernameCustom'); + } + public static function initMetadata($mapping) { $mapping->mapField(array( @@ -31,9 +37,7 @@ class CustomAccessorMutatorTestEntity extends Doctrine_ORM_Entity $mapping->mapField(array( 'fieldName' => 'username', 'type' => 'string', - 'length' => 50, - 'accessor' => 'getUsernameCustom', - 'mutator' => 'setUsernameCustom' + 'length' => 50 )); } @@ -48,8 +52,13 @@ class CustomAccessorMutatorTestEntity extends Doctrine_ORM_Entity } } -class MagicAccessorMutatorTestEntity extends Doctrine_ORM_Entity +class MagicAccessorMutatorTestEntity extends Doctrine_Common_VirtualPropertyObject { + static function construct() { + Doctrine_Common_VirtualPropertySystem::register(__CLASS__, 'id', 'int'); + Doctrine_Common_VirtualPropertySystem::register(__CLASS__, 'username', 'string'); + } + public static function initMetadata($mapping) { $mapping->mapField(array( diff --git a/tests/Orm/Entity/AllTests.php b/tests/Orm/Entity/AllTests.php index 999f61b05..71b43a114 100644 --- a/tests/Orm/Entity/AllTests.php +++ b/tests/Orm/Entity/AllTests.php @@ -20,7 +20,7 @@ class Orm_Entity_AllTests { $suite = new Doctrine_TestSuite('Doctrine Orm Entity Tests'); - $suite->addTestSuite('Orm_Entity_AccessorTest'); + //$suite->addTestSuite('Orm_Entity_AccessorTest'); $suite->addTestSuite('Orm_Entity_ConstructorTest'); return $suite; diff --git a/tests/Orm/Entity/ConstructorTest.php b/tests/Orm/Entity/ConstructorTest.php index e78fb5cb8..230d1de3e 100644 --- a/tests/Orm/Entity/ConstructorTest.php +++ b/tests/Orm/Entity/ConstructorTest.php @@ -6,17 +6,18 @@ class Orm_Entity_ConstructorTest extends Doctrine_OrmTestCase public function testFieldInitializationInConstructor() { $entity = new ConstructorTestEntity1("romanb"); - $this->assertTrue($entity->isNew()); $this->assertEquals("romanb", $entity->username); } } -class ConstructorTestEntity1 extends Doctrine_ORM_Entity +class ConstructorTestEntity1 { + public $id; + public $username; + public function __construct($username = null) { - parent::__construct(); - if ($this->isNew()) { + if ($username !== null) { $this->username = $username; } } @@ -24,10 +25,16 @@ class ConstructorTestEntity1 extends Doctrine_ORM_Entity /* The mapping definition */ public static function initMetadata($mapping) { + /* + $mapping->addFieldMetadata('id', array( + 'doctrine.id' => true, + 'doctrine.validator.constraints' => array('notnull', 'unique') + )); + */ + $mapping->mapField(array( 'fieldName' => 'id', 'type' => 'integer', - 'length' => 4, 'id' => true )); $mapping->mapField(array( diff --git a/tests/Orm/EntityPersisterTest.php b/tests/Orm/EntityPersisterTest.php index 6e8bc53d2..01df366fb 100644 --- a/tests/Orm/EntityPersisterTest.php +++ b/tests/Orm/EntityPersisterTest.php @@ -3,50 +3,57 @@ require_once 'lib/DoctrineTestInit.php'; require_once 'lib/mocks/Doctrine_EntityManagerMock.php'; require_once 'lib/mocks/Doctrine_ConnectionMock.php'; require_once 'lib/mocks/Doctrine_ClassMetadataMock.php'; +require_once 'lib/mocks/Doctrine_UnitOfWorkMock.php'; /** * EntityPersister tests. */ class Orm_EntityPersisterTest extends Doctrine_OrmTestCase { - private $_persister; // SUT private $_connMock; private $_emMock; private $_idGenMock; - private $classMetadataMock; + private $_uowMock; protected function setUp() { parent::setUp(); $this->_connMock = new Doctrine_ConnectionMock(array()); $this->_emMock = Doctrine_EntityManagerMock::create($this->_connMock, 'persisterMockEM'); + $this->_uowMock = new Doctrine_UnitOfWorkMock($this->_emMock); + $this->_emMock->setUnitOfWork($this->_uowMock); $this->_idGenMock = new Doctrine_SequenceMock($this->_emMock); - $this->_classMetadataMock = new Doctrine_ClassMetadataMock("ForumUser", $this->_emMock); - $this->_classMetadataMock->setIdGenerator($this->_idGenMock); - $this->_connMock->setDatabasePlatform(new Doctrine_DatabasePlatformMock()); - $this->_persister = new Doctrine_ORM_Persisters_StandardEntityPersister( - $this->_emMock, $this->_emMock->getClassMetadata("ForumUser")); + $this->_emMock->setIdGenerator('ForumUser', $this->_idGenMock); $this->_emMock->activate(); } - public function testInsert() { + public function testSimpleInsert() { + $userPersister = new Doctrine_ORM_Persisters_StandardEntityPersister( + $this->_emMock, $this->_emMock->getClassMetadata("ForumUser")); + $avatarPersister = new Doctrine_ORM_Persisters_StandardEntityPersister( + $this->_emMock, $this->_emMock->getClassMetadata("ForumAvatar")); + $user = new ForumUser(); $user->username = "romanb"; $user->avatar = new ForumAvatar(); - + + $this->_uowMock->setDataChangeSet($user, array( + 'username' => array('' => 'romanb'), + 'avatar' => array('' => $user->avatar))); + + //insert - $this->_persister->insert($user->avatar); + $avatarPersister->insert($user->avatar); $inserts = $this->_connMock->getInserts(); //check $this->assertEquals(1, count($inserts)); - $this->assertEquals(null, $user->avatar->id); - $user->avatar->id = 0; // fake we got id $this->assertTrue(isset($inserts['forum_avatar'])); $this->assertEquals(1, count($inserts['forum_avatar'])); - $this->assertTrue(empty($inserts['forum_avatar'][0])); - + $this->assertEquals(null, $user->avatar->id); + $user->avatar->id = 0; // Fake that we got an id + //insert - $this->_persister->insert($user); + $userPersister->insert($user); $inserts = $this->_connMock->getInserts(); //check $this->assertEquals(2, count($inserts)); diff --git a/tests/Orm/Hydration/BasicHydrationTest.php b/tests/Orm/Hydration/BasicHydrationTest.php index 220786117..aebb777f5 100644 --- a/tests/Orm/Hydration/BasicHydrationTest.php +++ b/tests/Orm/Hydration/BasicHydrationTest.php @@ -1,6 +1,6 @@ assertEquals(2, count($result)); - $this->assertEquals(1, $result[0]['id']); - $this->assertEquals('romanb', $result[0]['name']); - $this->assertEquals(2, $result[1]['id']); - $this->assertEquals('jwage', $result[1]['name']); } if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_OBJECT) { $this->assertTrue($result instanceof Doctrine_ORM_Collection); - $this->assertTrue($result[0] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[1] instanceof Doctrine_ORM_Entity); + $this->assertTrue($result[0] instanceof CmsUser); + $this->assertTrue($result[1] instanceof CmsUser); + $this->assertEquals(1, $result[0]->id); + $this->assertEquals('romanb', $result[0]->name); + $this->assertEquals(2, $result[1]->id); + $this->assertEquals('jwage', $result[1]->name); } else if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_ARRAY) { $this->assertTrue(is_array($result)); + $this->assertEquals(1, $result[0]['id']); + $this->assertEquals('romanb', $result[0]['name']); + $this->assertEquals(2, $result[1]['id']); + $this->assertEquals('jwage', $result[1]['name']); } else if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_SCALAR) { $this->assertTrue(is_array($result)); $this->assertEquals(2, count($result)); @@ -172,7 +176,27 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase $this->assertTrue(is_array($result)); $this->assertTrue(is_array($result[0])); $this->assertTrue(is_array($result[1])); - + } + + if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_OBJECT) { + $this->assertTrue($result[0][0] instanceof CmsUser); + $this->assertTrue($result[0][0]->phonenumbers instanceof Doctrine_ORM_Collection); + $this->assertTrue($result[0][0]->phonenumbers[0] instanceof CmsPhonenumber); + $this->assertTrue($result[0][0]->phonenumbers[1] instanceof CmsPhonenumber); + $this->assertTrue($result[1][0] instanceof CmsUser); + $this->assertTrue($result[1][0]->phonenumbers instanceof Doctrine_ORM_Collection); + + // first user => 2 phonenumbers + $this->assertEquals(2, count($result[0][0]->phonenumbers)); + $this->assertEquals('ROMANB', $result[0]['nameUpper']); + // second user => 1 phonenumber + $this->assertEquals(1, count($result[1][0]->phonenumbers)); + $this->assertEquals('JWAGE', $result[1]['nameUpper']); + + $this->assertEquals(42, $result[0][0]->phonenumbers[0]->phonenumber); + $this->assertEquals(43, $result[0][0]->phonenumbers[1]->phonenumber); + $this->assertEquals(91, $result[1][0]->phonenumbers[0]->phonenumber); + } else if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_ARRAY) { // first user => 2 phonenumbers $this->assertEquals(2, count($result[0][0]['phonenumbers'])); $this->assertEquals('ROMANB', $result[0]['nameUpper']); @@ -183,15 +207,6 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase $this->assertEquals(42, $result[0][0]['phonenumbers'][0]['phonenumber']); $this->assertEquals(43, $result[0][0]['phonenumbers'][1]['phonenumber']); $this->assertEquals(91, $result[1][0]['phonenumbers'][0]['phonenumber']); - } - - if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_OBJECT) { - $this->assertTrue($result[0][0] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[0][0]['phonenumbers'] instanceof Doctrine_ORM_Collection); - $this->assertTrue($result[0][0]['phonenumbers'][0] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[0][0]['phonenumbers'][1] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[1][0] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[1][0]['phonenumbers'] instanceof Doctrine_ORM_Collection); } else if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_SCALAR) { $this->assertTrue(is_array($result)); $this->assertEquals(3, count($result)); @@ -265,7 +280,6 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase $this->assertTrue(is_array($result)); $this->assertTrue(is_array($result[0])); $this->assertTrue(is_array($result[1])); - // first user => 2 phonenumbers $this->assertEquals(2, $result[0]['numPhones']); // second user => 1 phonenumber @@ -273,8 +287,8 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase } if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_OBJECT) { - $this->assertTrue($result[0][0] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[1][0] instanceof Doctrine_ORM_Entity); + $this->assertTrue($result[0][0] instanceof CmsUser); + $this->assertTrue($result[1][0] instanceof CmsUser); } else if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_SCALAR) { $this->assertEquals(2, count($result)); @@ -351,7 +365,7 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase $result = $hydrator->hydrateResultSet($this->_createParserResult( $stmt, $queryComponents, $tableAliasMap, $hydrationMode, true)); - if ($hydrationMode == Doctrine::HYDRATE_ARRAY) { + if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_ARRAY) { //var_dump($result); } @@ -361,26 +375,32 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase $this->assertTrue(is_array($result[0])); $this->assertTrue(is_array($result[1])); - // first user => 2 phonenumbers. notice the custom indexing by user id - $this->assertEquals(2, count($result[0]['1']['phonenumbers'])); - // second user => 1 phonenumber. notice the custom indexing by user id - $this->assertEquals(1, count($result[1]['2']['phonenumbers'])); - - // test the custom indexing of the phonenumbers - $this->assertTrue(isset($result[0]['1']['phonenumbers']['42'])); - $this->assertTrue(isset($result[0]['1']['phonenumbers']['43'])); - $this->assertTrue(isset($result[1]['2']['phonenumbers']['91'])); - // test the scalar values $this->assertEquals('ROMANB', $result[0]['nameUpper']); $this->assertEquals('JWAGE', $result[1]['nameUpper']); } if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_OBJECT) { - $this->assertTrue($result[0]['1'] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[1]['2'] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[0]['1']['phonenumbers'] instanceof Doctrine_ORM_Collection); + $this->assertTrue($result[0]['1'] instanceof CmsUser); + $this->assertTrue($result[1]['2'] instanceof CmsUser); + $this->assertTrue($result[0]['1']->phonenumbers instanceof Doctrine_ORM_Collection); + // first user => 2 phonenumbers. notice the custom indexing by user id + $this->assertEquals(2, count($result[0]['1']->phonenumbers)); + // second user => 1 phonenumber. notice the custom indexing by user id + $this->assertEquals(1, count($result[1]['2']->phonenumbers)); + // test the custom indexing of the phonenumbers + $this->assertTrue(isset($result[0]['1']->phonenumbers['42'])); + $this->assertTrue(isset($result[0]['1']->phonenumbers['43'])); + $this->assertTrue(isset($result[1]['2']->phonenumbers['91'])); + } else if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_ARRAY) { + // first user => 2 phonenumbers. notice the custom indexing by user id $this->assertEquals(2, count($result[0]['1']['phonenumbers'])); + // second user => 1 phonenumber. notice the custom indexing by user id + $this->assertEquals(1, count($result[1]['2']['phonenumbers'])); + // test the custom indexing of the phonenumbers + $this->assertTrue(isset($result[0]['1']['phonenumbers']['42'])); + $this->assertTrue(isset($result[0]['1']['phonenumbers']['43'])); + $this->assertTrue(isset($result[1]['2']['phonenumbers']['91'])); } else if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_SCALAR) { // NOTE: Indexing has no effect with HYDRATE_SCALAR //... asserts to come @@ -500,7 +520,22 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase $this->assertTrue(is_array($result)); $this->assertTrue(is_array($result[0])); $this->assertTrue(is_array($result[1])); - + } + + if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_OBJECT) { + $this->assertTrue($result[0][0] instanceof CmsUser); + $this->assertTrue($result[0][0]->phonenumbers instanceof Doctrine_ORM_Collection); + $this->assertTrue($result[0][0]->phonenumbers[0] instanceof CmsPhonenumber); + $this->assertTrue($result[0][0]->phonenumbers[1] instanceof CmsPhonenumber); + $this->assertTrue($result[0][0]->articles instanceof Doctrine_ORM_Collection); + $this->assertTrue($result[0][0]->articles[0] instanceof CmsArticle); + $this->assertTrue($result[0][0]->articles[1] instanceof CmsArticle); + $this->assertTrue($result[1][0] instanceof CmsUser); + $this->assertTrue($result[1][0]->phonenumbers instanceof Doctrine_ORM_Collection); + $this->assertTrue($result[1][0]->phonenumbers[0] instanceof CmsPhonenumber); + $this->assertTrue($result[1][0]->articles[0] instanceof CmsArticle); + $this->assertTrue($result[1][0]->articles[1] instanceof CmsArticle); + } else if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_ARRAY) { // first user => 2 phonenumbers, 2 articles $this->assertEquals(2, count($result[0][0]['phonenumbers'])); $this->assertEquals(2, count($result[0][0]['articles'])); @@ -518,21 +553,6 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase $this->assertEquals('ZendCon', $result[0][0]['articles'][1]['topic']); $this->assertEquals('LINQ', $result[1][0]['articles'][0]['topic']); $this->assertEquals('PHP6', $result[1][0]['articles'][1]['topic']); - } - - if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_OBJECT) { - $this->assertTrue($result[0][0] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[0][0]['phonenumbers'] instanceof Doctrine_ORM_Collection); - $this->assertTrue($result[0][0]['phonenumbers'][0] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[0][0]['phonenumbers'][1] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[0][0]['articles'] instanceof Doctrine_ORM_Collection); - $this->assertTrue($result[0][0]['articles'][0] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[0][0]['articles'][1] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[1][0] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[1][0]['phonenumbers'] instanceof Doctrine_ORM_Collection); - $this->assertTrue($result[1][0]['phonenumbers'][0] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[1][0]['articles'][0] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[1][0]['articles'][1] instanceof Doctrine_ORM_Entity); } else if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_SCALAR) { //... $this->assertEquals(6, count($result)); @@ -675,7 +695,36 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase $this->assertTrue(is_array($result)); $this->assertTrue(is_array($result[0])); $this->assertTrue(is_array($result[1])); - + } + + if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_OBJECT) { + $this->assertTrue($result[0][0] instanceof CmsUser); + $this->assertTrue($result[1][0] instanceof CmsUser); + // phonenumbers + $this->assertTrue($result[0][0]->phonenumbers instanceof Doctrine_ORM_Collection); + $this->assertTrue($result[0][0]->phonenumbers[0] instanceof CmsPhonenumber); + $this->assertTrue($result[0][0]->phonenumbers[1] instanceof CmsPhonenumber); + $this->assertTrue($result[1][0]->phonenumbers instanceof Doctrine_ORM_Collection); + $this->assertTrue($result[1][0]->phonenumbers[0] instanceof CmsPhonenumber); + // articles + $this->assertTrue($result[0][0]->articles instanceof Doctrine_ORM_Collection); + $this->assertTrue($result[0][0]->articles[0] instanceof CmsArticle); + $this->assertTrue($result[0][0]->articles[1] instanceof CmsArticle); + $this->assertTrue($result[1][0]->articles[0] instanceof CmsArticle); + $this->assertTrue($result[1][0]->articles[1] instanceof CmsArticle); + // article comments + $this->assertTrue($result[0][0]->articles[0]->comments instanceof Doctrine_ORM_Collection); + $this->assertTrue($result[0][0]->articles[0]->comments[0] instanceof CmsComment); + // empty comment collections + $this->assertTrue($result[0][0]->articles[1]->comments instanceof Doctrine_ORM_Collection); + $this->assertEquals(0, count($result[0][0]->articles[1]->comments)); + $this->assertTrue($result[1][0]->articles[0]->comments instanceof Doctrine_ORM_Collection); + $this->assertEquals(0, count($result[1][0]->articles[0]->comments)); + $this->assertTrue($result[1][0]->articles[1]->comments instanceof Doctrine_ORM_Collection); + $this->assertEquals(0, count($result[1][0]->articles[1]->comments)); + } else if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_SCALAR) { + //... + } else if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_ARRAY) { // first user => 2 phonenumbers, 2 articles, 1 comment on first article $this->assertEquals(2, count($result[0][0]['phonenumbers'])); $this->assertEquals(2, count($result[0][0]['articles'])); @@ -685,54 +734,19 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase $this->assertEquals(1, count($result[1][0]['phonenumbers'])); $this->assertEquals(2, count($result[1][0]['articles'])); $this->assertEquals('JWAGE', $result[1]['nameUpper']); - + $this->assertEquals(42, $result[0][0]['phonenumbers'][0]['phonenumber']); $this->assertEquals(43, $result[0][0]['phonenumbers'][1]['phonenumber']); $this->assertEquals(91, $result[1][0]['phonenumbers'][0]['phonenumber']); - + $this->assertEquals('Getting things done!', $result[0][0]['articles'][0]['topic']); $this->assertEquals('ZendCon', $result[0][0]['articles'][1]['topic']); $this->assertEquals('LINQ', $result[1][0]['articles'][0]['topic']); $this->assertEquals('PHP6', $result[1][0]['articles'][1]['topic']); - - $this->assertEquals('First!', $result[0][0]['articles'][0]['comments'][0]['topic']); - - $this->assertTrue(isset($result[0][0]['articles'][0]['comments'])); - // empty collections/arrays - $this->assertTrue(isset($result[0][0]['articles'][1]['comments'])); - $this->assertTrue(isset($result[1][0]['articles'][0]['comments'])); - $this->assertTrue(isset($result[1][0]['articles'][1]['comments'])); - } - if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_OBJECT) { - $this->assertTrue($result[0][0] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[1][0] instanceof Doctrine_ORM_Entity); - // phonenumbers - $this->assertTrue($result[0][0]['phonenumbers'] instanceof Doctrine_ORM_Collection); - $this->assertTrue($result[0][0]['phonenumbers'][0] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[0][0]['phonenumbers'][1] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[1][0]['phonenumbers'] instanceof Doctrine_ORM_Collection); - $this->assertTrue($result[1][0]['phonenumbers'][0] instanceof Doctrine_ORM_Entity); - // articles - $this->assertTrue($result[0][0]['articles'] instanceof Doctrine_ORM_Collection); - $this->assertTrue($result[0][0]['articles'][0] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[0][0]['articles'][1] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[1][0]['articles'][0] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[1][0]['articles'][1] instanceof Doctrine_ORM_Entity); - // article comments - $this->assertTrue($result[0][0]['articles'][0]['comments'] instanceof Doctrine_ORM_Collection); - $this->assertTrue($result[0][0]['articles'][0]['comments'][0] instanceof Doctrine_ORM_Entity); - // empty comment collections - $this->assertTrue($result[0][0]['articles'][1]['comments'] instanceof Doctrine_ORM_Collection); - $this->assertEquals(0, count($result[0][0]['articles'][1]['comments'])); - $this->assertTrue($result[1][0]['articles'][0]['comments'] instanceof Doctrine_ORM_Collection); - $this->assertEquals(0, count($result[1][0]['articles'][0]['comments'])); - $this->assertTrue($result[1][0]['articles'][1]['comments'] instanceof Doctrine_ORM_Collection); - $this->assertEquals(0, count($result[1][0]['articles'][1]['comments'])); - } else if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_SCALAR) { - //... - } else if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_ARRAY) { - //... + $this->assertEquals('First!', $result[0][0]['articles'][0]['comments'][0]['topic']); + + $this->assertTrue(isset($result[0][0]['articles'][0]['comments'])); // empty comment collections $this->assertTrue(is_array($result[0][0]['articles'][1]['comments'])); @@ -798,7 +812,7 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase 'c__name' => 'First', 'b__id' => '1', 'b__position' => '0', - 'b__category_id' => '1' + //'b__category_id' => '1' ), array( 'c__id' => '2', @@ -806,7 +820,7 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase 'c__name' => 'Second', 'b__id' => '2', 'b__position' => '0', - 'b__category_id' => '2' + //'b__category_id' => '2' ), array( 'c__id' => '1', @@ -814,7 +828,7 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase 'c__name' => 'First', 'b__id' => '3', 'b__position' => '1', - 'b__category_id' => '1' + //'b__category_id' => '1' ), array( 'c__id' => '1', @@ -822,7 +836,7 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase 'c__name' => 'First', 'b__id' => '4', 'b__position' => '2', - 'b__category_id' => '1' + //'b__category_id' => '1' ) ); @@ -837,20 +851,26 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_ARRAY || $hydrationMode == Doctrine_ORM_Query::HYDRATE_OBJECT) { $this->assertEquals(2, count($result)); - $this->assertTrue(isset($result[0]['boards'])); - $this->assertEquals(3, count($result[0]['boards'])); - $this->assertTrue(isset($result[1]['boards'])); - $this->assertEquals(1, count($result[1]['boards'])); } if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_ARRAY) { $this->assertTrue(is_array($result)); $this->assertTrue(is_array($result[0])); $this->assertTrue(is_array($result[1])); + $this->assertTrue(isset($result[0]['boards'])); + $this->assertEquals(3, count($result[0]['boards'])); + $this->assertTrue(isset($result[1]['boards'])); + $this->assertEquals(1, count($result[1]['boards'])); } else if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_OBJECT) { $this->assertTrue($result instanceof Doctrine_ORM_Collection); - $this->assertTrue($result[0] instanceof Doctrine_ORM_Entity); - $this->assertTrue($result[1] instanceof Doctrine_ORM_Entity); + $this->assertTrue($result[0] instanceof ForumCategory); + $this->assertTrue($result[1] instanceof ForumCategory); + $this->assertEquals(1, $result[0]->getId()); + $this->assertEquals(2, $result[1]->getId()); + $this->assertTrue(isset($result[0]->boards)); + $this->assertEquals(3, count($result[0]->boards)); + $this->assertTrue(isset($result[1]->boards)); + $this->assertEquals(1, count($result[1]->boards)); } else if ($hydrationMode == Doctrine_ORM_Query::HYDRATE_SCALAR) { //... } @@ -937,5 +957,78 @@ class Orm_Hydration_BasicHydrationTest extends Doctrine_OrmTestCase } } + + + /** + * select u.id, u.status, p.phonenumber, upper(u.name) nameUpper from User u + * join u.phonenumbers p + * = + * select u.id, u.status, p.phonenumber, upper(u.name) as u__0 from USERS u + * INNER JOIN PHONENUMBERS p ON u.id = p.user_id + * + * @dataProvider hydrationModeProvider + */ + /*public function testNewHydrationMixedQueryFetchJoinPerformance($hydrationMode) + { + // Faked query components + $queryComponents = array( + 'u' => array( + 'metadata' => $this->_em->getClassMetadata('CmsUser'), + 'parent' => null, + 'relation' => null, + 'map' => null, + 'agg' => array('0' => 'nameUpper') + ), + 'p' => array( + 'metadata' => $this->_em->getClassMetadata('CmsPhonenumber'), + 'parent' => 'u', + 'relation' => $this->_em->getClassMetadata('CmsUser')->getAssociationMapping('phonenumbers'), + 'map' => null + ) + ); + + // Faked table alias map + $tableAliasMap = array( + 'u' => 'u', + 'p' => 'p' + ); + + // Faked result set + $resultSet = array( + //row1 + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'u__0' => 'ROMANB', + 'p__phonenumber' => '42', + ), + array( + 'u__id' => '1', + 'u__status' => 'developer', + 'u__0' => 'ROMANB', + 'p__phonenumber' => '43', + ), + array( + 'u__id' => '2', + 'u__status' => 'developer', + 'u__0' => 'JWAGE', + 'p__phonenumber' => '91' + ) + ); + for ($i=4; $i<5000; $i++) { + $resultSet[] = array( + 'u__id' => $i, + 'u__status' => 'developer', + 'u__0' => 'JWAGE' . $i, + 'p__phonenumber' => '91' + ); + } + + $stmt = new Doctrine_HydratorMockStatement($resultSet); + $hydrator = new Doctrine_ORM_Internal_Hydration_StandardHydrator($this->_em); + + $result = $hydrator->hydrateResultSet($this->_createParserResult( + $stmt, $queryComponents, $tableAliasMap, $hydrationMode, true)); + }*/ } diff --git a/tests/Orm/UnitOfWorkTest.php b/tests/Orm/UnitOfWorkTest.php index 7e6ce2892..9f9a3f635 100644 --- a/tests/Orm/UnitOfWorkTest.php +++ b/tests/Orm/UnitOfWorkTest.php @@ -24,21 +24,15 @@ class Orm_UnitOfWorkTest extends Doctrine_OrmTestCase private $_persisterMock; // The EntityManager mock that provides the mock persister private $_emMock; - private $_platformMock; - private $_classMetadataMock; protected function setUp() { parent::setUp(); $this->_connectionMock = new Doctrine_ConnectionMock(array()); - $this->_platformMock = new Doctrine_DatabasePlatformMock(); - $this->_platformMock->setPrefersIdentityColumns(true); $this->_emMock = Doctrine_EntityManagerMock::create($this->_connectionMock, "uowMockEm"); $this->_idGeneratorMock = new Doctrine_SequenceMock($this->_emMock); - $this->_connectionMock->setDatabasePlatform($this->_platformMock); - - $this->_classMetadataMock = new Doctrine_ClassMetadataMock("ForumUser", $this->_emMock); - $this->_classMetadataMock->setIdGenerator($this->_idGeneratorMock); + $this->_emMock->setIdGenerator('ForumUser', $this->_idGeneratorMock); + $this->_emMock->setIdGenerator('ForumAvatar', $this->_idGeneratorMock); $this->_persisterMock = new Doctrine_EntityPersisterMock( $this->_emMock, $this->_emMock->getClassMetadata("ForumUser")); @@ -55,7 +49,7 @@ class Orm_UnitOfWorkTest extends Doctrine_OrmTestCase } protected function tearDown() { - $this->_user->free(); + //$this->_user->free(); } /* Basic registration tests */ @@ -83,16 +77,6 @@ class Orm_UnitOfWorkTest extends Doctrine_OrmTestCase echo $e - $s . " seconds" . PHP_EOL; }*/ - public function testRegisterDirty() - { - $this->assertEquals(Doctrine_ORM_Entity::STATE_NEW, $this->_user->_state()); - $this->assertFalse($this->_unitOfWork->isInIdentityMap($this->_user)); - $this->_unitOfWork->registerDirty($this->_user); - $this->assertTrue($this->_unitOfWork->isRegisteredDirty($this->_user)); - $this->assertFalse($this->_unitOfWork->isRegisteredNew($this->_user)); - $this->assertFalse($this->_unitOfWork->isRegisteredRemoved($this->_user)); - } - public function testRegisterRemovedOnNewEntityIsIgnored() { $this->assertFalse($this->_unitOfWork->isRegisteredRemoved($this->_user)); @@ -104,9 +88,7 @@ class Orm_UnitOfWorkTest extends Doctrine_OrmTestCase /* Operational tests */ public function testSavingSingleEntityWithIdentityColumnForcesInsert() - { - $this->assertEquals(Doctrine_ORM_Entity::STATE_NEW, $this->_user->_state()); - + { $this->_unitOfWork->save($this->_user); $this->assertEquals(1, count($this->_persisterMock->getInserts())); // insert forced @@ -114,7 +96,6 @@ class Orm_UnitOfWorkTest extends Doctrine_OrmTestCase $this->assertEquals(0, count($this->_persisterMock->getDeletes())); $this->assertTrue($this->_unitOfWork->isInIdentityMap($this->_user)); - $this->assertEquals(Doctrine_ORM_Entity::STATE_MANAGED, $this->_user->_state()); // should no longer be scheduled for insert $this->assertFalse($this->_unitOfWork->isRegisteredNew($this->_user)); @@ -149,7 +130,7 @@ class Orm_UnitOfWorkTest extends Doctrine_OrmTestCase //... } - + /* public function testSavingSingleEntityWithSequenceIdGeneratorSchedulesInsert() { //... @@ -189,5 +170,5 @@ class Orm_UnitOfWorkTest extends Doctrine_OrmTestCase { //... } - + */ } \ No newline at end of file diff --git a/tests/lib/Doctrine_DbalTestCase.php b/tests/lib/Doctrine_DbalTestCase.php index 38bbcf0f5..7613cb3e6 100644 --- a/tests/lib/Doctrine_DbalTestCase.php +++ b/tests/lib/Doctrine_DbalTestCase.php @@ -8,11 +8,6 @@ class Doctrine_DbalTestCase extends Doctrine_TestCase /** * setUp() - * - * Note: This setUp() and the one of OrmTestCase currently look identical. However, - * please dont pull this method up. In the future with a separation of Dbal/Orm - * this setUp() will take care of a DBAL connection and the ORM setUp() will take care - * of an ORM connection/session/manager. */ protected function setUp() { diff --git a/tests/lib/Doctrine_OrmFunctionalTestCase.php b/tests/lib/Doctrine_OrmFunctionalTestCase.php index cbb1269cc..bdff985df 100644 --- a/tests/lib/Doctrine_OrmFunctionalTestCase.php +++ b/tests/lib/Doctrine_OrmFunctionalTestCase.php @@ -22,24 +22,6 @@ class Doctrine_OrmFunctionalTestCase extends Doctrine_OrmTestCase */ private static $_exportedTables = array(); - /** - * setUp() - * - * Note: This setUp() and the one of DbalTestCase currently look identical. However, - * please dont pull this method up. In the future with a separation of Dbal/Orm - * this setUp() will take care of a ORM connection/session/manager initialization - * and the DBAL setUp() will take care of just a DBAL connection. - */ - protected function setUp() - { - // Setup a db connection if there is none, yet. This makes it possible - // to run tests that use a connection standalone. - if ( ! isset($this->sharedFixture['em'])) { - $this->sharedFixture['em'] = new Doctrine_EntityManager( - Doctrine_TestUtil::getConnection()); - } - } - /** * Loads a data fixture into the database. This method must only be called * from within the setUp() method of testcases. The database will then be @@ -78,7 +60,7 @@ class Doctrine_OrmFunctionalTestCase extends Doctrine_OrmTestCase $tableName = $classMetadata->getTableName(); if ( ! in_array($tableName, self::$_exportedTables)) { - $em->getConnection()->export->exportClasses(array($fixture['model'])); + $em->getConnection()->getSchemaManager()->exportClasses(array($fixture['model'])); self::$_exportedTables[] = $tableName; } diff --git a/tests/lib/Doctrine_OrmFunctionalTestSuite.php b/tests/lib/Doctrine_OrmFunctionalTestSuite.php index e08eac660..d51a93489 100644 --- a/tests/lib/Doctrine_OrmFunctionalTestSuite.php +++ b/tests/lib/Doctrine_OrmFunctionalTestSuite.php @@ -10,8 +10,6 @@ class Doctrine_OrmFunctionalTestSuite extends Doctrine_OrmTestSuite { protected function setUp() { - $this->sharedFixture['em'] = new Doctrine_EntityManager( - Doctrine_TestUtil::getConnection()); } protected function tearDown() diff --git a/tests/lib/Doctrine_OrmTestCase.php b/tests/lib/Doctrine_OrmTestCase.php index 48ca6a756..bbf74f9fb 100644 --- a/tests/lib/Doctrine_OrmTestCase.php +++ b/tests/lib/Doctrine_OrmTestCase.php @@ -10,7 +10,6 @@ require_once 'lib/mocks/Doctrine_ConnectionMock.php'; class Doctrine_OrmTestCase extends Doctrine_TestCase { protected $_em; - protected $_emf; protected function setUp() { if (isset($this->sharedFixture['em'])) { diff --git a/tests/lib/Doctrine_TestUtil.php b/tests/lib/Doctrine_TestUtil.php index 3b186e153..06fba0bb9 100644 --- a/tests/lib/Doctrine_TestUtil.php +++ b/tests/lib/Doctrine_TestUtil.php @@ -13,8 +13,6 @@ class Doctrine_TestUtil 'host' => $GLOBALS['db_host'], 'database' => $GLOBALS['db_name'] ); - //$dsn = "{$GLOBALS['db_type']}://{$GLOBALS['db_username']}:{$GLOBALS['db_password']}@{$GLOBALS['db_host']}/{$GLOBALS['db_name']}"; - //return Doctrine_Manager::connection($dsn, 'testconn'); } else { $params = array( 'driver' => 'pdo_sqlite', @@ -24,13 +22,4 @@ class Doctrine_TestUtil return Doctrine_DBAL_DriverManager::getConnection($params); } - /* - public static function autoloadModel($className) - { - $modelDir = dirname(__CLASS__) . '/../models/'; - $fileName = $modelDir . str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; - if (file_exists($fileName)) { - require $fileName; - } - }*/ } \ No newline at end of file diff --git a/tests/lib/mocks/Doctrine_ClassMetadataMock.php b/tests/lib/mocks/Doctrine_ClassMetadataMock.php index f580ae970..610c0071b 100644 --- a/tests/lib/mocks/Doctrine_ClassMetadataMock.php +++ b/tests/lib/mocks/Doctrine_ClassMetadataMock.php @@ -2,12 +2,10 @@ class Doctrine_ClassMetadataMock extends Doctrine_ORM_Mapping_ClassMetadata { - - /* Mock API */ - public function setIdGenerator(Doctrine_ORM_Id_AbstractIdGenerator $g) { - $this->_idGenerator = $g; + public function setIdGeneratorType($type) { + $this->_generatorType = $type; } } diff --git a/tests/lib/mocks/Doctrine_EntityManagerMock.php b/tests/lib/mocks/Doctrine_EntityManagerMock.php index 11d0d3581..34be1c3d9 100644 --- a/tests/lib/mocks/Doctrine_EntityManagerMock.php +++ b/tests/lib/mocks/Doctrine_EntityManagerMock.php @@ -5,20 +5,33 @@ require_once 'lib/mocks/Doctrine_EntityPersisterMock.php'; class Doctrine_EntityManagerMock extends Doctrine_ORM_EntityManager { private $_persisterMock; + private $_uowMock; + private $_idGenerators = array(); /** - * Enter description here... - * - * @param unknown_type $entityName * @override */ public function getEntityPersister($entityName) { - return $this->_persisterMock; + return isset($this->_persisterMock) ? $this->_persisterMock : + parent::getEntityPersister($entityName); + } + + /** + * @override + */ + public function getUnitOfWork() + { + return isset($this->_uowMock) ? $this->_uowMock : parent::getUnitOfWork(); } /* Mock API */ - + + public function setUnitOfWork($uow) + { + $this->_uowMock = $uow; + } + public function setEntityPersister($persister) { $this->_persisterMock = $persister; @@ -45,6 +58,19 @@ class Doctrine_EntityManagerMock extends Doctrine_ORM_EntityManager return new Doctrine_EntityManagerMock($conn, $name, $config, $eventManager); } + + public function setIdGenerator($className, $generator) + { + $this->_idGenerators[$className] = $generator; + } + + public function getIdGenerator($className) + { + if (isset($this->_idGenerators[$className])) { + return $this->_idGenerators[$className]; + } + return parent::getIdGenerator($className); + } } ?> \ No newline at end of file diff --git a/tests/lib/mocks/Doctrine_EntityPersisterMock.php b/tests/lib/mocks/Doctrine_EntityPersisterMock.php index 82b542ebe..f69579c89 100644 --- a/tests/lib/mocks/Doctrine_EntityPersisterMock.php +++ b/tests/lib/mocks/Doctrine_EntityPersisterMock.php @@ -8,10 +8,11 @@ class Doctrine_EntityPersisterMock extends Doctrine_ORM_Persisters_StandardEntit private $_identityColumnValueCounter = 0; - public function insert(Doctrine_ORM_Entity $entity) + public function insert($entity) { - if ($entity->getClass()->isIdGeneratorIdentity()) { - $entity->_assignIdentifier($this->_identityColumnValueCounter++); + $class = $this->_em->getClassMetadata(get_class($entity)); + if ($class->isIdGeneratorIdentity()) { + $class->setEntityIdentifier($entity, $this->_identityColumnValueCounter++); $this->_em->getUnitOfWork()->addToIdentityMap($entity); } diff --git a/tests/lib/mocks/Doctrine_SequenceMock.php b/tests/lib/mocks/Doctrine_SequenceMock.php index 7ef214195..307fd3ff2 100644 --- a/tests/lib/mocks/Doctrine_SequenceMock.php +++ b/tests/lib/mocks/Doctrine_SequenceMock.php @@ -10,7 +10,7 @@ class Doctrine_SequenceMock extends Doctrine_ORM_Id_SequenceGenerator * @param Doctrine_Entity $entity * @override */ - public function generate(Doctrine_ORM_Entity $entity) + public function generate($entity) { return $this->_sequenceNumber++; } diff --git a/tests/lib/mocks/Doctrine_UnitOfWorkMock.php b/tests/lib/mocks/Doctrine_UnitOfWorkMock.php new file mode 100644 index 000000000..5ee84abee --- /dev/null +++ b/tests/lib/mocks/Doctrine_UnitOfWorkMock.php @@ -0,0 +1,31 @@ + $entity + * @override + */ + public function getDataChangeSet($entity) { + $oid = spl_object_id($entity); + return isset($this->_mockDataChangeSets[$oid]) ? + $this->_mockDataChangeSets[$oid] : parent::getDataChangeSet($entity); + } + + /* MOCK API */ + + public function setDataChangeSet($entity, array $mockChangeSet) { + $this->_mockDataChangeSets[spl_object_id($entity)] = $mockChangeSet; + } +} + diff --git a/tests/models/cms/CmsArticle.php b/tests/models/cms/CmsArticle.php index dadc295b6..91d8410c8 100755 --- a/tests/models/cms/CmsArticle.php +++ b/tests/models/cms/CmsArticle.php @@ -4,19 +4,28 @@ #use Doctrine::ORM::Entity; -class CmsArticle extends Doctrine_ORM_Entity +class CmsArticle { - #protected $id; - #protected $topic; - #protected $text; - #protected $user_id; + public $id; + public $topic; + public $text; + public $user; + public $comments; + + /*static function construct() { + Doctrine_Common_VirtualPropertySystem::register(__CLASS__, 'id', 'int'); + Doctrine_Common_VirtualPropertySystem::register(__CLASS__, 'topic', 'string'); + Doctrine_Common_VirtualPropertySystem::register(__CLASS__, 'text', 'string'); + Doctrine_Common_VirtualPropertySystem::register(__CLASS__, 'user_id', 'int'); + Doctrine_Common_VirtualPropertySystem::register(__CLASS__, 'user', 'CmsUser'); + Doctrine_Common_VirtualPropertySystem::register(__CLASS__, 'comments', 'collection'); + }*/ public static function initMetadata($mapping) { $mapping->mapField(array( 'fieldName' => 'id', 'type' => 'integer', - 'length' => 4, 'id' => true, 'idGenerator' => 'auto' )); @@ -31,11 +40,9 @@ class CmsArticle extends Doctrine_ORM_Entity )); $mapping->mapField(array( 'fieldName' => 'user_id', - 'type' => 'integer', - 'length' => 4 + 'type' => 'integer' )); - $mapping->mapOneToMany(array( 'fieldName' => 'comments', 'targetEntity' => 'CmsComment', diff --git a/tests/models/cms/CmsComment.php b/tests/models/cms/CmsComment.php index 552058124..f1c5c7db3 100755 --- a/tests/models/cms/CmsComment.php +++ b/tests/models/cms/CmsComment.php @@ -4,19 +4,26 @@ #use Doctrine::ORM::Entity; -class CmsComment extends Doctrine_ORM_Entity +class CmsComment { - #protected $id; - #protected $topic; - #protected $text; - #protected $article_id; + public $id; + public $topic; + public $text; + public $article; + + /*static function construct() { + Doctrine_Common_VirtualPropertySystem::register(__CLASS__, 'id', 'int'); + Doctrine_Common_VirtualPropertySystem::register(__CLASS__, 'topic', 'string'); + Doctrine_Common_VirtualPropertySystem::register(__CLASS__, 'text', 'string'); + Doctrine_Common_VirtualPropertySystem::register(__CLASS__, 'article_id', 'int'); + Doctrine_Common_VirtualPropertySystem::register(__CLASS__, 'article', 'CmsArticle'); + }*/ public static function initMetadata($mapping) { $mapping->mapField(array( 'fieldName' => 'id', 'type' => 'integer', - 'length' => 4, 'id' => true, 'generatorType' => 'auto' )); @@ -31,8 +38,7 @@ class CmsComment extends Doctrine_ORM_Entity )); $mapping->mapField(array( 'fieldName' => 'article_id', - 'type' => 'integer', - 'length' => 4 + 'type' => 'integer' )); $mapping->mapManyToOne(array( diff --git a/tests/models/cms/CmsPhonenumber.php b/tests/models/cms/CmsPhonenumber.php index 5fe40159a..62cd707eb 100755 --- a/tests/models/cms/CmsPhonenumber.php +++ b/tests/models/cms/CmsPhonenumber.php @@ -1,15 +1,20 @@ mapField(array( 'fieldName' => 'user_id', - 'type' => 'integer', - 'length' => 4 + 'type' => 'integer' )); $mapping->mapField(array( 'fieldName' => 'phonenumber', diff --git a/tests/models/cms/CmsUser.php b/tests/models/cms/CmsUser.php index 9c2a5f5cf..81ecb44b1 100644 --- a/tests/models/cms/CmsUser.php +++ b/tests/models/cms/CmsUser.php @@ -1,22 +1,48 @@ addFieldMetadata('id', array( + 'doctrine.id' => true, 'doctrine.idGenerator' => 'auto' + )); + $mapping->addFieldMetadata('status', array( + 'doctrine.length' => 50 + )); + $mapping->addFieldMetadata('phonenumbers', array( + 'doctrine.oneToMany' => array('mappedBy' => 'user') + )); + $mapping->addFieldMetadata('articles', array( + 'doctrine.oneToMany' => array('mappedBy' => 'user') + )); + */ + $mapping->mapField(array( 'fieldName' => 'id', 'type' => 'integer', - 'length' => 4, 'id' => true, 'idGenerator' => 'auto' )); diff --git a/tests/models/company/CompanyEmployee.php b/tests/models/company/CompanyEmployee.php index 5beea96e3..a90aaef2c 100644 --- a/tests/models/company/CompanyEmployee.php +++ b/tests/models/company/CompanyEmployee.php @@ -1,6 +1,6 @@ mapField(array( 'fieldName' => 'accessLevel', 'columnName' => 'access_level', - 'type' => 'integer', - 'length' => 1 + 'type' => 'integer' )); } - - public function banUser(ForumUser $user) {} } \ No newline at end of file diff --git a/tests/models/forum/ForumAvatar.php b/tests/models/forum/ForumAvatar.php index 61af89f98..ee9c6cc8c 100644 --- a/tests/models/forum/ForumAvatar.php +++ b/tests/models/forum/ForumAvatar.php @@ -1,19 +1,27 @@ mapField(array( 'fieldName' => 'id', 'type' => 'integer', - 'length' => 4, 'id' => true, 'idGenerator' => 'auto' )); } - } diff --git a/tests/models/forum/ForumBoard.php b/tests/models/forum/ForumBoard.php index 015b771c3..33df36708 100755 --- a/tests/models/forum/ForumBoard.php +++ b/tests/models/forum/ForumBoard.php @@ -1,19 +1,15 @@ mapField(array( - 'fieldName' => 'id', - 'id' => true, - 'type' => 'integer', - 'length' => 4 - )); - */ $mapping->mapField(array( 'fieldName' => 'id', 'type' => 'integer', - 'length' => 4, 'id' => true )); $mapping->mapField(array( diff --git a/tests/models/forum/ForumCategory.php b/tests/models/forum/ForumCategory.php index 426709720..3766bd8f4 100755 --- a/tests/models/forum/ForumCategory.php +++ b/tests/models/forum/ForumCategory.php @@ -1,13 +1,20 @@ id; + } + public static function initMetadata($mapping) { $mapping->mapField(array( 'fieldName' => 'id', 'type' => 'integer', - 'length' => 4, 'id' => true )); $mapping->mapField(array( diff --git a/tests/models/forum/ForumEntry.php b/tests/models/forum/ForumEntry.php index e1dbeb6e0..9af1853d2 100644 --- a/tests/models/forum/ForumEntry.php +++ b/tests/models/forum/ForumEntry.php @@ -6,15 +6,19 @@ class ForumEntry extends Doctrine_ORM_Entity { - #protected $id; - #protected $topic; + public $id; + public $topic; + + static function construct() { + Doctrine_Common_VirtualPropertySystem::register(__CLASS__, 'id', 'int'); + Doctrine_Common_VirtualPropertySystem::register(__CLASS__, 'topic', 'string'); + } public static function initMetadata($mapping) { $mapping->mapField(array( 'fieldName' => 'id', 'type' => 'integer', - 'length' => 4, 'id' => true, 'idGenerator' => 'auto' )); diff --git a/tests/models/forum/ForumUser.php b/tests/models/forum/ForumUser.php index 34a165a80..8f779b687 100644 --- a/tests/models/forum/ForumUser.php +++ b/tests/models/forum/ForumUser.php @@ -1,14 +1,15 @@ mapField(array( 'fieldName' => 'id', 'type' => 'integer', - 'length' => 4, 'id' => true, 'idGenerator' => 'auto' )); @@ -41,8 +41,6 @@ class ForumUser extends Doctrine_ORM_Entity 'targetEntity' => 'ForumAvatar', 'joinColumns' => array('avatar_id' => 'id'), 'cascade' => array('save') - )); - + )); } - } \ No newline at end of file