Commit e578bad6 authored by romanb's avatar romanb

[2.0] Fixed several referential integrity issues. Fixed critical issue with...

[2.0] Fixed several referential integrity issues. Fixed critical issue with inserts being run twice on postgresql/oracle. Added support for additional tree walkers that modify the AST prior to SQL construction and started to play with it in a testcase.
parent 8452108e
......@@ -97,7 +97,7 @@
</fileset>
</batchtest>
</phpunit>
<!-- <phpunitreport infile="build/logs/testsuites.xml" format="frames" todir="reports/tests" />-->
<phpunitreport infile="build/logs/testsuites.xml" format="frames" todir="reports/tests" />
</target>
<!--
......
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://schemas.doctrine-project.org/orm/doctrine-mapping"
xmlns:orm="http://schemas.doctrine-project.org/orm/doctrine-mapping"
targetNamespace="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:orm="http://doctrine-project.org/schemas/orm/doctrine-mapping"
elementFormDefault="qualified">
<xs:annotation>
......
<?php
/*
* $Id: Inflector.php 3189 2007-11-18 20:37:44Z meus $
*
* 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\Common\Util;
/**
* Doctrine inflector has static methods for inflecting text
*
* The methods in these classes are from several different sources collected
* across several different php projects and several different authors. The
* original author names and emails are not known
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 1.0
* @version $Revision: 3189 $
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Jonathan H. Wage <jonwage@gmail.com>
*/
class Inflector
{
/**
* Convert word in to the format for a Doctrine table name. Converts 'ModelName' to 'model_name'
*
* @param string $word Word to tableize
* @return string $word Tableized word
*/
public static function tableize($word)
{
return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $word));
}
/**
* Convert a word in to the format for a Doctrine class name. Converts 'table_name' to 'TableName'
*
* @param string $word Word to classify
* @return string $word Classified word
*/
public static function classify($word)
{
$word = preg_replace('/[$]/', '', $word);
return preg_replace_callback('~(_?)(_)([\w])~', array(__CLASS__, "classifyCallback"), ucfirst(strtolower($word)));
}
/**
* Callback function to classify a classname properly.
*
* @param array $matches An array of matches from a pcre_replace call
* @return string $string A string with matches 1 and mathces 3 in upper case.
*/
public static function classifyCallback($matches)
{
return $matches[1] . strtoupper($matches[3]);
}
/**
* Check if a string has utf7 characters in it
*
* By bmorel at ssi dot fr
*
* @param string $string
* @return boolean $bool
*/
public static function seemsUtf8($string)
{
for ($i = 0; $i < strlen($string); $i++) {
if (ord($string[$i]) < 0x80) continue; # 0bbbbbbb
elseif ((ord($string[$i]) & 0xE0) == 0xC0) $n=1; # 110bbbbb
elseif ((ord($string[$i]) & 0xF0) == 0xE0) $n=2; # 1110bbbb
elseif ((ord($string[$i]) & 0xF8) == 0xF0) $n=3; # 11110bbb
elseif ((ord($string[$i]) & 0xFC) == 0xF8) $n=4; # 111110bb
elseif ((ord($string[$i]) & 0xFE) == 0xFC) $n=5; # 1111110b
else return false; # Does not match any model
for ($j=0; $j<$n; $j++) { # n bytes matching 10bbbbbb follow ?
if ((++$i == strlen($string)) || ((ord($string[$i]) & 0xC0) != 0x80))
return false;
}
}
return true;
}
/**
* Remove any illegal characters, accents, etc.
*
* @param string $string String to unaccent
* @return string $string Unaccented string
*/
public static function unaccent($string)
{
if ( ! preg_match('/[\x80-\xff]/', $string) ) {
return $string;
}
if (self::seemsUtf8($string)) {
$chars = array(
// Decompositions for Latin-1 Supplement
chr(195).chr(128) => 'A', chr(195).chr(129) => 'A',
chr(195).chr(130) => 'A', chr(195).chr(131) => 'A',
chr(195).chr(132) => 'A', chr(195).chr(133) => 'A',
chr(195).chr(135) => 'C', chr(195).chr(136) => 'E',
chr(195).chr(137) => 'E', chr(195).chr(138) => 'E',
chr(195).chr(139) => 'E', chr(195).chr(140) => 'I',
chr(195).chr(141) => 'I', chr(195).chr(142) => 'I',
chr(195).chr(143) => 'I', chr(195).chr(145) => 'N',
chr(195).chr(146) => 'O', chr(195).chr(147) => 'O',
chr(195).chr(148) => 'O', chr(195).chr(149) => 'O',
chr(195).chr(150) => 'O', chr(195).chr(153) => 'U',
chr(195).chr(154) => 'U', chr(195).chr(155) => 'U',
chr(195).chr(156) => 'U', chr(195).chr(157) => 'Y',
chr(195).chr(159) => 's', chr(195).chr(160) => 'a',
chr(195).chr(161) => 'a', chr(195).chr(162) => 'a',
chr(195).chr(163) => 'a', chr(195).chr(164) => 'a',
chr(195).chr(165) => 'a', chr(195).chr(167) => 'c',
chr(195).chr(168) => 'e', chr(195).chr(169) => 'e',
chr(195).chr(170) => 'e', chr(195).chr(171) => 'e',
chr(195).chr(172) => 'i', chr(195).chr(173) => 'i',
chr(195).chr(174) => 'i', chr(195).chr(175) => 'i',
chr(195).chr(177) => 'n', chr(195).chr(178) => 'o',
chr(195).chr(179) => 'o', chr(195).chr(180) => 'o',
chr(195).chr(181) => 'o', chr(195).chr(182) => 'o',
chr(195).chr(182) => 'o', chr(195).chr(185) => 'u',
chr(195).chr(186) => 'u', chr(195).chr(187) => 'u',
chr(195).chr(188) => 'u', chr(195).chr(189) => 'y',
chr(195).chr(191) => 'y',
// Decompositions for Latin Extended-A
chr(196).chr(128) => 'A', chr(196).chr(129) => 'a',
chr(196).chr(130) => 'A', chr(196).chr(131) => 'a',
chr(196).chr(132) => 'A', chr(196).chr(133) => 'a',
chr(196).chr(134) => 'C', chr(196).chr(135) => 'c',
chr(196).chr(136) => 'C', chr(196).chr(137) => 'c',
chr(196).chr(138) => 'C', chr(196).chr(139) => 'c',
chr(196).chr(140) => 'C', chr(196).chr(141) => 'c',
chr(196).chr(142) => 'D', chr(196).chr(143) => 'd',
chr(196).chr(144) => 'D', chr(196).chr(145) => 'd',
chr(196).chr(146) => 'E', chr(196).chr(147) => 'e',
chr(196).chr(148) => 'E', chr(196).chr(149) => 'e',
chr(196).chr(150) => 'E', chr(196).chr(151) => 'e',
chr(196).chr(152) => 'E', chr(196).chr(153) => 'e',
chr(196).chr(154) => 'E', chr(196).chr(155) => 'e',
chr(196).chr(156) => 'G', chr(196).chr(157) => 'g',
chr(196).chr(158) => 'G', chr(196).chr(159) => 'g',
chr(196).chr(160) => 'G', chr(196).chr(161) => 'g',
chr(196).chr(162) => 'G', chr(196).chr(163) => 'g',
chr(196).chr(164) => 'H', chr(196).chr(165) => 'h',
chr(196).chr(166) => 'H', chr(196).chr(167) => 'h',
chr(196).chr(168) => 'I', chr(196).chr(169) => 'i',
chr(196).chr(170) => 'I', chr(196).chr(171) => 'i',
chr(196).chr(172) => 'I', chr(196).chr(173) => 'i',
chr(196).chr(174) => 'I', chr(196).chr(175) => 'i',
chr(196).chr(176) => 'I', chr(196).chr(177) => 'i',
chr(196).chr(178) => 'IJ',chr(196).chr(179) => 'ij',
chr(196).chr(180) => 'J', chr(196).chr(181) => 'j',
chr(196).chr(182) => 'K', chr(196).chr(183) => 'k',
chr(196).chr(184) => 'k', chr(196).chr(185) => 'L',
chr(196).chr(186) => 'l', chr(196).chr(187) => 'L',
chr(196).chr(188) => 'l', chr(196).chr(189) => 'L',
chr(196).chr(190) => 'l', chr(196).chr(191) => 'L',
chr(197).chr(128) => 'l', chr(197).chr(129) => 'L',
chr(197).chr(130) => 'l', chr(197).chr(131) => 'N',
chr(197).chr(132) => 'n', chr(197).chr(133) => 'N',
chr(197).chr(134) => 'n', chr(197).chr(135) => 'N',
chr(197).chr(136) => 'n', chr(197).chr(137) => 'N',
chr(197).chr(138) => 'n', chr(197).chr(139) => 'N',
chr(197).chr(140) => 'O', chr(197).chr(141) => 'o',
chr(197).chr(142) => 'O', chr(197).chr(143) => 'o',
chr(197).chr(144) => 'O', chr(197).chr(145) => 'o',
chr(197).chr(146) => 'OE',chr(197).chr(147) => 'oe',
chr(197).chr(148) => 'R', chr(197).chr(149) => 'r',
chr(197).chr(150) => 'R', chr(197).chr(151) => 'r',
chr(197).chr(152) => 'R', chr(197).chr(153) => 'r',
chr(197).chr(154) => 'S', chr(197).chr(155) => 's',
chr(197).chr(156) => 'S', chr(197).chr(157) => 's',
chr(197).chr(158) => 'S', chr(197).chr(159) => 's',
chr(197).chr(160) => 'S', chr(197).chr(161) => 's',
chr(197).chr(162) => 'T', chr(197).chr(163) => 't',
chr(197).chr(164) => 'T', chr(197).chr(165) => 't',
chr(197).chr(166) => 'T', chr(197).chr(167) => 't',
chr(197).chr(168) => 'U', chr(197).chr(169) => 'u',
chr(197).chr(170) => 'U', chr(197).chr(171) => 'u',
chr(197).chr(172) => 'U', chr(197).chr(173) => 'u',
chr(197).chr(174) => 'U', chr(197).chr(175) => 'u',
chr(197).chr(176) => 'U', chr(197).chr(177) => 'u',
chr(197).chr(178) => 'U', chr(197).chr(179) => 'u',
chr(197).chr(180) => 'W', chr(197).chr(181) => 'w',
chr(197).chr(182) => 'Y', chr(197).chr(183) => 'y',
chr(197).chr(184) => 'Y', chr(197).chr(185) => 'Z',
chr(197).chr(186) => 'z', chr(197).chr(187) => 'Z',
chr(197).chr(188) => 'z', chr(197).chr(189) => 'Z',
chr(197).chr(190) => 'z', chr(197).chr(191) => 's',
// Euro Sign
chr(226).chr(130).chr(172) => 'E',
// GBP (Pound) Sign
chr(194).chr(163) => '',
'Ä' => 'Ae', 'ä' => 'ae', 'Ü' => 'Ue', 'ü' => 'ue',
'Ö' => 'Oe', 'ö' => 'oe', 'ß' => 'ss');
$string = strtr($string, $chars);
} else {
// Assume ISO-8859-1 if not UTF-8
$chars['in'] = chr(128).chr(131).chr(138).chr(142).chr(154).chr(158)
.chr(159).chr(162).chr(165).chr(181).chr(192).chr(193).chr(194)
.chr(195).chr(196).chr(197).chr(199).chr(200).chr(201).chr(202)
.chr(203).chr(204).chr(205).chr(206).chr(207).chr(209).chr(210)
.chr(211).chr(212).chr(213).chr(214).chr(216).chr(217).chr(218)
.chr(219).chr(220).chr(221).chr(224).chr(225).chr(226).chr(227)
.chr(228).chr(229).chr(231).chr(232).chr(233).chr(234).chr(235)
.chr(236).chr(237).chr(238).chr(239).chr(241).chr(242).chr(243)
.chr(244).chr(245).chr(246).chr(248).chr(249).chr(250).chr(251)
.chr(252).chr(253).chr(255);
$chars['out'] = "EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy";
$string = strtr($string, $chars['in'], $chars['out']);
$doubleChars['in'] = array(chr(140), chr(156), chr(198), chr(208), chr(222), chr(223), chr(230), chr(240), chr(254));
$doubleChars['out'] = array('OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th');
$string = str_replace($doubleChars['in'], $doubleChars['out'], $string);
}
return $string;
}
/**
* Convert any passed string to a url friendly string. Converts 'My first blog post' to 'my-first-blog-post'
*
* @param string $text Text to urlize
* @return string $text Urlized text
*/
public static function urlize($text)
{
// Remove all non url friendly characters with the unaccent function
$text = self::unaccent($text);
if (function_exists('mb_strtolower'))
{
$text = mb_strtolower($text);
} else {
$text = strtolower($text);
}
// Remove all none word characters
$text = preg_replace('/\W/', ' ', $text);
// More stripping. Replace spaces with dashes
$text = strtolower(preg_replace('/[^A-Z^a-z^0-9^\/]+/', '-',
preg_replace('/([a-z\d])([A-Z])/', '\1_\2',
preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1_\2',
preg_replace('/::/', '/', $text)))));
return trim($text, '-');
}
}
\ No newline at end of file
......@@ -87,7 +87,7 @@ class Configuration
public function setCustomTypes(array $types, $override = false)
{
foreach ($types as $name => $typeClassName) {
$method = (Type::hasType($name) ? 'override' : 'add') . 'Type';
$method = (Type::hasType($name) && $override ? 'override' : 'add') . 'Type';
Type::$method($name, $typeClassName);
}
......
......@@ -47,7 +47,6 @@ class Driver implements \Doctrine\DBAL\Driver
$password,
$driverOptions
);
$conn->setAttribute(Connection::ATTR_AUTOCOMMIT, false);
return $conn;
}
......
......@@ -141,7 +141,7 @@ interface Statement
* bound parameters in the SQL statement being executed.
* @return boolean Returns TRUE on success or FALSE on failure.
*/
function execute($params = null);
function execute($params = array());
/**
* fetch
......
......@@ -585,19 +585,18 @@ class EntityManager
*/
public static function create($conn, Configuration $config = null, EventManager $eventManager = null)
{
if (is_array($conn)) {
$conn = \Doctrine\DBAL\DriverManager::getConnection($conn, $config, $eventManager);
} else if ( ! $conn instanceof Connection) {
throw DoctrineException::updateMe("Invalid parameter '$conn'.");
}
$config = $config ?: new Configuration();
if ($config === null) {
$config = new Configuration();
if (is_array($conn)) {
$conn = \Doctrine\DBAL\DriverManager::getConnection($conn, $config, ($eventManager ?: new EventManager()));
} else if ($conn instanceof Connection) {
if ($eventManager !== null && $conn->getEventManager() !== $eventManager) {
throw DoctrineException::updateMe("Cannot use different EventManagers for EntityManager and Connection.");
}
if ($eventManager === null) {
$eventManager = new EventManager();
} else {
throw DoctrineException::updateMe("Invalid parameter '$conn'.");
}
return new EntityManager($conn, $config, $eventManager);
return new EntityManager($conn, $config, $conn->getEventManager());
}
}
......@@ -26,12 +26,11 @@ namespace Doctrine\ORM;
* business specific methods for retrieving entities.
*
* This class is designed for inheritance and users can subclass this class to
* write their own repositories.
* write their own repositories with business-specific methods to locate entities.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision$
* @author Roman Borschel <roman@code-factory.org>
*/
class EntityRepository
......@@ -53,21 +52,6 @@ class EntityRepository
$this->_class = $class;
}
/**
* Creates a new Doctrine_Query object and adds the component name
* of this table as the query 'from' part.
*
* @param string Optional alias name for component aliasing.
* @return Doctrine_Query
*/
protected function _createQuery($alias = '')
{
if ( ! empty($alias)) {
$alias = ' ' . trim($alias);
}
return $this->_em->createQuery()->from($this->_entityName . $alias);
}
/**
* Clears the repository, causing all managed entities to become detached.
*/
......@@ -81,9 +65,9 @@ class EntityRepository
*
* @param $id The identifier.
* @param int $hydrationMode The hydration mode to use.
* @return mixed Array or Object or false if no result.
* @return object The entity.
*/
public function find($id, $hydrationMode = null)
public function find($id)
{
// Check identity map first
if ($entity = $this->_em->getUnitOfWork()->tryGetById($id, $this->_class->rootEntityName)) {
......@@ -102,48 +86,41 @@ class EntityRepository
* Finds all entities in the repository.
*
* @param int $hydrationMode
* @return mixed
* @return array The entities.
*/
public function findAll($hydrationMode = null)
public function findAll()
{
return $this->_createQuery()->execute(array(), $hydrationMode);
return $this->findBy(array());
}
/**
* findBy
* Finds entities by a set of criteria.
*
* @param string $column
* @param string $value
* @param string $hydrationMode
* @return void
* @return array
*/
protected function findBy($fieldName, $value, $hydrationMode = null)
public function findBy(array $criteria)
{
return $this->_createQuery()->where($fieldName . ' = ?')->execute(array($value), $hydrationMode);
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->loadAll($criteria);
}
/**
* findOneBy
* Finds a single entity by a set of criteria.
*
* @param string $column
* @param string $value
* @param string $hydrationMode
* @return void
* @return object
*/
protected function findOneBy($fieldName, $value, $hydrationMode = null)
public function findOneBy(array $criteria)
{
$results = $this->_createQuery()->where($fieldName . ' = ?')
->setMaxResults(1)
->execute(array($value), $hydrationMode);
return $hydrationMode === Query::HYDRATE_ARRAY ? array_shift($results) : $results->getFirst();
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($criteria);
}
/**
* Adds support for magic finders.
* findByColumnName, findByRelationAlias
* findById, findByContactId, etc.
*
* @return void
* @return array|object The found entity/entities.
* @throws BadMethodCallException If the method called is an invalid find* method
* or no find* method at all and therefore an invalid
* method call.
......@@ -160,25 +137,16 @@ class EntityRepository
throw new BadMethodCallException("Undefined method '$method'.");
}
if (isset($by)) {
if ( ! isset($arguments[0])) {
throw DoctrineException::updateMe('You must specify the value to findBy.');
}
$fieldName = Doctrine::tableize($by);
$hydrationMode = isset($arguments[1]) ? $arguments[1]:null;
$fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by));
if ($this->_class->hasField($fieldName)) {
return $this->$method($fieldName, $arguments[0], $hydrationMode);
} else if ($this->_class->hasRelation($by)) {
$relation = $this->_class->getRelation($by);
if ($relation['type'] === Doctrine_Relation::MANY) {
throw DoctrineException::updateMe('Cannot findBy many relationship.');
}
return $this->$method($relation['local'], $arguments[0], $hydrationMode);
return $this->$method(array($fieldName => $arguments[0]));
} else {
throw DoctrineException::updateMe('Cannot find by: ' . $by . '. Invalid field or relationship alias.');
}
throw \Doctrine\Common\DoctrineException::updateMe('Cannot find by: ' . $by . '. Invalid field.');
}
}
}
\ No newline at end of file
......@@ -207,11 +207,11 @@ class ObjectHydrator extends AbstractHydrator
);
$pColl->setOwner($entity, $assoc);
$reflField->setValue($entity, $pColl);
if ( ! $assoc->isLazilyFetched()) {
if ($assoc->isLazilyFetched()) {
$pColl->setInitialized(false);
} else {
//TODO: Allow more efficient and configurable batching of these loads
$assoc->load($entity, $pColl, $this->_em);
} else {
$pColl->setInitialized(false);
}
}
}
......
......@@ -415,6 +415,28 @@ class StandardEntityPersister
return $this->_createEntity($result, $entity);
}
/**
* Loads all entities by a list of field criteria.
*
* @param array $criteria
* @return array
*/
public function loadAll(array $criteria = array())
{
$entities = array();
$stmt = $this->_conn->prepare($this->_getSelectEntitiesSql($criteria));
$stmt->execute(array_values($criteria));
$result = $stmt->fetchAll(Connection::FETCH_ASSOC);
$stmt->closeCursor();
foreach ($result as $row) {
$entities[] = $this->_createEntity($row);
}
return $entities;
}
/**
* Loads a collection of entities into a one-to-many association.
*
......@@ -570,7 +592,7 @@ class StandardEntityPersister
return 'SELECT ' . $columnList
. ' FROM ' . $this->_class->getQuotedTableName($this->_platform)
. $joinSql
. ' WHERE ' . $conditionSql;
. ($conditionSql ? ' WHERE ' . $conditionSql : '');
}
/**
......
......@@ -21,8 +21,8 @@
namespace Doctrine\ORM\Proxy;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\EntityManager,
Doctrine\ORM\Mapping\ClassMetadata;
/**
* The ProxyClassGenerator is used to generate proxy objects for entities at runtime.
......@@ -42,7 +42,8 @@ class ProxyClassGenerator
* Generates and stores proxy class files in the given cache directory.
*
* @param EntityManager $em
* @param string $cacheDir
* @param string $cacheDir The directory where generated proxy classes will be saved.
* If not set, sys_get_temp_dir() is used.
*/
public function __construct(EntityManager $em, $cacheDir = null)
{
......@@ -84,7 +85,7 @@ class ProxyClassGenerator
return $this->_generateClass($originalClassName, $proxyClassName, self::$_assocProxyClassTemplate);
}
protected function _generateClass($originalClassName, $proxyClassName, $file)
private function _generateClass($originalClassName, $proxyClassName, $file)
{
$proxyFullyQualifiedClassName = self::$_ns . $proxyClassName;
......@@ -124,7 +125,7 @@ class ProxyClassGenerator
return $proxyFullyQualifiedClassName;
}
protected function _generateMethods(ClassMetadata $class)
private function _generateMethods(ClassMetadata $class)
{
$methods = '';
......@@ -165,7 +166,7 @@ class ProxyClassGenerator
return $methods;
}
public function _generateSleep(ClassMetadata $class)
private function _generateSleep(ClassMetadata $class)
{
$sleepImpl = '';
......
......@@ -21,8 +21,8 @@
namespace Doctrine\ORM;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Query\Parser,
Doctrine\ORM\Query\QueryException;
/**
* A Query object represents a DQL query.
......@@ -75,6 +75,7 @@ final class Query extends AbstractQuery
*/
const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns';
const HINT_CUSTOM_TREE_WALKERS = 'doctrine.customTreeWalkers';
//const HINT_READ_ONLY = 'doctrine.readOnly';
/**
......@@ -169,6 +170,8 @@ final class Query extends AbstractQuery
// Check query cache
if ($queryCache = $this->getQueryCacheDriver()) {
// Calculate hash for dql query.
// TODO: Probably need to include query hints in hash calculation, because query hints
// can have influence on the SQL.
$hash = md5($this->getDql() . 'DOCTRINE_QUERY_CACHE_SALT');
$cached = ($this->_expireQueryCache) ? false : $queryCache->fetch($hash);
......
......@@ -42,11 +42,6 @@ class QuantifiedExpression extends Node
$this->subselect = $subselect;
}
public function getSubselect()
{
return $this->_subselect;
}
public function isAll()
{
return strtoupper($this->type) == 'ALL';
......
......@@ -116,11 +116,18 @@ class Parser
private $_nestingLevel = 0;
/**
* Tree walker chain
* Any additional custom tree walkers that modify the AST.
*
* @var array
*/
private $_customTreeWalkers = array();
/**
* The custom last tree walker, if any, that is responsible for producing the output.
*
* @var TreeWalker
*/
private $_treeWalker = 'Doctrine\ORM\Query\SqlWalker';
private $_customOutputWalker;
/**
* Creates a new query parser object.
......@@ -136,13 +143,24 @@ class Parser
}
/**
* Sets the custom tree walker.
* Sets a custom tree walker that produces output.
* This tree walker will be run last over the AST, after any other walkers.
*
* @param string $className
*/
public function setCustomOutputTreeWalker($className)
{
$this->_customOutputWalker = $className;
}
/**
* Adds a custom tree walker for modifying the AST.
*
* @param string $treeWalker
* @param string $className
*/
public function setTreeWalker($treeWalker)
public function addCustomTreeWalker($className)
{
$this->_treeWalker = $treeWalker;
$this->_customTreeWalkers[] = $className;
}
/**
......@@ -263,25 +281,37 @@ class Parser
$this->syntaxError('end of string');
}
// Create TreeWalker who creates the SQL from the AST
$treeWalker = new $this->_treeWalker(
if ($customWalkers = $this->_query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) {
$this->_customTreeWalkers = $customWalkers;
}
// Run any custom tree walkers over the AST
if ($this->_customTreeWalkers) {
$treeWalkerChain = new TreeWalkerChain($this->_query, $this->_parserResult, $this->_queryComponents);
foreach ($this->_customTreeWalkers as $walker) {
$treeWalkerChain->addTreeWalker($walker);
}
if ($AST instanceof AST\SelectStatement) {
$treeWalkerChain->walkSelectStatement($AST);
} else if ($AST instanceof AST\UpdateStatement) {
$treeWalkerChain->walkUpdateStatement($AST);
} else {
$treeWalkerChain->walkDeleteStatement($AST);
}
}
if ($this->_customOutputWalker) {
$outputWalker = new $this->_customOutputWalker(
$this->_query, $this->_parserResult, $this->_queryComponents
);
/*if ($this->_treeWalkers) {
// We got additional walkers, so build a chain.
$treeWalker = new TreeWalkerChain($this->_query, $this->_parserResult, $this->_queryComponents);
foreach ($this->_treeWalkers as $walker) {
$treeWalker->addTreeWalker(new $walker($this->_query, $this->_parserResult, $this->_queryComponents));
}
$treeWalker->setLastTreeWalker('Doctrine\ORM\Query\SqlWalker');
} else {
$treeWalker = new SqlWalker(
$outputWalker = new SqlWalker(
$this->_query, $this->_parserResult, $this->_queryComponents
);
}*/
}
// Assign an SQL executor to the parser result
$this->_parserResult->setSqlExecutor($treeWalker->getExecutor($AST));
$this->_parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
return $this->_parserResult;
}
......
......@@ -30,8 +30,6 @@ use Doctrine\ORM\Query,
*
* @author Roman Borschel <roman@code-factory.org>
* @since 2.0
* @todo Code review for schema usage with table names.
* (Prepend schema name to tables IF schema is defined AND platform supports them)
*/
class SqlWalker implements TreeWalker
{
......@@ -417,14 +415,7 @@ class SqlWalker implements TreeWalker
$sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
}
if (isset($class->associationMappings[$fieldName])) {
//FIXME: Inverse side support
//FIXME: Throw exception on composite key
$assoc = $class->associationMappings[$fieldName];
$sql .= $assoc->getQuotedJoinColumnName($assoc->joinColumns[0]['name'], $this->_platform);
} else {
$sql .= $class->getQuotedColumnName($fieldName, $this->_platform);
}
break;
case AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION:
......@@ -604,7 +595,7 @@ class SqlWalker implements TreeWalker
*/
public function walkHavingClause($havingClause)
{
$condExpr = $havingClause->getConditionalExpression();
$condExpr = $havingClause->conditionalExpression;
return ' HAVING ' . implode(
' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms)
......@@ -846,7 +837,7 @@ class SqlWalker implements TreeWalker
public function walkQuantifiedExpression($qExpr)
{
return ' ' . strtoupper($qExpr->type)
. '(' . $this->walkSubselect($qExpr->getSubselect()) . ')';
. '(' . $this->walkSubselect($qExpr->subselect) . ')';
}
/**
......@@ -921,7 +912,6 @@ class SqlWalker implements TreeWalker
if ($expr instanceof AST\PathExpression) {
$sql .= ' ' . $this->walkPathExpression($expr);
//...
} else if ($expr instanceof AST\AggregateExpression) {
if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
$alias = $this->_scalarAliasCounter++;
......@@ -932,7 +922,7 @@ class SqlWalker implements TreeWalker
$sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias;
} else {
// IdentificationVariable
// FIXME: Composite key support, or select all columns? Does that make
// FIXME: Composite key support, or select all columns? Does that make sense
// in a subquery?
$class = $this->_queryComponents[$expr]['metadata'];
$sql .= ' ' . $this->getSqlTableAlias($class->getTableName(), $expr) . '.'
......@@ -1420,7 +1410,7 @@ class SqlWalker implements TreeWalker
if ($leftExpr instanceof AST\Node) {
$sql .= $leftExpr->dispatch($this);
} else {
$sql .= $this->_conn->quote($leftExpr);
$sql .= is_numeric($leftExpr) ? $leftExpr : $this->_conn->quote($leftExpr);
}
$sql .= ' ' . $compExpr->operator . ' ';
......@@ -1428,7 +1418,7 @@ class SqlWalker implements TreeWalker
if ($rightExpr instanceof AST\Node) {
$sql .= $rightExpr->dispatch($this);
} else {
$sql .= $this->_conn->quote($rightExpr);
$sql .= is_numeric($rightExpr) ? $rightExpr : $this->_conn->quote($rightExpr);
}
return $sql;
......
......@@ -30,10 +30,24 @@ namespace Doctrine\ORM\Query;
*/
abstract class TreeWalkerAdapter implements TreeWalker
{
private $_query;
private $_parserResult;
private $_queryComponents;
/**
* @inheritdoc
*/
public function __construct($query, $parserResult, array $queryComponents) {}
public function __construct($query, $parserResult, array $queryComponents)
{
$this->_query = $query;
$this->_parserResult = $parserResult;
$this->_queryComponents = $queryComponents;
}
protected function _getQueryComponents()
{
return $this->_queryComponents;
}
/**
* Walks down a SelectStatement AST node, thereby generating the appropriate SQL.
......@@ -366,4 +380,11 @@ abstract class TreeWalkerAdapter implements TreeWalker
* @return string The SQL.
*/
public function walkPathExpression($pathExpr) {}
/**
* Gets an executor that can be used to execute the result of this walker.
*
* @return AbstractExecutor
*/
public function getExecutor($AST) {}
}
\ No newline at end of file
<?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\Query;
/**
* Represents a chain of tree walkers that modify an AST and finally emit output.
* Only the last walker in the chain can emit output. Any previous walkers can modify
* the AST to influence the final output produced by the last walker.
*
* @author Roman Borschel <roman@code-factory.org>
* @since 2.0
*/
class TreeWalkerChain implements TreeWalker
{
/** The tree walkers. */
private $_walkers = array();
/** The original Query. */
private $_query;
/** The ParserResult of the original query that was produced by the Parser. */
private $_parserResult;
/** The query components of the original query (the "symbol table") that was produced by the Parser. */
private $_queryComponents;
/**
* @inheritdoc
*/
public function __construct($query, $parserResult, array $queryComponents)
{
$this->_query = $query;
$this->_parserResult = $parserResult;
$this->_queryComponents = $queryComponents;
}
/**
* Adds a tree walker to the chain.
*
* @param string $walkerClass The class of the walker to instantiate.
*/
public function addTreeWalker($walkerClass)
{
$this->_walkers[] = new $walkerClass($this->_query, $this->_parserResult, $this->_queryComponents);
}
/**
* Walks down a SelectStatement AST node, thereby generating the appropriate SQL.
*
* @return string The SQL.
*/
public function walkSelectStatement(AST\SelectStatement $AST)
{
foreach ($this->_walkers as $walker) {
$walker->walkSelectStatement($AST);
}
}
/**
* Walks down a SelectClause AST node, thereby generating the appropriate SQL.
*
* @return string The SQL.
*/
public function walkSelectClause($selectClause)
{
foreach ($this->_walkers as $walker) {
$walker->walkSelectClause($selectClause);
}
}
/**
* Walks down a FromClause AST node, thereby generating the appropriate SQL.
*
* @return string The SQL.
*/
public function walkFromClause($fromClause)
{
foreach ($this->_walkers as $walker) {
$walker->walkFromClause($fromClause);
}
}
/**
* Walks down a FunctionNode AST node, thereby generating the appropriate SQL.
*
* @return string The SQL.
*/
public function walkFunction($function)
{
foreach ($this->_walkers as $walker) {
$walker->walkFunction($function);
}
}
/**
* Walks down an OrderByClause AST node, thereby generating the appropriate SQL.
*
* @param OrderByClause
* @return string The SQL.
*/
public function walkOrderByClause($orderByClause)
{
foreach ($this->_walkers as $walker) {
$walker->walkOrderByClause($orderByClause);
}
}
/**
* Walks down an OrderByItem AST node, thereby generating the appropriate SQL.
*
* @param OrderByItem
* @return string The SQL.
*/
public function walkOrderByItem($orderByItem)
{
foreach ($this->_walkers as $walker) {
$walker->walkOrderByItem($orderByItem);
}
}
/**
* Walks down a HavingClause AST node, thereby generating the appropriate SQL.
*
* @param HavingClause
* @return string The SQL.
*/
public function walkHavingClause($havingClause)
{
foreach ($this->_walkers as $walker) {
$walker->walkHavingClause($havingClause);
}
}
/**
* Walks down a JoinVariableDeclaration AST node and creates the corresponding SQL.
*
* @param JoinVariableDeclaration $joinVarDecl
* @return string The SQL.
*/
public function walkJoinVariableDeclaration($joinVarDecl)
{
foreach ($this->_walkers as $walker) {
$walker->walkJoinVariableDeclaration($joinVarDecl);
}
}
/**
* Walks down a SelectExpression AST node and generates the corresponding SQL.
*
* @param SelectExpression $selectExpression
* @return string The SQL.
*/
public function walkSelectExpression($selectExpression)
{
foreach ($this->_walkers as $walker) {
$walker->walkSelectExpression($selectExpression);
}
}
/**
* Walks down a QuantifiedExpression AST node, thereby generating the appropriate SQL.
*
* @param QuantifiedExpression
* @return string The SQL.
*/
public function walkQuantifiedExpression($qExpr)
{
foreach ($this->_walkers as $walker) {
$walker->walkQuantifiedExpression($qExpr);
}
}
/**
* Walks down a Subselect AST node, thereby generating the appropriate SQL.
*
* @param Subselect
* @return string The SQL.
*/
public function walkSubselect($subselect)
{
foreach ($this->_walkers as $walker) {
$walker->walkSubselect($subselect);
}
}
/**
* Walks down a SubselectFromClause AST node, thereby generating the appropriate SQL.
*
* @param SubselectFromClause
* @return string The SQL.
*/
public function walkSubselectFromClause($subselectFromClause)
{
foreach ($this->_walkers as $walker) {
$walker->walkSubselectFromClause($subselectFromClause);
}
}
/**
* Walks down a SimpleSelectClause AST node, thereby generating the appropriate SQL.
*
* @param SimpleSelectClause
* @return string The SQL.
*/
public function walkSimpleSelectClause($simpleSelectClause)
{
foreach ($this->_walkers as $walker) {
$walker->walkSimpleSelectClause($simpleSelectClause);
}
}
/**
* Walks down a SimpleSelectExpression AST node, thereby generating the appropriate SQL.
*
* @param SimpleSelectExpression
* @return string The SQL.
*/
public function walkSimpleSelectExpression($simpleSelectExpression)
{
foreach ($this->_walkers as $walker) {
$walker->walkSimpleSelectExpression($simpleSelectExpression);
}
}
/**
* Walks down an AggregateExpression AST node, thereby generating the appropriate SQL.
*
* @param AggregateExpression
* @return string The SQL.
*/
public function walkAggregateExpression($aggExpression)
{
foreach ($this->_walkers as $walker) {
$walker->walkAggregateExpression($aggExpression);
}
}
/**
* Walks down a GroupByClause AST node, thereby generating the appropriate SQL.
*
* @param GroupByClause
* @return string The SQL.
*/
public function walkGroupByClause($groupByClause)
{
foreach ($this->_walkers as $walker) {
$walker->walkGroupByClause($groupByClause);
}
}
/**
* Walks down a GroupByItem AST node, thereby generating the appropriate SQL.
*
* @param GroupByItem
* @return string The SQL.
*/
public function walkGroupByItem(AST\PathExpression $pathExpr)
{
foreach ($this->_walkers as $walker) {
$walker->walkGroupByItem($pathExpr);
}
}
/**
* Walks down an UpdateStatement AST node, thereby generating the appropriate SQL.
*
* @param UpdateStatement
* @return string The SQL.
*/
public function walkUpdateStatement(AST\UpdateStatement $AST)
{
foreach ($this->_walkers as $walker) {
$walker->walkUpdateStatement($AST);
}
}
/**
* Walks down a DeleteStatement AST node, thereby generating the appropriate SQL.
*
* @param DeleteStatement
* @return string The SQL.
*/
public function walkDeleteStatement(AST\DeleteStatement $AST)
{
foreach ($this->_walkers as $walker) {
$walker->walkDeleteStatement($AST);
}
}
/**
* Walks down a DeleteClause AST node, thereby generating the appropriate SQL.
*
* @param DeleteClause
* @return string The SQL.
*/
public function walkDeleteClause(AST\DeleteClause $deleteClause)
{
foreach ($this->_walkers as $walker) {
$walker->walkDeleteClause($deleteClause);
}
}
/**
* Walks down an UpdateClause AST node, thereby generating the appropriate SQL.
*
* @param UpdateClause
* @return string The SQL.
*/
public function walkUpdateClause($updateClause)
{
foreach ($this->_walkers as $walker) {
$walker->walkUpdateClause($updateClause);
}
}
/**
* Walks down an UpdateItem AST node, thereby generating the appropriate SQL.
*
* @param UpdateItem
* @return string The SQL.
*/
public function walkUpdateItem($updateItem)
{
foreach ($this->_walkers as $walker) {
$walker->walkUpdateItem($updateItem);
}
}
/**
* Walks down a WhereClause AST node, thereby generating the appropriate SQL.
*
* @param WhereClause
* @return string The SQL.
*/
public function walkWhereClause($whereClause)
{
foreach ($this->_walkers as $walker) {
$walker->walkWhereClause($whereClause);
}
}
/**
* Walks down a ConditionalTerm AST node, thereby generating the appropriate SQL.
*
* @param ConditionalTerm
* @return string The SQL.
*/
public function walkConditionalTerm($condTerm)
{
foreach ($this->_walkers as $walker) {
$walker->walkConditionalTerm($condTerm);
}
}
/**
* Walks down a ConditionalFactor AST node, thereby generating the appropriate SQL.
*
* @param ConditionalFactor
* @return string The SQL.
*/
public function walkConditionalFactor($factor)
{
foreach ($this->_walkers as $walker) {
$walker->walkConditionalFactor($factor);
}
}
/**
* Walks down an ExistsExpression AST node, thereby generating the appropriate SQL.
*
* @param ExistsExpression
* @return string The SQL.
*/
public function walkExistsExpression($existsExpr)
{
foreach ($this->_walkers as $walker) {
$walker->walkExistsExpression($existsExpr);
}
}
/**
* Walks down a CollectionMemberExpression AST node, thereby generating the appropriate SQL.
*
* @param CollectionMemberExpression
* @return string The SQL.
*/
public function walkCollectionMemberExpression($collMemberExpr)
{
foreach ($this->_walkers as $walker) {
$walker->walkCollectionMemberExpression($collMemberExpr);
}
}
/**
* Walks down an EmptyCollectionComparisonExpression AST node, thereby generating the appropriate SQL.
*
* @param EmptyCollectionComparisonExpression
* @return string The SQL.
*/
public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr)
{
foreach ($this->_walkers as $walker) {
$walker->walkEmptyCollectionComparisonExpression($emptyCollCompExpr);
}
}
/**
* Walks down a NullComparisonExpression AST node, thereby generating the appropriate SQL.
*
* @param NullComparisonExpression
* @return string The SQL.
*/
public function walkNullComparisonExpression($nullCompExpr)
{
foreach ($this->_walkers as $walker) {
$walker->walkNullComparisonExpression($nullCompExpr);
}
}
/**
* Walks down an InExpression AST node, thereby generating the appropriate SQL.
*
* @param InExpression
* @return string The SQL.
*/
public function walkInExpression($inExpr)
{
foreach ($this->_walkers as $walker) {
$walker->walkInExpression($inExpr);
}
}
/**
* Walks down a literal that represents an AST node, thereby generating the appropriate SQL.
*
* @param mixed
* @return string The SQL.
*/
public function walkLiteral($literal)
{
foreach ($this->_walkers as $walker) {
$walker->walkLiteral($literal);
}
}
/**
* Walks down a BetweenExpression AST node, thereby generating the appropriate SQL.
*
* @param BetweenExpression
* @return string The SQL.
*/
public function walkBetweenExpression($betweenExpr)
{
foreach ($this->_walkers as $walker) {
$walker->walkBetweenExpression($betweenExpr);
}
}
/**
* Walks down a LikeExpression AST node, thereby generating the appropriate SQL.
*
* @param LikeExpression
* @return string The SQL.
*/
public function walkLikeExpression($likeExpr)
{
foreach ($this->_walkers as $walker) {
$walker->walkLikeExpression($likeExpr);
}
}
/**
* Walks down a StateFieldPathExpression AST node, thereby generating the appropriate SQL.
*
* @param StateFieldPathExpression
* @return string The SQL.
*/
public function walkStateFieldPathExpression($stateFieldPathExpression)
{
foreach ($this->_walkers as $walker) {
$walker->walkStateFieldPathExpression($stateFieldPathExpression);
}
}
/**
* Walks down a ComparisonExpression AST node, thereby generating the appropriate SQL.
*
* @param ComparisonExpression
* @return string The SQL.
*/
public function walkComparisonExpression($compExpr)
{
foreach ($this->_walkers as $walker) {
$walker->walkComparisonExpression($compExpr);
}
}
/**
* Walks down an InputParameter AST node, thereby generating the appropriate SQL.
*
* @param InputParameter
* @return string The SQL.
*/
public function walkInputParameter($inputParam)
{
foreach ($this->_walkers as $walker) {
$walker->walkInputParameter($inputParam);
}
}
/**
* Walks down an ArithmeticExpression AST node, thereby generating the appropriate SQL.
*
* @param ArithmeticExpression
* @return string The SQL.
*/
public function walkArithmeticExpression($arithmeticExpr)
{
foreach ($this->_walkers as $walker) {
$walker->walkArithmeticExpression($arithmeticExpr);
}
}
/**
* Walks down an ArithmeticTerm AST node, thereby generating the appropriate SQL.
*
* @param mixed
* @return string The SQL.
*/
public function walkArithmeticTerm($term)
{
foreach ($this->_walkers as $walker) {
$walker->walkArithmeticTerm($term);
}
}
/**
* Walks down a StringPrimary that represents an AST node, thereby generating the appropriate SQL.
*
* @param mixed
* @return string The SQL.
*/
public function walkStringPrimary($stringPrimary)
{
foreach ($this->_walkers as $walker) {
$walker->walkStringPrimary($stringPrimary);
}
}
/**
* Walks down an ArithmeticFactor that represents an AST node, thereby generating the appropriate SQL.
*
* @param mixed
* @return string The SQL.
*/
public function walkArithmeticFactor($factor)
{
foreach ($this->_walkers as $walker) {
$walker->walkArithmeticFactor($factor);
}
}
/**
* Walks down an SimpleArithmeticExpression AST node, thereby generating the appropriate SQL.
*
* @param SimpleArithmeticExpression
* @return string The SQL.
*/
public function walkSimpleArithmeticExpression($simpleArithmeticExpr)
{
foreach ($this->_walkers as $walker) {
$walker->walkSimpleArithmeticExpression($simpleArithmeticExpr);
}
}
/**
* Walks down an PathExpression AST node, thereby generating the appropriate SQL.
*
* @param mixed
* @return string The SQL.
*/
public function walkPathExpression($pathExpr)
{
foreach ($this->_walkers as $walker) {
$walker->walkPathExpression($pathExpr);
}
}
/**
* Gets an executor that can be used to execute the result of this walker.
*
* @return AbstractExecutor
*/
public function getExecutor($AST)
{}
}
\ No newline at end of file
......@@ -158,13 +158,6 @@ class UnitOfWork implements PropertyChangedListener
*/
private $_collectionDeletions = array();
/**
* All pending collection creations.
*
* @var array
*/
//private $_collectionCreations = array();
/**
* All pending collection updates.
*
......@@ -304,7 +297,6 @@ class UnitOfWork implements PropertyChangedListener
$this->getCollectionPersister($collectionToUpdate->getMapping())
->update($collectionToUpdate);
}
//TODO: collection recreations (insertions of complete collections)
// Entity deletions come last and need to be in reverse commit order
if ($this->_entityDeletions) {
......@@ -377,7 +369,7 @@ class UnitOfWork implements PropertyChangedListener
public function computeChangeSets()
{
// Compute changes for INSERTed entities first. This must always happen.
foreach ($this->_entityInsertions as $entity) {
foreach ($this->_entityInsertions as $oid => $entity) {
$class = $this->_em->getClassMetadata(get_class($entity));
$this->_computeEntityChanges($class, $entity);
// Look for changes in associations of the entity
......@@ -403,9 +395,9 @@ class UnitOfWork implements PropertyChangedListener
$this->_scheduledForDirtyCheck[$className] : $entities;
foreach ($entitiesToProcess as $entity) {
// Only MANAGED entities that are NOT INSERTED are processed here.
// Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here.
$oid = spl_object_hash($entity);
if (isset($this->_entityStates[$oid]) && ! isset($entityInsertions[$oid])) {
if ( ! isset($this->_entityInsertions[$oid]) && isset($this->_entityStates[$oid])) {
$this->_computeEntityChanges($class, $entity);
// Look for changes in associations of the entity
foreach ($class->associationMappings as $assoc) {
......@@ -581,7 +573,6 @@ class UnitOfWork implements PropertyChangedListener
$data[$name] = $refProp->getValue($entry);
$changeSet[$name] = array(null, $data[$name]);
if (isset($targetClass->associationMappings[$name])) {
//TODO: Prevent infinite recursion
$this->_computeAssociationChanges($targetClass->associationMappings[$name], $data[$name]);
}
}
......@@ -600,7 +591,7 @@ class UnitOfWork implements PropertyChangedListener
}
/**
* INTERNAL, EXPERIMENTAL:
* INTERNAL:
* Computes the changeset of an individual entity, independently of the
* computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit().
*
......@@ -810,7 +801,7 @@ class UnitOfWork implements PropertyChangedListener
if ( ! $this->_commitOrderCalculator->hasClass($targetClass->name)) {
$this->_commitOrderCalculator->addClass($targetClass);
}
// add dependency
// add dependency ($targetClass before $class)
$this->_commitOrderCalculator->addDependency($targetClass, $class);
}
}
......@@ -823,7 +814,7 @@ class UnitOfWork implements PropertyChangedListener
* Schedules an entity for insertion into the database.
* If the entity already has an identifier, it will be added to the identity map.
*
* @param object $entity
* @param object $entity The entity to schedule for insertion.
*/
public function scheduleForInsert($entity)
{
......@@ -840,13 +831,14 @@ class UnitOfWork implements PropertyChangedListener
}
$this->_entityInsertions[$oid] = $entity;
if (isset($this->_entityIdentifiers[$oid])) {
$this->addToIdentityMap($entity);
}
}
/**
* Checks whether an entity is registered as new on this unit of work.
* Checks whether an entity is scheduled for insertion.
*
* @param object $entity
* @return boolean
......@@ -857,9 +849,9 @@ class UnitOfWork implements PropertyChangedListener
}
/**
* Registers a dirty entity.
* Schedules an entity for being updated.
*
* @param object $entity
* @param object $entity The entity to schedule for being updated.
*/
public function scheduleForUpdate($entity)
{
......@@ -880,7 +872,7 @@ class UnitOfWork implements PropertyChangedListener
/**
* INTERNAL:
* Schedules an extra update that will be executed immediately after the
* regular entity updates.
* regular entity updates within the currently running commit cycle.
*
* @param $entity
* @param $changeset
......@@ -1588,7 +1580,6 @@ class UnitOfWork implements PropertyChangedListener
$this->_entityUpdates =
$this->_entityDeletions =
$this->_collectionDeletions =
//$this->_collectionCreations =
$this->_collectionUpdates =
$this->_orphanRemovals = array();
$this->_commitOrderCalculator->clear();
......@@ -1778,7 +1769,7 @@ class UnitOfWork implements PropertyChangedListener
*/
public function tryGetById($id, $rootClassName)
{
$idHash = implode(' ', (array)$id);
$idHash = implode(' ', (array) $id);
if (isset($this->_identityMap[$rootClassName][$idHash])) {
return $this->_identityMap[$rootClassName][$idHash];
}
......@@ -1924,23 +1915,4 @@ class UnitOfWork implements PropertyChangedListener
$this->_entityUpdates[$oid] = $entity;
}
}
public function dump()
{
var_dump($this->_identityMap);
var_dump($this->_entityIdentifiers);
var_dump($this->_originalEntityData);
var_dump($this->_entityChangeSets);
var_dump($this->_entityStates);
var_dump($this->_scheduledForDirtyCheck);
var_dump($this->_entityInsertions);
var_dump($this->_entityUpdates);
var_dump($this->_entityDeletions);
var_dump($this->_collectionDeletions);
//$this->_collectionCreations =
var_dump($this->_collectionUpdates);
var_dump($this->_orphanRemovals);
//var_dump($this->_commitOrderCalculator->clear();
}
}
......@@ -23,7 +23,6 @@ class AllTests
$suite->addTestSuite('Doctrine\Tests\ORM\EntityManagerTest');
$suite->addTestSuite('Doctrine\Tests\ORM\CommitOrderCalculatorTest');
$suite->addTestSuite('Doctrine\Tests\ORM\QueryBuilderTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Proxy\ProxyClassGeneratorTest');
$suite->addTest(Query\AllTests::suite());
$suite->addTest(Hydration\AllTests::suite());
$suite->addTest(Entity\AllTests::suite());
......@@ -32,6 +31,7 @@ class AllTests
$suite->addTest(Mapping\AllTests::suite());
$suite->addTest(Functional\AllTests::suite());
$suite->addTest(Id\AllTests::suite());
$suite->addTest(Proxy\AllTests::suite());
return $suite;
}
......
<?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\Tests\ORM\Functional;
use Doctrine\ORM\Query;
require_once __DIR__ . '/../../TestInit.php';
/**
* Test case for custom AST walking and modification.
*
* @author Roman Borschel <roman@code-factory.org>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://www.doctrine-project.org
* @since 2.0
*/
class CustomTreeWalkersTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp() {
$this->useModelSet('cms');
parent::setUp();
}
public function testSupportsQueriesWithoutWhere()
{
$q = $this->_em->createQuery('select u from Doctrine\Tests\Models\CMS\CmsUser u where u.name = :name or u.name = :otherName');
$q->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\Tests\ORM\Functional\CustomTreeWalker'));
$this->assertEquals("SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE (c0_.name = ? OR c0_.name = ?) AND c0_.id = 1", $q->getSql());
$q->setDql('select u from Doctrine\Tests\Models\CMS\CmsUser u');
$this->assertEquals("SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE c0_.id = 1", $q->getSql());
$q->setDql('select u from Doctrine\Tests\Models\CMS\CmsUser u where u.name = :name');
$this->assertEquals("SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE c0_.name = ? AND c0_.id = 1", $q->getSql());
}
}
class CustomTreeWalker extends Query\TreeWalkerAdapter
{
public function walkSelectStatement(Query\AST\SelectStatement $selectStatement)
{
// Get the DQL aliases of all the classes we want to modify
$dqlAliases = array();
foreach ($this->_getQueryComponents() as $dqlAlias => $comp) {
// Hard-coded check just for demonstration: We want to modify the query if
// it involves the CmsUser class.
if ($comp['metadata']->name == 'Doctrine\Tests\Models\CMS\CmsUser') {
$dqlAliases[] = $dqlAlias;
}
}
// Create our conditions for all involved classes
$factors = array();
foreach ($dqlAliases as $alias) {
$pathExpr = new Query\AST\PathExpression(Query\AST\PathExpression::TYPE_STATE_FIELD, $alias, array('id'));
$pathExpr->type = Query\AST\PathExpression::TYPE_STATE_FIELD;
$comparisonExpr = new Query\AST\ComparisonExpression($pathExpr, '=', 1);
$condPrimary = new Query\AST\ConditionalPrimary;
$condPrimary->simpleConditionalExpression = $comparisonExpr;
$factor = new Query\AST\ConditionalFactor($condPrimary);
$factors[] = $factor;
}
if ($selectStatement->whereClause !== null) {
// There is already a WHERE clause, so append the conditions
$existingTerms = $selectStatement->whereClause->conditionalExpression->conditionalTerms;
if (count($existingTerms) > 1) {
// More than one term, so we need to wrap all these terms in a single root term
// i.e: "WHERE u.name = :foo or u.other = :bar" => "WHERE (u.name = :foo or u.other = :bar) AND <our condition>"
$primary = new Query\AST\ConditionalPrimary;
$primary->conditionalExpression = new Query\AST\ConditionalExpression($existingTerms);
$existingFactor = new Query\AST\ConditionalFactor($primary);
$term = new Query\AST\ConditionalTerm(array_merge(array($existingFactor), $factors));
$selectStatement->whereClause->conditionalExpression->conditionalTerms = array($term);
} else {
// Just one term so we can simply append our factors to that term
$singleTerm = $selectStatement->whereClause->conditionalExpression->conditionalTerms[0];
$singleTerm->conditionalFactors = array_merge($singleTerm->conditionalFactors, $factors);
$selectStatement->whereClause->conditionalExpression->conditionalTerms = array($singleTerm);
}
} else {
// Create a new WHERE clause with our factors
$term = new Query\AST\ConditionalTerm($factors);
$condExpr = new Query\AST\ConditionalExpression(array($term));
$whereClause = new Query\AST\WhereClause($condExpr);
$selectStatement->whereClause = $whereClause;
}
}
}
......@@ -58,6 +58,10 @@ class OneToOneSelfReferentialAssociationTest extends \Doctrine\Tests\OrmFunction
$this->assertLoadingOfAssociation($customer);
}
/**
* @group mine
* @return unknown_type
*/
public function testLazyLoadsAssociation()
{
$this->_createFixture();
......@@ -66,7 +70,7 @@ class OneToOneSelfReferentialAssociationTest extends \Doctrine\Tests\OrmFunction
$metadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceCustomer');
$metadata->getAssociationMapping('mentor')->fetchMode = AssociationMapping::FETCH_LAZY;
$query = $this->_em->createQuery('select c from Doctrine\Tests\Models\ECommerce\ECommerceCustomer c');
$query = $this->_em->createQuery("select c from Doctrine\Tests\Models\ECommerce\ECommerceCustomer c where c.name='Luke Skywalker'");
$result = $query->getResult();
$customer = $result[0];
$this->assertLoadingOfAssociation($customer);
......
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://schemas.doctrine-project.org/orm/doctrine-mapping"
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://schemas.doctrine-project.org/orm/doctrine-mapping
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
/Users/robo/dev/php/Doctrine/doctrine-mapping.xsd">
<entity name="XmlMappingTest\User" table="cms_users">
......
<?php
namespace Doctrine\Tests\ORM\Proxy;
if (!defined('PHPUnit_MAIN_METHOD')) {
define('PHPUnit_MAIN_METHOD', 'Orm_Proxy_AllTests::main');
}
require_once __DIR__ . '/../../TestInit.php';
class AllTests
{
public static function main()
{
\PHPUnit_TextUI_TestRunner::run(self::suite());
}
public static function suite()
{
$suite = new \Doctrine\Tests\DoctrineTestSuite('Doctrine Orm Proxy');
$suite->addTestSuite('Doctrine\Tests\ORM\Proxy\ProxyClassGeneratorTest');
return $suite;
}
}
if (PHPUnit_MAIN_METHOD == 'Orm_Proxy_AllTests::main') {
AllTests::main();
}
\ No newline at end of file
......@@ -109,14 +109,16 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase
$proxy->getDescription();
}
/**
* @expectedException PHPUnit_Framework_Error
*/
public function testReferenceProxyRespectsMethodsParametersTypeHinting()
{
$proxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature');
$proxy = new $proxyClass($this->_getMockPersister(), null);
$proxy->setProduct(array('invalid parameter'));
$method = new \ReflectionMethod(get_class($proxy), 'setProduct');
$params = $method->getParameters();
$this->assertEquals(1, count($params));
$this->assertEquals('Doctrine\Tests\Models\ECommerce\ECommerceProduct', $params[0]->getClass()->getName());
}
protected function _getMockPersister()
......
......@@ -47,8 +47,8 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
}
$parser = new \Doctrine\ORM\Query\Parser($query);
// We do NOT test SQL construction here. That only unnecessarily slows down the tests!
$parser->setTreeWalker(new \Doctrine\Tests\Mocks\MockTreeWalker(null, null, array()));
// We do NOT test SQL output here. That only unnecessarily slows down the tests!
$parser->setCustomOutputTreeWalker('Doctrine\Tests\Mocks\MockTreeWalker');
return $parser->parse();
}
......@@ -340,6 +340,12 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
$this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.phonenumbers IS EMPTY');
}
public function testSingleValuedAssociationFieldInWhere()
{
$this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.address = ?1');
$this->assertValidDql('SELECT p FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p WHERE p.user = ?1');
}
/**
* This checks for invalid attempt to hydrate a proxy. It should throw an exception
*
......
......@@ -382,4 +382,17 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
"SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE (SELECT COUNT(c1_.user_id) FROM cms_phonenumbers c1_ WHERE c1_.user_id = c0_.id) > 0"
);
}
/* Not yet implemented, needs more thought
public function testSingleValuedAssociationFieldInWhere()
{
$this->assertSqlGeneration(
"SELECT p FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p WHERE p.user = ?1",
"SELECT c0_.phonenumber AS phonenumber0 FROM cms_phonenumbers c0_ WHERE c0_.user_id = ?"
);
$this->assertSqlGeneration(
"SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.address = ?1",
"SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE c0_.id = (SELECT c1_.user_id FROM cms_addresses c1_ WHERE c1_.id = ?)"
);
}*/
}
......@@ -21,8 +21,8 @@
namespace Doctrine\Tests\ORM;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder,
Doctrine\ORM\Query\Expr;
require_once __DIR__ . '/../TestInit.php';
......@@ -36,8 +36,6 @@ require_once __DIR__ . '/../TestInit.php';
* @link http://www.phpdoctrine.org
* @since 2.0
* @version $Revision$
* @todo Remove QueryBuilder::create. Use constructor in tests instead. Users will use
* $em->createQueryBuilder().
*/
class QueryBuilderTest extends \Doctrine\Tests\OrmTestCase
{
......
......@@ -85,12 +85,14 @@ class OrmFunctionalTestCase extends OrmTestCase
$conn->executeUpdate('DELETE FROM ecommerce_features');
$conn->executeUpdate('DELETE FROM ecommerce_products');
$conn->executeUpdate('DELETE FROM ecommerce_shippings');
$conn->executeUpdate('UPDATE ecommerce_categories SET parent_id = NULL');
$conn->executeUpdate('DELETE FROM ecommerce_categories');
}
if (isset($this->_usedModelSets['company'])) {
$conn->executeUpdate('DELETE FROM company_persons_friends');
$conn->executeUpdate('DELETE FROM company_managers');
$conn->executeUpdate('DELETE FROM company_employees');
$conn->executeUpdate('UPDATE company_persons SET spouse_id = NULL');
$conn->executeUpdate('DELETE FROM company_persons');
}
if (isset($this->_usedModelSets['generic'])) {
......@@ -155,9 +157,8 @@ class OrmFunctionalTestCase extends OrmTestCase
$config = new \Doctrine\ORM\Configuration();
$config->setMetadataCacheImpl(self::$_metadataCacheImpl);
$config->setQueryCacheImpl(self::$_queryCacheImpl);
$eventManager = new \Doctrine\Common\EventManager();
$conn = $this->sharedFixture['conn'];
return \Doctrine\ORM\EntityManager::create($conn, $config, $eventManager);
return \Doctrine\ORM\EntityManager::create($conn, $config);
}
}
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