Commit e0488ff8 authored by romanb's avatar romanb

[2.0] First draft of EntityManager#merge(). First draft of DynamicProxyGenerator.

parent 1da8f672
...@@ -95,6 +95,7 @@ class ClassLoader ...@@ -95,6 +95,7 @@ class ClassLoader
$prefix = substr($className, 0, strpos($className, $this->_namespaceSeparator)); $prefix = substr($className, 0, strpos($className, $this->_namespaceSeparator));
$class = ''; $class = '';
if (isset($this->_basePaths[$prefix])) { if (isset($this->_basePaths[$prefix])) {
$class .= $this->_basePaths[$prefix] . DIRECTORY_SEPARATOR; $class .= $this->_basePaths[$prefix] . DIRECTORY_SEPARATOR;
} }
......
...@@ -398,7 +398,7 @@ abstract class AbstractQuery ...@@ -398,7 +398,7 @@ abstract class AbstractQuery
if (count($result) > 1) { if (count($result) > 1) {
throw QueryException::nonUniqueResult(); throw QueryException::nonUniqueResult();
} }
return $result->getFirst(); return $result->first();
} }
return $result; return $result;
} }
......
...@@ -45,10 +45,21 @@ class Configuration extends \Doctrine\DBAL\Configuration ...@@ -45,10 +45,21 @@ class Configuration extends \Doctrine\DBAL\Configuration
'queryCacheImpl' => null, 'queryCacheImpl' => null,
'metadataCacheImpl' => null, 'metadataCacheImpl' => null,
'metadataDriverImpl' => new AnnotationDriver(), 'metadataDriverImpl' => new AnnotationDriver(),
'dqlClassAliasMap' => array() 'dqlClassAliasMap' => array(),
'cacheDir' => null
)); ));
} }
public function setCacheDir($dir)
{
$this->_attributes['cacheDir'] = $dir;
}
public function getCacheDir()
{
return $this->_attributes['cacheDir'];
}
public function getDqlClassAliasMap() public function getDqlClassAliasMap()
{ {
return $this->_attributes['dqlClassAliasMap']; return $this->_attributes['dqlClassAliasMap'];
......
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM;
/**
* The DynamicProxyGenerator is used to generate proxy objects for entities.
* For that purpose he generates proxy class files on the fly as needed.
*
* @author Roman Borschel <roman@code-factory.org>
*/
class DynamicProxyGenerator
{
private $_cacheDir = '/Users/robo/dev/php/tmp/gen/';
private $_em;
public function __construct(EntityManager $em, $cacheDir = null)
{
$this->_em = $em;
if ($cacheDir === null) {
$cacheDir = sys_get_tmp_dir();
}
$this->_cacheDir = $cacheDir;
}
/**
*
*
* @param <type> $className
* @param <type> $identifier
* @return <type>
*/
public function getProxy($className, $identifier)
{
$proxyClassName = str_replace('\\', '_', $className) . 'Proxy';
if ( ! class_exists($proxyClassName, false)) {
$fileName = $this->_cacheDir . $proxyClassName . '.g.php';
if ( ! file_exists($fileName)) {
$this->_generateProxyClass($className, $identifier, $proxyClassName, $fileName);
}
require $fileName;
}
$proxyClassName = '\Doctrine\Generated\Proxies\\' . $proxyClassName;
return new $proxyClassName($this->_em, $this->_em->getClassMetadata($className), $identifier);
}
/**
* Generates a proxy class.
*
* @param <type> $className
* @param <type> $id
* @param <type> $proxyClassName
* @param <type> $fileName
*/
private function _generateProxyClass($className, $id, $proxyClassName, $fileName)
{
$class = $this->_em->getClassMetadata($className);
$file = self::$_proxyClassTemplate;
if (is_array($id) && count($id) > 1) {
// it's a composite key. keys = field names, values = values.
$values = array_values($id);
$keys = array_keys($id);
} else {
$values = is_array($id) ? array_values($id) : array($id);
$keys = $class->getIdentifierFieldNames();
}
$paramIndex = 1;
$identifierCondition = 'prx.' . $keys[0] . ' = ?' . $paramIndex++;
for ($i=1, $c=count($keys); $i < $c; ++$i) {
$identifierCondition .= ' AND prx.' . $keys[$i] . ' = ?' . $paramIndex++;
}
$parameters = 'array(';
$first = true;
foreach ($values as $value) {
if ($first) {
$first = false;
} else {
$parameters = ', ';
}
$parameters .= "'" . $value . "'";
}
$parameters .= ')';
$hydrationSetters = '';
foreach ($class->getReflectionProperties() as $name => $prop) {
if ( ! $class->hasAssociation($name)) {
$hydrationSetters .= '$this->_class->setValue($this, \'' . $name . '\', $scalar[0][\'prx_' . $name . '\']);' . PHP_EOL;
}
}
$methods = '';
foreach ($class->getReflectionClass()->getMethods() as $method) {
if ($method->isPublic() && ! $method->isFinal()) {
$methods .= PHP_EOL . 'public function ' . $method->getName() . '(';
$firstParam = true;
$parameterString = '';
foreach ($method->getParameters() as $param) {
if ($firstParam) {
$firstParam = false;
} else {
$parameterString .= ', ';
}
$parameterString .= '$' . $param->getName();
}
$methods .= $parameterString . ') {' . PHP_EOL;
$methods .= '$this->_load();' . PHP_EOL;
$methods .= 'return parent::' . $method->getName() . '(' . $parameterString . ');';
$methods .= '}' . PHP_EOL;
}
}
$sleepImpl = '';
if ($class->getReflectionClass()->hasMethod('__sleep')) {
$sleepImpl .= 'return parent::__sleep();';
} else {
$sleepImpl .= 'return array(';
$first = true;
foreach ($class->getReflectionProperties() as $name => $prop) {
if ($first) {
$first = false;
} else {
$sleepImpl .= ', ';
}
$sleepImpl .= "'" . $name . "'";
}
$sleepImpl .= ');';
}
$placeholders = array(
'<proxyClassName>', '<className>', '<identifierCondition>',
'<parameters>', '<hydrationSetters>', '<methods>', '<sleepImpl>'
);
$replacements = array(
$proxyClassName, $className, $identifierCondition, $parameters,
$hydrationSetters, $methods, $sleepImpl
);
$file = str_replace($placeholders, $replacements, $file);
file_put_contents($fileName, $file);
}
/** Proxy class code template */
private static $_proxyClassTemplate =
'<?php
/** This class was generated by the Doctrine ORM. DO NOT EDIT THIS FILE. */
namespace Doctrine\Generated\Proxies {
class <proxyClassName> extends \<className> {
private $_em;
private $_class;
private $_loaded = false;
public function __construct($em, $class, $identifier) {
$this->_em = $em;
$this->_class = $class;
$this->_class->setIdentifierValues($this, $identifier);
}
private function _load() {
if ( ! $this->_loaded) {
$scalar = $this->_em->createQuery(\'select prx from <className> prx where <identifierCondition>\')->execute(<parameters>, \Doctrine\ORM\Query::HYDRATE_SCALAR);
<hydrationSetters>
unset($this->_em);
unset($this->_class);
$this->_loaded = true;
}
}
<methods>
public function __sleep() {
if (!$this->_loaded) {
throw new RuntimeException("Not fully loaded proxy can not be serialized.");
}
<sleepImpl>
}
}
}';
}
...@@ -42,22 +42,22 @@ class EntityManager ...@@ -42,22 +42,22 @@ class EntityManager
* IMMEDIATE: Flush occurs automatically after each operation that issues database * IMMEDIATE: Flush occurs automatically after each operation that issues database
* queries. No operations are queued. * queries. No operations are queued.
*/ */
const FLUSHMODE_IMMEDIATE = 'immediate'; const FLUSHMODE_IMMEDIATE = 1;
/** /**
* AUTO: Flush occurs automatically in the following situations: * AUTO: Flush occurs automatically in the following situations:
* - Before any query executions (to prevent getting stale data) * - Before any query executions (to prevent getting stale data)
* - On EntityManager#commit() * - On EntityManager#commit()
*/ */
const FLUSHMODE_AUTO = 'auto'; const FLUSHMODE_AUTO = 2;
/** /**
* COMMIT: Flush occurs automatically only on EntityManager#commit(). * COMMIT: Flush occurs automatically only on EntityManager#commit().
*/ */
const FLUSHMODE_COMMIT = 'commit'; const FLUSHMODE_COMMIT = 3;
/** /**
* MANUAL: Flush occurs never automatically. The only way to flush is * MANUAL: Flush occurs never automatically. The only way to flush is
* through EntityManager#flush(). * through EntityManager#flush().
*/ */
const FLUSHMODE_MANUAL = 'manual'; const FLUSHMODE_MANUAL = 4;
/** /**
* The used Configuration. * The used Configuration.
...@@ -92,7 +92,7 @@ class EntityManager ...@@ -92,7 +92,7 @@ class EntityManager
* *
* @var string * @var string
*/ */
private $_flushMode = 'commit'; private $_flushMode = self::FLUSHMODE_COMMIT;
/** /**
* The UnitOfWork used to coordinate object-level transactions. * The UnitOfWork used to coordinate object-level transactions.
...@@ -115,6 +115,13 @@ class EntityManager ...@@ -115,6 +115,13 @@ class EntityManager
*/ */
private $_hydrators = array(); private $_hydrators = array();
/**
* The proxy generator.
*
* @var DynamicProxyGenerator
*/
private $_proxyGenerator;
/** /**
* Whether the EntityManager is closed or not. * Whether the EntityManager is closed or not.
*/ */
...@@ -211,17 +218,6 @@ class EntityManager ...@@ -211,17 +218,6 @@ class EntityManager
return $query; return $query;
} }
/**
* Detaches an entity from the manager. It's lifecycle is no longer managed.
*
* @param object $entity
* @return boolean
*/
public function detach($entity)
{
return $this->_unitOfWork->removeFromIdentityMap($entity);
}
/** /**
* Creates a DQL query with the specified name. * Creates a DQL query with the specified name.
* *
...@@ -237,6 +233,7 @@ class EntityManager ...@@ -237,6 +233,7 @@ class EntityManager
* Creates a native SQL query. * Creates a native SQL query.
* *
* @param string $sql * @param string $sql
* @param ResultSetMapping $rsm The ResultSetMapping to use.
* @return Query * @return Query
*/ */
public function createNativeQuery($sql, \Doctrine\ORM\Query\ResultSetMapping $rsm) public function createNativeQuery($sql, \Doctrine\ORM\Query\ResultSetMapping $rsm)
...@@ -287,6 +284,23 @@ class EntityManager ...@@ -287,6 +284,23 @@ class EntityManager
{ {
return $this->getRepository($entityName)->find($identifier); return $this->getRepository($entityName)->find($identifier);
} }
/**
* Gets a reference to the entity identified by the given type and identifier
* without actually loading it. Only the identifier of the returned entity
* will be populated.
*
* NOTE: There is currently no magic proxying in place, that means the full state
* of the entity will not be loaded upon accessing it.
*
* @return object The entity reference.
*/
public function getReference($entityName, $identifier)
{
$entity = new $entityName;
$this->getClassMetadata($entityName)->setEntityIdentifier($entity, $identifier);
return $entity;
}
/** /**
* Sets the flush mode to use. * Sets the flush mode to use.
...@@ -309,10 +323,7 @@ class EntityManager ...@@ -309,10 +323,7 @@ class EntityManager
*/ */
private function _isFlushMode($value) private function _isFlushMode($value)
{ {
return $value == self::FLUSHMODE_AUTO || return $value >= 1 && $value <= 4;
$value == self::FLUSHMODE_COMMIT ||
$value == self::FLUSHMODE_IMMEDIATE ||
$value == self::FLUSHMODE_MANUAL;
} }
/** /**
...@@ -326,7 +337,10 @@ class EntityManager ...@@ -326,7 +337,10 @@ class EntityManager
} }
/** /**
* Clears the persistence context, effectively detaching all managed entities. * Clears the EntityManager. All entities that are currently managed
* by this EntityManager become detached.
*
* @param string $entityName
*/ */
public function clear($entityName = null) public function clear($entityName = null)
{ {
...@@ -334,14 +348,18 @@ class EntityManager ...@@ -334,14 +348,18 @@ class EntityManager
$this->_unitOfWork->detachAll(); $this->_unitOfWork->detachAll();
} else { } else {
//TODO //TODO
throw DoctrineException::notImplemented();
} }
} }
/** /**
* Closes the EntityManager. * Closes the EntityManager. All entities that are currently managed
* by this EntityManager become detached. The EntityManager may no longer
* be used after it is closed.
*/ */
public function close() public function close()
{ {
$this->clear();
$this->_closed = true; $this->_closed = true;
} }
...@@ -378,52 +396,51 @@ class EntityManager ...@@ -378,52 +396,51 @@ class EntityManager
* overriding any local changes that have not yet been persisted. * overriding any local changes that have not yet been persisted.
* *
* @param object $entity * @param object $entity
* @todo FIX Impl * @todo Implemntation
*/ */
public function refresh($entity) public function refresh($entity)
{ {
throw DoctrineException::notImplemented();
/*$this->_mergeData($entity, $this->getRepository(get_class($entity))->find( /*$this->_mergeData($entity, $this->getRepository(get_class($entity))->find(
$entity->identifier(), Query::HYDRATE_ARRAY), $entity->identifier(), Query::HYDRATE_ARRAY),
true);*/ true);*/
} }
/**
* Detaches an entity from the EntityManager. Its lifecycle is no longer managed.
*
* @param object $entity The entity to detach.
* @return boolean
*/
public function detach($entity)
{
return $this->_unitOfWork->removeFromIdentityMap($entity);
}
/**
* Merges the state of a detached entity into the persistence context
* of this EntityManager.
*
* @param object $entity The entity to merge into the persistence context.
* @return object The managed copy of the entity.
*/
public function merge($entity)
{
return $this->_unitOfWork->merge($entity);
}
/** /**
* Creates a copy of the given entity. Can create a shallow or a deep copy. * Creates a copy of the given entity. Can create a shallow or a deep copy.
* *
* @param object $entity The entity to copy. * @param object $entity The entity to copy.
* @return object The new entity. * @return object The new entity.
* @todo Implementation or remove.
*/ */
public function copy($entity, $deep = false) public function copy($entity, $deep = false)
{ {
//... throw DoctrineException::notImplemented();
} }
/*
public function toArray($entity, $deep = false)
{
$array = array();
foreach ($entity as $key => $value) {
if ($deep && is_object($value)) {
$array[$key] = $this->toArray($value, $deep);
} else if ( ! is_object($value)) {
$array[$key] = $value;
}
}
return $array;
}
public function fromArray($entity, array $array, $deep = false)
{
foreach ($array as $key => $value) {
if ($deep && is_array($value)) {
$entity->$key = $this->fromArray($entity, $value, $deep);
} else if ( ! is_array($value)) {
$entity->$key = $value;
}
}
}
*/
/** /**
* Gets the repository for an entity class. * Gets the repository for an entity class.
* *
...@@ -528,11 +545,11 @@ class EntityManager ...@@ -528,11 +545,11 @@ class EntityManager
$this->_hydrators[$hydrationMode] = new \Doctrine\ORM\Internal\Hydration\NoneHydrator($this); $this->_hydrators[$hydrationMode] = new \Doctrine\ORM\Internal\Hydration\NoneHydrator($this);
break; break;
default: default:
\Doctrine\Common\DoctrineException::updateMe("No hydrator found for hydration mode '$hydrationMode'."); throw DoctrineException::updateMe("No hydrator found for hydration mode '$hydrationMode'.");
} }
} else if ($this->_hydrators[$hydrationMode] instanceof Closure) { }/* else if ($this->_hydrators[$hydrationMode] instanceof Closure) {
$this->_hydrators[$hydrationMode] = $this->_hydrators[$hydrationMode]($this); $this->_hydrators[$hydrationMode] = $this->_hydrators[$hydrationMode]($this);
} }*/
return $this->_hydrators[$hydrationMode]; return $this->_hydrators[$hydrationMode];
} }
...@@ -540,13 +557,13 @@ class EntityManager ...@@ -540,13 +557,13 @@ class EntityManager
* Sets a hydrator for a hydration mode. * Sets a hydrator for a hydration mode.
* *
* @param mixed $hydrationMode * @param mixed $hydrationMode
* @param object $hydrator Either a hydrator instance or a closure that creates a * @param object $hydrator Either a hydrator instance or a Closure that creates a
* hydrator instance. * hydrator instance.
*/ */
public function setHydrator($hydrationMode, $hydrator) /*public function setHydrator($hydrationMode, $hydrator)
{ {
$this->_hydrators[$hydrationMode] = $hydrator; $this->_hydrators[$hydrationMode] = $hydrator;
} }*/
/** /**
* Factory method to create EntityManager instances. * Factory method to create EntityManager instances.
...@@ -563,7 +580,7 @@ class EntityManager ...@@ -563,7 +580,7 @@ class EntityManager
if (is_array($conn)) { if (is_array($conn)) {
$conn = \Doctrine\DBAL\DriverManager::getConnection($conn, $config, $eventManager); $conn = \Doctrine\DBAL\DriverManager::getConnection($conn, $config, $eventManager);
} else if ( ! $conn instanceof Connection) { } else if ( ! $conn instanceof Connection) {
\Doctrine\Common\DoctrineException::updateMe("Invalid parameter '$conn'."); throw DoctrineException::updateMe("Invalid parameter '$conn'.");
} }
if ($config === null) { if ($config === null) {
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
* *
* This software consists of voluntary contributions made by many individuals * This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see * and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.org>. * <http://www.doctrine-project.org>.
*/ */
namespace Doctrine\ORM; namespace Doctrine\ORM;
...@@ -80,10 +80,6 @@ class EntityRepository ...@@ -80,10 +80,6 @@ class EntityRepository
*/ */
public function find($id, $hydrationMode = null) public function find($id, $hydrationMode = null)
{ {
if ($id === null) {
return false;
}
if (is_array($id) && count($id) > 1) { if (is_array($id) && count($id) > 1) {
// it's a composite key. keys = field names, values = values. // it's a composite key. keys = field names, values = values.
$values = array_values($id); $values = array_values($id);
...@@ -98,9 +94,21 @@ class EntityRepository ...@@ -98,9 +94,21 @@ class EntityRepository
return $entity; // Hit! return $entity; // Hit!
} }
return $this->_createQuery() $dql = 'select e from ' . $this->_classMetadata->getClassName() . ' e where ';
->where(implode(' = ? AND ', $keys) . ' = ?') $conditionDql = '';
->fetchOne($values, $hydrationMode); $paramIndex = 1;
foreach ($keys as $key) {
if ($conditionDql != '') $conditionDql .= ' and ';
$conditionDql .= 'e.' . $key . ' = ?' . $paramIndex++;
}
$dql .= $conditionDql;
$q = $this->_em->createQuery($dql);
foreach ($values as $index => $value) {
$q->setParameter($index, $value);
}
return $q->getSingleResult($hydrationMode);
} }
/** /**
...@@ -172,7 +180,7 @@ class EntityRepository ...@@ -172,7 +180,7 @@ class EntityRepository
*/ */
public function findByDql($dql, array $params = array(), $hydrationMode = null) public function findByDql($dql, array $params = array(), $hydrationMode = null)
{ {
$query = new Doctrine_Query($this->_em); $query = new Query($this->_em);
$component = $this->getComponentName(); $component = $this->getComponentName();
$dql = 'FROM ' . $component . ' WHERE ' . $dql; $dql = 'FROM ' . $component . ' WHERE ' . $dql;
......
...@@ -103,6 +103,7 @@ class ObjectHydrator extends AbstractHydrator ...@@ -103,6 +103,7 @@ class ObjectHydrator extends AbstractHydrator
$this->_classMetadatas = array(); $this->_classMetadatas = array();
$e = microtime(true); $e = microtime(true);
echo 'Hydration took: ' . ($e - $s) . PHP_EOL; echo 'Hydration took: ' . ($e - $s) . PHP_EOL;
return $result; return $result;
......
...@@ -50,6 +50,7 @@ abstract class AssociationMapping ...@@ -50,6 +50,7 @@ abstract class AssociationMapping
protected $_isCascadeDelete; protected $_isCascadeDelete;
protected $_isCascadeSave; protected $_isCascadeSave;
protected $_isCascadeRefresh; protected $_isCascadeRefresh;
protected $_isCascadeMerge;
/** /**
* The fetch mode used for the association. * The fetch mode used for the association.
...@@ -207,6 +208,20 @@ abstract class AssociationMapping ...@@ -207,6 +208,20 @@ abstract class AssociationMapping
} }
return $this->_isCascadeRefresh; return $this->_isCascadeRefresh;
} }
/**
* Whether the association cascades merge() operations from the source entity
* to the target entity/entities.
*
* @return boolean
*/
public function isCascadeMerge()
{
if ($this->_isCascadeMerge === null) {
$this->_isCascadeMerge = in_array('merge', $this->_cascades);
}
return $this->_isCascadeMerge;
}
/** /**
* Whether the target entity/entities of the association are eagerly fetched. * Whether the target entity/entities of the association are eagerly fetched.
...@@ -262,6 +277,7 @@ abstract class AssociationMapping ...@@ -262,6 +277,7 @@ abstract class AssociationMapping
* Whether the association is optional (0..X), or not (1..X). * Whether the association is optional (0..X), or not (1..X).
* *
* @return boolean TRUE if the association is optional, FALSE otherwise. * @return boolean TRUE if the association is optional, FALSE otherwise.
* @todo Only applicable to OneToOne. Move there.
*/ */
public function isOptional() public function isOptional()
{ {
...@@ -319,26 +335,51 @@ abstract class AssociationMapping ...@@ -319,26 +335,51 @@ abstract class AssociationMapping
{ {
return $this->_mappedByFieldName; return $this->_mappedByFieldName;
} }
/**
* Whether the association is a one-to-one association.
*
* @return boolean
*/
public function isOneToOne() public function isOneToOne()
{ {
return false; return false;
} }
/**
* Whether the association is a one-to-many association.
*
* @return boolean
*/
public function isOneToMany() public function isOneToMany()
{ {
return false; return false;
} }
/**
* Whether the association is a many-to-many association.
*
* @return boolean
*/
public function isManyToMany() public function isManyToMany()
{ {
return false; return false;
} }
/**
* Whether the association uses a join table for the mapping.
*
* @return boolean
*/
public function usesJoinTable() public function usesJoinTable()
{ {
return (bool)$this->_joinTable; return (bool)$this->_joinTable;
} }
/**
*
* @param <type> $entity
* @param <type> $entityManager
*/
abstract public function lazyLoadFor($entity, $entityManager); abstract public function lazyLoadFor($entity, $entityManager);
} }
\ No newline at end of file
...@@ -885,6 +885,45 @@ final class ClassMetadata ...@@ -885,6 +885,45 @@ final class ClassMetadata
$this->_reflectionProperties[$field]->setValue($entity, $value); $this->_reflectionProperties[$field]->setValue($entity, $value);
} }
} }
/**
* Extracts the identifier values of an entity of this class.
*
* @param object $entity
* @return mixed
*/
public function getIdentifierValues($entity)
{
if ($this->_isIdentifierComposite) {
$id = array();
foreach ($this->_identifier as $idField) {
$value = $this->_reflectionProperties[$idField]->getValue($entity);
if ($value !== null) {
$id[] = $value;
}
}
return $id;
} else {
return $this->_reflectionProperties[$this->_identifier[0]]->getValue($entity);
}
}
/**
* Populates the entity identifier of an entity.
*
* @param object $entity
* @param mixed $id
*/
public function setIdentifierValues($entity, $id)
{
if ($this->_isIdentifierComposite) {
foreach ((array)$id as $idField => $idValue) {
$this->_reflectionProperties[$idField]->setValue($entity, $idValue);
}
} else {
$this->_reflectionProperties[$this->_identifier[0]]->setValue($entity, $id);
}
}
/** /**
* Gets all field mappings. * Gets all field mappings.
......
...@@ -58,20 +58,26 @@ final class DoctrineOneToOne extends \Addendum\Annotation { ...@@ -58,20 +58,26 @@ final class DoctrineOneToOne extends \Addendum\Annotation {
public $targetEntity; public $targetEntity;
public $mappedBy; public $mappedBy;
public $cascade; public $cascade;
public $fetch;
public $optional;
} }
final class DoctrineOneToMany extends \Addendum\Annotation { final class DoctrineOneToMany extends \Addendum\Annotation {
public $mappedBy; public $mappedBy;
public $targetEntity; public $targetEntity;
public $cascade; public $cascade;
public $fetch;
} }
final class DoctrineManyToOne extends \Addendum\Annotation { final class DoctrineManyToOne extends \Addendum\Annotation {
public $targetEntity; public $targetEntity;
public $cascade; public $cascade;
public $fetch;
public $optional;
} }
final class DoctrineManyToMany extends \Addendum\Annotation { final class DoctrineManyToMany extends \Addendum\Annotation {
public $targetEntity; public $targetEntity;
public $mappedBy; public $mappedBy;
public $cascade; public $cascade;
public $fetch;
} }
final class DoctrineElementCollection extends \Addendum\Annotation { final class DoctrineElementCollection extends \Addendum\Annotation {
public $tableName; public $tableName;
......
...@@ -146,7 +146,7 @@ abstract class AbstractCollectionPersister ...@@ -146,7 +146,7 @@ abstract class AbstractCollectionPersister
abstract protected function _getUpdateRowSql(PersistentCollection $coll); abstract protected function _getUpdateRowSql(PersistentCollection $coll);
/** /**
* Gets the SQL statement used for inserting a row from to the collection. * Gets the SQL statement used for inserting a row in the collection.
* *
* @param PersistentCollection $coll * @param PersistentCollection $coll
*/ */
......
...@@ -919,7 +919,7 @@ class Query extends AbstractQuery ...@@ -919,7 +919,7 @@ class Query extends AbstractQuery
$this->_dqlParts[$queryPartName] = array($queryPart); $this->_dqlParts[$queryPartName] = array($queryPart);
} }
$this->_state = Doctrine_ORM_Query::STATE_DIRTY; $this->_state = self::STATE_DIRTY;
return $this; return $this;
} }
......
This diff is collapsed.
...@@ -26,7 +26,7 @@ use Doctrine\ORM\Mapping\AssociationMapping; ...@@ -26,7 +26,7 @@ use Doctrine\ORM\Mapping\AssociationMapping;
/** /**
* Represents a virtual proxy that is used for lazy to-one associations. * Represents a virtual proxy that is used for lazy to-one associations.
* *
* @author robo * @author Roman Borschel <roman@code-factory.org>
* @since 2.0 * @since 2.0
*/ */
class VirtualProxy class VirtualProxy
......
...@@ -46,6 +46,22 @@ class CmsUser ...@@ -46,6 +46,22 @@ class CmsUser
*/ */
public $groups; public $groups;
public function getId() {
return $this->id;
}
public function getStatus() {
return $this->status;
}
public function getUsername() {
return $this->username;
}
public function getName() {
return $this->name;
}
/** /**
* Adds a phonenumber to the user. * Adds a phonenumber to the user.
* *
...@@ -58,6 +74,10 @@ class CmsUser ...@@ -58,6 +74,10 @@ class CmsUser
} }
} }
public function getPhonenumbers() {
return $this->phonenumbers;
}
public function addArticle(CmsArticle $article) { public function addArticle(CmsArticle $article) {
$this->articles[] = $article; $this->articles[] = $article;
if ($article->user !== $this) { if ($article->user !== $this) {
......
...@@ -22,6 +22,7 @@ class AllTests ...@@ -22,6 +22,7 @@ class AllTests
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\BasicFunctionalTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\BasicFunctionalTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\NativeQueryTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\NativeQueryTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\SingleTableInheritanceTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\SingleTableInheritanceTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\DetachedEntityTest');
return $suite; return $suite;
} }
......
...@@ -306,5 +306,14 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase ...@@ -306,5 +306,14 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$query = $this->_em->createQuery("select u, g from Doctrine\Tests\Models\CMS\CmsUser u inner join u.groups g"); $query = $this->_em->createQuery("select u, g from Doctrine\Tests\Models\CMS\CmsUser u inner join u.groups g");
$this->assertEquals(0, count($query->getResultList())); $this->assertEquals(0, count($query->getResultList()));
/* RB: TEST
\Doctrine\ORM\DynamicProxyGenerator::configure($this->_em);
$proxy = \Doctrine\ORM\DynamicProxyGenerator::getReferenceProxy('Doctrine\Tests\Models\CMS\CmsUser', 1);
echo $proxy->getId();
var_dump(serialize($proxy));
*/
} }
} }
\ No newline at end of file
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\ORM\UnitOfWork;
require_once __DIR__ . '/../../TestInit.php';
/**
* Description of DetachedEntityTest
*
* @author robo
*/
class DetachedEntityTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp() {
$this->useModelSet('cms');
parent::setUp();
}
public function testSimpleDetachMerge() {
$user = new CmsUser;
$user->name = 'Roman';
$user->username = 'romanb';
$user->status = 'dev';
$this->_em->save($user);
$this->_em->flush();
$this->_em->clear();
// $user is now detached
$this->assertFalse($this->_em->contains($user));
$user->name = 'Roman B.';
//$this->assertEquals(UnitOfWork::STATE_DETACHED, $this->_em->getUnitOfWork()->getEntityState($user));
$user2 = $this->_em->merge($user);
$this->assertFalse($user === $user2);
$this->assertTrue($this->_em->contains($user2));
$this->assertEquals('Roman B.', $user2->name);
}
}
...@@ -693,16 +693,18 @@ class ObjectHydratorTest extends HydrationTest ...@@ -693,16 +693,18 @@ class ObjectHydratorTest extends HydrationTest
{ {
$rsm = new ResultSetMapping; $rsm = new ResultSetMapping;
$rsm->addEntityResult($this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'), 'u'); $rsm->addEntityResult($this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'), 'u');
$rsm->addJoinedEntityResult( /*$rsm->addJoinedEntityResult(
$this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsPhonenumber'), $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsPhonenumber'),
'p', 'p',
'u', 'u',
$this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser')->getAssociationMapping('phonenumbers') $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser')->getAssociationMapping('phonenumbers')
); );*/
$rsm->addFieldResult('u', 'u__id', 'id'); $rsm->addFieldResult('u', 'u__id', 'id');
$rsm->addFieldResult('u', 'u__status', 'status'); $rsm->addFieldResult('u', 'u__status', 'status');
$rsm->addScalarResult('sclr0', 'nameUpper'); $rsm->addFieldResult('u', 'u__username', 'username');
$rsm->addFieldResult('p', 'p__phonenumber', 'phonenumber'); $rsm->addFieldResult('u', 'u__name', 'name');
//$rsm->addScalarResult('sclr0', 'nameUpper');
//$rsm->addFieldResult('p', 'p__phonenumber', 'phonenumber');
// Faked result set // Faked result set
$resultSet = array( $resultSet = array(
...@@ -710,29 +712,37 @@ class ObjectHydratorTest extends HydrationTest ...@@ -710,29 +712,37 @@ class ObjectHydratorTest extends HydrationTest
array( array(
'u__id' => '1', 'u__id' => '1',
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => 'ROMANB', 'u__username' => 'romanb',
'p__phonenumber' => '42', 'u__name' => 'Roman',
//'sclr0' => 'ROMANB',
//'p__phonenumber' => '42',
), ),
array( array(
'u__id' => '1', 'u__id' => '1',
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => 'ROMANB', 'u__username' => 'romanb',
'p__phonenumber' => '43', 'u__name' => 'Roman',
//'sclr0' => 'ROMANB',
//'p__phonenumber' => '43',
), ),
array( array(
'u__id' => '2', 'u__id' => '2',
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => 'JWAGE', 'u__username' => 'romanb',
'p__phonenumber' => '91' 'u__name' => 'Roman',
//'sclr0' => 'JWAGE',
//'p__phonenumber' => '91'
) )
); );
for ($i = 4; $i < 300; $i++) { for ($i = 4; $i < 1000; ++$i) {
$resultSet[] = array( $resultSet[] = array(
'u__id' => $i, 'u__id' => $i,
'u__status' => 'developer', 'u__status' => 'developer',
'sclr0' => 'JWAGE' . $i, 'u__username' => 'jwage',
'p__phonenumber' => '91' 'u__name' => 'Jonathan',
//'sclr0' => 'JWAGE' . $i,
//'p__phonenumber' => '91'
); );
} }
......
...@@ -20,13 +20,3 @@ set_include_path( ...@@ -20,13 +20,3 @@ set_include_path(
. PATH_SEPARATOR . $modelDir . DIRECTORY_SEPARATOR . 'forum' . PATH_SEPARATOR . $modelDir . DIRECTORY_SEPARATOR . 'forum'
); );
// Some of these classes depend on Doctrine_* classes
/*require_once 'DoctrineTestCase.php';
require_once 'TestUtil.php';
require_once 'DbalTestCase.php';
require_once 'OrmTestCase.php';
require_once 'OrmFunctionalTestCase.php';
require_once 'DoctrineTestSuite.php';
require_once 'OrmTestSuite.php';
require_once 'OrmFunctionalTestSuite.php';
require_once 'DbalTestSuite.php';*/
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment