diff --git a/lib/Doctrine/DBAL/Configuration.php b/lib/Doctrine/DBAL/Configuration.php index 3ad9e36a0..76ce1c4f0 100644 --- a/lib/Doctrine/DBAL/Configuration.php +++ b/lib/Doctrine/DBAL/Configuration.php @@ -1,7 +1,5 @@ * @author Jonathan Wage * @author Roman Borschel @@ -46,22 +41,12 @@ class Configuration */ protected $_attributes = array(); - /** - * Creates a new DBAL configuration instance. - */ - public function __construct() - { - $this->_attributes = array( - 'sqlLogger' => null - ); - } - /** * Sets the SQL logger to use. Defaults to NULL which means SQL logging is disabled. * * @param SQLLogger $logger */ - public function setSQLLogger($logger) + public function setSQLLogger(SQLLogger $logger) { $this->_attributes['sqlLogger'] = $logger; } @@ -73,6 +58,7 @@ class Configuration */ public function getSQLLogger() { - return $this->_attributes['sqlLogger']; + return isset($this->_attributes['sqlLogger']) ? + $this->_attributes['sqlLogger'] : null; } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index 229126635..b6fa3a99e 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -27,8 +27,7 @@ use Doctrine\Common\Cache\Cache, * It combines all configuration options from DBAL & ORM. * * @since 2.0 - * @internal When adding a new configuration option just write a getter/setter - * pair and add the option to the _attributes array with a proper default value. + * @internal When adding a new configuration option just write a getter/setter pair. * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage @@ -36,31 +35,6 @@ use Doctrine\Common\Cache\Cache, */ class Configuration extends \Doctrine\DBAL\Configuration { - /** - * Creates a new configuration that can be used for Doctrine. - */ - public function __construct() - { - parent::__construct(); - $this->_attributes = array_merge($this->_attributes, array( - 'resultCacheImpl' => null, - 'queryCacheImpl' => null, - 'metadataCacheImpl' => null, - 'metadataDriverImpl' => null, - 'proxyDir' => null, - 'useCExtension' => false, - 'autoGenerateProxyClasses' => true, - 'proxyNamespace' => null, - 'entityNamespaces' => array(), - 'namedNativeQueries' => array(), - 'namedQueries' => array(), - // Custom DQL Functions - 'customDatetimeFunctions' => array(), - 'customNumericFunctions' => array(), - 'customStringFunctions' => array() - )); - } - /** * Sets the directory where Doctrine generates any necessary proxy class files. * @@ -78,7 +52,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getProxyDir() { - return $this->_attributes['proxyDir']; + return isset($this->_attributes['proxyDir']) ? + $this->_attributes['proxyDir'] : null; } /** @@ -89,7 +64,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getAutoGenerateProxyClasses() { - return $this->_attributes['autoGenerateProxyClasses']; + return isset($this->_attributes['autoGenerateProxyClasses']) ? + $this->_attributes['autoGenerateProxyClasses'] : true; } /** @@ -110,7 +86,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getProxyNamespace() { - return $this->_attributes['proxyNamespace']; + return isset($this->_attributes['proxyNamespace']) ? + $this->_attributes['proxyNamespace'] : null; } /** @@ -195,7 +172,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getMetadataDriverImpl() { - return $this->_attributes['metadataDriverImpl']; + return isset($this->_attributes['metadataDriverImpl']) ? + $this->_attributes['metadataDriverImpl'] : null; } /** @@ -205,7 +183,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getResultCacheImpl() { - return $this->_attributes['resultCacheImpl']; + return isset($this->_attributes['resultCacheImpl']) ? + $this->_attributes['resultCacheImpl'] : null; } /** @@ -225,7 +204,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getQueryCacheImpl() { - return $this->_attributes['queryCacheImpl']; + return isset($this->_attributes['queryCacheImpl']) ? + $this->_attributes['queryCacheImpl'] : null; } /** @@ -245,7 +225,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getMetadataCacheImpl() { - return $this->_attributes['metadataCacheImpl']; + return isset($this->_attributes['metadataCacheImpl']) ? + $this->_attributes['metadataCacheImpl'] : null; } /** @@ -266,7 +247,8 @@ class Configuration extends \Doctrine\DBAL\Configuration */ public function getUseCExtension() { - return $this->_attributes['useCExtension']; + return isset($this->_attributes['useCExtension']) ? + $this->_attributes['useCExtension'] : false; } /** @@ -357,6 +339,8 @@ class Configuration extends \Doctrine\DBAL\Configuration * Such a function can then be used in any DQL statement in any place where string * functions are allowed. * + * DQL function names are case-insensitive. + * * @param string $name * @param string $className */ @@ -374,18 +358,23 @@ class Configuration extends \Doctrine\DBAL\Configuration public function getCustomStringFunction($name) { $name = strtolower($name); - return isset($this->_attributes['customStringFunctions'][$name]) ? $this->_attributes['customStringFunctions'][$name] : null; } /** - * Clean custom DQL functions that produces string values. + * Sets a map of custom DQL string functions. * + * Keys must be function names and values the FQCN of the implementing class. + * The function names will be case-insensitive in DQL. + * + * Any previously added string functions are discarded. + * + * @param array $functions The map of custom DQL string functions. */ - public function clearCustomStringFunctions() + public function setCustomStringFunctions(array $functions) { - $this->_attributes['customStringFunctions'] = array(); + $this->_attributes['customStringFunctions'] = array_change_key_case($functions); } /** @@ -393,6 +382,8 @@ class Configuration extends \Doctrine\DBAL\Configuration * Such a function can then be used in any DQL statement in any place where numeric * functions are allowed. * + * DQL function names are case-insensitive. + * * @param string $name * @param string $className */ @@ -410,18 +401,23 @@ class Configuration extends \Doctrine\DBAL\Configuration public function getCustomNumericFunction($name) { $name = strtolower($name); - return isset($this->_attributes['customNumericFunctions'][$name]) ? $this->_attributes['customNumericFunctions'][$name] : null; } /** - * Clean custom DQL functions that produces numeric values. + * Sets a map of custom DQL numeric functions. * + * Keys must be function names and values the FQCN of the implementing class. + * The function names will be case-insensitive in DQL. + * + * Any previously added numeric functions are discarded. + * + * @param array $functions The map of custom DQL numeric functions. */ - public function clearCustomNumericFunctions() + public function setCustomNumericFunctions(array $functions) { - $this->_attributes['customNumericFunctions'] = array(); + $this->_attributes['customNumericFunctions'] = array_change_key_case($functions); } /** @@ -429,6 +425,8 @@ class Configuration extends \Doctrine\DBAL\Configuration * Such a function can then be used in any DQL statement in any place where date/time * functions are allowed. * + * DQL function names are case-insensitive. + * * @param string $name * @param string $className */ @@ -446,17 +444,22 @@ class Configuration extends \Doctrine\DBAL\Configuration public function getCustomDatetimeFunction($name) { $name = strtolower($name); - return isset($this->_attributes['customDatetimeFunctions'][$name]) ? $this->_attributes['customDatetimeFunctions'][$name] : null; } /** - * Clean custom DQL functions that produces date/time values. - * + * Sets a map of custom DQL date/time functions. + * + * Keys must be function names and values the FQCN of the implementing class. + * The function names will be case-insensitive in DQL. + * + * Any previously added date/time functions are discarded. + * + * @param array $functions The map of custom DQL date/time functions. */ - public function clearCustomDatetimeFunctions() + public function setCustomDatetimeFunctions(array $functions) { - $this->_attributes['customDatetimeFunctions'] = array(); + $this->_attributes['customDatetimeFunctions'] = array_change_key_case($functions); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php index a0adbea67..5f5a8a4d7 100644 --- a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php +++ b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php @@ -26,7 +26,6 @@ use Doctrine\ORM\Mapping\ClassMetadata; * SINGLE_TABLE strategy. * * @author Roman Borschel - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @since 2.0 * @link http://martinfowler.com/eaaCatalog/singleTableInheritance.html */ diff --git a/lib/Doctrine/ORM/Query/AST/PathExpression.php b/lib/Doctrine/ORM/Query/AST/PathExpression.php index 4f8d0e277..ed856f679 100644 --- a/lib/Doctrine/ORM/Query/AST/PathExpression.php +++ b/lib/Doctrine/ORM/Query/AST/PathExpression.php @@ -1,7 +1,5 @@ * @author Jonathan Wage * @author Roman Borschel diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 5d9766262..960ae426d 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -1,7 +1,5 @@ * @author Jonathan Wage * @author Roman Borschel @@ -2624,9 +2619,10 @@ class Parser public function CustomFunctionsReturningNumerics() { - $funcNameLower = strtolower($this->_lexer->lookahead['value']); - $funcClass = $this->_em->getConfiguration()->getCustomNumericFunction($funcNameLower); - $function = new $funcClass($funcNameLower); + $funcName = strtolower($this->_lexer->lookahead['value']); + // getCustomNumericFunction is case-insensitive + $funcClass = $this->_em->getConfiguration()->getCustomNumericFunction($funcName); + $function = new $funcClass($funcName); $function->parse($this); return $function; @@ -2647,9 +2643,10 @@ class Parser public function CustomFunctionsReturningDatetime() { - $funcNameLower = strtolower($this->_lexer->lookahead['value']); - $funcClass = $this->_em->getConfiguration()->getCustomDatetimeFunction($funcNameLower); - $function = new $funcClass($funcNameLower); + $funcName = $this->_lexer->lookahead['value']; + // getCustomDatetimeFunction is case-insensitive + $funcClass = $this->_em->getConfiguration()->getCustomDatetimeFunction($funcName); + $function = new $funcClass($funcName); $function->parse($this); return $function; @@ -2675,9 +2672,10 @@ class Parser public function CustomFunctionsReturningStrings() { - $funcNameLower = strtolower($this->_lexer->lookahead['value']); - $funcClass = $this->_em->getConfiguration()->getCustomStringFunction($funcNameLower); - $function = new $funcClass($funcNameLower); + $funcName = $this->_lexer->lookahead['value']; + // getCustomStringFunction is case-insensitive + $funcClass = $this->_em->getConfiguration()->getCustomStringFunction($funcName); + $function = new $funcClass($funcName); $function->parse($this); return $function; diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index ece61bd50..78017e634 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -1,7 +1,5 @@ getSqlTableAlias($class->table['name'], $dqlAlias); + $baseTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); // INNER JOIN parent class tables foreach ($class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); - $tableAlias = $this->getSqlTableAlias($parentClass->table['name'], $dqlAlias); - $sql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) + $tableAlias = $this->getSQLTableAlias($parentClass->table['name'], $dqlAlias); + // If this is a joined association we must use left joins to preserve the correct result. + $sql .= isset($this->_queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER '; + $sql .= 'JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; - foreach ($class->identifier as $idField) { if ($first) $first = false; else $sql .= ' AND '; @@ -260,14 +259,13 @@ class SqlWalker implements TreeWalker } } - // LEFT JOIN subclass tables, if partial objects disallowed + // LEFT JOIN subclass tables, if partial objects disallowed. if ( ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { foreach ($class->subClasses as $subClassName) { $subClass = $this->_em->getClassMetadata($subClassName); - $tableAlias = $this->getSqlTableAlias($subClass->table['name'], $dqlAlias); + $tableAlias = $this->getSQLTableAlias($subClass->table['name'], $dqlAlias); $sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; - $first = true; foreach ($class->identifier as $idField) { if ($first) $first = false; else $sql .= ' AND '; diff --git a/tests/Doctrine/Tests/Models/Company/CompanyEvent.php b/tests/Doctrine/Tests/Models/Company/CompanyEvent.php index f0bb730cf..2db4986ae 100644 --- a/tests/Doctrine/Tests/Models/Company/CompanyEvent.php +++ b/tests/Doctrine/Tests/Models/Company/CompanyEvent.php @@ -6,9 +6,9 @@ namespace Doctrine\Tests\Models\Company; * @Entity @Table(name="company_events") * @InheritanceType("JOINED") * @DiscriminatorColumn(name="event_type", type="string") - * @DiscriminatorMap({"auction" = "CompanyAuction", "raffle" = "CompanyRaffle"}) + * @DiscriminatorMap({"auction"="CompanyAuction", "raffle"="CompanyRaffle"}) */ -class CompanyEvent { +abstract class CompanyEvent { /** * @Id @Column(type="integer") * @GeneratedValue diff --git a/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php index 206a567b6..ea1ad15d8 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ClassTableInheritanceTest.php @@ -294,5 +294,4 @@ class ClassTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase ->getResult()) > 0); } - } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC512Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC512Test.php new file mode 100644 index 000000000..72b11aaab --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC512Test.php @@ -0,0 +1,94 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC512Customer'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC512OfferItem'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC512Item'), + )); + } + + public function testIssue() + { + $customer1 = new DDC512Customer(); + $item = new DDC512OfferItem(); + $customer1->item = $item; + $this->_em->persist($customer1); + + $customer2 = new DDC512Customer(); + $this->_em->persist($customer2); + + $this->_em->flush(); + $this->_em->clear(); + + $q = $this->_em->createQuery("select u,i from ".__NAMESPACE__."\\DDC512Customer u left join u.item i"); + $result = $q->getResult(); + + $this->assertEquals(2, count($result)); + $this->assertTrue($result[0] instanceof DDC512Customer); + $this->assertTrue($result[1] instanceof DDC512Customer); + if ($result[0]->id == $customer1->id) { + $this->assertTrue($result[0]->item instanceof DDC512OfferItem); + $this->assertEquals($item->id, $result[0]->item->id); + $this->assertNull($result[1]->item); + } else { + $this->assertTrue($result[1]->item instanceof DDC512OfferItem); + $this->assertNull($result[0]->item); + } + } +} + +/** + * @Entity + */ +class DDC512Customer { + /** + * @Id + * @Column(type="integer") + * @GeneratedValue(strategy="AUTO") + */ + public $id; + + /** + * NOTE that we can currently not name the join column the same as the field + * (item = item), this currently confuses Doctrine. + * + * @OneToOne(targetEntity="DDC512OfferItem", cascade={"remove","persist"}) + * @JoinColumn(name="item_id", referencedColumnName="id") + */ + public $item; +} + +/** + * @Entity + */ +class DDC512OfferItem extends DDC512Item +{ +} + +/** + * @Entity + * @InheritanceType("JOINED") + * @DiscriminatorColumn(name="discr", type="string") + * @DiscriminatorMap({"item" = "DDC512Item", "offerItem" = "DDC512OfferItem"}) + */ +class DDC512Item +{ + /** + * @Id + * @Column(type="integer") + * @GeneratedValue(strategy="AUTO") + */ + public $id; +} + + + + diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 1ef832571..a85873a92 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -610,7 +610,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase 'SELECT ABS(c0_.phonenumber) AS sclr0 FROM cms_phonenumbers c0_' ); - $config->clearCustomNumericFunctions(); + $config->setCustomNumericFunctions(array()); } }