Commit f572c372 authored by romanb's avatar romanb

[2.0] Fixed DDC-18. Simplified proxy classes. Just 1 proxy class per entity now, instead of 2.

parent aa72619c
......@@ -323,7 +323,7 @@ class EntityManager
if ( ! is_array($identifier)) {
$identifier = array($class->identifier[0] => $identifier);
}
$entity = $this->_proxyFactory->getReferenceProxy($entityName, $identifier);
$entity = $this->_proxyFactory->getProxy($entityName, $identifier);
$this->_unitOfWork->registerManaged($entity, $identifier, array());
return $entity;
......
......@@ -75,7 +75,6 @@ class ObjectHydrator extends AbstractHydrator
if (isset($this->_rsm->relationMap[$dqlAlias])) {
$targetClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]];
$targetClass = $this->_getClassMetadata($targetClassName);
$this->_ce[$targetClassName] = $targetClass;
$assoc = $targetClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]];
$this->_hints['fetched'][$assoc->sourceEntityName][$assoc->sourceFieldName] = true;
if ($assoc->mappedByFieldName) {
......
......@@ -8,4 +8,9 @@ class ORMException extends \Exception
{
return new self("Entity of type " . get_class($entity) . " is missing an assigned ID.");
}
public static function unrecognizedField($field)
{
return new self("Unrecognized field: $field");
}
}
......@@ -368,7 +368,8 @@ final class PersistentCollection implements \Doctrine\Common\Collections\Collect
$removed = $this->_coll->remove($key);
if ($removed) {
$this->_changed();
if ($this->_association->isOneToMany() && $this->_association->orphanRemoval) {
if ($this->_association !== null && $this->_association->isOneToMany() &&
$this->_association->orphanRemoval) {
$this->_em->getUnitOfWork()->scheduleOrphanRemoval($removed);
}
}
......
......@@ -22,6 +22,7 @@
namespace Doctrine\ORM\Persisters;
use Doctrine\Common\DoctrineException,
Doctrine\ORM\ORMException,
Doctrine\Common\Collections\ArrayCollection,
Doctrine\DBAL\Connection,
Doctrine\DBAL\Types\Type,
......@@ -410,13 +411,6 @@ class StandardEntityPersister
public function load(array $criteria, $entity = null, $assoc = null)
{
$stmt = $this->_conn->prepare($this->_getSelectEntitiesSql($criteria, $assoc));
if ($stmt === null) {
try {
throw new \Exception();
} catch (\Exception $e) {
var_dump($e->getTraceAsString());
}
}
$stmt->execute(array_values($criteria));
$result = $stmt->fetch(Connection::FETCH_ASSOC);
$stmt->closeCursor();
......@@ -464,12 +458,10 @@ class StandardEntityPersister
if ($assoc->isOwningSide) {
$joinColumnValues = array();
$targetColumns = array();
foreach ($assoc->targetToSourceKeyColumns as $targetColumn => $srcColumn) {
if ($metaColumns[$srcColumn] !== null) {
$joinColumnValues[] = $metaColumns[$srcColumn];
$joinColumnValues[$targetColumn] = $metaColumns[$srcColumn];
}
$targetColumns[] = $targetColumn;
}
if ( ! $joinColumnValues && $value !== null) {
$this->_class->reflFields[$field]->setValue($entity, null);
......@@ -486,16 +478,16 @@ class StandardEntityPersister
$targetClass->reflFields[$inverseAssoc->sourceFieldName]->setValue($found, $entity);
}
$newData[$field] = $found;
} else if ((array)$this->_class->getIdentifierValues($value) != $joinColumnValues) {
$proxy = $this->_em->getProxyFactory()->getAssociationProxy($entity, $assoc, $joinColumnValues);
} else {
$proxy = $this->_em->getProxyFactory()->getProxy($assoc->targetEntityName, $joinColumnValues);
$this->_class->reflFields[$field]->setValue($entity, $proxy);
$newData[$field] = $proxy;
$this->_em->getUnitOfWork()->addToIdentityMap($proxy);
}
}
} else {
// Inverse side of 1-1/1-x can never be lazy
$assoc->load($entity, null, $this->_em);
$newData[$field] = $this->_class->reflFields[$field]->getValue($entity);
// Inverse side of 1-1/1-x can never be lazy.
$newData[$field] = $assoc->load($entity, null, $this->_em);
}
} else if ($value instanceof PersistentCollection && $value->isInitialized()) {
$value->setInitialized(false);
......@@ -643,10 +635,12 @@ class StandardEntityPersister
if (isset($this->_class->columnNames[$field])) {
$conditionSql .= $this->_class->getQuotedColumnName($field, $this->_platform);
} else if (isset($this->_class->fieldNames[$field])) {
$conditionSql .= $this->_class->getQuotedColumnName($this->_class->fieldNames[$field], $this->_platform);
} else if ($assoc !== null) {
$conditionSql .= $assoc->getQuotedJoinColumnName($field, $this->_platform);
} else {
throw DoctrineException::unrecognizedField($field);
throw ORMException::unrecognizedField($field);
}
$conditionSql .= ' = ?';
}
......
......@@ -75,9 +75,9 @@ class ProxyFactory
* @param mixed $identifier
* @return object
*/
public function getReferenceProxy($className, $identifier)
public function getProxy($className, $identifier)
{
$proxyClassName = str_replace('\\', '', $className) . 'RProxy';
$proxyClassName = str_replace('\\', '', $className) . 'Proxy';
$fqn = $this->_proxyNamespace . '\\' . $proxyClassName;
if ($this->_autoGenerate && ! class_exists($fqn, false)) {
......@@ -95,32 +95,6 @@ class ProxyFactory
return new $fqn($entityPersister, $identifier);
}
/**
* Gets an association proxy instance.
*
* @param object $owner
* @param AssociationMapping $assoc
* @param array $joinColumnValues
* @return object
*/
public function getAssociationProxy($owner, AssociationMapping $assoc, array $joinColumnValues)
{
$proxyClassName = str_replace('\\', '', $assoc->targetEntityName) . 'AProxy';
$fqn = $this->_proxyNamespace . '\\' . $proxyClassName;
if ($this->_autoGenerate && ! class_exists($fqn, false)) {
$fileName = $this->_proxyDir . DIRECTORY_SEPARATOR . $proxyClassName . '.php';
$this->_generateProxyClass($this->_em->getClassMetadata($assoc->targetEntityName), $proxyClassName, $fileName, self::$_assocProxyClassTemplate);
require $fileName;
}
if ( ! $this->_em->getMetadataFactory()->hasMetadataFor($fqn)) {
$this->_em->getMetadataFactory()->setMetadataFor($fqn, $this->_em->getClassMetadata($assoc->targetEntityName));
}
return new $fqn($this->_em, $assoc, $owner, $joinColumnValues);
}
/**
* Generates proxy classes for all given classes.
*
......@@ -134,12 +108,9 @@ class ProxyFactory
$proxyDir = $toDir ?: $this->_proxyDir;
$proxyDir = rtrim($proxyDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
foreach ($classes as $class) {
$AproxyClassName = str_replace('\\', '', $class->name) . 'AProxy';
$RproxyClassName = str_replace('\\', '', $class->name) . 'RProxy';
$AproxyFileName = $proxyDir . $AproxyClassName . '.php';
$RproxyFileName = $proxyDir . $RproxyClassName . '.php';
$this->_generateProxyClass($class, $RproxyClassName, $RproxyFileName, self::$_proxyClassTemplate);
$this->_generateProxyClass($class, $AproxyClassName, $AproxyFileName, self::$_assocProxyClassTemplate);
$proxyClassName = str_replace('\\', '', $class->name) . 'Proxy';
$proxyFileName = $proxyDir . $proxyClassName . '.php';
$this->_generateProxyClass($class, $proxyClassName, $proxyFileName, self::$_proxyClassTemplate);
}
}
......@@ -301,47 +272,4 @@ namespace <namespace> {
}
}
}';
/** Association Proxy class code template */
private static $_assocProxyClassTemplate =
'<?php
namespace <namespace> {
/**
* THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
*/
class <proxyClassName> extends \<className> implements \Doctrine\ORM\Proxy\Proxy {
private $_em;
private $_assoc;
private $_owner;
private $_joinColumnValues;
private $_loaded = false;
public function __construct($em, $assoc, $owner, array $joinColumnValues) {
$this->_em = $em;
$this->_assoc = $assoc;
$this->_owner = $owner;
$this->_joinColumnValues = $joinColumnValues;
<constructorInvocation>
}
private function _load() {
if ( ! $this->_loaded) {
$this->_assoc->load($this->_owner, $this, $this->_em, $this->_joinColumnValues);
unset($this->_em);
unset($this->_owner);
unset($this->_assoc);
unset($this->_joinColumnValues);
$this->_loaded = true;
}
}
public function __isInitialized__() { return $this->_loaded; }
<methods>
public function __sleep() {
if (!$this->_loaded) {
throw new \RuntimeException("Not fully loaded proxy can not be serialized.");
}
<sleepImpl>
}
}
}';
}
......@@ -59,8 +59,7 @@ final class Query extends AbstractQuery
const HINT_REFRESH = 'doctrine.refresh';
/**
* The forcePartialLoad query hint forces a particular query to return
* partial objects when partial objects in general are disallowed in the
* configuration.
* partial objects.
*
* @var string
*/
......
......@@ -1330,10 +1330,17 @@ class UnitOfWork implements PropertyChangedListener
$prop->setValue($managedCopy, $prop->getValue($entity));
} else {
$assoc2 = $class->associationMappings[$name];
if ($assoc2->isOneToOne() && ! $assoc2->isCascadeMerge) {
if ($assoc2->isOneToOne()) {
if ( ! $assoc2->isCascadeMerge) {
$other = $class->reflFields[$name]->getValue($entity);
if ($other !== null) {
$targetClass = $this->_em->getClassMetadata($assoc2->targetEntityName);
$prop->setValue($managedCopy, $this->_em->getProxyFactory()
->getReferenceProxy($assoc2->targetEntityName, $targetClass->getIdentifierValues($entity)));
$id = $targetClass->getIdentifierValues($other);
$proxy = $this->_em->getProxyFactory()->getProxy($assoc2->targetEntityName, $id);
$prop->setValue($managedCopy, $proxy);
$this->registerManaged($proxy, (array)$id, array());
}
}
} else {
$coll = new PersistentCollection($this->_em,
$this->_em->getClassMetadata($assoc2->targetEntityName),
......@@ -1695,28 +1702,41 @@ class UnitOfWork implements PropertyChangedListener
if ( ! isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) {
foreach ($class->associationMappings as $field => $assoc) {
// Check if the association is not among the fetch-joined associations already.
if ( ! isset($hints['fetched'][$className][$field])) {
if (isset($hints['fetched'][$className][$field])) {
continue;
}
$targetClass = $this->_em->getClassMetadata($assoc->targetEntityName);
if ($assoc->isOneToOne()) {
if ($assoc->isOwningSide) {
$joinColumns = array();
foreach ($assoc->targetToSourceKeyColumns as $srcColumn) {
$joinColumnValue = $data[$assoc->joinColumnFieldNames[$srcColumn]];
$associatedId = array();
foreach ($assoc->targetToSourceKeyColumns as $targetColumn => $srcColumn) {
$joinColumnValue = $data[$srcColumn];
if ($joinColumnValue !== null) {
$joinColumns[$srcColumn] = $joinColumnValue;
$associatedId[$targetColumn] = $joinColumnValue;
}
}
if ( ! $joinColumns) {
if ( ! $associatedId) {
$class->reflFields[$field]->setValue($entity, null);
$this->_originalEntityData[$oid][$field] = null;
} else {
//TODO: If its in the identity map just get it from there if possible!
// Inject proxy
$proxy = $this->_em->getProxyFactory()->getAssociationProxy($entity, $assoc, $joinColumns);
$this->_originalEntityData[$oid][$field] = $proxy;
$class->reflFields[$field]->setValue($entity, $proxy);
// Check identity map first
// FIXME: Can break easily with composite keys if join column values are in
// wrong order. The correct order is the one in ClassMetadata#identifier.
$relatedIdHash = implode(' ', $associatedId);
if (isset($this->_identityMap[$targetClass->rootEntityName][$relatedIdHash])) {
$newValue = $this->_identityMap[$targetClass->rootEntityName][$relatedIdHash];
} else {
$newValue = $this->_em->getProxyFactory()->getProxy($assoc->targetEntityName, $associatedId);
$this->_entityIdentifiers[spl_object_hash($newValue)] = $associatedId;
$this->_identityMap[$targetClass->rootEntityName][$relatedIdHash] = $newValue;
}
$this->_originalEntityData[$oid][$field] = $newValue;
$class->reflFields[$field]->setValue($entity, $newValue);
}
} else {
// Inverse side can never be lazy.
// Inverse side can never be lazy
$targetEntity = $assoc->load($entity, new $assoc->targetEntityName, $this->_em);
$class->reflFields[$field]->setValue($entity, $targetEntity);
}
......@@ -1724,8 +1744,7 @@ class UnitOfWork implements PropertyChangedListener
// Inject collection
$reflField = $class->reflFields[$field];
$pColl = new PersistentCollection(
$this->_em,
$this->_em->getClassMetadata($assoc->targetEntityName),
$this->_em, $targetClass,
$reflField->getValue($entity) ?: new ArrayCollection
);
$pColl->setOwner($entity, $assoc);
......@@ -1733,7 +1752,6 @@ class UnitOfWork implements PropertyChangedListener
if ($assoc->isLazilyFetched()) {
$pColl->setInitialized(false);
} else {
//TODO: Allow more efficient and configurable batching of these loads
$assoc->load($entity, $pColl, $this->_em);
}
$this->_originalEntityData[$oid][$field] = $pColl;
......@@ -1741,7 +1759,6 @@ class UnitOfWork implements PropertyChangedListener
}
}
}
}
//TODO: These should be invoked later, after hydration, because associations may not yet be loaded here.
if (isset($class->lifecycleCallbacks[Events::postLoad])) {
......
......@@ -74,8 +74,6 @@ class DetachedEntityTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->persist($ph2);
//$removed = $user->removePhonenumber(1); // [romanb] this is currently broken, I'm on it.
// Merge back in
$user = $this->_em->merge($user); // merge cascaded to phonenumbers
$this->_em->flush();
......
......@@ -38,7 +38,7 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase
$id = $product->getId();
$productProxy = $this->_factory->getReferenceProxy('Doctrine\Tests\Models\ECommerce\ECommerceProduct', array('id' => $id));
$productProxy = $this->_factory->getProxy('Doctrine\Tests\Models\ECommerce\ECommerceProduct', array('id' => $id));
$this->assertEquals('Doctrine Cookbook', $productProxy->getName());
}
}
......@@ -122,12 +122,12 @@ class ObjectHydratorTest extends HydrationTestCase
);
// mocking the proxy factory
$proxyFactory = $this->getMock('Doctrine\ORM\Proxy\ProxyFactory', array('getAssociationProxy'), array(), '', false, false, false);
$proxyFactory = $this->getMock('Doctrine\ORM\Proxy\ProxyFactory', array('getProxy'), array(), '', false, false, false);
$proxyFactory->expects($this->once())
->method('getAssociationProxy')
->with($this->isInstanceOf('Doctrine\Tests\Models\ECommerce\ECommerceProduct'),
$this->isInstanceOf('Doctrine\ORM\Mapping\OneToOneMapping'),
array('shipping_id' => 42));
->method('getProxy')
->with($this->equalTo('Doctrine\Tests\Models\ECommerce\ECommerceShipping'),
array('id' => 42))
->will($this->returnValue(new \stdClass));
$this->_em->setProxyFactory($proxyFactory);
......
......@@ -48,11 +48,11 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase
public function testReferenceProxyDelegatesLoadingToThePersister()
{
$identifier = array('id' => 42);
$proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureRProxy';
$proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureProxy';
$persister = $this->_getMockPersister();
$this->_uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister);
$proxy = $this->_proxyFactory->getReferenceProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $identifier);
$proxy = $this->_proxyFactory->getProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $identifier);
$persister->expects($this->atLeastOnce())
->method('load')
......@@ -64,10 +64,10 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase
public function testReferenceProxyExecutesLoadingOnlyOnce()
{
$identifier = array('id' => 42);
$proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureRProxy';
$proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureProxy';
$persister = $this->_getMockPersister();
$this->_uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister);
$proxy = $this->_proxyFactory->getReferenceProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $identifier);
$proxy = $this->_proxyFactory->getProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $identifier);
$persister->expects($this->atLeastOnce())
->method('load')
->with($this->equalTo($identifier), $this->isInstanceOf($proxyClass));
......@@ -77,10 +77,10 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase
public function testReferenceProxyRespectsMethodsParametersTypeHinting()
{
$proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureRProxy';
$proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureProxy';
$persister = $this->_getMockPersister();
$this->_uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister);
$proxy = $this->_proxyFactory->getReferenceProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', null);
$proxy = $this->_proxyFactory->getProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', null);
$method = new \ReflectionMethod(get_class($proxy), 'setProduct');
$params = $method->getParameters();
......@@ -91,43 +91,18 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase
public function testCreatesAssociationProxyAsSubclassOfTheOriginalOne()
{
$proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureRProxy';
$proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureProxy';
$this->assertTrue(is_subclass_of($proxyClass, 'Doctrine\Tests\Models\ECommerce\ECommerceFeature'));
}
public function testAllowsConcurrentCreationOfBothProxyTypes()
{
$referenceProxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureRProxy';
$referenceProxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureProxy';
$associationProxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureAProxy';
$this->assertNotEquals($referenceProxyClass, $associationProxyClass);
}
public function testAssociationProxyDelegatesLoadingToTheAssociation()
{
$proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureAProxy';
$product = new ECommerceProduct;
$foreignKeys = array('customer_id' => 42);
$assoc = $this->_getAssociationMock();
$assoc->targetEntityName = 'Doctrine\Tests\Models\ECommerce\ECommerceFeature';
$proxy = $this->_proxyFactory->getAssociationProxy($product, $assoc, $foreignKeys);
$assoc->expects($this->atLeastOnce())
->method('load')
->with($product, $this->isInstanceOf($proxyClass), $this->isInstanceOf('Doctrine\Tests\Mocks\EntityManagerMock'), $foreignKeys);
$proxy->getDescription();
}
public function testAssociationProxyExecutesLoadingOnlyOnce()
{
$proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureAProxy';
$assoc = $this->_getAssociationMock();
$assoc->targetEntityName = 'Doctrine\Tests\Models\ECommerce\ECommerceFeature';
$proxy = $this->_proxyFactory->getAssociationProxy(null, $assoc, array());
$assoc->expects($this->once())->method('load');
$proxy->getDescription();
$proxy->getDescription();
}
protected function _getAssociationMock()
{
$assoc = $this->getMock('Doctrine\ORM\Mapping\AssociationMapping', array('load'), array(), '', false, false, false);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment