package.xml0000644000175100017510000012253712143374610013314 0ustar beberleibeberlei DoctrineORM pear.doctrine-project.org Doctrine Object Relational Mapper The Doctrine ORM package is the primary package containing the object relational mapper. Jonathan H. Wage jwage jonwage@gmail.com yes Guilherme Blanco guilhermeblanco guilhermeblanco@gmail.com yes Roman Borschel romanb roman@code-factory.org yes Benjamin Eberlei beberlei kontakt@beberlei.de yes 2013-05-11 2.3.3 2.3.3 stable stable LGPL - 5.3.0 1.6.0 1.6.1 DoctrineCommon pear.doctrine-project.org 2.2.0beta1 DoctrineDBAL pear.doctrine-project.org 2.2.0beta1 Console pear.symfony.com 2.0.0 Yaml pear.symfony.com 2.0.0 2.3.3 2.3.3 stable stable 2013-05-11 LGPL - DoctrineORM-2.3.3/bin/doctrine0000755000175100017510000000006312143374607016265 0ustar beberleibeberlei#!/usr/bin/env php . */ require_once 'Doctrine/Common/ClassLoader.php'; $classLoader = new \Doctrine\Common\ClassLoader('Doctrine'); $classLoader->register(); $classLoader = new \Doctrine\Common\ClassLoader('Symfony'); $classLoader->register(); $configFile = getcwd() . DIRECTORY_SEPARATOR . 'cli-config.php'; $helperSet = null; if (file_exists($configFile)) { if ( ! is_readable($configFile)) { trigger_error( 'Configuration file [' . $configFile . '] does not have read permission.', E_ERROR ); } require $configFile; foreach ($GLOBALS as $helperSetCandidate) { if ($helperSetCandidate instanceof \Symfony\Component\Console\Helper\HelperSet) { $helperSet = $helperSetCandidate; break; } } } $helperSet = ($helperSet) ?: new \Symfony\Component\Console\Helper\HelperSet(); \Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet); DoctrineORM-2.3.3/bin/doctrine.bat0000644000175100017510000000033412143374607017030 0ustar beberleibeberlei@echo off if "%PHPBIN%" == "" set PHPBIN=@php_bin@ if not exist "%PHPBIN%" if "%PHP_PEAR_PHP_BIN%" neq "" goto USE_PEAR_PATH GOTO RUN :USE_PEAR_PATH set PHPBIN=%PHP_PEAR_PHP_BIN% :RUN "%PHPBIN%" "@bin_dir@\doctrine" %* DoctrineORM-2.3.3/Doctrine/ORM/AbstractQuery.php0000644000175100017510000005741412143374607021502 0ustar beberleibeberlei. */ namespace Doctrine\ORM; use Doctrine\Common\Util\ClassUtils; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\ORM\Query\QueryException; /** * Base contract for ORM queries. Base class for Query and NativeQuery. * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Konsta Vesterinen */ abstract class AbstractQuery { /* Hydration mode constants */ /** * Hydrates an object graph. This is the default behavior. */ const HYDRATE_OBJECT = 1; /** * Hydrates an array graph. */ const HYDRATE_ARRAY = 2; /** * Hydrates a flat, rectangular result set with scalar values. */ const HYDRATE_SCALAR = 3; /** * Hydrates a single scalar value. */ const HYDRATE_SINGLE_SCALAR = 4; /** * Very simple object hydrator (optimized for performance). */ const HYDRATE_SIMPLEOBJECT = 5; /** * @var \Doctrine\Common\Collections\ArrayCollection The parameter map of this query. */ protected $parameters; /** * @var ResultSetMapping The user-specified ResultSetMapping to use. */ protected $_resultSetMapping; /** * @var \Doctrine\ORM\EntityManager The entity manager used by this query object. */ protected $_em; /** * @var array The map of query hints. */ protected $_hints = array(); /** * @var integer The hydration mode. */ protected $_hydrationMode = self::HYDRATE_OBJECT; /** * @param \Doctrine\DBAL\Cache\QueryCacheProfile */ protected $_queryCacheProfile; /** * @var boolean Boolean value that indicates whether or not expire the result cache. */ protected $_expireResultCache = false; /** * @param \Doctrine\DBAL\Cache\QueryCacheProfile */ protected $_hydrationCacheProfile; /** * Initializes a new instance of a class derived from AbstractQuery. * * @param \Doctrine\ORM\EntityManager $entityManager */ public function __construct(EntityManager $em) { $this->_em = $em; $this->parameters = new ArrayCollection(); } /** * Gets the SQL query that corresponds to this query object. * The returned SQL syntax depends on the connection driver that is used * by this query object at the time of this method call. * * @return string SQL query */ abstract public function getSQL(); /** * Retrieves the associated EntityManager of this Query instance. * * @return \Doctrine\ORM\EntityManager */ public function getEntityManager() { return $this->_em; } /** * Frees the resources used by the query object. * * Resets Parameters, Parameter Types and Query Hints. * * @return void */ public function free() { $this->parameters = new ArrayCollection(); $this->_hints = array(); } /** * Get all defined parameters. * * @return \Doctrine\Common\Collections\ArrayCollection The defined query parameters. */ public function getParameters() { return $this->parameters; } /** * Gets a query parameter. * * @param mixed $key The key (index or name) of the bound parameter. * * @return mixed The value of the bound parameter. */ public function getParameter($key) { $filteredParameters = $this->parameters->filter( function ($parameter) use ($key) { // Must not be identical because of string to integer conversion return ($key == $parameter->getName()); } ); return count($filteredParameters) ? $filteredParameters->first() : null; } /** * Sets a collection of query parameters. * * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters * * @return \Doctrine\ORM\AbstractQuery This query instance. */ public function setParameters($parameters) { // BC compatibility with 2.3- if (is_array($parameters)) { $parameterCollection = new ArrayCollection(); foreach ($parameters as $key => $value) { $parameter = new Query\Parameter($key, $value); $parameterCollection->add($parameter); } $parameters = $parameterCollection; } $this->parameters = $parameters; return $this; } /** * Sets a query parameter. * * @param string|integer $key The parameter position or name. * @param mixed $value The parameter value. * @param string $type The parameter type. If specified, the given value will be run through * the type conversion of this type. This is usually not needed for * strings and numeric types. * * @return \Doctrine\ORM\AbstractQuery This query instance. */ public function setParameter($key, $value, $type = null) { $filteredParameters = $this->parameters->filter( function ($parameter) use ($key) { // Must not be identical because of string to integer conversion return ($key == $parameter->getName()); } ); if (count($filteredParameters)) { $parameter = $filteredParameters->first(); $parameter->setValue($value, $type); return $this; } $parameter = new Query\Parameter($key, $value, $type); $this->parameters->add($parameter); return $this; } /** * Process an individual parameter value * * @param mixed $value * @return array */ public function processParameterValue($value) { switch (true) { case is_array($value): foreach ($value as $key => $paramValue) { $paramValue = $this->processParameterValue($paramValue); $value[$key] = is_array($paramValue) ? $paramValue[key($paramValue)] : $paramValue; } return $value; case is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value)): return $this->convertObjectParameterToScalarValue($value); default: return $value; } } private function convertObjectParameterToScalarValue($value) { $class = $this->_em->getClassMetadata(get_class($value)); if ($class->isIdentifierComposite) { throw new \InvalidArgumentException( "Binding an entity with a composite primary key to a query is not supported. " . "You should split the parameter into the explicit fields and bind them seperately." ); } $values = ($this->_em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED) ? $this->_em->getUnitOfWork()->getEntityIdentifier($value) : $class->getIdentifierValues($value); $value = $values[$class->getSingleIdentifierFieldName()]; if (null === $value) { throw new \InvalidArgumentException( "Binding entities to query parameters only allowed for entities that have an identifier." ); } return $value; } /** * Sets the ResultSetMapping that should be used for hydration. * * @param ResultSetMapping $rsm * @return \Doctrine\ORM\AbstractQuery */ public function setResultSetMapping(Query\ResultSetMapping $rsm) { $this->_resultSetMapping = $rsm; return $this; } /** * Set a cache profile for hydration caching. * * If no result cache driver is set in the QueryCacheProfile, the default * result cache driver is used from the configuration. * * Important: Hydration caching does NOT register entities in the * UnitOfWork when retrieved from the cache. Never use result cached * entities for requests that also flush the EntityManager. If you want * some form of caching with UnitOfWork registration you should use * {@see AbstractQuery::setResultCacheProfile()}. * * @example * $lifetime = 100; * $resultKey = "abc"; * $query->setHydrationCacheProfile(new QueryCacheProfile()); * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey)); * * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile * @return \Doctrine\ORM\AbstractQuery */ public function setHydrationCacheProfile(QueryCacheProfile $profile = null) { if ( ! $profile->getResultCacheDriver()) { $resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl(); $profile = $profile->setResultCacheDriver($resultCacheDriver); } $this->_hydrationCacheProfile = $profile; return $this; } /** * @return \Doctrine\DBAL\Cache\QueryCacheProfile */ public function getHydrationCacheProfile() { return $this->_hydrationCacheProfile; } /** * Set a cache profile for the result cache. * * If no result cache driver is set in the QueryCacheProfile, the default * result cache driver is used from the configuration. * * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile * @return \Doctrine\ORM\AbstractQuery */ public function setResultCacheProfile(QueryCacheProfile $profile = null) { if ( ! $profile->getResultCacheDriver()) { $resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl(); $profile = $profile->setResultCacheDriver($resultCacheDriver); } $this->_queryCacheProfile = $profile; return $this; } /** * Defines a cache driver to be used for caching result sets and implictly enables caching. * * @param \Doctrine\Common\Cache\Cache $driver Cache driver * @return \Doctrine\ORM\AbstractQuery */ public function setResultCacheDriver($resultCacheDriver = null) { if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) { throw ORMException::invalidResultCacheDriver(); } $this->_queryCacheProfile = $this->_queryCacheProfile ? $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver) : new QueryCacheProfile(0, null, $resultCacheDriver); return $this; } /** * Returns the cache driver used for caching result sets. * * @deprecated * @return \Doctrine\Common\Cache\Cache Cache driver */ public function getResultCacheDriver() { if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) { return $this->_queryCacheProfile->getResultCacheDriver(); } return $this->_em->getConfiguration()->getResultCacheImpl(); } /** * Set whether or not to cache the results of this query and if so, for * how long and which ID to use for the cache entry. * * @param boolean $bool * @param integer $lifetime * @param string $resultCacheId * @return \Doctrine\ORM\AbstractQuery This query instance. */ public function useResultCache($bool, $lifetime = null, $resultCacheId = null) { if ($bool) { $this->setResultCacheLifetime($lifetime); $this->setResultCacheId($resultCacheId); return $this; } $this->_queryCacheProfile = null; return $this; } /** * Defines how long the result cache will be active before expire. * * @param integer $lifetime How long the cache entry is valid. * @return \Doctrine\ORM\AbstractQuery This query instance. */ public function setResultCacheLifetime($lifetime) { $lifetime = ($lifetime !== null) ? (int) $lifetime : 0; $this->_queryCacheProfile = $this->_queryCacheProfile ? $this->_queryCacheProfile->setLifetime($lifetime) : new QueryCacheProfile($lifetime, null, $this->_em->getConfiguration()->getResultCacheImpl()); return $this; } /** * Retrieves the lifetime of resultset cache. * * @deprecated * @return integer */ public function getResultCacheLifetime() { return $this->_queryCacheProfile ? $this->_queryCacheProfile->getLifetime() : 0; } /** * Defines if the result cache is active or not. * * @param boolean $expire Whether or not to force resultset cache expiration. * @return \Doctrine\ORM\AbstractQuery This query instance. */ public function expireResultCache($expire = true) { $this->_expireResultCache = $expire; return $this; } /** * Retrieves if the resultset cache is active or not. * * @return boolean */ public function getExpireResultCache() { return $this->_expireResultCache; } /** * @return QueryCacheProfile */ public function getQueryCacheProfile() { return $this->_queryCacheProfile; } /** * Change the default fetch mode of an association for this query. * * $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY * * @param string $class * @param string $assocName * @param int $fetchMode * @return AbstractQuery */ public function setFetchMode($class, $assocName, $fetchMode) { if ($fetchMode !== Mapping\ClassMetadata::FETCH_EAGER) { $fetchMode = Mapping\ClassMetadata::FETCH_LAZY; } $this->_hints['fetchMode'][$class][$assocName] = $fetchMode; return $this; } /** * Defines the processing mode to be used during hydration / result set transformation. * * @param integer $hydrationMode Doctrine processing mode to be used during hydration process. * One of the Query::HYDRATE_* constants. * @return \Doctrine\ORM\AbstractQuery This query instance. */ public function setHydrationMode($hydrationMode) { $this->_hydrationMode = $hydrationMode; return $this; } /** * Gets the hydration mode currently used by the query. * * @return integer */ public function getHydrationMode() { return $this->_hydrationMode; } /** * Gets the list of results for the query. * * Alias for execute(null, $hydrationMode = HYDRATE_OBJECT). * * @return array */ public function getResult($hydrationMode = self::HYDRATE_OBJECT) { return $this->execute(null, $hydrationMode); } /** * Gets the array of results for the query. * * Alias for execute(null, HYDRATE_ARRAY). * * @return array */ public function getArrayResult() { return $this->execute(null, self::HYDRATE_ARRAY); } /** * Gets the scalar results for the query. * * Alias for execute(null, HYDRATE_SCALAR). * * @return array */ public function getScalarResult() { return $this->execute(null, self::HYDRATE_SCALAR); } /** * Get exactly one result or null. * * @throws NonUniqueResultException * @param int $hydrationMode * @return mixed */ public function getOneOrNullResult($hydrationMode = null) { $result = $this->execute(null, $hydrationMode); if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) { return null; } if ( ! is_array($result)) { return $result; } if (count($result) > 1) { throw new NonUniqueResultException; } return array_shift($result); } /** * Gets the single result of the query. * * Enforces the presence as well as the uniqueness of the result. * * If the result is not unique, a NonUniqueResultException is thrown. * If there is no result, a NoResultException is thrown. * * @param integer $hydrationMode * @return mixed * @throws NonUniqueResultException If the query result is not unique. * @throws NoResultException If the query returned no result. */ public function getSingleResult($hydrationMode = null) { $result = $this->execute(null, $hydrationMode); if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) { throw new NoResultException; } if ( ! is_array($result)) { return $result; } if (count($result) > 1) { throw new NonUniqueResultException; } return array_shift($result); } /** * Gets the single scalar result of the query. * * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR). * * @return mixed * @throws QueryException If the query result is not unique. */ public function getSingleScalarResult() { return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR); } /** * Sets a query hint. If the hint name is not recognized, it is silently ignored. * * @param string $name The name of the hint. * @param mixed $value The value of the hint. * @return \Doctrine\ORM\AbstractQuery */ public function setHint($name, $value) { $this->_hints[$name] = $value; return $this; } /** * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned. * * @param string $name The name of the hint. * @return mixed The value of the hint or FALSE, if the hint name is not recognized. */ public function getHint($name) { return isset($this->_hints[$name]) ? $this->_hints[$name] : false; } /** * Return the key value map of query hints that are currently set. * * @return array */ public function getHints() { return $this->_hints; } /** * Executes the query and returns an IterableResult that can be used to incrementally * iterate over the result. * * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters The query parameters. * @param integer $hydrationMode The hydration mode to use. * @return \Doctrine\ORM\Internal\Hydration\IterableResult */ public function iterate($parameters = null, $hydrationMode = null) { if ($hydrationMode !== null) { $this->setHydrationMode($hydrationMode); } if ( ! empty($parameters)) { $this->setParameters($parameters); } $stmt = $this->_doExecute(); return $this->_em->newHydrator($this->_hydrationMode)->iterate( $stmt, $this->_resultSetMapping, $this->_hints ); } /** * Executes the query. * * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters Query parameters. * @param integer $hydrationMode Processing mode to be used during the hydration process. * @return mixed */ public function execute($parameters = null, $hydrationMode = null) { if ($hydrationMode !== null) { $this->setHydrationMode($hydrationMode); } if ( ! empty($parameters)) { $this->setParameters($parameters); } $setCacheEntry = function() {}; if ($this->_hydrationCacheProfile !== null) { list($cacheKey, $realCacheKey) = $this->getHydrationCacheId(); $queryCacheProfile = $this->getHydrationCacheProfile(); $cache = $queryCacheProfile->getResultCacheDriver(); $result = $cache->fetch($cacheKey); if (isset($result[$realCacheKey])) { return $result[$realCacheKey]; } if ( ! $result) { $result = array(); } $setCacheEntry = function($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) { $result[$realCacheKey] = $data; $cache->save($cacheKey, $result, $queryCacheProfile->getLifetime()); }; } $stmt = $this->_doExecute(); if (is_numeric($stmt)) { $setCacheEntry($stmt); return $stmt; } $data = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll( $stmt, $this->_resultSetMapping, $this->_hints ); $setCacheEntry($data); return $data; } /** * Get the result cache id to use to store the result set cache entry. * Will return the configured id if it exists otherwise a hash will be * automatically generated for you. * * @return array ($key, $hash) */ protected function getHydrationCacheId() { $parameters = array(); foreach ($this->getParameters() as $parameter) { $parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue()); } $sql = $this->getSQL(); $queryCacheProfile = $this->getHydrationCacheProfile(); $hints = $this->getHints(); $hints['hydrationMode'] = $this->getHydrationMode(); ksort($hints); return $queryCacheProfile->generateCacheKeys($sql, $parameters, $hints); } /** * Set the result cache id to use to store the result set cache entry. * If this is not explicitly set by the developer then a hash is automatically * generated for you. * * @param string $id * @return \Doctrine\ORM\AbstractQuery This query instance. */ public function setResultCacheId($id) { $this->_queryCacheProfile = $this->_queryCacheProfile ? $this->_queryCacheProfile->setCacheKey($id) : new QueryCacheProfile(0, $id, $this->_em->getConfiguration()->getResultCacheImpl()); return $this; } /** * Get the result cache id to use to store the result set cache entry if set. * * @deprecated * @return string */ public function getResultCacheId() { return $this->_queryCacheProfile ? $this->_queryCacheProfile->getCacheKey() : null; } /** * Executes the query and returns a the resulting Statement object. * * @return \Doctrine\DBAL\Driver\Statement The executed database statement that holds the results. */ abstract protected function _doExecute(); /** * Cleanup Query resource when clone is called. * * @return void */ public function __clone() { $this->parameters = new ArrayCollection(); $this->_hints = array(); } } DoctrineORM-2.3.3/Doctrine/ORM/Configuration.php0000644000175100017510000005104612143374607021513 0ustar beberleibeberlei. */ namespace Doctrine\ORM; use Doctrine\Common\Cache\Cache, Doctrine\Common\Cache\ArrayCache, Doctrine\Common\Annotations\AnnotationRegistry, Doctrine\Common\Annotations\AnnotationReader, Doctrine\Common\Persistence\Mapping\Driver\MappingDriver, Doctrine\ORM\Mapping\Driver\AnnotationDriver, Doctrine\ORM\Mapping\QuoteStrategy, Doctrine\ORM\Mapping\DefaultQuoteStrategy, Doctrine\ORM\Mapping\NamingStrategy, Doctrine\ORM\Mapping\DefaultNamingStrategy, Doctrine\Common\Annotations\SimpleAnnotationReader, Doctrine\Common\Annotations\CachedReader; /** * Configuration container for all configuration options of Doctrine. * It combines all configuration options from DBAL & ORM. * * @since 2.0 * @internal When adding a new configuration option just write a getter/setter pair. * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Configuration extends \Doctrine\DBAL\Configuration { /** * Sets the directory where Doctrine generates any necessary proxy class files. * * @param string $dir */ public function setProxyDir($dir) { $this->_attributes['proxyDir'] = $dir; } /** * Gets the directory where Doctrine generates any necessary proxy class files. * * @return string */ public function getProxyDir() { return isset($this->_attributes['proxyDir']) ? $this->_attributes['proxyDir'] : null; } /** * Gets a boolean flag that indicates whether proxy classes should always be regenerated * during each script execution. * * @return boolean */ public function getAutoGenerateProxyClasses() { return isset($this->_attributes['autoGenerateProxyClasses']) ? $this->_attributes['autoGenerateProxyClasses'] : true; } /** * Sets a boolean flag that indicates whether proxy classes should always be regenerated * during each script execution. * * @param boolean $bool */ public function setAutoGenerateProxyClasses($bool) { $this->_attributes['autoGenerateProxyClasses'] = $bool; } /** * Gets the namespace where proxy classes reside. * * @return string */ public function getProxyNamespace() { return isset($this->_attributes['proxyNamespace']) ? $this->_attributes['proxyNamespace'] : null; } /** * Sets the namespace where proxy classes reside. * * @param string $ns */ public function setProxyNamespace($ns) { $this->_attributes['proxyNamespace'] = $ns; } /** * Sets the cache driver implementation that is used for metadata caching. * * @param MappingDriver $driverImpl * @todo Force parameter to be a Closure to ensure lazy evaluation * (as soon as a metadata cache is in effect, the driver never needs to initialize). */ public function setMetadataDriverImpl(MappingDriver $driverImpl) { $this->_attributes['metadataDriverImpl'] = $driverImpl; } /** * Add a new default annotation driver with a correctly configured annotation reader. If $useSimpleAnnotationReader * is true, the notation `@Entity` will work, otherwise, the notation `@ORM\Entity` will be supported. * * @param array $paths * @param bool $useSimpleAnnotationReader * @return AnnotationDriver */ public function newDefaultAnnotationDriver($paths = array(), $useSimpleAnnotationReader = true) { AnnotationRegistry::registerFile(__DIR__ . '/Mapping/Driver/DoctrineAnnotations.php'); if ($useSimpleAnnotationReader) { // Register the ORM Annotations in the AnnotationRegistry $reader = new SimpleAnnotationReader(); $reader->addNamespace('Doctrine\ORM\Mapping'); $cachedReader = new CachedReader($reader, new ArrayCache()); return new AnnotationDriver($cachedReader, (array) $paths); } return new AnnotationDriver( new CachedReader(new AnnotationReader(), new ArrayCache()), (array) $paths ); } /** * Adds a namespace under a certain alias. * * @param string $alias * @param string $namespace */ public function addEntityNamespace($alias, $namespace) { $this->_attributes['entityNamespaces'][$alias] = $namespace; } /** * Resolves a registered namespace alias to the full namespace. * * @param string $entityNamespaceAlias * @throws ORMException * @return string */ public function getEntityNamespace($entityNamespaceAlias) { if ( ! isset($this->_attributes['entityNamespaces'][$entityNamespaceAlias])) { throw ORMException::unknownEntityNamespace($entityNamespaceAlias); } return trim($this->_attributes['entityNamespaces'][$entityNamespaceAlias], '\\'); } /** * Set the entity alias map * * @param array $entityNamespaces */ public function setEntityNamespaces(array $entityNamespaces) { $this->_attributes['entityNamespaces'] = $entityNamespaces; } /** * Retrieves the list of registered entity namespace aliases. * * @return array */ public function getEntityNamespaces() { return $this->_attributes['entityNamespaces']; } /** * Gets the cache driver implementation that is used for the mapping metadata. * * @throws ORMException * @return MappingDriver */ public function getMetadataDriverImpl() { return isset($this->_attributes['metadataDriverImpl']) ? $this->_attributes['metadataDriverImpl'] : null; } /** * Gets the cache driver implementation that is used for the query cache (SQL cache). * * @return \Doctrine\Common\Cache\Cache */ public function getQueryCacheImpl() { return isset($this->_attributes['queryCacheImpl']) ? $this->_attributes['queryCacheImpl'] : null; } /** * Sets the cache driver implementation that is used for the query cache (SQL cache). * * @param \Doctrine\Common\Cache\Cache $cacheImpl */ public function setQueryCacheImpl(Cache $cacheImpl) { $this->_attributes['queryCacheImpl'] = $cacheImpl; } /** * Gets the cache driver implementation that is used for the hydration cache (SQL cache). * * @return \Doctrine\Common\Cache\Cache */ public function getHydrationCacheImpl() { return isset($this->_attributes['hydrationCacheImpl']) ? $this->_attributes['hydrationCacheImpl'] : null; } /** * Sets the cache driver implementation that is used for the hydration cache (SQL cache). * * @param \Doctrine\Common\Cache\Cache $cacheImpl */ public function setHydrationCacheImpl(Cache $cacheImpl) { $this->_attributes['hydrationCacheImpl'] = $cacheImpl; } /** * Gets the cache driver implementation that is used for metadata caching. * * @return \Doctrine\Common\Cache\Cache */ public function getMetadataCacheImpl() { return isset($this->_attributes['metadataCacheImpl']) ? $this->_attributes['metadataCacheImpl'] : null; } /** * Sets the cache driver implementation that is used for metadata caching. * * @param \Doctrine\Common\Cache\Cache $cacheImpl */ public function setMetadataCacheImpl(Cache $cacheImpl) { $this->_attributes['metadataCacheImpl'] = $cacheImpl; } /** * Adds a named DQL query to the configuration. * * @param string $name The name of the query. * @param string $dql The DQL query string. */ public function addNamedQuery($name, $dql) { $this->_attributes['namedQueries'][$name] = $dql; } /** * Gets a previously registered named DQL query. * * @param string $name The name of the query. * @throws ORMException * @return string The DQL query. */ public function getNamedQuery($name) { if ( ! isset($this->_attributes['namedQueries'][$name])) { throw ORMException::namedQueryNotFound($name); } return $this->_attributes['namedQueries'][$name]; } /** * Adds a named native query to the configuration. * * @param string $name The name of the query. * @param string $sql The native SQL query string. * @param Query\ResultSetMapping $rsm The ResultSetMapping used for the results of the SQL query. */ public function addNamedNativeQuery($name, $sql, Query\ResultSetMapping $rsm) { $this->_attributes['namedNativeQueries'][$name] = array($sql, $rsm); } /** * Gets the components of a previously registered named native query. * * @param string $name The name of the query. * @throws ORMException * @return array A tuple with the first element being the SQL string and the second * element being the ResultSetMapping. */ public function getNamedNativeQuery($name) { if ( ! isset($this->_attributes['namedNativeQueries'][$name])) { throw ORMException::namedNativeQueryNotFound($name); } return $this->_attributes['namedNativeQueries'][$name]; } /** * Ensures that this Configuration instance contains settings that are * suitable for a production environment. * * @throws ORMException If a configuration setting has a value that is not * suitable for a production environment. */ public function ensureProductionSettings() { if ( ! $this->getQueryCacheImpl()) { throw ORMException::queryCacheNotConfigured(); } if ( ! $this->getMetadataCacheImpl()) { throw ORMException::metadataCacheNotConfigured(); } if ($this->getAutoGenerateProxyClasses()) { throw ORMException::proxyClassesAlwaysRegenerating(); } } /** * Registers a custom DQL function that produces a string value. * 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 * @throws ORMException */ public function addCustomStringFunction($name, $className) { if (Query\Parser::isInternalFunction($name)) { throw ORMException::overwriteInternalDQLFunctionNotAllowed($name); } $this->_attributes['customStringFunctions'][strtolower($name)] = $className; } /** * Gets the implementation class name of a registered custom string DQL function. * * @param string $name * @return string */ public function getCustomStringFunction($name) { $name = strtolower($name); return isset($this->_attributes['customStringFunctions'][$name]) ? $this->_attributes['customStringFunctions'][$name] : null; } /** * 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 setCustomStringFunctions(array $functions) { foreach ($functions as $name => $className) { $this->addCustomStringFunction($name, $className); } } /** * Registers a custom DQL function that produces a numeric value. * 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 * @throws ORMException */ public function addCustomNumericFunction($name, $className) { if (Query\Parser::isInternalFunction($name)) { throw ORMException::overwriteInternalDQLFunctionNotAllowed($name); } $this->_attributes['customNumericFunctions'][strtolower($name)] = $className; } /** * Gets the implementation class name of a registered custom numeric DQL function. * * @param string $name * @return string */ public function getCustomNumericFunction($name) { $name = strtolower($name); return isset($this->_attributes['customNumericFunctions'][$name]) ? $this->_attributes['customNumericFunctions'][$name] : null; } /** * 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 setCustomNumericFunctions(array $functions) { foreach ($functions as $name => $className) { $this->addCustomNumericFunction($name, $className); } } /** * Registers a custom DQL function that produces a date/time value. * 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 * @throws ORMException */ public function addCustomDatetimeFunction($name, $className) { if (Query\Parser::isInternalFunction($name)) { throw ORMException::overwriteInternalDQLFunctionNotAllowed($name); } $this->_attributes['customDatetimeFunctions'][strtolower($name)] = $className; } /** * Gets the implementation class name of a registered custom date/time DQL function. * * @param string $name * @return string */ public function getCustomDatetimeFunction($name) { $name = strtolower($name); return isset($this->_attributes['customDatetimeFunctions'][$name]) ? $this->_attributes['customDatetimeFunctions'][$name] : null; } /** * 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 setCustomDatetimeFunctions(array $functions) { foreach ($functions as $name => $className) { $this->addCustomDatetimeFunction($name, $className); } } /** * Set the custom hydrator modes in one pass. * * @param array An array of ($modeName => $hydrator) */ public function setCustomHydrationModes($modes) { $this->_attributes['customHydrationModes'] = array(); foreach ($modes as $modeName => $hydrator) { $this->addCustomHydrationMode($modeName, $hydrator); } } /** * Get the hydrator class for the given hydration mode name. * * @param string $modeName The hydration mode name. * @return string $hydrator The hydrator class name. */ public function getCustomHydrationMode($modeName) { return isset($this->_attributes['customHydrationModes'][$modeName]) ? $this->_attributes['customHydrationModes'][$modeName] : null; } /** * Add a custom hydration mode. * * @param string $modeName The hydration mode name. * @param string $hydrator The hydrator class name. */ public function addCustomHydrationMode($modeName, $hydrator) { $this->_attributes['customHydrationModes'][$modeName] = $hydrator; } /** * Set a class metadata factory. * * @param string $cmfName */ public function setClassMetadataFactoryName($cmfName) { $this->_attributes['classMetadataFactoryName'] = $cmfName; } /** * @return string */ public function getClassMetadataFactoryName() { if ( ! isset($this->_attributes['classMetadataFactoryName'])) { $this->_attributes['classMetadataFactoryName'] = 'Doctrine\ORM\Mapping\ClassMetadataFactory'; } return $this->_attributes['classMetadataFactoryName']; } /** * Add a filter to the list of possible filters. * * @param string $name The name of the filter. * @param string $className The class name of the filter. */ public function addFilter($name, $className) { $this->_attributes['filters'][$name] = $className; } /** * Gets the class name for a given filter name. * * @param string $name The name of the filter. * * @return string The class name of the filter, or null of it is not * defined. */ public function getFilterClassName($name) { return isset($this->_attributes['filters'][$name]) ? $this->_attributes['filters'][$name] : null; } /** * Set default repository class. * * @since 2.2 * @param string $className * @throws ORMException If not is a \Doctrine\Common\Persistence\ObjectRepository */ public function setDefaultRepositoryClassName($className) { $reflectionClass = new \ReflectionClass($className); if ( ! $reflectionClass->implementsInterface('Doctrine\Common\Persistence\ObjectRepository')) { throw ORMException::invalidEntityRepository($className); } $this->_attributes['defaultRepositoryClassName'] = $className; } /** * Get default repository class. * * @since 2.2 * @return string */ public function getDefaultRepositoryClassName() { return isset($this->_attributes['defaultRepositoryClassName']) ? $this->_attributes['defaultRepositoryClassName'] : 'Doctrine\ORM\EntityRepository'; } /** * Set naming strategy. * * @since 2.3 * @param NamingStrategy $namingStrategy */ public function setNamingStrategy(NamingStrategy $namingStrategy) { $this->_attributes['namingStrategy'] = $namingStrategy; } /** * Get naming strategy.. * * @since 2.3 * @return NamingStrategy */ public function getNamingStrategy() { if ( ! isset($this->_attributes['namingStrategy'])) { $this->_attributes['namingStrategy'] = new DefaultNamingStrategy(); } return $this->_attributes['namingStrategy']; } /** * Set quote strategy. * * @since 2.3 * @param Doctrine\ORM\Mapping\QuoteStrategy $quoteStrategy */ public function setQuoteStrategy(QuoteStrategy $quoteStrategy) { $this->_attributes['quoteStrategy'] = $quoteStrategy; } /** * Get quote strategy. * * @since 2.3 * @return Doctrine\ORM\Mapping\QuoteStrategy */ public function getQuoteStrategy() { if ( ! isset($this->_attributes['quoteStrategy'])) { $this->_attributes['quoteStrategy'] = new DefaultQuoteStrategy(); } return $this->_attributes['quoteStrategy']; } } DoctrineORM-2.3.3/Doctrine/ORM/EntityManager.php0000644000175100017510000006505212143374607021455 0ustar beberleibeberlei. */ namespace Doctrine\ORM; use Exception, Doctrine\Common\EventManager, Doctrine\Common\Persistence\ObjectManager, Doctrine\DBAL\Connection, Doctrine\DBAL\LockMode, Doctrine\ORM\Mapping\ClassMetadata, Doctrine\ORM\Mapping\ClassMetadataFactory, Doctrine\ORM\Query\ResultSetMapping, Doctrine\ORM\Proxy\ProxyFactory, Doctrine\ORM\Query\FilterCollection; /** * The EntityManager is the central access point to ORM functionality. * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class EntityManager implements ObjectManager { /** * The used Configuration. * * @var \Doctrine\ORM\Configuration */ private $config; /** * The database connection used by the EntityManager. * * @var \Doctrine\DBAL\Connection */ private $conn; /** * The metadata factory, used to retrieve the ORM metadata of entity classes. * * @var \Doctrine\ORM\Mapping\ClassMetadataFactory */ private $metadataFactory; /** * The EntityRepository instances. * * @var array */ private $repositories = array(); /** * The UnitOfWork used to coordinate object-level transactions. * * @var \Doctrine\ORM\UnitOfWork */ private $unitOfWork; /** * The event manager that is the central point of the event system. * * @var \Doctrine\Common\EventManager */ private $eventManager; /** * The maintained (cached) hydrators. One instance per type. * * @var array */ private $hydrators = array(); /** * The proxy factory used to create dynamic proxies. * * @var \Doctrine\ORM\Proxy\ProxyFactory */ private $proxyFactory; /** * The expression builder instance used to generate query expressions. * * @var \Doctrine\ORM\Query\Expr */ private $expressionBuilder; /** * Whether the EntityManager is closed or not. * * @var bool */ private $closed = false; /** * Collection of query filters. * * @var Doctrine\ORM\Query\FilterCollection */ private $filterCollection; /** * Creates a new EntityManager that operates on the given database connection * and uses the given Configuration and EventManager implementations. * * @param \Doctrine\DBAL\Connection $conn * @param \Doctrine\ORM\Configuration $config * @param \Doctrine\Common\EventManager $eventManager */ protected function __construct(Connection $conn, Configuration $config, EventManager $eventManager) { $this->conn = $conn; $this->config = $config; $this->eventManager = $eventManager; $metadataFactoryClassName = $config->getClassMetadataFactoryName(); $this->metadataFactory = new $metadataFactoryClassName; $this->metadataFactory->setEntityManager($this); $this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl()); $this->unitOfWork = new UnitOfWork($this); $this->proxyFactory = new ProxyFactory( $this, $config->getProxyDir(), $config->getProxyNamespace(), $config->getAutoGenerateProxyClasses() ); } /** * Gets the database connection object used by the EntityManager. * * @return \Doctrine\DBAL\Connection */ public function getConnection() { return $this->conn; } /** * Gets the metadata factory used to gather the metadata of classes. * * @return \Doctrine\ORM\Mapping\ClassMetadataFactory */ public function getMetadataFactory() { return $this->metadataFactory; } /** * Gets an ExpressionBuilder used for object-oriented construction of query expressions. * * Example: * * * $qb = $em->createQueryBuilder(); * $expr = $em->getExpressionBuilder(); * $qb->select('u')->from('User', 'u') * ->where($expr->orX($expr->eq('u.id', 1), $expr->eq('u.id', 2))); * * * @return \Doctrine\ORM\Query\Expr */ public function getExpressionBuilder() { if ($this->expressionBuilder === null) { $this->expressionBuilder = new Query\Expr; } return $this->expressionBuilder; } /** * Starts a transaction on the underlying database connection. */ public function beginTransaction() { $this->conn->beginTransaction(); } /** * Executes a function in a transaction. * * The function gets passed this EntityManager instance as an (optional) parameter. * * {@link flush} is invoked prior to transaction commit. * * If an exception occurs during execution of the function or flushing or transaction commit, * the transaction is rolled back, the EntityManager closed and the exception re-thrown. * * @param callable $func The function to execute transactionally. * @return mixed Returns the non-empty value returned from the closure or true instead */ public function transactional($func) { if (!is_callable($func)) { throw new \InvalidArgumentException('Expected argument of type "callable", got "' . gettype($func) . '"'); } $this->conn->beginTransaction(); try { $return = call_user_func($func, $this); $this->flush(); $this->conn->commit(); return $return ?: true; } catch (Exception $e) { $this->close(); $this->conn->rollback(); throw $e; } } /** * Commits a transaction on the underlying database connection. */ public function commit() { $this->conn->commit(); } /** * Performs a rollback on the underlying database connection. */ public function rollback() { $this->conn->rollback(); } /** * Returns the ORM metadata descriptor for a class. * * The class name must be the fully-qualified class name without a leading backslash * (as it is returned by get_class($obj)) or an aliased class name. * * Examples: * MyProject\Domain\User * sales:PriceRequest * * @return \Doctrine\ORM\Mapping\ClassMetadata * @internal Performance-sensitive method. */ public function getClassMetadata($className) { return $this->metadataFactory->getMetadataFor($className); } /** * Creates a new Query object. * * @param string $dql The DQL string. * @return \Doctrine\ORM\Query */ public function createQuery($dql = "") { $query = new Query($this); if ( ! empty($dql)) { $query->setDql($dql); } return $query; } /** * Creates a Query from a named query. * * @param string $name * @return \Doctrine\ORM\Query */ public function createNamedQuery($name) { return $this->createQuery($this->config->getNamedQuery($name)); } /** * Creates a native SQL query. * * @param string $sql * @param ResultSetMapping $rsm The ResultSetMapping to use. * @return NativeQuery */ public function createNativeQuery($sql, ResultSetMapping $rsm) { $query = new NativeQuery($this); $query->setSql($sql); $query->setResultSetMapping($rsm); return $query; } /** * Creates a NativeQuery from a named native query. * * @param string $name * @return \Doctrine\ORM\NativeQuery */ public function createNamedNativeQuery($name) { list($sql, $rsm) = $this->config->getNamedNativeQuery($name); return $this->createNativeQuery($sql, $rsm); } /** * Create a QueryBuilder instance * * @return QueryBuilder $qb */ public function createQueryBuilder() { return new QueryBuilder($this); } /** * Flushes all changes to objects that have been queued up to now to the database. * This effectively synchronizes the in-memory state of managed objects with the * database. * * If an entity is explicitly passed to this method only this entity and * the cascade-persist semantics + scheduled inserts/removals are synchronized. * * @param object $entity * @throws \Doctrine\ORM\OptimisticLockException If a version check on an entity that * makes use of optimistic locking fails. */ public function flush($entity = null) { $this->errorIfClosed(); $this->unitOfWork->commit($entity); } /** * Finds an Entity by its identifier. * * @param string $entityName * @param mixed $id * @param integer $lockMode * @param integer $lockVersion * * @return object */ public function find($entityName, $id, $lockMode = LockMode::NONE, $lockVersion = null) { $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\')); if ( ! is_array($id)) { $id = array($class->identifier[0] => $id); } $sortedId = array(); foreach ($class->identifier as $identifier) { if ( ! isset($id[$identifier])) { throw ORMException::missingIdentifierField($class->name, $identifier); } $sortedId[$identifier] = $id[$identifier]; } $unitOfWork = $this->getUnitOfWork(); // Check identity map first if (($entity = $unitOfWork->tryGetById($sortedId, $class->rootEntityName)) !== false) { if ( ! ($entity instanceof $class->name)) { return null; } switch ($lockMode) { case LockMode::OPTIMISTIC: $this->lock($entity, $lockMode, $lockVersion); break; case LockMode::PESSIMISTIC_READ: case LockMode::PESSIMISTIC_WRITE: $persister = $unitOfWork->getEntityPersister($class->name); $persister->refresh($sortedId, $entity, $lockMode); break; } return $entity; // Hit! } $persister = $unitOfWork->getEntityPersister($class->name); switch ($lockMode) { case LockMode::NONE: return $persister->load($sortedId); case LockMode::OPTIMISTIC: if ( ! $class->isVersioned) { throw OptimisticLockException::notVersioned($class->name); } $entity = $persister->load($sortedId); $unitOfWork->lock($entity, $lockMode, $lockVersion); return $entity; default: if ( ! $this->getConnection()->isTransactionActive()) { throw TransactionRequiredException::transactionRequired(); } return $persister->load($sortedId, null, null, array(), $lockMode); } } /** * Gets a reference to the entity identified by the given type and identifier * without actually loading it, if the entity is not yet loaded. * * @param string $entityName The name of the entity type. * @param mixed $id The entity identifier. * @return object The entity reference. */ public function getReference($entityName, $id) { $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\')); if ( ! is_array($id)) { $id = array($class->identifier[0] => $id); } $sortedId = array(); foreach ($class->identifier as $identifier) { if ( ! isset($id[$identifier])) { throw ORMException::missingIdentifierField($class->name, $identifier); } $sortedId[$identifier] = $id[$identifier]; } // Check identity map first, if its already in there just return it. if (($entity = $this->unitOfWork->tryGetById($sortedId, $class->rootEntityName)) !== false) { return ($entity instanceof $class->name) ? $entity : null; } if ($class->subClasses) { return $this->find($entityName, $sortedId); } if ( ! is_array($sortedId)) { $sortedId = array($class->identifier[0] => $sortedId); } $entity = $this->proxyFactory->getProxy($class->name, $sortedId); $this->unitOfWork->registerManaged($entity, $sortedId, array()); return $entity; } /** * Gets a partial reference to the entity identified by the given type and identifier * without actually loading it, if the entity is not yet loaded. * * The returned reference may be a partial object if the entity is not yet loaded/managed. * If it is a partial object it will not initialize the rest of the entity state on access. * Thus you can only ever safely access the identifier of an entity obtained through * this method. * * The use-cases for partial references involve maintaining bidirectional associations * without loading one side of the association or to update an entity without loading it. * Note, however, that in the latter case the original (persistent) entity data will * never be visible to the application (especially not event listeners) as it will * never be loaded in the first place. * * @param string $entityName The name of the entity type. * @param mixed $identifier The entity identifier. * @return object The (partial) entity reference. */ public function getPartialReference($entityName, $identifier) { $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\')); // Check identity map first, if its already in there just return it. if (($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) !== false) { return ($entity instanceof $class->name) ? $entity : null; } if ( ! is_array($identifier)) { $identifier = array($class->identifier[0] => $identifier); } $entity = $class->newInstance(); $class->setIdentifierValues($entity, $identifier); $this->unitOfWork->registerManaged($entity, $identifier, array()); $this->unitOfWork->markReadOnly($entity); return $entity; } /** * Clears the EntityManager. All entities that are currently managed * by this EntityManager become detached. * * @param string $entityName if given, only entities of this type will get detached */ public function clear($entityName = null) { $this->unitOfWork->clear($entityName); } /** * Closes the EntityManager. All entities that are currently managed * by this EntityManager become detached. The EntityManager may no longer * be used after it is closed. */ public function close() { $this->clear(); $this->closed = true; } /** * Tells the EntityManager to make an instance managed and persistent. * * The entity will be entered into the database at or before transaction * commit or as a result of the flush operation. * * NOTE: The persist operation always considers entities that are not yet known to * this EntityManager as NEW. Do not pass detached entities to the persist operation. * * @param object $object The instance to make managed and persistent. */ public function persist($entity) { if ( ! is_object($entity)) { throw ORMInvalidArgumentException::invalidObject('EntityManager#persist()' , $entity); } $this->errorIfClosed(); $this->unitOfWork->persist($entity); } /** * Removes an entity instance. * * A removed entity will be removed from the database at or before transaction commit * or as a result of the flush operation. * * @param object $entity The entity instance to remove. */ public function remove($entity) { if ( ! is_object($entity)) { throw ORMInvalidArgumentException::invalidObject('EntityManager#remove()' , $entity); } $this->errorIfClosed(); $this->unitOfWork->remove($entity); } /** * Refreshes the persistent state of an entity from the database, * overriding any local changes that have not yet been persisted. * * @param object $entity The entity to refresh. */ public function refresh($entity) { if ( ! is_object($entity)) { throw ORMInvalidArgumentException::invalidObject('EntityManager#refresh()' , $entity); } $this->errorIfClosed(); $this->unitOfWork->refresh($entity); } /** * Detaches an entity from the EntityManager, causing a managed entity to * become detached. Unflushed changes made to the entity if any * (including removal of the entity), will not be synchronized to the database. * Entities which previously referenced the detached entity will continue to * reference it. * * @param object $entity The entity to detach. */ public function detach($entity) { if ( ! is_object($entity)) { throw ORMInvalidArgumentException::invalidObject('EntityManager#detach()' , $entity); } $this->unitOfWork->detach($entity); } /** * Merges the state of a detached entity into the persistence context * of this EntityManager and returns the managed copy of the entity. * The entity passed to merge will not become associated/managed with this EntityManager. * * @param object $entity The detached entity to merge into the persistence context. * @return object The managed copy of the entity. */ public function merge($entity) { if ( ! is_object($entity)) { throw ORMInvalidArgumentException::invalidObject('EntityManager#merge()' , $entity); } $this->errorIfClosed(); return $this->unitOfWork->merge($entity); } /** * Creates a copy of the given entity. Can create a shallow or a deep copy. * * @param object $entity The entity to copy. * @return object The new entity. * @todo Implementation need. This is necessary since $e2 = clone $e1; throws an E_FATAL when access anything on $e: * Fatal error: Maximum function nesting level of '100' reached, aborting! */ public function copy($entity, $deep = false) { throw new \BadMethodCallException("Not implemented."); } /** * Acquire a lock on the given entity. * * @param object $entity * @param int $lockMode * @param int $lockVersion * @throws OptimisticLockException * @throws PessimisticLockException */ public function lock($entity, $lockMode, $lockVersion = null) { $this->unitOfWork->lock($entity, $lockMode, $lockVersion); } /** * Gets the repository for an entity class. * * @param string $entityName The name of the entity. * @return EntityRepository The repository class. */ public function getRepository($entityName) { $entityName = ltrim($entityName, '\\'); if (isset($this->repositories[$entityName])) { return $this->repositories[$entityName]; } $metadata = $this->getClassMetadata($entityName); $repositoryClassName = $metadata->customRepositoryClassName; if ($repositoryClassName === null) { $repositoryClassName = $this->config->getDefaultRepositoryClassName(); } $repository = new $repositoryClassName($this, $metadata); $this->repositories[$entityName] = $repository; return $repository; } /** * Determines whether an entity instance is managed in this EntityManager. * * @param object $entity * @return boolean TRUE if this EntityManager currently manages the given entity, FALSE otherwise. */ public function contains($entity) { return $this->unitOfWork->isScheduledForInsert($entity) || $this->unitOfWork->isInIdentityMap($entity) && ! $this->unitOfWork->isScheduledForDelete($entity); } /** * Gets the EventManager used by the EntityManager. * * @return \Doctrine\Common\EventManager */ public function getEventManager() { return $this->eventManager; } /** * Gets the Configuration used by the EntityManager. * * @return \Doctrine\ORM\Configuration */ public function getConfiguration() { return $this->config; } /** * Throws an exception if the EntityManager is closed or currently not active. * * @throws ORMException If the EntityManager is closed. */ private function errorIfClosed() { if ($this->closed) { throw ORMException::entityManagerClosed(); } } /** * Check if the Entity manager is open or closed. * * @return bool */ public function isOpen() { return (!$this->closed); } /** * Gets the UnitOfWork used by the EntityManager to coordinate operations. * * @return \Doctrine\ORM\UnitOfWork */ public function getUnitOfWork() { return $this->unitOfWork; } /** * Gets a hydrator for the given hydration mode. * * This method caches the hydrator instances which is used for all queries that don't * selectively iterate over the result. * * @param int $hydrationMode * @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator */ public function getHydrator($hydrationMode) { if ( ! isset($this->hydrators[$hydrationMode])) { $this->hydrators[$hydrationMode] = $this->newHydrator($hydrationMode); } return $this->hydrators[$hydrationMode]; } /** * Create a new instance for the given hydration mode. * * @param int $hydrationMode * @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator */ public function newHydrator($hydrationMode) { switch ($hydrationMode) { case Query::HYDRATE_OBJECT: return new Internal\Hydration\ObjectHydrator($this); case Query::HYDRATE_ARRAY: return new Internal\Hydration\ArrayHydrator($this); case Query::HYDRATE_SCALAR: return new Internal\Hydration\ScalarHydrator($this); case Query::HYDRATE_SINGLE_SCALAR: return new Internal\Hydration\SingleScalarHydrator($this); case Query::HYDRATE_SIMPLEOBJECT: return new Internal\Hydration\SimpleObjectHydrator($this); default: if (($class = $this->config->getCustomHydrationMode($hydrationMode)) !== null) { return new $class($this); } } throw ORMException::invalidHydrationMode($hydrationMode); } /** * Gets the proxy factory used by the EntityManager to create entity proxies. * * @return ProxyFactory */ public function getProxyFactory() { return $this->proxyFactory; } /** * Helper method to initialize a lazy loading proxy or persistent collection. * * This method is a no-op for other objects * * @param object $obj */ public function initializeObject($obj) { $this->unitOfWork->initializeObject($obj); } /** * Factory method to create EntityManager instances. * * @param mixed $conn An array with the connection parameters or an existing * Connection instance. * @param Configuration $config The Configuration instance to use. * @param EventManager $eventManager The EventManager instance to use. * @return EntityManager The created EntityManager. */ public static function create($conn, Configuration $config, EventManager $eventManager = null) { if ( ! $config->getMetadataDriverImpl()) { throw ORMException::missingMappingDriverImpl(); } switch (true) { case (is_array($conn)): $conn = \Doctrine\DBAL\DriverManager::getConnection( $conn, $config, ($eventManager ?: new EventManager()) ); break; case ($conn instanceof Connection): if ($eventManager !== null && $conn->getEventManager() !== $eventManager) { throw ORMException::mismatchedEventManager(); } break; default: throw new \InvalidArgumentException("Invalid argument: " . $conn); } return new EntityManager($conn, $config, $conn->getEventManager()); } /** * Gets the enabled filters. * * @return FilterCollection The active filter collection. */ public function getFilters() { if (null === $this->filterCollection) { $this->filterCollection = new FilterCollection($this); } return $this->filterCollection; } /** * Checks whether the state of the filter collection is clean. * * @return boolean True, if the filter collection is clean. */ public function isFiltersStateClean() { return null === $this->filterCollection || $this->filterCollection->isClean(); } /** * Checks whether the Entity Manager has filters. * * @return True, if the EM has a filter collection. */ public function hasFilters() { return null !== $this->filterCollection; } } DoctrineORM-2.3.3/Doctrine/ORM/EntityNotFoundException.php0000644000175100017510000000237712143374607023517 0ustar beberleibeberlei. */ namespace Doctrine\ORM; /** * Exception thrown when a Proxy fails to retrieve an Entity result. * * @author robo * @since 2.0 */ class EntityNotFoundException extends ORMException { public function __construct() { parent::__construct('Entity was not found.'); } } DoctrineORM-2.3.3/Doctrine/ORM/EntityRepository.php0000644000175100017510000002022112143374607022247 0ustar beberleibeberlei. */ namespace Doctrine\ORM; use Doctrine\DBAL\LockMode; use Doctrine\Common\Persistence\ObjectRepository; use Doctrine\Common\Collections\Selectable; use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ExpressionBuilder; /** * An EntityRepository serves as a repository for entities with generic as well as * business specific methods for retrieving entities. * * This class is designed for inheritance and users can subclass this class to * write their own repositories with business-specific methods to locate entities. * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class EntityRepository implements ObjectRepository, Selectable { /** * @var string */ protected $_entityName; /** * @var EntityManager */ protected $_em; /** * @var \Doctrine\ORM\Mapping\ClassMetadata */ protected $_class; /** * Initializes a new EntityRepository. * * @param EntityManager $em The EntityManager to use. * @param ClassMetadata $classMetadata The class descriptor. */ public function __construct($em, Mapping\ClassMetadata $class) { $this->_entityName = $class->name; $this->_em = $em; $this->_class = $class; } /** * Create a new QueryBuilder instance that is prepopulated for this entity name * * @param string $alias * @return QueryBuilder $qb */ public function createQueryBuilder($alias) { return $this->_em->createQueryBuilder() ->select($alias) ->from($this->_entityName, $alias); } /** * Create a new Query instance based on a predefined metadata named query. * * @param string $queryName * @return Query */ public function createNamedQuery($queryName) { return $this->_em->createQuery($this->_class->getNamedQuery($queryName)); } /** * Creates a native SQL query. * * @param string $queryName * @return NativeQuery */ public function createNativeNamedQuery($queryName) { $queryMapping = $this->_class->getNamedNativeQuery($queryName); $rsm = new Query\ResultSetMappingBuilder($this->_em); $rsm->addNamedNativeQueryMapping($this->_class, $queryMapping); return $this->_em->createNativeQuery($queryMapping['query'], $rsm); } /** * Clears the repository, causing all managed entities to become detached. */ public function clear() { $this->_em->clear($this->_class->rootEntityName); } /** * Finds an entity by its primary key / identifier. * * @param mixed $id The identifier. * @param integer $lockMode * @param integer $lockVersion * * @return object The entity. */ public function find($id, $lockMode = LockMode::NONE, $lockVersion = null) { return $this->_em->find($this->_entityName, $id, $lockMode, $lockVersion); } /** * Finds all entities in the repository. * * @return array The entities. */ public function findAll() { return $this->findBy(array()); } /** * Finds entities by a set of criteria. * * @param array $criteria * @param array|null $orderBy * @param int|null $limit * @param int|null $offset * @return array The objects. */ public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) { $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName); return $persister->loadAll($criteria, $orderBy, $limit, $offset); } /** * Finds a single entity by a set of criteria. * * @param array $criteria * @param array|null $orderBy * @return object */ public function findOneBy(array $criteria, array $orderBy = null) { $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName); return $persister->load($criteria, null, null, array(), 0, 1, $orderBy); } /** * Adds support for magic finders. * * @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. */ public function __call($method, $arguments) { switch (true) { case (0 === strpos($method, 'findBy')): $by = substr($method, 6); $method = 'findBy'; break; case (0 === strpos($method, 'findOneBy')): $by = substr($method, 9); $method = 'findOneBy'; break; default: throw new \BadMethodCallException( "Undefined method '$method'. The method name must start with ". "either findBy or findOneBy!" ); } if (empty($arguments)) { throw ORMException::findByRequiresParameter($method . $by); } $fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by)); if ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName)) { switch (count($arguments)) { case 1: return $this->$method(array($fieldName => $arguments[0])); case 2: return $this->$method(array($fieldName => $arguments[0]), $arguments[1]); case 3: return $this->$method(array($fieldName => $arguments[0]), $arguments[1], $arguments[2]); case 4: return $this->$method(array($fieldName => $arguments[0]), $arguments[1], $arguments[2], $arguments[3]); default: // Do nothing } } throw ORMException::invalidFindByCall($this->_entityName, $fieldName, $method.$by); } /** * @return string */ protected function getEntityName() { return $this->_entityName; } /** * @return string */ public function getClassName() { return $this->getEntityName(); } /** * @return EntityManager */ protected function getEntityManager() { return $this->_em; } /** * @return Mapping\ClassMetadata */ protected function getClassMetadata() { return $this->_class; } /** * Select all elements from a selectable that match the expression and * return a new collection containing these elements. * * @param \Doctrine\Common\Collections\Criteria $criteria * * @return \Doctrine\Common\Collections\Collection */ public function matching(Criteria $criteria) { $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName); return new ArrayCollection($persister->loadCriteria($criteria)); } } DoctrineORM-2.3.3/Doctrine/ORM/Events.php0000644000175100017510000001175012143374607020146 0ustar beberleibeberlei. */ namespace Doctrine\ORM; /** * Container for all ORM events. * * This class cannot be instantiated. * * @author Roman Borschel * @since 2.0 */ final class Events { private function __construct() {} /** * The preRemove event occurs for a given entity before the respective * EntityManager remove operation for that entity is executed. * * This is an entity lifecycle event. * * @var string */ const preRemove = 'preRemove'; /** * The postRemove event occurs for an entity after the entity has * been deleted. It will be invoked after the database delete operations. * * This is an entity lifecycle event. * * @var string */ const postRemove = 'postRemove'; /** * The prePersist event occurs for a given entity before the respective * EntityManager persist operation for that entity is executed. * * This is an entity lifecycle event. * * @var string */ const prePersist = 'prePersist'; /** * The postPersist event occurs for an entity after the entity has * been made persistent. It will be invoked after the database insert operations. * Generated primary key values are available in the postPersist event. * * This is an entity lifecycle event. * * @var string */ const postPersist = 'postPersist'; /** * The preUpdate event occurs before the database update operations to * entity data. * * This is an entity lifecycle event. * * @var string */ const preUpdate = 'preUpdate'; /** * The postUpdate event occurs after the database update operations to * entity data. * * This is an entity lifecycle event. * * @var string */ const postUpdate = 'postUpdate'; /** * The postLoad event occurs for an entity after the entity has been loaded * into the current EntityManager from the database or after the refresh operation * has been applied to it. * * Note that the postLoad event occurs for an entity before any associations have been * initialized. Therefore it is not safe to access associations in a postLoad callback * or event handler. * * This is an entity lifecycle event. * * @var string */ const postLoad = 'postLoad'; /** * The loadClassMetadata event occurs after the mapping metadata for a class * has been loaded from a mapping source (annotations/xml/yaml). * * @var string */ const loadClassMetadata = 'loadClassMetadata'; /** * The preFlush event occurs when the EntityManager#flush() operation is invoked, * but before any changes to managed entites have been calculated. This event is * always raised right after EntityManager#flush() call. */ const preFlush = 'preFlush'; /** * The onFlush event occurs when the EntityManager#flush() operation is invoked, * after any changes to managed entities have been determined but before any * actual database operations are executed. The event is only raised if there is * actually something to do for the underlying UnitOfWork. If nothing needs to be done, * the onFlush event is not raised. * * @var string */ const onFlush = 'onFlush'; /** * The postFlush event occurs when the EntityManager#flush() operation is invoked and * after all actual database operations are executed successfully. The event is only raised if there is * actually something to do for the underlying UnitOfWork. If nothing needs to be done, * the postFlush event is not raised. The event won't be raised if an error occurs during the * flush operation. * * @var string */ const postFlush = 'postFlush'; /** * The onClear event occurs when the EntityManager#clear() operation is invoked, * after all references to entities have been removed from the unit of work. * * @var string */ const onClear = 'onClear'; } DoctrineORM-2.3.3/Doctrine/ORM/NativeQuery.php0000644000175100017510000000502312143374607021152 0ustar beberleibeberlei. */ namespace Doctrine\ORM; /** * Represents a native SQL query. * * @author Roman Borschel * @since 2.0 */ final class NativeQuery extends AbstractQuery { private $_sql; /** * Sets the SQL of the query. * * @param string $sql * @return NativeQuery This query instance. */ public function setSQL($sql) { $this->_sql = $sql; return $this; } /** * Gets the SQL query. * * @return mixed The built SQL query or an array of all SQL queries. * @override */ public function getSQL() { return $this->_sql; } /** * {@inheritdoc} */ protected function _doExecute() { $parameters = array(); $types = array(); foreach ($this->getParameters() as $parameter) { $name = $parameter->getName(); $value = $this->processParameterValue($parameter->getValue()); $type = ($parameter->getValue() === $value) ? $parameter->getType() : Query\ParameterTypeInferer::inferType($value); $parameters[$name] = $value; $types[$name] = $type; } if ($parameters && is_int(key($parameters))) { ksort($parameters); ksort($types); $parameters = array_values($parameters); $types = array_values($types); } return $this->_em->getConnection()->executeQuery( $this->_sql, $parameters, $types, $this->_queryCacheProfile ); } } DoctrineORM-2.3.3/Doctrine/ORM/NonUniqueResultException.php0000644000175100017510000000226612143374607023703 0ustar beberleibeberlei. */ namespace Doctrine\ORM; /** * Exception thrown when an ORM query unexpectedly returns more than one result. * * @author robo * @since 2.0 */ class NonUniqueResultException extends UnexpectedResultException { } DoctrineORM-2.3.3/Doctrine/ORM/NoResultException.php0000644000175100017510000000250112143374607022326 0ustar beberleibeberlei. */ namespace Doctrine\ORM; /** * Exception thrown when an ORM query unexpectedly does not return any results. * * @author robo * @since 2.0 */ class NoResultException extends UnexpectedResultException { public function __construct() { parent::__construct('No result was found for query although at least one row was expected.'); } } DoctrineORM-2.3.3/Doctrine/ORM/OptimisticLockException.php0000644000175100017510000000426212143374607023516 0ustar beberleibeberlei. */ namespace Doctrine\ORM; /** * An OptimisticLockException is thrown when a version check on an object * that uses optimistic locking through a version field fails. * * @author Roman Borschel * @author Benjamin Eberlei * @since 2.0 */ class OptimisticLockException extends ORMException { private $entity; public function __construct($msg, $entity) { parent::__construct($msg); $this->entity = $entity; } /** * Gets the entity that caused the exception. * * @return object */ public function getEntity() { return $this->entity; } public static function lockFailed($entity) { return new self("The optimistic lock on an entity failed.", $entity); } public static function lockFailedVersionMissmatch($entity, $expectedLockVersion, $actualLockVersion) { return new self("The optimistic lock failed, version " . $expectedLockVersion . " was expected, but is actually ".$actualLockVersion, $entity); } public static function notVersioned($entityName) { return new self("Cannot obtain optimistic lock on unversioned entity " . $entityName, null); } } DoctrineORM-2.3.3/Doctrine/ORM/ORMException.php0000644000175100017510000001414712143374607021221 0ustar beberleibeberlei. */ namespace Doctrine\ORM; use Exception; /** * Base exception class for all ORM exceptions. * * @author Roman Borschel * @since 2.0 */ class ORMException extends Exception { public static function missingMappingDriverImpl() { return new self("It's a requirement to specify a Metadata Driver and pass it ". "to Doctrine\\ORM\\Configuration::setMetadataDriverImpl()."); } public static function namedQueryNotFound($queryName) { return new self('Could not find a named query by the name "' . $queryName . '"'); } public static function namedNativeQueryNotFound($nativeQueryName) { return new self('Could not find a named native query by the name "' . $nativeQueryName . '"'); } public static function entityMissingForeignAssignedId($entity, $relatedEntity) { return new self( "Entity of type " . get_class($entity) . " has identity through a foreign entity " . get_class($relatedEntity) . ", " . "however this entity has no identity itself. You have to call EntityManager#persist() on the related entity " . "and make sure that an identifier was generated before trying to persist '" . get_class($entity) . "'. In case " . "of Post Insert ID Generation (such as MySQL Auto-Increment or PostgreSQL SERIAL) this means you have to call " . "EntityManager#flush() between both persist operations." ); } public static function entityMissingAssignedIdForField($entity, $field) { return new self("Entity of type " . get_class($entity) . " is missing an assigned ID for field '" . $field . "'. " . "The identifier generation strategy for this entity requires the ID field to be populated before ". "EntityManager#persist() is called. If you want automatically generated identifiers instead " . "you need to adjust the metadata mapping accordingly." ); } public static function unrecognizedField($field) { return new self("Unrecognized field: $field"); } /** * @param string $className * @param string $field */ public static function invalidOrientation($className, $field) { return new self("Invalid order by orientation specified for " . $className . "#" . $field); } public static function invalidFlushMode($mode) { return new self("'$mode' is an invalid flush mode."); } public static function entityManagerClosed() { return new self("The EntityManager is closed."); } public static function invalidHydrationMode($mode) { return new self("'$mode' is an invalid hydration mode."); } public static function mismatchedEventManager() { return new self("Cannot use different EventManager instances for EntityManager and Connection."); } public static function findByRequiresParameter($methodName) { return new self("You need to pass a parameter to '".$methodName."'"); } public static function invalidFindByCall($entityName, $fieldName, $method) { return new self( "Entity '".$entityName."' has no field '".$fieldName."'. ". "You can therefore not call '".$method."' on the entities' repository" ); } public static function invalidFindByInverseAssociation($entityName, $associationFieldName) { return new self( "You cannot search for the association field '".$entityName."#".$associationFieldName."', ". "because it is the inverse side of an association. Find methods only work on owning side associations." ); } public static function invalidResultCacheDriver() { return new self("Invalid result cache driver; it must implement Doctrine\\Common\\Cache\\Cache."); } public static function notSupported() { return new self("This behaviour is (currently) not supported by Doctrine 2"); } public static function queryCacheNotConfigured() { return new self('Query Cache is not configured.'); } public static function metadataCacheNotConfigured() { return new self('Class Metadata Cache is not configured.'); } public static function proxyClassesAlwaysRegenerating() { return new self('Proxy Classes are always regenerating.'); } public static function unknownEntityNamespace($entityNamespaceAlias) { return new self( "Unknown Entity namespace alias '$entityNamespaceAlias'." ); } public static function invalidEntityRepository($className) { return new self("Invalid repository class '".$className."'. It must be a Doctrine\Common\Persistence\ObjectRepository."); } public static function missingIdentifierField($className, $fieldName) { return new self("The identifier $fieldName is missing for a query of " . $className); } public static function overwriteInternalDQLFunctionNotAllowed($functionName) { return new self("It is not allowed to overwrite internal function '$functionName' in the DQL parser through user-defined functions."); } } DoctrineORM-2.3.3/Doctrine/ORM/ORMInvalidArgumentException.php0000644000175100017510000001202112143374607024220 0ustar beberleibeberlei. */ namespace Doctrine\ORM; /** * Contains exception messages for all invalid lifecycle state exceptions inside UnitOfWork * * @author Benjamin Eberlei */ class ORMInvalidArgumentException extends \InvalidArgumentException { static public function scheduleInsertForManagedEntity($entity) { return new self("A managed+dirty entity " . self::objToStr($entity) . " can not be scheduled for insertion."); } static public function scheduleInsertForRemovedEntity($entity) { return new self("Removed entity " . self::objToStr($entity) . " can not be scheduled for insertion."); } static public function scheduleInsertTwice($entity) { return new self("Entity " . self::objToStr($entity) . " can not be scheduled for insertion twice."); } static public function entityWithoutIdentity($className, $entity) { return new self( "The given entity of type '" . $className . "' (".self::objToStr($entity).") has no identity/no " . "id values set. It cannot be added to the identity map." ); } static public function readOnlyRequiresManagedEntity($entity) { return new self("Only managed entities can be marked or checked as read only. But " . self::objToStr($entity) . " is not"); } static public function newEntityFoundThroughRelationship(array $assoc, $entry) { return new self("A new entity was found through the relationship '" . $assoc['sourceEntity'] . "#" . $assoc['fieldName'] . "' that was not" . " configured to cascade persist operations for entity: " . self::objToStr($entry) . "." . " To solve this issue: Either explicitly call EntityManager#persist()" . " on this unknown entity or configure cascade persist " . " this association in the mapping for example @ManyToOne(..,cascade={\"persist\"})." . (method_exists($entry, '__toString') ? "": " If you cannot find out which entity causes the problem" ." implement '" . $assoc['targetEntity'] . "#__toString()' to get a clue.")); } static public function detachedEntityFoundThroughRelationship(array $assoc, $entry) { return new self("A detached entity of type " . $assoc['targetEntity'] . " (" . self::objToStr($entry) . ") " . " was found through the relationship '" . $assoc['sourceEntity'] . "#" . $assoc['fieldName'] . "' " . "during cascading a persist operation."); } static public function entityNotManaged($entity) { return new self("Entity " . self::objToStr($entity) . " is not managed. An entity is managed if its fetched " . "from the database or registered as new through EntityManager#persist"); } static public function entityHasNoIdentity($entity, $operation) { return new self("Entity has no identity, therefore " . $operation ." cannot be performed. " . self::objToStr($entity)); } static public function entityIsRemoved($entity, $operation) { return new self("Entity is removed, therefore " . $operation ." cannot be performed. " . self::objToStr($entity)); } static public function detachedEntityCannot($entity, $operation) { return new self("A detached entity was found during " . $operation . " " . self::objToStr($entity)); } public static function invalidObject($context, $given, $parameterIndex = 1) { return new self($context .' expects parameter ' . $parameterIndex . ' to be an entity object, '. gettype($given) . ' given.'); } /** * Helper method to show an object as string. * * @param object $obj * @return string */ private static function objToStr($obj) { return method_exists($obj, '__toString') ? (string)$obj : get_class($obj).'@'.spl_object_hash($obj); } } DoctrineORM-2.3.3/Doctrine/ORM/PersistentCollection.php0000644000175100017510000005176412143374607023067 0ustar beberleibeberlei. */ namespace Doctrine\ORM; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Selectable; use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Collections\ExpressionBuilder; use Closure; /** * A PersistentCollection represents a collection of elements that have persistent state. * * Collections of entities represent only the associations (links) to those entities. * That means, if the collection is part of a many-many mapping and you remove * entities from the collection, only the links in the relation table are removed (on flush). * Similarly, if you remove entities from a collection that is part of a one-many * mapping this will only result in the nulling out of the foreign keys on flush. * * @since 2.0 * @author Konsta Vesterinen * @author Roman Borschel * @author Giorgio Sironi * @author Stefano Rodriguez * @todo Design for inheritance to allow custom implementations? */ final class PersistentCollection implements Collection, Selectable { /** * A snapshot of the collection at the moment it was fetched from the database. * This is used to create a diff of the collection at commit time. * * @var array */ private $snapshot = array(); /** * The entity that owns this collection. * * @var object */ private $owner; /** * The association mapping the collection belongs to. * This is currently either a OneToManyMapping or a ManyToManyMapping. * * @var array */ private $association; /** * The EntityManager that manages the persistence of the collection. * * @var \Doctrine\ORM\EntityManager */ private $em; /** * The name of the field on the target entities that points to the owner * of the collection. This is only set if the association is bi-directional. * * @var string */ private $backRefFieldName; /** * The class descriptor of the collection's entity type. */ private $typeClass; /** * Whether the collection is dirty and needs to be synchronized with the database * when the UnitOfWork that manages its persistent state commits. * * @var boolean */ private $isDirty = false; /** * Whether the collection has already been initialized. * * @var boolean */ private $initialized = true; /** * The wrapped Collection instance. * * @var Collection */ private $coll; /** * Creates a new persistent collection. * * @param EntityManager $em The EntityManager the collection will be associated with. * @param ClassMetadata $class The class descriptor of the entity type of this collection. * @param array The collection elements. */ public function __construct(EntityManager $em, $class, $coll) { $this->coll = $coll; $this->em = $em; $this->typeClass = $class; } /** * INTERNAL: * Sets the collection's owning entity together with the AssociationMapping that * describes the association between the owner and the elements of the collection. * * @param object $entity * @param AssociationMapping $assoc */ public function setOwner($entity, array $assoc) { $this->owner = $entity; $this->association = $assoc; $this->backRefFieldName = $assoc['inversedBy'] ?: $assoc['mappedBy']; } /** * INTERNAL: * Gets the collection owner. * * @return object */ public function getOwner() { return $this->owner; } public function getTypeClass() { return $this->typeClass; } /** * INTERNAL: * Adds an element to a collection during hydration. This will automatically * complete bidirectional associations in the case of a one-to-many association. * * @param mixed $element The element to add. */ public function hydrateAdd($element) { $this->coll->add($element); // If _backRefFieldName is set and its a one-to-many association, // we need to set the back reference. if ($this->backRefFieldName && $this->association['type'] === ClassMetadata::ONE_TO_MANY) { // Set back reference to owner $this->typeClass->reflFields[$this->backRefFieldName]->setValue( $element, $this->owner ); $this->em->getUnitOfWork()->setOriginalEntityProperty( spl_object_hash($element), $this->backRefFieldName, $this->owner ); } } /** * INTERNAL: * Sets a keyed element in the collection during hydration. * * @param mixed $key The key to set. * $param mixed $value The element to set. */ public function hydrateSet($key, $element) { $this->coll->set($key, $element); // If _backRefFieldName is set, then the association is bidirectional // and we need to set the back reference. if ($this->backRefFieldName && $this->association['type'] === ClassMetadata::ONE_TO_MANY) { // Set back reference to owner $this->typeClass->reflFields[$this->backRefFieldName]->setValue( $element, $this->owner ); } } /** * Initializes the collection by loading its contents from the database * if the collection is not yet initialized. */ public function initialize() { if ($this->initialized || ! $this->association) { return; } // Has NEW objects added through add(). Remember them. $newObjects = array(); if ($this->isDirty) { $newObjects = $this->coll->toArray(); } $this->coll->clear(); $this->em->getUnitOfWork()->loadCollection($this); $this->takeSnapshot(); // Reattach NEW objects added through add(), if any. if ($newObjects) { foreach ($newObjects as $obj) { $this->coll->add($obj); } $this->isDirty = true; } $this->initialized = true; } /** * INTERNAL: * Tells this collection to take a snapshot of its current state. */ public function takeSnapshot() { $this->snapshot = $this->coll->toArray(); $this->isDirty = false; } /** * INTERNAL: * Returns the last snapshot of the elements in the collection. * * @return array The last snapshot of the elements. */ public function getSnapshot() { return $this->snapshot; } /** * INTERNAL: * getDeleteDiff * * @return array */ public function getDeleteDiff() { return array_udiff_assoc( $this->snapshot, $this->coll->toArray(), function($a, $b) { return $a === $b ? 0 : 1; } ); } /** * INTERNAL: * getInsertDiff * * @return array */ public function getInsertDiff() { return array_udiff_assoc( $this->coll->toArray(), $this->snapshot, function($a, $b) { return $a === $b ? 0 : 1; } ); } /** * INTERNAL: Gets the association mapping of the collection. * * @return array */ public function getMapping() { return $this->association; } /** * Marks this collection as changed/dirty. */ private function changed() { if ($this->isDirty) { return; } $this->isDirty = true; if ($this->association !== null && $this->association['isOwningSide'] && $this->association['type'] === ClassMetadata::MANY_TO_MANY && $this->owner && $this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) { $this->em->getUnitOfWork()->scheduleForDirtyCheck($this->owner); } } /** * Gets a boolean flag indicating whether this collection is dirty which means * its state needs to be synchronized with the database. * * @return boolean TRUE if the collection is dirty, FALSE otherwise. */ public function isDirty() { return $this->isDirty; } /** * Sets a boolean flag, indicating whether this collection is dirty. * * @param boolean $dirty Whether the collection should be marked dirty or not. */ public function setDirty($dirty) { $this->isDirty = $dirty; } /** * Sets the initialized flag of the collection, forcing it into that state. * * @param boolean $bool */ public function setInitialized($bool) { $this->initialized = $bool; } /** * Checks whether this collection has been initialized. * * @return boolean */ public function isInitialized() { return $this->initialized; } /** {@inheritdoc} */ public function first() { $this->initialize(); return $this->coll->first(); } /** {@inheritdoc} */ public function last() { $this->initialize(); return $this->coll->last(); } /** * {@inheritdoc} */ public function remove($key) { // TODO: If the keys are persistent as well (not yet implemented) // and the collection is not initialized and orphanRemoval is // not used we can issue a straight SQL delete/update on the // association (table). Without initializing the collection. $this->initialize(); $removed = $this->coll->remove($key); if ( ! $removed) { return $removed; } $this->changed(); if ($this->association !== null && $this->association['type'] & ClassMetadata::TO_MANY && $this->owner && $this->association['orphanRemoval']) { $this->em->getUnitOfWork()->scheduleOrphanRemoval($removed); } return $removed; } /** * {@inheritdoc} */ public function removeElement($element) { if ( ! $this->initialized && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { if ($this->coll->contains($element)) { return $this->coll->removeElement($element); } $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association); if ($persister->removeElement($this, $element)) { return $element; } return null; } $this->initialize(); $removed = $this->coll->removeElement($element); if ( ! $removed) { return $removed; } $this->changed(); if ($this->association !== null && $this->association['type'] & ClassMetadata::TO_MANY && $this->owner && $this->association['orphanRemoval']) { $this->em->getUnitOfWork()->scheduleOrphanRemoval($element); } return $removed; } /** * {@inheritdoc} */ public function containsKey($key) { $this->initialize(); return $this->coll->containsKey($key); } /** * {@inheritdoc} */ public function contains($element) { if ( ! $this->initialized && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association); return $this->coll->contains($element) || $persister->contains($this, $element); } $this->initialize(); return $this->coll->contains($element); } /** * {@inheritdoc} */ public function exists(Closure $p) { $this->initialize(); return $this->coll->exists($p); } /** * {@inheritdoc} */ public function indexOf($element) { $this->initialize(); return $this->coll->indexOf($element); } /** * {@inheritdoc} */ public function get($key) { $this->initialize(); return $this->coll->get($key); } /** * {@inheritdoc} */ public function getKeys() { $this->initialize(); return $this->coll->getKeys(); } /** * {@inheritdoc} */ public function getValues() { $this->initialize(); return $this->coll->getValues(); } /** * {@inheritdoc} */ public function count() { if ( ! $this->initialized && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association); return $persister->count($this) + ($this->isDirty ? $this->coll->count() : 0); } $this->initialize(); return $this->coll->count(); } /** * {@inheritdoc} */ public function set($key, $value) { $this->initialize(); $this->coll->set($key, $value); $this->changed(); } /** * {@inheritdoc} */ public function add($value) { $this->coll->add($value); $this->changed(); return true; } /** * {@inheritdoc} */ public function isEmpty() { $this->initialize(); return $this->coll->isEmpty(); } /** * {@inheritdoc} */ public function getIterator() { $this->initialize(); return $this->coll->getIterator(); } /** * {@inheritdoc} */ public function map(Closure $func) { $this->initialize(); return $this->coll->map($func); } /** * {@inheritdoc} */ public function filter(Closure $p) { $this->initialize(); return $this->coll->filter($p); } /** * {@inheritdoc} */ public function forAll(Closure $p) { $this->initialize(); return $this->coll->forAll($p); } /** * {@inheritdoc} */ public function partition(Closure $p) { $this->initialize(); return $this->coll->partition($p); } /** * {@inheritdoc} */ public function toArray() { $this->initialize(); return $this->coll->toArray(); } /** * {@inheritdoc} */ public function clear() { if ($this->initialized && $this->isEmpty()) { return; } $uow = $this->em->getUnitOfWork(); if ($this->association['type'] & ClassMetadata::TO_MANY && $this->association['orphanRemoval'] && $this->owner) { // we need to initialize here, as orphan removal acts like implicit cascadeRemove, // hence for event listeners we need the objects in memory. $this->initialize(); foreach ($this->coll as $element) { $uow->scheduleOrphanRemoval($element); } } $this->coll->clear(); $this->initialized = true; // direct call, {@link initialize()} is too expensive if ($this->association['isOwningSide'] && $this->owner) { $this->changed(); $uow->scheduleCollectionDeletion($this); $this->takeSnapshot(); } } /** * Called by PHP when this collection is serialized. Ensures that only the * elements are properly serialized. * * @internal Tried to implement Serializable first but that did not work well * with circular references. This solution seems simpler and works well. */ public function __sleep() { return array('coll', 'initialized'); } /* ArrayAccess implementation */ /** * @see containsKey() */ public function offsetExists($offset) { return $this->containsKey($offset); } /** * @see get() */ public function offsetGet($offset) { return $this->get($offset); } /** * @see add() * @see set() */ public function offsetSet($offset, $value) { if ( ! isset($offset)) { return $this->add($value); } return $this->set($offset, $value); } /** * @see remove() */ public function offsetUnset($offset) { return $this->remove($offset); } public function key() { return $this->coll->key(); } /** * Gets the element of the collection at the current iterator position. */ public function current() { return $this->coll->current(); } /** * Moves the internal iterator position to the next element. */ public function next() { return $this->coll->next(); } /** * Retrieves the wrapped Collection instance. * * @return \Doctrine\Common\Collections\Collection */ public function unwrap() { return $this->coll; } /** * Extract a slice of $length elements starting at position $offset from the Collection. * * If $length is null it returns all elements from $offset to the end of the Collection. * Keys have to be preserved by this method. Calling this method will only return the * selected slice and NOT change the elements contained in the collection slice is called on. * * @param int $offset * @param int $length * * @return array */ public function slice($offset, $length = null) { if ( ! $this->initialized && ! $this->isDirty && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association); return $persister->slice($this, $offset, $length); } $this->initialize(); return $this->coll->slice($offset, $length); } /** * Cleanup internal state of cloned persistent collection. * * The following problems have to be prevented: * 1. Added entities are added to old PC * 2. New collection is not dirty, if reused on other entity nothing * changes. * 3. Snapshot leads to invalid diffs being generated. * 4. Lazy loading grabs entities from old owner object. * 5. New collection is connected to old owner and leads to duplicate keys. */ public function __clone() { if (is_object($this->coll)) { $this->coll = clone $this->coll; } $this->initialize(); $this->owner = null; $this->snapshot = array(); $this->changed(); } /** * Select all elements from a selectable that match the expression and * return a new collection containing these elements. * * @param \Doctrine\Common\Collections\Criteria $criteria * @return Collection */ public function matching(Criteria $criteria) { if ($this->isDirty) { $this->initialize(); } if ($this->initialized) { return $this->coll->matching($criteria); } if ($this->association['type'] !== ClassMetadata::ONE_TO_MANY) { throw new \RuntimeException("Matching Criteria on PersistentCollection only works on OneToMany assocations at the moment."); } $id = $this->em ->getClassMetadata(get_class($this->owner)) ->getSingleIdReflectionProperty() ->getValue($this->owner); $builder = Criteria::expr(); $ownerExpression = $builder->eq($this->backRefFieldName, $id); $expression = $criteria->getWhereExpression(); $expression = $expression ? $builder->andX($expression, $ownerExpression) : $ownerExpression; $criteria->where($expression); $persister = $this->em->getUnitOfWork()->getEntityPersister($this->association['targetEntity']); return new ArrayCollection($persister->loadCriteria($criteria)); } } DoctrineORM-2.3.3/Doctrine/ORM/PessimisticLockException.php0000644000175100017510000000267112143374607023670 0ustar beberleibeberlei. */ namespace Doctrine\ORM; /** * Pessimistic Lock Exception * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @author Benjamin Eberlei * @author Roman Borschel */ class PessimisticLockException extends ORMException { public static function lockFailed() { return new self("The pessimistic lock failed."); } } DoctrineORM-2.3.3/Doctrine/ORM/Query.php0000644000175100017510000004121312143374607020004 0ustar beberleibeberlei. */ namespace Doctrine\ORM; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\DBAL\LockMode; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\ParserResult; use Doctrine\ORM\Query\QueryException; /** * A Query object represents a DQL query. * * @since 1.0 * @author Guilherme Blanco * @author Konsta Vesterinen * @author Roman Borschel */ final class Query extends AbstractQuery { /** * A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts. */ const STATE_CLEAN = 1; /** * A query object is in state DIRTY when it has DQL parts that have not yet been * parsed/processed. This is automatically defined as DIRTY when addDqlQueryPart * is called. */ const STATE_DIRTY = 2; /* Query HINTS */ /** * The refresh hint turns any query into a refresh query with the result that * any local changes in entities are overridden with the fetched values. * * @var string */ const HINT_REFRESH = 'doctrine.refresh'; /** * Internal hint: is set to the proxy entity that is currently triggered for loading * * @var string */ const HINT_REFRESH_ENTITY = 'doctrine.refresh.entity'; /** * The forcePartialLoad query hint forces a particular query to return * partial objects. * * @var string * @todo Rename: HINT_OPTIMIZE */ const HINT_FORCE_PARTIAL_LOAD = 'doctrine.forcePartialLoad'; /** * The includeMetaColumns query hint causes meta columns like foreign keys and * discriminator columns to be selected and returned as part of the query result. * * This hint does only apply to non-object queries. * * @var string */ const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns'; /** * An array of class names that implement \Doctrine\ORM\Query\TreeWalker and * are iterated and executed after the DQL has been parsed into an AST. * * @var string */ const HINT_CUSTOM_TREE_WALKERS = 'doctrine.customTreeWalkers'; /** * A string with a class name that implements \Doctrine\ORM\Query\TreeWalker * and is used for generating the target SQL from any DQL AST tree. * * @var string */ const HINT_CUSTOM_OUTPUT_WALKER = 'doctrine.customOutputWalker'; //const HINT_READ_ONLY = 'doctrine.readOnly'; /** * @var string */ const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration'; /** * @var string */ const HINT_LOCK_MODE = 'doctrine.lockMode'; /** * @var integer $_state The current state of this query. */ private $_state = self::STATE_CLEAN; /** * @var string $_dql Cached DQL query. */ private $_dql = null; /** * @var \Doctrine\ORM\Query\ParserResult The parser result that holds DQL => SQL information. */ private $_parserResult; /** * @var integer The first result to return (the "offset"). */ private $_firstResult = null; /** * @var integer The maximum number of results to return (the "limit"). */ private $_maxResults = null; /** * @var CacheDriver The cache driver used for caching queries. */ private $_queryCache; /** * @var boolean Boolean value that indicates whether or not expire the query cache. */ private $_expireQueryCache = false; /** * @var int Query Cache lifetime. */ private $_queryCacheTTL; /** * @var boolean Whether to use a query cache, if available. Defaults to TRUE. */ private $_useQueryCache = true; /** * Initializes a new Query instance. * * @param \Doctrine\ORM\EntityManager $entityManager */ /*public function __construct(EntityManager $entityManager) { parent::__construct($entityManager); }*/ /** * Gets the SQL query/queries that correspond to this DQL query. * * @return mixed The built sql query or an array of all sql queries. * @override */ public function getSQL() { return $this->_parse()->getSQLExecutor()->getSQLStatements(); } /** * Returns the corresponding AST for this DQL query. * * @return \Doctrine\ORM\Query\AST\SelectStatement | * \Doctrine\ORM\Query\AST\UpdateStatement | * \Doctrine\ORM\Query\AST\DeleteStatement */ public function getAST() { $parser = new Parser($this); return $parser->getAST(); } /** * Parses the DQL query, if necessary, and stores the parser result. * * Note: Populates $this->_parserResult as a side-effect. * * @return \Doctrine\ORM\Query\ParserResult */ private function _parse() { // Return previous parser result if the query and the filter collection are both clean if ($this->_state === self::STATE_CLEAN && $this->_em->isFiltersStateClean()) { return $this->_parserResult; } $this->_state = self::STATE_CLEAN; // Check query cache. if ( ! ($this->_useQueryCache && ($queryCache = $this->getQueryCacheDriver()))) { $parser = new Parser($this); $this->_parserResult = $parser->parse(); return $this->_parserResult; } $hash = $this->_getQueryCacheId(); $cached = $this->_expireQueryCache ? false : $queryCache->fetch($hash); if ($cached instanceof ParserResult) { // Cache hit. $this->_parserResult = $cached; return $this->_parserResult; } // Cache miss. $parser = new Parser($this); $this->_parserResult = $parser->parse(); $queryCache->save($hash, $this->_parserResult, $this->_queryCacheTTL); return $this->_parserResult; } /** * {@inheritdoc} */ protected function _doExecute() { $executor = $this->_parse()->getSqlExecutor(); if ($this->_queryCacheProfile) { $executor->setQueryCacheProfile($this->_queryCacheProfile); } // Prepare parameters $paramMappings = $this->_parserResult->getParameterMappings(); if (count($paramMappings) != count($this->parameters)) { throw QueryException::invalidParameterNumber(); } list($sqlParams, $types) = $this->processParameterMappings($paramMappings); if ($this->_resultSetMapping === null) { $this->_resultSetMapping = $this->_parserResult->getResultSetMapping(); } return $executor->execute($this->_em->getConnection(), $sqlParams, $types); } /** * Processes query parameter mappings * * @param array $paramMappings * @return array */ private function processParameterMappings($paramMappings) { $sqlParams = array(); $types = array(); foreach ($this->parameters as $parameter) { $key = $parameter->getName(); if ( ! isset($paramMappings[$key])) { throw QueryException::unknownParameter($key); } $value = $this->processParameterValue($parameter->getValue()); $type = ($parameter->getValue() === $value) ? $parameter->getType() : Query\ParameterTypeInferer::inferType($value); foreach ($paramMappings[$key] as $position) { $types[$position] = $type; } $sqlPositions = $paramMappings[$key]; // optimized multi value sql positions away for now, // they are not allowed in DQL anyways. $value = array($value); $countValue = count($value); for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) { $sqlParams[$sqlPositions[$i]] = $value[($i % $countValue)]; } } if (count($sqlParams) != count($types)) { throw QueryException::parameterTypeMissmatch(); } if ($sqlParams) { ksort($sqlParams); $sqlParams = array_values($sqlParams); ksort($types); $types = array_values($types); } return array($sqlParams, $types); } /** * Defines a cache driver to be used for caching queries. * * @param Doctrine_Cache_Interface|null $driver Cache driver * @return Query This query instance. */ public function setQueryCacheDriver($queryCache) { $this->_queryCache = $queryCache; return $this; } /** * Defines whether the query should make use of a query cache, if available. * * @param boolean $bool * @return @return Query This query instance. */ public function useQueryCache($bool) { $this->_useQueryCache = $bool; return $this; } /** * Returns the cache driver used for query caching. * * @return CacheDriver The cache driver used for query caching or NULL, if * this Query does not use query caching. */ public function getQueryCacheDriver() { if ($this->_queryCache) { return $this->_queryCache; } return $this->_em->getConfiguration()->getQueryCacheImpl(); } /** * Defines how long the query cache will be active before expire. * * @param integer $timeToLive How long the cache entry is valid * @return Query This query instance. */ public function setQueryCacheLifetime($timeToLive) { if ($timeToLive !== null) { $timeToLive = (int) $timeToLive; } $this->_queryCacheTTL = $timeToLive; return $this; } /** * Retrieves the lifetime of resultset cache. * * @return int */ public function getQueryCacheLifetime() { return $this->_queryCacheTTL; } /** * Defines if the query cache is active or not. * * @param boolean $expire Whether or not to force query cache expiration. * @return Query This query instance. */ public function expireQueryCache($expire = true) { $this->_expireQueryCache = $expire; return $this; } /** * Retrieves if the query cache is active or not. * * @return bool */ public function getExpireQueryCache() { return $this->_expireQueryCache; } /** * @override */ public function free() { parent::free(); $this->_dql = null; $this->_state = self::STATE_CLEAN; } /** * Sets a DQL query string. * * @param string $dqlQuery DQL Query * @return \Doctrine\ORM\AbstractQuery */ public function setDQL($dqlQuery) { if ($dqlQuery !== null) { $this->_dql = $dqlQuery; $this->_state = self::STATE_DIRTY; } return $this; } /** * Returns the DQL query that is represented by this query object. * * @return string DQL query */ public function getDQL() { return $this->_dql; } /** * Returns the state of this query object * By default the type is Doctrine_ORM_Query_Abstract::STATE_CLEAN but if it appears any unprocessed DQL * part, it is switched to Doctrine_ORM_Query_Abstract::STATE_DIRTY. * * @see AbstractQuery::STATE_CLEAN * @see AbstractQuery::STATE_DIRTY * * @return integer Return the query state */ public function getState() { return $this->_state; } /** * Method to check if an arbitrary piece of DQL exists * * @param string $dql Arbitrary piece of DQL to check for * @return boolean */ public function contains($dql) { return stripos($this->getDQL(), $dql) === false ? false : true; } /** * Sets the position of the first result to retrieve (the "offset"). * * @param integer $firstResult The first result to return. * @return Query This query object. */ public function setFirstResult($firstResult) { $this->_firstResult = $firstResult; $this->_state = self::STATE_DIRTY; return $this; } /** * Gets the position of the first result the query object was set to retrieve (the "offset"). * Returns NULL if {@link setFirstResult} was not applied to this query. * * @return integer The position of the first result. */ public function getFirstResult() { return $this->_firstResult; } /** * Sets the maximum number of results to retrieve (the "limit"). * * @param integer $maxResults * @return Query This query object. */ public function setMaxResults($maxResults) { $this->_maxResults = $maxResults; $this->_state = self::STATE_DIRTY; return $this; } /** * Gets the maximum number of results the query object was set to retrieve (the "limit"). * Returns NULL if {@link setMaxResults} was not applied to this query. * * @return integer Maximum number of results. */ public function getMaxResults() { return $this->_maxResults; } /** * Executes the query and returns an IterableResult that can be used to incrementally * iterated over the result. * * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters The query parameters. * @param integer $hydrationMode The hydration mode to use. * @return \Doctrine\ORM\Internal\Hydration\IterableResult */ public function iterate($parameters = null, $hydrationMode = self::HYDRATE_OBJECT) { $this->setHint(self::HINT_INTERNAL_ITERATION, true); return parent::iterate($parameters, $hydrationMode); } /** * {@inheritdoc} */ public function setHint($name, $value) { $this->_state = self::STATE_DIRTY; return parent::setHint($name, $value); } /** * {@inheritdoc} */ public function setHydrationMode($hydrationMode) { $this->_state = self::STATE_DIRTY; return parent::setHydrationMode($hydrationMode); } /** * Set the lock mode for this Query. * * @see \Doctrine\DBAL\LockMode * @param int $lockMode * @return Query */ public function setLockMode($lockMode) { if (in_array($lockMode, array(LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE))) { if ( ! $this->_em->getConnection()->isTransactionActive()) { throw TransactionRequiredException::transactionRequired(); } } $this->setHint(self::HINT_LOCK_MODE, $lockMode); return $this; } /** * Get the current lock mode for this query. * * @return int */ public function getLockMode() { $lockMode = $this->getHint(self::HINT_LOCK_MODE); if ( ! $lockMode) { return LockMode::NONE; } return $lockMode; } /** * Generate a cache id for the query cache - reusing the Result-Cache-Id generator. * * The query cache * * @return string */ protected function _getQueryCacheId() { ksort($this->_hints); return md5( $this->getDql() . var_export($this->_hints, true) . ($this->_em->hasFilters() ? $this->_em->getFilters()->getHash() : '') . '&firstResult=' . $this->_firstResult . '&maxResult=' . $this->_maxResults . '&hydrationMode='.$this->_hydrationMode.'DOCTRINE_QUERY_CACHE_SALT' ); } /** * Cleanup Query resource when clone is called. * * @return void */ public function __clone() { parent::__clone(); $this->_state = self::STATE_DIRTY; } } DoctrineORM-2.3.3/Doctrine/ORM/QueryBuilder.php0000644000175100017510000010521212143374607021313 0ustar beberleibeberlei. */ namespace Doctrine\ORM; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Query\Expr; /** * This class is responsible for building DQL query strings via an object oriented * PHP interface. * * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class QueryBuilder { /* The query types. */ const SELECT = 0; const DELETE = 1; const UPDATE = 2; /** The builder states. */ const STATE_DIRTY = 0; const STATE_CLEAN = 1; /** * @var EntityManager The EntityManager used by this QueryBuilder. */ private $_em; /** * @var array The array of DQL parts collected. */ private $_dqlParts = array( 'distinct' => false, 'select' => array(), 'from' => array(), 'join' => array(), 'set' => array(), 'where' => null, 'groupBy' => array(), 'having' => null, 'orderBy' => array() ); /** * @var integer The type of query this is. Can be select, update or delete. */ private $_type = self::SELECT; /** * @var integer The state of the query object. Can be dirty or clean. */ private $_state = self::STATE_CLEAN; /** * @var string The complete DQL string for this query. */ private $_dql; /** * @var \Doctrine\Common\Collections\ArrayCollection The query parameters. */ private $parameters = array(); /** * @var integer The index of the first result to retrieve. */ private $_firstResult = null; /** * @var integer The maximum number of results to retrieve. */ private $_maxResults = null; /** * @var array Keeps root entity alias names for join entities. */ private $joinRootAliases = array(); /** * Initializes a new QueryBuilder that uses the given EntityManager. * * @param EntityManager $em The EntityManager to use. */ public function __construct(EntityManager $em) { $this->_em = $em; $this->parameters = new ArrayCollection(); } /** * Gets an ExpressionBuilder used for object-oriented construction of query expressions. * This producer method is intended for convenient inline usage. Example: * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->where($qb->expr()->eq('u.id', 1)); * * * For more complex expression construction, consider storing the expression * builder object in a local variable. * * @return Query\Expr */ public function expr() { return $this->_em->getExpressionBuilder(); } /** * Get the type of the currently built query. * * @return integer */ public function getType() { return $this->_type; } /** * Get the associated EntityManager for this query builder. * * @return EntityManager */ public function getEntityManager() { return $this->_em; } /** * Get the state of this query builder instance. * * @return integer Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN. */ public function getState() { return $this->_state; } /** * Get the complete DQL string formed by the current specifications of this QueryBuilder. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * echo $qb->getDql(); // SELECT u FROM User u * * * @return string The DQL query string. */ public function getDQL() { if ($this->_dql !== null && $this->_state === self::STATE_CLEAN) { return $this->_dql; } $dql = ''; switch ($this->_type) { case self::DELETE: $dql = $this->_getDQLForDelete(); break; case self::UPDATE: $dql = $this->_getDQLForUpdate(); break; case self::SELECT: default: $dql = $this->_getDQLForSelect(); break; } $this->_state = self::STATE_CLEAN; $this->_dql = $dql; return $dql; } /** * Constructs a Query instance from the current specifications of the builder. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u'); * $q = $qb->getQuery(); * $results = $q->execute(); * * * @return Query */ public function getQuery() { $parameters = clone $this->parameters; return $this->_em->createQuery($this->getDQL()) ->setParameters($parameters) ->setFirstResult($this->_firstResult) ->setMaxResults($this->_maxResults); } /** * Finds the root entity alias of the joined entity. * * @param string $alias The alias of the new join entity * @param string $parentAlias The parent entity alias of the join relationship * @return string */ private function findRootAlias($alias, $parentAlias) { $rootAlias = null; if (in_array($parentAlias, $this->getRootAliases())) { $rootAlias = $parentAlias; } elseif (isset($this->joinRootAliases[$parentAlias])) { $rootAlias = $this->joinRootAliases[$parentAlias]; } else { // Should never happen with correct joining order. Might be // thoughtful to throw exception instead. $rootAlias = $this->getRootAlias(); } $this->joinRootAliases[$alias] = $rootAlias; return $rootAlias; } /** * Gets the FIRST root alias of the query. This is the first entity alias involved * in the construction of the query. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u'); * * echo $qb->getRootAlias(); // u * * * @deprecated Please use $qb->getRootAliases() instead. * @return string $rootAlias */ public function getRootAlias() { $aliases = $this->getRootAliases(); return $aliases[0]; } /** * Gets the root aliases of the query. This is the entity aliases involved * in the construction of the query. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u'); * * $qb->getRootAliases(); // array('u') * * * @return array $rootAliases */ public function getRootAliases() { $aliases = array(); foreach ($this->_dqlParts['from'] as &$fromClause) { if (is_string($fromClause)) { $spacePos = strrpos($fromClause, ' '); $from = substr($fromClause, 0, $spacePos); $alias = substr($fromClause, $spacePos + 1); $fromClause = new Query\Expr\From($from, $alias); } $aliases[] = $fromClause->getAlias(); } return $aliases; } /** * Gets the root entities of the query. This is the entity aliases involved * in the construction of the query. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u'); * * $qb->getRootEntities(); // array('User') * * * @return array $rootEntities */ public function getRootEntities() { $entities = array(); foreach ($this->_dqlParts['from'] as &$fromClause) { if (is_string($fromClause)) { $spacePos = strrpos($fromClause, ' '); $from = substr($fromClause, 0, $spacePos); $alias = substr($fromClause, $spacePos + 1); $fromClause = new Query\Expr\From($from, $alias); } $entities[] = $fromClause->getFrom(); } return $entities; } /** * Sets a query parameter for the query being constructed. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->where('u.id = :user_id') * ->setParameter('user_id', 1); * * * @param string|integer $key The parameter position or name. * @param mixed $value The parameter value. * @param string|null $type PDO::PARAM_* or \Doctrine\DBAL\Types\Type::* constant * @return QueryBuilder This QueryBuilder instance. */ public function setParameter($key, $value, $type = null) { $filteredParameters = $this->parameters->filter( function ($parameter) use ($key) { // Must not be identical because of string to integer conversion return ($key == $parameter->getName()); } ); if (count($filteredParameters)) { $parameter = $filteredParameters->first(); $parameter->setValue($value, $type); return $this; } $parameter = new Query\Parameter($key, $value, $type); $this->parameters->add($parameter); return $this; } /** * Sets a collection of query parameters for the query being constructed. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->where('u.id = :user_id1 OR u.id = :user_id2') * ->setParameters(new ArrayCollection(array( * new Parameter('user_id1', 1), * new Parameter('user_id2', 2) ))); * * * @param \Doctrine\Common\Collections\ArrayCollection|array $params The query parameters to set. * @return QueryBuilder This QueryBuilder instance. */ public function setParameters($parameters) { // BC compatibility with 2.3- if (is_array($parameters)) { $parameterCollection = new ArrayCollection(); foreach ($parameters as $key => $value) { $parameter = new Query\Parameter($key, $value); $parameterCollection->add($parameter); } $parameters = $parameterCollection; } $this->parameters = $parameters; return $this; } /** * Gets all defined query parameters for the query being constructed. * * @return \Doctrine\Common\Collections\ArrayCollection The currently defined query parameters. */ public function getParameters() { return $this->parameters; } /** * Gets a (previously set) query parameter of the query being constructed. * * @param mixed $key The key (index or name) of the bound parameter. * * @return Query\Parameter|null The value of the bound parameter. */ public function getParameter($key) { $filteredParameters = $this->parameters->filter( function ($parameter) use ($key) { // Must not be identical because of string to integer conversion return ($key == $parameter->getName()); } ); return count($filteredParameters) ? $filteredParameters->first() : null; } /** * Sets the position of the first result to retrieve (the "offset"). * * @param integer $firstResult The first result to return. * @return QueryBuilder This QueryBuilder instance. */ public function setFirstResult($firstResult) { $this->_firstResult = $firstResult; return $this; } /** * Gets the position of the first result the query object was set to retrieve (the "offset"). * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder. * * @return integer The position of the first result. */ public function getFirstResult() { return $this->_firstResult; } /** * Sets the maximum number of results to retrieve (the "limit"). * * @param integer $maxResults The maximum number of results to retrieve. * @return QueryBuilder This QueryBuilder instance. */ public function setMaxResults($maxResults) { $this->_maxResults = $maxResults; return $this; } /** * Gets the maximum number of results the query object was set to retrieve (the "limit"). * Returns NULL if {@link setMaxResults} was not applied to this query builder. * * @return integer Maximum number of results. */ public function getMaxResults() { return $this->_maxResults; } /** * Either appends to or replaces a single, generic query part. * * The available parts are: 'select', 'from', 'join', 'set', 'where', * 'groupBy', 'having' and 'orderBy'. * * @param string $dqlPartName * @param string $dqlPart * @param string $append * @return QueryBuilder This QueryBuilder instance. */ public function add($dqlPartName, $dqlPart, $append = false) { $isMultiple = is_array($this->_dqlParts[$dqlPartName]); // This is introduced for backwards compatibility reasons. // TODO: Remove for 3.0 if ($dqlPartName == 'join') { $newDqlPart = array(); foreach ($dqlPart as $k => $v) { $k = is_numeric($k) ? $this->getRootAlias() : $k; $newDqlPart[$k] = $v; } $dqlPart = $newDqlPart; } if ($append && $isMultiple) { if (is_array($dqlPart)) { $key = key($dqlPart); $this->_dqlParts[$dqlPartName][$key][] = $dqlPart[$key]; } else { $this->_dqlParts[$dqlPartName][] = $dqlPart; } } else { $this->_dqlParts[$dqlPartName] = ($isMultiple) ? array($dqlPart) : $dqlPart; } $this->_state = self::STATE_DIRTY; return $this; } /** * Specifies an item that is to be returned in the query result. * Replaces any previously specified selections, if any. * * * $qb = $em->createQueryBuilder() * ->select('u', 'p') * ->from('User', 'u') * ->leftJoin('u.Phonenumbers', 'p'); * * * @param mixed $select The selection expressions. * @return QueryBuilder This QueryBuilder instance. */ public function select($select = null) { $this->_type = self::SELECT; if (empty($select)) { return $this; } $selects = is_array($select) ? $select : func_get_args(); return $this->add('select', new Expr\Select($selects), false); } /** * Add a DISTINCT flag to this query. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->distinct() * ->from('User', 'u'); * * * @param bool * @return QueryBuilder */ public function distinct($flag = true) { $this->_dqlParts['distinct'] = (bool) $flag; return $this; } /** * Adds an item that is to be returned in the query result. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->addSelect('p') * ->from('User', 'u') * ->leftJoin('u.Phonenumbers', 'p'); * * * @param mixed $select The selection expression. * @return QueryBuilder This QueryBuilder instance. */ public function addSelect($select = null) { $this->_type = self::SELECT; if (empty($select)) { return $this; } $selects = is_array($select) ? $select : func_get_args(); return $this->add('select', new Expr\Select($selects), true); } /** * Turns the query being built into a bulk delete query that ranges over * a certain entity type. * * * $qb = $em->createQueryBuilder() * ->delete('User', 'u') * ->where('u.id = :user_id'); * ->setParameter('user_id', 1); * * * @param string $delete The class/type whose instances are subject to the deletion. * @param string $alias The class/type alias used in the constructed query. * @return QueryBuilder This QueryBuilder instance. */ public function delete($delete = null, $alias = null) { $this->_type = self::DELETE; if ( ! $delete) { return $this; } return $this->add('from', new Expr\From($delete, $alias)); } /** * Turns the query being built into a bulk update query that ranges over * a certain entity type. * * * $qb = $em->createQueryBuilder() * ->update('User', 'u') * ->set('u.password', md5('password')) * ->where('u.id = ?'); * * * @param string $update The class/type whose instances are subject to the update. * @param string $alias The class/type alias used in the constructed query. * @return QueryBuilder This QueryBuilder instance. */ public function update($update = null, $alias = null) { $this->_type = self::UPDATE; if ( ! $update) { return $this; } return $this->add('from', new Expr\From($update, $alias)); } /** * Create and add a query root corresponding to the entity identified by the given alias, * forming a cartesian product with any existing query roots. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * * * @param string $from The class name. * @param string $alias The alias of the class. * @param string $indexBy The index for the from. * @return QueryBuilder This QueryBuilder instance. */ public function from($from, $alias, $indexBy = null) { return $this->add('from', new Expr\From($from, $alias, $indexBy), true); } /** * Creates and adds a join over an entity association to the query. * * The entities in the joined association will be fetched as part of the query * result if the alias used for the joined association is placed in the select * expressions. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->join('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); * * * @param string $join The relationship to join * @param string $alias The alias of the join * @param string $conditionType The condition type constant. Either ON or WITH. * @param string $condition The condition for the join * @param string $indexBy The index for the join * @return QueryBuilder This QueryBuilder instance. */ public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null) { return $this->innerJoin($join, $alias, $conditionType, $condition, $indexBy); } /** * Creates and adds a join over an entity association to the query. * * The entities in the joined association will be fetched as part of the query * result if the alias used for the joined association is placed in the select * expressions. * * [php] * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->innerJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); * * @param string $join The relationship to join * @param string $alias The alias of the join * @param string $conditionType The condition type constant. Either ON or WITH. * @param string $condition The condition for the join * @param string $indexBy The index for the join * @return QueryBuilder This QueryBuilder instance. */ public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null) { $parentAlias = substr($join, 0, strpos($join, '.')); $rootAlias = $this->findRootAlias($alias, $parentAlias); $join = new Expr\Join( Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition, $indexBy ); return $this->add('join', array($rootAlias => $join), true); } /** * Creates and adds a left join over an entity association to the query. * * The entities in the joined association will be fetched as part of the query * result if the alias used for the joined association is placed in the select * expressions. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); * * * @param string $join The relationship to join * @param string $alias The alias of the join * @param string $conditionType The condition type constant. Either ON or WITH. * @param string $condition The condition for the join * @param string $indexBy The index for the join * @return QueryBuilder This QueryBuilder instance. */ public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null) { $parentAlias = substr($join, 0, strpos($join, '.')); $rootAlias = $this->findRootAlias($alias, $parentAlias); $join = new Expr\Join( Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy ); return $this->add('join', array($rootAlias => $join), true); } /** * Sets a new value for a field in a bulk update query. * * * $qb = $em->createQueryBuilder() * ->update('User', 'u') * ->set('u.password', md5('password')) * ->where('u.id = ?'); * * * @param string $key The key/field to set. * @param string $value The value, expression, placeholder, etc. * @return QueryBuilder This QueryBuilder instance. */ public function set($key, $value) { return $this->add('set', new Expr\Comparison($key, Expr\Comparison::EQ, $value), true); } /** * Specifies one or more restrictions to the query result. * Replaces any previously specified restrictions, if any. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->where('u.id = ?'); * * // You can optionally programatically build and/or expressions * $qb = $em->createQueryBuilder(); * * $or = $qb->expr()->orx(); * $or->add($qb->expr()->eq('u.id', 1)); * $or->add($qb->expr()->eq('u.id', 2)); * * $qb->update('User', 'u') * ->set('u.password', md5('password')) * ->where($or); * * * @param mixed $predicates The restriction predicates. * @return QueryBuilder This QueryBuilder instance. */ public function where($predicates) { if ( ! (func_num_args() == 1 && $predicates instanceof Expr\Composite)) { $predicates = new Expr\Andx(func_get_args()); } return $this->add('where', $predicates); } /** * Adds one or more restrictions to the query results, forming a logical * conjunction with any previously specified restrictions. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->where('u.username LIKE ?') * ->andWhere('u.is_active = 1'); * * * @param mixed $where The query restrictions. * @return QueryBuilder This QueryBuilder instance. * @see where() */ public function andWhere($where) { $where = $this->getDQLPart('where'); $args = func_get_args(); if ($where instanceof Expr\Andx) { $where->addMultiple($args); } else { array_unshift($args, $where); $where = new Expr\Andx($args); } return $this->add('where', $where, true); } /** * Adds one or more restrictions to the query results, forming a logical * disjunction with any previously specified restrictions. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->where('u.id = 1') * ->orWhere('u.id = 2'); * * * @param mixed $where The WHERE statement * @return QueryBuilder $qb * @see where() */ public function orWhere($where) { $where = $this->getDqlPart('where'); $args = func_get_args(); if ($where instanceof Expr\Orx) { $where->addMultiple($args); } else { array_unshift($args, $where); $where = new Expr\Orx($args); } return $this->add('where', $where, true); } /** * Specifies a grouping over the results of the query. * Replaces any previously specified groupings, if any. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->groupBy('u.id'); * * * @param string $groupBy The grouping expression. * @return QueryBuilder This QueryBuilder instance. */ public function groupBy($groupBy) { return $this->add('groupBy', new Expr\GroupBy(func_get_args())); } /** * Adds a grouping expression to the query. * * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->groupBy('u.lastLogin'); * ->addGroupBy('u.createdAt') * * * @param string $groupBy The grouping expression. * @return QueryBuilder This QueryBuilder instance. */ public function addGroupBy($groupBy) { return $this->add('groupBy', new Expr\GroupBy(func_get_args()), true); } /** * Specifies a restriction over the groups of the query. * Replaces any previous having restrictions, if any. * * @param mixed $having The restriction over the groups. * @return QueryBuilder This QueryBuilder instance. */ public function having($having) { if ( ! (func_num_args() == 1 && ($having instanceof Expr\Andx || $having instanceof Expr\Orx))) { $having = new Expr\Andx(func_get_args()); } return $this->add('having', $having); } /** * Adds a restriction over the groups of the query, forming a logical * conjunction with any existing having restrictions. * * @param mixed $having The restriction to append. * @return QueryBuilder This QueryBuilder instance. */ public function andHaving($having) { $having = $this->getDqlPart('having'); $args = func_get_args(); if ($having instanceof Expr\Andx) { $having->addMultiple($args); } else { array_unshift($args, $having); $having = new Expr\Andx($args); } return $this->add('having', $having); } /** * Adds a restriction over the groups of the query, forming a logical * disjunction with any existing having restrictions. * * @param mixed $having The restriction to add. * @return QueryBuilder This QueryBuilder instance. */ public function orHaving($having) { $having = $this->getDqlPart('having'); $args = func_get_args(); if ($having instanceof Expr\Orx) { $having->addMultiple($args); } else { array_unshift($args, $having); $having = new Expr\Orx($args); } return $this->add('having', $having); } /** * Specifies an ordering for the query results. * Replaces any previously specified orderings, if any. * * @param string $sort The ordering expression. * @param string $order The ordering direction. * @return QueryBuilder This QueryBuilder instance. */ public function orderBy($sort, $order = null) { $orderBy = ($sort instanceof Expr\OrderBy) ? $sort : new Expr\OrderBy($sort, $order); return $this->add('orderBy', $orderBy); } /** * Adds an ordering to the query results. * * @param string $sort The ordering expression. * @param string $order The ordering direction. * @return QueryBuilder This QueryBuilder instance. */ public function addOrderBy($sort, $order = null) { return $this->add('orderBy', new Expr\OrderBy($sort, $order), true); } /** * Get a query part by its name. * * @param string $queryPartName * @return mixed $queryPart * @todo Rename: getQueryPart (or remove?) */ public function getDQLPart($queryPartName) { return $this->_dqlParts[$queryPartName]; } /** * Get all query parts. * * @return array $dqlParts * @todo Rename: getQueryParts (or remove?) */ public function getDQLParts() { return $this->_dqlParts; } private function _getDQLForDelete() { return 'DELETE' . $this->_getReducedDQLQueryPart('from', array('pre' => ' ', 'separator' => ', ')) . $this->_getReducedDQLQueryPart('where', array('pre' => ' WHERE ')) . $this->_getReducedDQLQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', ')); } private function _getDQLForUpdate() { return 'UPDATE' . $this->_getReducedDQLQueryPart('from', array('pre' => ' ', 'separator' => ', ')) . $this->_getReducedDQLQueryPart('set', array('pre' => ' SET ', 'separator' => ', ')) . $this->_getReducedDQLQueryPart('where', array('pre' => ' WHERE ')) . $this->_getReducedDQLQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', ')); } private function _getDQLForSelect() { $dql = 'SELECT' . ($this->_dqlParts['distinct']===true ? ' DISTINCT' : '') . $this->_getReducedDQLQueryPart('select', array('pre' => ' ', 'separator' => ', ')); $fromParts = $this->getDQLPart('from'); $joinParts = $this->getDQLPart('join'); $fromClauses = array(); // Loop through all FROM clauses if ( ! empty($fromParts)) { $dql .= ' FROM '; foreach ($fromParts as $from) { $fromClause = (string) $from; if ($from instanceof Expr\From && isset($joinParts[$from->getAlias()])) { foreach ($joinParts[$from->getAlias()] as $join) { $fromClause .= ' ' . ((string) $join); } } $fromClauses[] = $fromClause; } } $dql .= implode(', ', $fromClauses) . $this->_getReducedDQLQueryPart('where', array('pre' => ' WHERE ')) . $this->_getReducedDQLQueryPart('groupBy', array('pre' => ' GROUP BY ', 'separator' => ', ')) . $this->_getReducedDQLQueryPart('having', array('pre' => ' HAVING ')) . $this->_getReducedDQLQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', ')); return $dql; } private function _getReducedDQLQueryPart($queryPartName, $options = array()) { $queryPart = $this->getDQLPart($queryPartName); if (empty($queryPart)) { return (isset($options['empty']) ? $options['empty'] : ''); } return (isset($options['pre']) ? $options['pre'] : '') . (is_array($queryPart) ? implode($options['separator'], $queryPart) : $queryPart) . (isset($options['post']) ? $options['post'] : ''); } /** * Reset DQL parts * * @param array $parts * @return QueryBuilder */ public function resetDQLParts($parts = null) { if (is_null($parts)) { $parts = array_keys($this->_dqlParts); } foreach ($parts as $part) { $this->resetDQLPart($part); } return $this; } /** * Reset single DQL part * * @param string $part * @return QueryBuilder; */ public function resetDQLPart($part) { $this->_dqlParts[$part] = is_array($this->_dqlParts[$part]) ? array() : null; $this->_state = self::STATE_DIRTY; return $this; } /** * Gets a string representation of this QueryBuilder which corresponds to * the final DQL query being constructed. * * @return string The string representation of this QueryBuilder. */ public function __toString() { return $this->getDQL(); } /** * Deep clone of all expression objects in the DQL parts. * * @return void */ public function __clone() { foreach ($this->_dqlParts as $part => $elements) { if (is_array($this->_dqlParts[$part])) { foreach ($this->_dqlParts[$part] as $idx => $element) { if (is_object($element)) { $this->_dqlParts[$part][$idx] = clone $element; } } } else if (is_object($elements)) { $this->_dqlParts[$part] = clone $elements; } } $parameters = array(); foreach ($this->parameters as $parameter) { $parameters[] = clone $parameter; } $this->parameters = new ArrayCollection($parameters); } } DoctrineORM-2.3.3/Doctrine/ORM/README.markdown0000644000175100017510000000000012143374607020654 0ustar beberleibeberleiDoctrineORM-2.3.3/Doctrine/ORM/TransactionRequiredException.php0000644000175100017510000000303612143374607024545 0ustar beberleibeberlei. */ namespace Doctrine\ORM; /** * Is thrown when a transaction is required for the current operation, but there is none open. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @author Benjamin Eberlei * @author Roman Borschel */ class TransactionRequiredException extends ORMException { static public function transactionRequired() { return new self('An open transaction is required for this operation.'); } } DoctrineORM-2.3.3/Doctrine/ORM/UnexpectedResultException.php0000644000175100017510000000225612143374607024065 0ustar beberleibeberlei. */ namespace Doctrine\ORM; /** * Exception for a unexpected query result. * * @author Fabio B. Silva * @since 2.3 */ class UnexpectedResultException extends ORMException { } DoctrineORM-2.3.3/Doctrine/ORM/UnitOfWork.php0000644000175100017510000032036712143374607020760 0ustar beberleibeberlei. */ namespace Doctrine\ORM; use Exception, InvalidArgumentException, UnexpectedValueException, Doctrine\Common\Collections\ArrayCollection, Doctrine\Common\Collections\Collection, Doctrine\Common\NotifyPropertyChanged, Doctrine\Common\PropertyChangedListener, Doctrine\Common\Persistence\ObjectManagerAware, Doctrine\ORM\Event\LifecycleEventArgs, Doctrine\ORM\Mapping\ClassMetadata, Doctrine\ORM\Proxy\Proxy; /** * The UnitOfWork is responsible for tracking changes to objects during an * "object-level" transaction and for writing out changes to the database * in the correct order. * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @internal This class contains highly performance-sensitive code. */ class UnitOfWork implements PropertyChangedListener { /** * An entity is in MANAGED state when its persistence is managed by an EntityManager. */ const STATE_MANAGED = 1; /** * An entity is new if it has just been instantiated (i.e. using the "new" operator) * and is not (yet) managed by an EntityManager. */ const STATE_NEW = 2; /** * A detached entity is an instance with persistent state and identity that is not * (or no longer) associated with an EntityManager (and a UnitOfWork). */ const STATE_DETACHED = 3; /** * A removed entity instance is an instance with a persistent identity, * associated with an EntityManager, whose persistent state will be deleted * on commit. */ const STATE_REMOVED = 4; /** * The identity map that holds references to all managed entities that have * an identity. The entities are grouped by their class name. * Since all classes in a hierarchy must share the same identifier set, * we always take the root class name of the hierarchy. * * @var array */ private $identityMap = array(); /** * Map of all identifiers of managed entities. * Keys are object ids (spl_object_hash). * * @var array */ private $entityIdentifiers = array(); /** * Map of the original entity data of managed entities. * Keys are object ids (spl_object_hash). This is used for calculating changesets * at commit time. * * @var array * @internal Note that PHPs "copy-on-write" behavior helps a lot with memory usage. * A value will only really be copied if the value in the entity is modified * by the user. */ private $originalEntityData = array(); /** * Map of entity changes. Keys are object ids (spl_object_hash). * Filled at the beginning of a commit of the UnitOfWork and cleaned at the end. * * @var array */ private $entityChangeSets = array(); /** * The (cached) states of any known entities. * Keys are object ids (spl_object_hash). * * @var array */ private $entityStates = array(); /** * Map of entities that are scheduled for dirty checking at commit time. * This is only used for entities with a change tracking policy of DEFERRED_EXPLICIT. * Keys are object ids (spl_object_hash). * * @var array * @todo rename: scheduledForSynchronization */ private $scheduledForDirtyCheck = array(); /** * A list of all pending entity insertions. * * @var array */ private $entityInsertions = array(); /** * A list of all pending entity updates. * * @var array */ private $entityUpdates = array(); /** * Any pending extra updates that have been scheduled by persisters. * * @var array */ private $extraUpdates = array(); /** * A list of all pending entity deletions. * * @var array */ private $entityDeletions = array(); /** * All pending collection deletions. * * @var array */ private $collectionDeletions = array(); /** * All pending collection updates. * * @var array */ private $collectionUpdates = array(); /** * List of collections visited during changeset calculation on a commit-phase of a UnitOfWork. * At the end of the UnitOfWork all these collections will make new snapshots * of their data. * * @var array */ private $visitedCollections = array(); /** * The EntityManager that "owns" this UnitOfWork instance. * * @var \Doctrine\ORM\EntityManager */ private $em; /** * The calculator used to calculate the order in which changes to * entities need to be written to the database. * * @var \Doctrine\ORM\Internal\CommitOrderCalculator */ private $commitOrderCalculator; /** * The entity persister instances used to persist entity instances. * * @var array */ private $persisters = array(); /** * The collection persister instances used to persist collections. * * @var array */ private $collectionPersisters = array(); /** * The EventManager used for dispatching events. * * @var \Doctrine\Common\EventManager */ private $evm; /** * Orphaned entities that are scheduled for removal. * * @var array */ private $orphanRemovals = array(); /** * Read-Only objects are never evaluated * * @var array */ private $readOnlyObjects = array(); /** * Map of Entity Class-Names and corresponding IDs that should eager loaded when requested. * * @var array */ private $eagerLoadingEntities = array(); /** * Initializes a new UnitOfWork instance, bound to the given EntityManager. * * @param \Doctrine\ORM\EntityManager $em */ public function __construct(EntityManager $em) { $this->em = $em; $this->evm = $em->getEventManager(); } /** * Commits the UnitOfWork, executing all operations that have been postponed * up to this point. The state of all managed entities will be synchronized with * the database. * * The operations are executed in the following order: * * 1) All entity insertions * 2) All entity updates * 3) All collection deletions * 4) All collection updates * 5) All entity deletions * * @param null|object|array $entity * * @throws \Exception * * @return void */ public function commit($entity = null) { // Raise preFlush if ($this->evm->hasListeners(Events::preFlush)) { $this->evm->dispatchEvent(Events::preFlush, new Event\PreFlushEventArgs($this->em)); } // Compute changes done since last commit. if ($entity === null) { $this->computeChangeSets(); } elseif (is_object($entity)) { $this->computeSingleEntityChangeSet($entity); } elseif (is_array($entity)) { foreach ($entity as $object) { $this->computeSingleEntityChangeSet($object); } } if ( ! ($this->entityInsertions || $this->entityDeletions || $this->entityUpdates || $this->collectionUpdates || $this->collectionDeletions || $this->orphanRemovals)) { return; // Nothing to do. } if ($this->orphanRemovals) { foreach ($this->orphanRemovals as $orphan) { $this->remove($orphan); } } // Raise onFlush if ($this->evm->hasListeners(Events::onFlush)) { $this->evm->dispatchEvent(Events::onFlush, new Event\OnFlushEventArgs($this->em)); } // Now we need a commit order to maintain referential integrity $commitOrder = $this->getCommitOrder(); $conn = $this->em->getConnection(); $conn->beginTransaction(); try { if ($this->entityInsertions) { foreach ($commitOrder as $class) { $this->executeInserts($class); } } if ($this->entityUpdates) { foreach ($commitOrder as $class) { $this->executeUpdates($class); } } // Extra updates that were requested by persisters. if ($this->extraUpdates) { $this->executeExtraUpdates(); } // Collection deletions (deletions of complete collections) foreach ($this->collectionDeletions as $collectionToDelete) { $this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete); } // Collection updates (deleteRows, updateRows, insertRows) foreach ($this->collectionUpdates as $collectionToUpdate) { $this->getCollectionPersister($collectionToUpdate->getMapping())->update($collectionToUpdate); } // Entity deletions come last and need to be in reverse commit order if ($this->entityDeletions) { for ($count = count($commitOrder), $i = $count - 1; $i >= 0; --$i) { $this->executeDeletions($commitOrder[$i]); } } $conn->commit(); } catch (Exception $e) { $this->em->close(); $conn->rollback(); throw $e; } // Take new snapshots from visited collections foreach ($this->visitedCollections as $coll) { $coll->takeSnapshot(); } // Raise postFlush if ($this->evm->hasListeners(Events::postFlush)) { $this->evm->dispatchEvent(Events::postFlush, new Event\PostFlushEventArgs($this->em)); } // Clear up $this->entityInsertions = $this->entityUpdates = $this->entityDeletions = $this->extraUpdates = $this->entityChangeSets = $this->collectionUpdates = $this->collectionDeletions = $this->visitedCollections = $this->scheduledForDirtyCheck = $this->orphanRemovals = array(); } /** * Compute the changesets of all entities scheduled for insertion * * @return void */ private function computeScheduleInsertsChangeSets() { foreach ($this->entityInsertions as $entity) { $class = $this->em->getClassMetadata(get_class($entity)); $this->computeChangeSet($class, $entity); } } /** * Only flush the given entity according to a ruleset that keeps the UoW consistent. * * 1. All entities scheduled for insertion, (orphan) removals and changes in collections are processed as well! * 2. Read Only entities are skipped. * 3. Proxies are skipped. * 4. Only if entity is properly managed. * * @param object $entity * * @throws \InvalidArgumentException * * @return void */ private function computeSingleEntityChangeSet($entity) { if ( $this->getEntityState($entity) !== self::STATE_MANAGED) { throw new \InvalidArgumentException("Entity has to be managed for single computation " . self::objToStr($entity)); } $class = $this->em->getClassMetadata(get_class($entity)); if ($class->isChangeTrackingDeferredImplicit()) { $this->persist($entity); } // Compute changes for INSERTed entities first. This must always happen even in this case. $this->computeScheduleInsertsChangeSets(); if ($class->isReadOnly) { return; } // Ignore uninitialized proxy objects if ($entity instanceof Proxy && ! $entity->__isInitialized__) { return; } // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here. $oid = spl_object_hash($entity); if ( ! isset($this->entityInsertions[$oid]) && isset($this->entityStates[$oid])) { $this->computeChangeSet($class, $entity); } } /** * Executes any extra updates that have been scheduled. */ private function executeExtraUpdates() { foreach ($this->extraUpdates as $oid => $update) { list ($entity, $changeset) = $update; $this->entityChangeSets[$oid] = $changeset; $this->getEntityPersister(get_class($entity))->update($entity); } } /** * Gets the changeset for an entity. * * @param object $entity * * @return array */ public function getEntityChangeSet($entity) { $oid = spl_object_hash($entity); if (isset($this->entityChangeSets[$oid])) { return $this->entityChangeSets[$oid]; } return array(); } /** * Computes the changes that happened to a single entity. * * Modifies/populates the following properties: * * {@link _originalEntityData} * If the entity is NEW or MANAGED but not yet fully persisted (only has an id) * then it was not fetched from the database and therefore we have no original * entity data yet. All of the current entity data is stored as the original entity data. * * {@link _entityChangeSets} * The changes detected on all properties of the entity are stored there. * A change is a tuple array where the first entry is the old value and the second * entry is the new value of the property. Changesets are used by persisters * to INSERT/UPDATE the persistent entity state. * * {@link _entityUpdates} * If the entity is already fully MANAGED (has been fetched from the database before) * and any changes to its properties are detected, then a reference to the entity is stored * there to mark it for an update. * * {@link _collectionDeletions} * If a PersistentCollection has been de-referenced in a fully MANAGED entity, * then this collection is marked for deletion. * * @ignore * @internal Don't call from the outside. * @param ClassMetadata $class The class descriptor of the entity. * @param object $entity The entity for which to compute the changes. */ public function computeChangeSet(ClassMetadata $class, $entity) { $oid = spl_object_hash($entity); if (isset($this->readOnlyObjects[$oid])) { return; } if ( ! $class->isInheritanceTypeNone()) { $class = $this->em->getClassMetadata(get_class($entity)); } // Fire PreFlush lifecycle callbacks if (isset($class->lifecycleCallbacks[Events::preFlush])) { $class->invokeLifecycleCallbacks(Events::preFlush, $entity); } $actualData = array(); foreach ($class->reflFields as $name => $refProp) { $value = $refProp->getValue($entity); if ($class->isCollectionValuedAssociation($name) && $value !== null && ! ($value instanceof PersistentCollection)) { // If $value is not a Collection then use an ArrayCollection. if ( ! $value instanceof Collection) { $value = new ArrayCollection($value); } $assoc = $class->associationMappings[$name]; // Inject PersistentCollection $value = new PersistentCollection( $this->em, $this->em->getClassMetadata($assoc['targetEntity']), $value ); $value->setOwner($entity, $assoc); $value->setDirty( ! $value->isEmpty()); $class->reflFields[$name]->setValue($entity, $value); $actualData[$name] = $value; continue; } if (( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) && ($name !== $class->versionField)) { $actualData[$name] = $value; } } if ( ! isset($this->originalEntityData[$oid])) { // Entity is either NEW or MANAGED but not yet fully persisted (only has an id). // These result in an INSERT. $this->originalEntityData[$oid] = $actualData; $changeSet = array(); foreach ($actualData as $propName => $actualValue) { if ( ! isset($class->associationMappings[$propName])) { $changeSet[$propName] = array(null, $actualValue); continue; } $assoc = $class->associationMappings[$propName]; if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { $changeSet[$propName] = array(null, $actualValue); } } $this->entityChangeSets[$oid] = $changeSet; } else { // Entity is "fully" MANAGED: it was already fully persisted before // and we have a copy of the original data $originalData = $this->originalEntityData[$oid]; $isChangeTrackingNotify = $class->isChangeTrackingNotify(); $changeSet = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid])) ? $this->entityChangeSets[$oid] : array(); foreach ($actualData as $propName => $actualValue) { // skip field, its a partially omitted one! if ( ! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) { continue; } $orgValue = $originalData[$propName]; // skip if value havent changed if ($orgValue === $actualValue) { continue; } // if regular field if ( ! isset($class->associationMappings[$propName])) { if ($isChangeTrackingNotify) { continue; } $changeSet[$propName] = array($orgValue, $actualValue); continue; } $assoc = $class->associationMappings[$propName]; // Persistent collection was exchanged with the "originally" // created one. This can only mean it was cloned and replaced // on another entity. if ($actualValue instanceof PersistentCollection) { $owner = $actualValue->getOwner(); if ($owner === null) { // cloned $actualValue->setOwner($entity, $assoc); } else if ($owner !== $entity) { // no clone, we have to fix if (!$actualValue->isInitialized()) { $actualValue->initialize(); // we have to do this otherwise the cols share state } $newValue = clone $actualValue; $newValue->setOwner($entity, $assoc); $class->reflFields[$propName]->setValue($entity, $newValue); } } if ($orgValue instanceof PersistentCollection) { // A PersistentCollection was de-referenced, so delete it. $coid = spl_object_hash($orgValue); if (isset($this->collectionDeletions[$coid])) { continue; } $this->collectionDeletions[$coid] = $orgValue; $changeSet[$propName] = $orgValue; // Signal changeset, to-many assocs will be ignored. continue; } if ($assoc['type'] & ClassMetadata::TO_ONE) { if ($assoc['isOwningSide']) { $changeSet[$propName] = array($orgValue, $actualValue); } if ($orgValue !== null && $assoc['orphanRemoval']) { $this->scheduleOrphanRemoval($orgValue); } } } if ($changeSet) { $this->entityChangeSets[$oid] = $changeSet; $this->originalEntityData[$oid] = $actualData; $this->entityUpdates[$oid] = $entity; } } // Look for changes in associations of the entity foreach ($class->associationMappings as $field => $assoc) { if (($val = $class->reflFields[$field]->getValue($entity)) !== null) { $this->computeAssociationChanges($assoc, $val); if (!isset($this->entityChangeSets[$oid]) && $assoc['isOwningSide'] && $assoc['type'] == ClassMetadata::MANY_TO_MANY && $val instanceof PersistentCollection && $val->isDirty()) { $this->entityChangeSets[$oid] = array(); $this->originalEntityData[$oid] = $actualData; $this->entityUpdates[$oid] = $entity; } } } } /** * Computes all the changes that have been done to entities and collections * since the last commit and stores these changes in the _entityChangeSet map * temporarily for access by the persisters, until the UoW commit is finished. */ public function computeChangeSets() { // Compute changes for INSERTed entities first. This must always happen. $this->computeScheduleInsertsChangeSets(); // Compute changes for other MANAGED entities. Change tracking policies take effect here. foreach ($this->identityMap as $className => $entities) { $class = $this->em->getClassMetadata($className); // Skip class if instances are read-only if ($class->isReadOnly) { continue; } // If change tracking is explicit or happens through notification, then only compute // changes on entities of that type that are explicitly marked for synchronization. switch (true) { case ($class->isChangeTrackingDeferredImplicit()): $entitiesToProcess = $entities; break; case (isset($this->scheduledForDirtyCheck[$className])): $entitiesToProcess = $this->scheduledForDirtyCheck[$className]; break; default: $entitiesToProcess = array(); } foreach ($entitiesToProcess as $entity) { // Ignore uninitialized proxy objects if ($entity instanceof Proxy && ! $entity->__isInitialized__) { continue; } // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here. $oid = spl_object_hash($entity); if ( ! isset($this->entityInsertions[$oid]) && isset($this->entityStates[$oid])) { $this->computeChangeSet($class, $entity); } } } } /** * Computes the changes of an association. * * @param array $assoc * @param mixed $value The value of the association. * * @throws ORMInvalidArgumentException * @throws ORMException * * @return void */ private function computeAssociationChanges($assoc, $value) { if ($value instanceof Proxy && ! $value->__isInitialized__) { return; } if ($value instanceof PersistentCollection && $value->isDirty()) { $coid = spl_object_hash($value); if ($assoc['isOwningSide']) { $this->collectionUpdates[$coid] = $value; } $this->visitedCollections[$coid] = $value; } // Look through the entities, and in any of their associations, // for transient (new) entities, recursively. ("Persistence by reachability") // Unwrap. Uninitialized collections will simply be empty. $unwrappedValue = ($assoc['type'] & ClassMetadata::TO_ONE) ? array($value) : $value->unwrap(); $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); foreach ($unwrappedValue as $key => $entry) { $state = $this->getEntityState($entry, self::STATE_NEW); if ( ! ($entry instanceof $assoc['targetEntity'])) { throw new ORMException( sprintf( 'Found entity of type %s on association %s#%s, but expecting %s', get_class($entry), $assoc['sourceEntity'], $assoc['fieldName'], $targetClass->name ) ); } switch ($state) { case self::STATE_NEW: if ( ! $assoc['isCascadePersist']) { throw ORMInvalidArgumentException::newEntityFoundThroughRelationship($assoc, $entry); } $this->persistNew($targetClass, $entry); $this->computeChangeSet($targetClass, $entry); break; case self::STATE_REMOVED: // Consume the $value as array (it's either an array or an ArrayAccess) // and remove the element from Collection. if ($assoc['type'] & ClassMetadata::TO_MANY) { unset($value[$key]); } break; case self::STATE_DETACHED: // Can actually not happen right now as we assume STATE_NEW, // so the exception will be raised from the DBAL layer (constraint violation). throw ORMInvalidArgumentException::detachedEntityFoundThroughRelationship($assoc, $entry); break; default: // MANAGED associated entities are already taken into account // during changeset calculation anyway, since they are in the identity map. } } } /** * @param ClassMetadata $class * @param object $entity */ private function persistNew($class, $entity) { $oid = spl_object_hash($entity); if (isset($class->lifecycleCallbacks[Events::prePersist])) { $class->invokeLifecycleCallbacks(Events::prePersist, $entity); } if ($this->evm->hasListeners(Events::prePersist)) { $this->evm->dispatchEvent(Events::prePersist, new LifecycleEventArgs($entity, $this->em)); } $idGen = $class->idGenerator; if ( ! $idGen->isPostInsertGenerator()) { $idValue = $idGen->generate($this->em, $entity); if ( ! $idGen instanceof \Doctrine\ORM\Id\AssignedGenerator) { $idValue = array($class->identifier[0] => $idValue); $class->setIdentifierValues($entity, $idValue); } $this->entityIdentifiers[$oid] = $idValue; } $this->entityStates[$oid] = self::STATE_MANAGED; $this->scheduleForInsert($entity); } /** * INTERNAL: * Computes the changeset of an individual entity, independently of the * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit(). * * The passed entity must be a managed entity. If the entity already has a change set * because this method is invoked during a commit cycle then the change sets are added. * whereby changes detected in this method prevail. * * @ignore * @param ClassMetadata $class The class descriptor of the entity. * @param object $entity The entity for which to (re)calculate the change set. * * @throws ORMInvalidArgumentException If the passed entity is not MANAGED. */ public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity) { $oid = spl_object_hash($entity); if ( ! isset($this->entityStates[$oid]) || $this->entityStates[$oid] != self::STATE_MANAGED) { throw ORMInvalidArgumentException::entityNotManaged($entity); } // skip if change tracking is "NOTIFY" if ($class->isChangeTrackingNotify()) { return; } if ( ! $class->isInheritanceTypeNone()) { $class = $this->em->getClassMetadata(get_class($entity)); } $actualData = array(); foreach ($class->reflFields as $name => $refProp) { if ( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) { $actualData[$name] = $refProp->getValue($entity); } } $originalData = $this->originalEntityData[$oid]; $changeSet = array(); foreach ($actualData as $propName => $actualValue) { $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null; if (is_object($orgValue) && $orgValue !== $actualValue) { $changeSet[$propName] = array($orgValue, $actualValue); } else if ($orgValue != $actualValue || ($orgValue === null ^ $actualValue === null)) { $changeSet[$propName] = array($orgValue, $actualValue); } } if ($changeSet) { if (isset($this->entityChangeSets[$oid])) { $this->entityChangeSets[$oid] = array_merge($this->entityChangeSets[$oid], $changeSet); } $this->originalEntityData[$oid] = $actualData; } } /** * Executes all entity insertions for entities of the specified type. * * @param \Doctrine\ORM\Mapping\ClassMetadata $class */ private function executeInserts($class) { $className = $class->name; $persister = $this->getEntityPersister($className); $entities = array(); $hasLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postPersist]); $hasListeners = $this->evm->hasListeners(Events::postPersist); foreach ($this->entityInsertions as $oid => $entity) { if ($this->em->getClassMetadata(get_class($entity))->name !== $className) { continue; } $persister->addInsert($entity); unset($this->entityInsertions[$oid]); if ($hasLifecycleCallbacks || $hasListeners) { $entities[] = $entity; } } $postInsertIds = $persister->executeInserts(); if ($postInsertIds) { // Persister returned post-insert IDs foreach ($postInsertIds as $id => $entity) { $oid = spl_object_hash($entity); $idField = $class->identifier[0]; $class->reflFields[$idField]->setValue($entity, $id); $this->entityIdentifiers[$oid] = array($idField => $id); $this->entityStates[$oid] = self::STATE_MANAGED; $this->originalEntityData[$oid][$idField] = $id; $this->addToIdentityMap($entity); } } foreach ($entities as $entity) { if ($hasLifecycleCallbacks) { $class->invokeLifecycleCallbacks(Events::postPersist, $entity); } if ($hasListeners) { $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($entity, $this->em)); } } } /** * Executes all entity updates for entities of the specified type. * * @param \Doctrine\ORM\Mapping\ClassMetadata $class */ private function executeUpdates($class) { $className = $class->name; $persister = $this->getEntityPersister($className); $hasPreUpdateLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::preUpdate]); $hasPreUpdateListeners = $this->evm->hasListeners(Events::preUpdate); $hasPostUpdateLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postUpdate]); $hasPostUpdateListeners = $this->evm->hasListeners(Events::postUpdate); foreach ($this->entityUpdates as $oid => $entity) { if ($this->em->getClassMetadata(get_class($entity))->name !== $className) { continue; } if ($hasPreUpdateLifecycleCallbacks) { $class->invokeLifecycleCallbacks(Events::preUpdate, $entity); $this->recomputeSingleEntityChangeSet($class, $entity); } if ($hasPreUpdateListeners) { $this->evm->dispatchEvent( Events::preUpdate, new Event\PreUpdateEventArgs($entity, $this->em, $this->entityChangeSets[$oid]) ); } if (!empty($this->entityChangeSets[$oid])) { $persister->update($entity); } unset($this->entityUpdates[$oid]); if ($hasPostUpdateLifecycleCallbacks) { $class->invokeLifecycleCallbacks(Events::postUpdate, $entity); } if ($hasPostUpdateListeners) { $this->evm->dispatchEvent(Events::postUpdate, new LifecycleEventArgs($entity, $this->em)); } } } /** * Executes all entity deletions for entities of the specified type. * * @param \Doctrine\ORM\Mapping\ClassMetadata $class */ private function executeDeletions($class) { $className = $class->name; $persister = $this->getEntityPersister($className); $hasLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postRemove]); $hasListeners = $this->evm->hasListeners(Events::postRemove); foreach ($this->entityDeletions as $oid => $entity) { if ($this->em->getClassMetadata(get_class($entity))->name !== $className) { continue; } $persister->delete($entity); unset( $this->entityDeletions[$oid], $this->entityIdentifiers[$oid], $this->originalEntityData[$oid], $this->entityStates[$oid] ); // Entity with this $oid after deletion treated as NEW, even if the $oid // is obtained by a new entity because the old one went out of scope. //$this->entityStates[$oid] = self::STATE_NEW; if ( ! $class->isIdentifierNatural()) { $class->reflFields[$class->identifier[0]]->setValue($entity, null); } if ($hasLifecycleCallbacks) { $class->invokeLifecycleCallbacks(Events::postRemove, $entity); } if ($hasListeners) { $this->evm->dispatchEvent(Events::postRemove, new LifecycleEventArgs($entity, $this->em)); } } } /** * Gets the commit order. * * @param array $entityChangeSet * * @return array */ private function getCommitOrder(array $entityChangeSet = null) { if ($entityChangeSet === null) { $entityChangeSet = array_merge($this->entityInsertions, $this->entityUpdates, $this->entityDeletions); } $calc = $this->getCommitOrderCalculator(); // See if there are any new classes in the changeset, that are not in the // commit order graph yet (dont have a node). // We have to inspect changeSet to be able to correctly build dependencies. // It is not possible to use IdentityMap here because post inserted ids // are not yet available. $newNodes = array(); foreach ($entityChangeSet as $entity) { $className = $this->em->getClassMetadata(get_class($entity))->name; if ($calc->hasClass($className)) { continue; } $class = $this->em->getClassMetadata($className); $calc->addClass($class); $newNodes[] = $class; } // Calculate dependencies for new nodes while ($class = array_pop($newNodes)) { foreach ($class->associationMappings as $assoc) { if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) { continue; } $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); if ( ! $calc->hasClass($targetClass->name)) { $calc->addClass($targetClass); $newNodes[] = $targetClass; } $calc->addDependency($targetClass, $class); // If the target class has mapped subclasses, these share the same dependency. if ( ! $targetClass->subClasses) { continue; } foreach ($targetClass->subClasses as $subClassName) { $targetSubClass = $this->em->getClassMetadata($subClassName); if ( ! $calc->hasClass($subClassName)) { $calc->addClass($targetSubClass); $newNodes[] = $targetSubClass; } $calc->addDependency($targetSubClass, $class); } } } return $calc->getCommitOrder(); } /** * 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 The entity to schedule for insertion. * * @throws ORMInvalidArgumentException * @throws \InvalidArgumentException */ public function scheduleForInsert($entity) { $oid = spl_object_hash($entity); if (isset($this->entityUpdates[$oid])) { throw new InvalidArgumentException("Dirty entity can not be scheduled for insertion."); } if (isset($this->entityDeletions[$oid])) { throw ORMInvalidArgumentException::scheduleInsertForRemovedEntity($entity); } if (isset($this->originalEntityData[$oid]) && ! isset($this->entityInsertions[$oid])) { throw ORMInvalidArgumentException::scheduleInsertForManagedEntity($entity); } if (isset($this->entityInsertions[$oid])) { throw ORMInvalidArgumentException::scheduleInsertTwice($entity); } $this->entityInsertions[$oid] = $entity; if (isset($this->entityIdentifiers[$oid])) { $this->addToIdentityMap($entity); } if ($entity instanceof NotifyPropertyChanged) { $entity->addPropertyChangedListener($this); } } /** * Checks whether an entity is scheduled for insertion. * * @param object $entity * * @return boolean */ public function isScheduledForInsert($entity) { return isset($this->entityInsertions[spl_object_hash($entity)]); } /** * Schedules an entity for being updated. * * @param object $entity The entity to schedule for being updated. * * @throws ORMInvalidArgumentException */ public function scheduleForUpdate($entity) { $oid = spl_object_hash($entity); if ( ! isset($this->entityIdentifiers[$oid])) { throw ORMInvalidArgumentException::entityHasNoIdentity($entity, "scheduling for update"); } if (isset($this->entityDeletions[$oid])) { throw ORMInvalidArgumentException::entityIsRemoved($entity, "schedule for update"); } if ( ! isset($this->entityUpdates[$oid]) && ! isset($this->entityInsertions[$oid])) { $this->entityUpdates[$oid] = $entity; } } /** * INTERNAL: * Schedules an extra update that will be executed immediately after the * regular entity updates within the currently running commit cycle. * * Extra updates for entities are stored as (entity, changeset) tuples. * * @ignore * @param object $entity The entity for which to schedule an extra update. * @param array $changeset The changeset of the entity (what to update). */ public function scheduleExtraUpdate($entity, array $changeset) { $oid = spl_object_hash($entity); $extraUpdate = array($entity, $changeset); if (isset($this->extraUpdates[$oid])) { list($ignored, $changeset2) = $this->extraUpdates[$oid]; $extraUpdate = array($entity, $changeset + $changeset2); } $this->extraUpdates[$oid] = $extraUpdate; } /** * Checks whether an entity is registered as dirty in the unit of work. * Note: Is not very useful currently as dirty entities are only registered * at commit time. * * @param object $entity * * @return boolean */ public function isScheduledForUpdate($entity) { return isset($this->entityUpdates[spl_object_hash($entity)]); } /** * Checks whether an entity is registered to be checked in the unit of work. * * @param object $entity * * @return boolean */ public function isScheduledForDirtyCheck($entity) { $rootEntityName = $this->em->getClassMetadata(get_class($entity))->rootEntityName; return isset($this->scheduledForDirtyCheck[$rootEntityName][spl_object_hash($entity)]); } /** * INTERNAL: * Schedules an entity for deletion. * * @param object $entity */ public function scheduleForDelete($entity) { $oid = spl_object_hash($entity); if (isset($this->entityInsertions[$oid])) { if ($this->isInIdentityMap($entity)) { $this->removeFromIdentityMap($entity); } unset($this->entityInsertions[$oid], $this->entityStates[$oid]); return; // entity has not been persisted yet, so nothing more to do. } if ( ! $this->isInIdentityMap($entity)) { return; } $this->removeFromIdentityMap($entity); if (isset($this->entityUpdates[$oid])) { unset($this->entityUpdates[$oid]); } if ( ! isset($this->entityDeletions[$oid])) { $this->entityDeletions[$oid] = $entity; $this->entityStates[$oid] = self::STATE_REMOVED; } } /** * Checks whether an entity is registered as removed/deleted with the unit * of work. * * @param object $entity * * @return boolean */ public function isScheduledForDelete($entity) { return isset($this->entityDeletions[spl_object_hash($entity)]); } /** * Checks whether an entity is scheduled for insertion, update or deletion. * * @param $entity * * @return boolean */ public function isEntityScheduled($entity) { $oid = spl_object_hash($entity); return isset($this->entityInsertions[$oid]) || isset($this->entityUpdates[$oid]) || isset($this->entityDeletions[$oid]); } /** * INTERNAL: * Registers an entity in the identity map. * Note that entities in a hierarchy are registered with the class name of * the root entity. * * @ignore * @param object $entity The entity to register. * * @throws ORMInvalidArgumentException * * @return boolean TRUE if the registration was successful, FALSE if the identity of * the entity in question is already managed. */ public function addToIdentityMap($entity) { $classMetadata = $this->em->getClassMetadata(get_class($entity)); $idHash = implode(' ', $this->entityIdentifiers[spl_object_hash($entity)]); if ($idHash === '') { throw ORMInvalidArgumentException::entityWithoutIdentity($classMetadata->name, $entity); } $className = $classMetadata->rootEntityName; if (isset($this->identityMap[$className][$idHash])) { return false; } $this->identityMap[$className][$idHash] = $entity; return true; } /** * Gets the state of an entity with regard to the current unit of work. * * @param object $entity * @param integer $assume The state to assume if the state is not yet known (not MANAGED or REMOVED). * This parameter can be set to improve performance of entity state detection * by potentially avoiding a database lookup if the distinction between NEW and DETACHED * is either known or does not matter for the caller of the method. * * @return int The entity state. */ public function getEntityState($entity, $assume = null) { $oid = spl_object_hash($entity); if (isset($this->entityStates[$oid])) { return $this->entityStates[$oid]; } if ($assume !== null) { return $assume; } // State can only be NEW or DETACHED, because MANAGED/REMOVED states are known. // Note that you can not remember the NEW or DETACHED state in _entityStates since // the UoW does not hold references to such objects and the object hash can be reused. // More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it. $class = $this->em->getClassMetadata(get_class($entity)); $id = $class->getIdentifierValues($entity); if ( ! $id) { return self::STATE_NEW; } if ($class->containsForeignIdentifier) { $id = $this->flattenIdentifier($class, $id); } switch (true) { case ($class->isIdentifierNatural()); // Check for a version field, if available, to avoid a db lookup. if ($class->isVersioned) { return ($class->getFieldValue($entity, $class->versionField)) ? self::STATE_DETACHED : self::STATE_NEW; } // Last try before db lookup: check the identity map. if ($this->tryGetById($id, $class->rootEntityName)) { return self::STATE_DETACHED; } // db lookup if ($this->getEntityPersister($class->name)->exists($entity)) { return self::STATE_DETACHED; } return self::STATE_NEW; case ( ! $class->idGenerator->isPostInsertGenerator()): // if we have a pre insert generator we can't be sure that having an id // really means that the entity exists. We have to verify this through // the last resort: a db lookup // Last try before db lookup: check the identity map. if ($this->tryGetById($id, $class->rootEntityName)) { return self::STATE_DETACHED; } // db lookup if ($this->getEntityPersister($class->name)->exists($entity)) { return self::STATE_DETACHED; } return self::STATE_NEW; default: return self::STATE_DETACHED; } } /** * INTERNAL: * Removes an entity from the identity map. This effectively detaches the * entity from the persistence management of Doctrine. * * @ignore * @param object $entity * * @throws ORMInvalidArgumentException * * @return boolean */ public function removeFromIdentityMap($entity) { $oid = spl_object_hash($entity); $classMetadata = $this->em->getClassMetadata(get_class($entity)); $idHash = implode(' ', $this->entityIdentifiers[$oid]); if ($idHash === '') { throw ORMInvalidArgumentException::entityHasNoIdentity($entity, "remove from identity map"); } $className = $classMetadata->rootEntityName; if (isset($this->identityMap[$className][$idHash])) { unset($this->identityMap[$className][$idHash]); unset($this->readOnlyObjects[$oid]); //$this->entityStates[$oid] = self::STATE_DETACHED; return true; } return false; } /** * INTERNAL: * Gets an entity in the identity map by its identifier hash. * * @ignore * @param string $idHash * @param string $rootClassName * * @return object */ public function getByIdHash($idHash, $rootClassName) { return $this->identityMap[$rootClassName][$idHash]; } /** * INTERNAL: * Tries to get an entity by its identifier hash. If no entity is found for * the given hash, FALSE is returned. * * @ignore * @param string $idHash * @param string $rootClassName * * @return mixed The found entity or FALSE. */ public function tryGetByIdHash($idHash, $rootClassName) { if (isset($this->identityMap[$rootClassName][$idHash])) { return $this->identityMap[$rootClassName][$idHash]; } return false; } /** * Checks whether an entity is registered in the identity map of this UnitOfWork. * * @param object $entity * * @return boolean */ public function isInIdentityMap($entity) { $oid = spl_object_hash($entity); if ( ! isset($this->entityIdentifiers[$oid])) { return false; } $classMetadata = $this->em->getClassMetadata(get_class($entity)); $idHash = implode(' ', $this->entityIdentifiers[$oid]); if ($idHash === '') { return false; } return isset($this->identityMap[$classMetadata->rootEntityName][$idHash]); } /** * INTERNAL: * Checks whether an identifier hash exists in the identity map. * * @ignore * @param string $idHash * @param string $rootClassName * * @return boolean */ public function containsIdHash($idHash, $rootClassName) { return isset($this->identityMap[$rootClassName][$idHash]); } /** * Persists an entity as part of the current unit of work. * * @param object $entity The entity to persist. */ public function persist($entity) { $visited = array(); $this->doPersist($entity, $visited); } /** * Persists an entity as part of the current unit of work. * * This method is internally called during persist() cascades as it tracks * the already visited entities to prevent infinite recursions. * * @param object $entity The entity to persist. * @param array $visited The already visited entities. * * @throws ORMInvalidArgumentException * @throws UnexpectedValueException */ private function doPersist($entity, array &$visited) { $oid = spl_object_hash($entity); if (isset($visited[$oid])) { return; // Prevent infinite recursion } $visited[$oid] = $entity; // Mark visited $class = $this->em->getClassMetadata(get_class($entity)); // We assume NEW, so DETACHED entities result in an exception on flush (constraint violation). // If we would detect DETACHED here we would throw an exception anyway with the same // consequences (not recoverable/programming error), so just assuming NEW here // lets us avoid some database lookups for entities with natural identifiers. $entityState = $this->getEntityState($entity, self::STATE_NEW); switch ($entityState) { case self::STATE_MANAGED: // Nothing to do, except if policy is "deferred explicit" if ($class->isChangeTrackingDeferredExplicit()) { $this->scheduleForDirtyCheck($entity); } break; case self::STATE_NEW: $this->persistNew($class, $entity); break; case self::STATE_REMOVED: // Entity becomes managed again unset($this->entityDeletions[$oid]); $this->entityStates[$oid] = self::STATE_MANAGED; break; case self::STATE_DETACHED: // Can actually not happen right now since we assume STATE_NEW. throw ORMInvalidArgumentException::detachedEntityCannot($entity, "persisted"); default: throw new UnexpectedValueException("Unexpected entity state: $entityState." . self::objToStr($entity)); } $this->cascadePersist($entity, $visited); } /** * Deletes an entity as part of the current unit of work. * * @param object $entity The entity to remove. */ public function remove($entity) { $visited = array(); $this->doRemove($entity, $visited); } /** * Deletes an entity as part of the current unit of work. * * This method is internally called during delete() cascades as it tracks * the already visited entities to prevent infinite recursions. * * @param object $entity The entity to delete. * @param array $visited The map of the already visited entities. * * @throws ORMInvalidArgumentException If the instance is a detached entity. * @throws UnexpectedValueException */ private function doRemove($entity, array &$visited) { $oid = spl_object_hash($entity); if (isset($visited[$oid])) { return; // Prevent infinite recursion } $visited[$oid] = $entity; // mark visited // Cascade first, because scheduleForDelete() removes the entity from the identity map, which // can cause problems when a lazy proxy has to be initialized for the cascade operation. $this->cascadeRemove($entity, $visited); $class = $this->em->getClassMetadata(get_class($entity)); $entityState = $this->getEntityState($entity); switch ($entityState) { case self::STATE_NEW: case self::STATE_REMOVED: // nothing to do break; case self::STATE_MANAGED: if (isset($class->lifecycleCallbacks[Events::preRemove])) { $class->invokeLifecycleCallbacks(Events::preRemove, $entity); } if ($this->evm->hasListeners(Events::preRemove)) { $this->evm->dispatchEvent(Events::preRemove, new LifecycleEventArgs($entity, $this->em)); } $this->scheduleForDelete($entity); break; case self::STATE_DETACHED: throw ORMInvalidArgumentException::detachedEntityCannot($entity, "removed"); default: throw new UnexpectedValueException("Unexpected entity state: $entityState." . self::objToStr($entity)); } } /** * Merges the state of the given detached entity into this UnitOfWork. * * @param object $entity * * @throws OptimisticLockException If the entity uses optimistic locking through a version * attribute and the version check against the managed copy fails. * * @return object The managed copy of the entity. * * @todo Require active transaction!? OptimisticLockException may result in undefined state!? */ public function merge($entity) { $visited = array(); return $this->doMerge($entity, $visited); } /** * convert foreign identifiers into scalar foreign key values to avoid object to string conversion failures. * * @param ClassMetadata $class * @param array $id * @return array */ private function flattenIdentifier($class, $id) { $flatId = array(); foreach ($id as $idField => $idValue) { if (isset($class->associationMappings[$idField])) { $targetClassMetadata = $this->em->getClassMetadata($class->associationMappings[$idField]['targetEntity']); $associatedId = $this->getEntityIdentifier($idValue); $flatId[$idField] = $associatedId[$targetClassMetadata->identifier[0]]; } } return $flatId; } /** * Executes a merge operation on an entity. * * @param object $entity * @param array $visited * @param object $prevManagedCopy * @param array $assoc * * @throws OptimisticLockException If the entity uses optimistic locking through a version * attribute and the version check against the managed copy fails. * @throws ORMInvalidArgumentException If the entity instance is NEW. * @throws EntityNotFoundException * * @return object The managed copy of the entity. */ private function doMerge($entity, array &$visited, $prevManagedCopy = null, $assoc = null) { $oid = spl_object_hash($entity); if (isset($visited[$oid])) { return $visited[$oid]; // Prevent infinite recursion } $visited[$oid] = $entity; // mark visited $class = $this->em->getClassMetadata(get_class($entity)); // First we assume DETACHED, although it can still be NEW but we can avoid // an extra db-roundtrip this way. If it is not MANAGED but has an identity, // we need to fetch it from the db anyway in order to merge. // MANAGED entities are ignored by the merge operation. $managedCopy = $entity; if ($this->getEntityState($entity, self::STATE_DETACHED) !== self::STATE_MANAGED) { if ($entity instanceof Proxy && ! $entity->__isInitialized__) { $entity->__load(); } // Try to look the entity up in the identity map. $id = $class->getIdentifierValues($entity); // If there is no ID, it is actually NEW. if ( ! $id) { $managedCopy = $this->newInstance($class); $this->persistNew($class, $managedCopy); } else { $flatId = ($class->containsForeignIdentifier) ? $this->flattenIdentifier($class, $id) : $id; $managedCopy = $this->tryGetById($flatId, $class->rootEntityName); if ($managedCopy) { // We have the entity in-memory already, just make sure its not removed. if ($this->getEntityState($managedCopy) == self::STATE_REMOVED) { throw ORMInvalidArgumentException::entityIsRemoved($managedCopy, "merge"); } } else { // We need to fetch the managed copy in order to merge. $managedCopy = $this->em->find($class->name, $flatId); } if ($managedCopy === null) { // If the identifier is ASSIGNED, it is NEW, otherwise an error // since the managed entity was not found. if ( ! $class->isIdentifierNatural()) { throw new EntityNotFoundException; } $managedCopy = $this->newInstance($class); $class->setIdentifierValues($managedCopy, $id); $this->persistNew($class, $managedCopy); } else { if ($managedCopy instanceof Proxy && ! $managedCopy->__isInitialized__) { $managedCopy->__load(); } } } if ($class->isVersioned) { $managedCopyVersion = $class->reflFields[$class->versionField]->getValue($managedCopy); $entityVersion = $class->reflFields[$class->versionField]->getValue($entity); // Throw exception if versions dont match. if ($managedCopyVersion != $entityVersion) { throw OptimisticLockException::lockFailedVersionMissmatch($entity, $entityVersion, $managedCopyVersion); } } // Merge state of $entity into existing (managed) entity foreach ($class->reflClass->getProperties() as $prop) { $name = $prop->name; $prop->setAccessible(true); if ( ! isset($class->associationMappings[$name])) { if ( ! $class->isIdentifier($name)) { $prop->setValue($managedCopy, $prop->getValue($entity)); } } else { $assoc2 = $class->associationMappings[$name]; if ($assoc2['type'] & ClassMetadata::TO_ONE) { $other = $prop->getValue($entity); if ($other === null) { $prop->setValue($managedCopy, null); } else if ($other instanceof Proxy && !$other->__isInitialized__) { // do not merge fields marked lazy that have not been fetched. continue; } else if ( ! $assoc2['isCascadeMerge']) { if ($this->getEntityState($other, self::STATE_DETACHED) !== self::STATE_MANAGED) { $targetClass = $this->em->getClassMetadata($assoc2['targetEntity']); $relatedId = $targetClass->getIdentifierValues($other); if ($targetClass->subClasses) { $other = $this->em->find($targetClass->name, $relatedId); } else { $other = $this->em->getProxyFactory()->getProxy($assoc2['targetEntity'], $relatedId); $this->registerManaged($other, $relatedId, array()); } } $prop->setValue($managedCopy, $other); } } else { $mergeCol = $prop->getValue($entity); if ($mergeCol instanceof PersistentCollection && !$mergeCol->isInitialized()) { // do not merge fields marked lazy that have not been fetched. // keep the lazy persistent collection of the managed copy. continue; } $managedCol = $prop->getValue($managedCopy); if (!$managedCol) { $managedCol = new PersistentCollection($this->em, $this->em->getClassMetadata($assoc2['targetEntity']), new ArrayCollection ); $managedCol->setOwner($managedCopy, $assoc2); $prop->setValue($managedCopy, $managedCol); $this->originalEntityData[$oid][$name] = $managedCol; } if ($assoc2['isCascadeMerge']) { $managedCol->initialize(); // clear and set dirty a managed collection if its not also the same collection to merge from. if (!$managedCol->isEmpty() && $managedCol !== $mergeCol) { $managedCol->unwrap()->clear(); $managedCol->setDirty(true); if ($assoc2['isOwningSide'] && $assoc2['type'] == ClassMetadata::MANY_TO_MANY && $class->isChangeTrackingNotify()) { $this->scheduleForDirtyCheck($managedCopy); } } } } } if ($class->isChangeTrackingNotify()) { // Just treat all properties as changed, there is no other choice. $this->propertyChanged($managedCopy, $name, null, $prop->getValue($managedCopy)); } } if ($class->isChangeTrackingDeferredExplicit()) { $this->scheduleForDirtyCheck($entity); } } if ($prevManagedCopy !== null) { $assocField = $assoc['fieldName']; $prevClass = $this->em->getClassMetadata(get_class($prevManagedCopy)); if ($assoc['type'] & ClassMetadata::TO_ONE) { $prevClass->reflFields[$assocField]->setValue($prevManagedCopy, $managedCopy); } else { $prevClass->reflFields[$assocField]->getValue($prevManagedCopy)->add($managedCopy); if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) { $class->reflFields[$assoc['mappedBy']]->setValue($managedCopy, $prevManagedCopy); } } } // Mark the managed copy visited as well $visited[spl_object_hash($managedCopy)] = true; $this->cascadeMerge($entity, $managedCopy, $visited); return $managedCopy; } /** * Detaches an entity from the persistence management. It's persistence will * no longer be managed by Doctrine. * * @param object $entity The entity to detach. */ public function detach($entity) { $visited = array(); $this->doDetach($entity, $visited); } /** * Executes a detach operation on the given entity. * * @param object $entity * @param array $visited * @param boolean $noCascade if true, don't cascade detach operation */ private function doDetach($entity, array &$visited, $noCascade = false) { $oid = spl_object_hash($entity); if (isset($visited[$oid])) { return; // Prevent infinite recursion } $visited[$oid] = $entity; // mark visited switch ($this->getEntityState($entity, self::STATE_DETACHED)) { case self::STATE_MANAGED: if ($this->isInIdentityMap($entity)) { $this->removeFromIdentityMap($entity); } unset( $this->entityInsertions[$oid], $this->entityUpdates[$oid], $this->entityDeletions[$oid], $this->entityIdentifiers[$oid], $this->entityStates[$oid], $this->originalEntityData[$oid] ); break; case self::STATE_NEW: case self::STATE_DETACHED: return; } if ( ! $noCascade) { $this->cascadeDetach($entity, $visited); } } /** * Refreshes the state of the given entity from the database, overwriting * any local, unpersisted changes. * * @param object $entity The entity to refresh. * * @throws InvalidArgumentException If the entity is not MANAGED. */ public function refresh($entity) { $visited = array(); $this->doRefresh($entity, $visited); } /** * Executes a refresh operation on an entity. * * @param object $entity The entity to refresh. * @param array $visited The already visited entities during cascades. * * @throws ORMInvalidArgumentException If the entity is not MANAGED. */ private function doRefresh($entity, array &$visited) { $oid = spl_object_hash($entity); if (isset($visited[$oid])) { return; // Prevent infinite recursion } $visited[$oid] = $entity; // mark visited $class = $this->em->getClassMetadata(get_class($entity)); if ($this->getEntityState($entity) !== self::STATE_MANAGED) { throw ORMInvalidArgumentException::entityNotManaged($entity); } $this->getEntityPersister($class->name)->refresh( array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]), $entity ); $this->cascadeRefresh($entity, $visited); } /** * Cascades a refresh operation to associated entities. * * @param object $entity * @param array $visited */ private function cascadeRefresh($entity, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); $associationMappings = array_filter( $class->associationMappings, function ($assoc) { return $assoc['isCascadeRefresh']; } ); foreach ($associationMappings as $assoc) { $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); switch (true) { case ($relatedEntities instanceof PersistentCollection): // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); // break; is commented intentionally! case ($relatedEntities instanceof Collection): case (is_array($relatedEntities)): foreach ($relatedEntities as $relatedEntity) { $this->doRefresh($relatedEntity, $visited); } break; case ($relatedEntities !== null): $this->doRefresh($relatedEntities, $visited); break; default: // Do nothing } } } /** * Cascades a detach operation to associated entities. * * @param object $entity * @param array $visited */ private function cascadeDetach($entity, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); $associationMappings = array_filter( $class->associationMappings, function ($assoc) { return $assoc['isCascadeDetach']; } ); foreach ($associationMappings as $assoc) { $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); switch (true) { case ($relatedEntities instanceof PersistentCollection): // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); // break; is commented intentionally! case ($relatedEntities instanceof Collection): case (is_array($relatedEntities)): foreach ($relatedEntities as $relatedEntity) { $this->doDetach($relatedEntity, $visited); } break; case ($relatedEntities !== null): $this->doDetach($relatedEntities, $visited); break; default: // Do nothing } } } /** * Cascades a merge operation to associated entities. * * @param object $entity * @param object $managedCopy * @param array $visited */ private function cascadeMerge($entity, $managedCopy, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); $associationMappings = array_filter( $class->associationMappings, function ($assoc) { return $assoc['isCascadeMerge']; } ); foreach ($associationMappings as $assoc) { $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); if ($relatedEntities instanceof Collection) { if ($relatedEntities === $class->reflFields[$assoc['fieldName']]->getValue($managedCopy)) { continue; } if ($relatedEntities instanceof PersistentCollection) { // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); } foreach ($relatedEntities as $relatedEntity) { $this->doMerge($relatedEntity, $visited, $managedCopy, $assoc); } } else if ($relatedEntities !== null) { $this->doMerge($relatedEntities, $visited, $managedCopy, $assoc); } } } /** * Cascades the save operation to associated entities. * * @param object $entity * @param array $visited * * @return void */ private function cascadePersist($entity, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); $associationMappings = array_filter( $class->associationMappings, function ($assoc) { return $assoc['isCascadePersist']; } ); foreach ($associationMappings as $assoc) { $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); switch (true) { case ($relatedEntities instanceof PersistentCollection): // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); // break; is commented intentionally! case ($relatedEntities instanceof Collection): case (is_array($relatedEntities)): foreach ($relatedEntities as $relatedEntity) { $this->doPersist($relatedEntity, $visited); } break; case ($relatedEntities !== null): $this->doPersist($relatedEntities, $visited); break; default: // Do nothing } } } /** * Cascades the delete operation to associated entities. * * @param object $entity * @param array $visited */ private function cascadeRemove($entity, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); $associationMappings = array_filter( $class->associationMappings, function ($assoc) { return $assoc['isCascadeRemove']; } ); foreach ($associationMappings as $assoc) { if ($entity instanceof Proxy && !$entity->__isInitialized__) { $entity->__load(); } $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); switch (true) { case ($relatedEntities instanceof Collection): case (is_array($relatedEntities)): // If its a PersistentCollection initialization is intended! No unwrap! foreach ($relatedEntities as $relatedEntity) { $this->doRemove($relatedEntity, $visited); } break; case ($relatedEntities !== null): $this->doRemove($relatedEntities, $visited); break; default: // Do nothing } } } /** * Acquire a lock on the given entity. * * @param object $entity * @param int $lockMode * @param int $lockVersion * * @throws ORMInvalidArgumentException * @throws TransactionRequiredException * @throws OptimisticLockException * * @return void */ public function lock($entity, $lockMode, $lockVersion = null) { if ($this->getEntityState($entity, self::STATE_DETACHED) != self::STATE_MANAGED) { throw ORMInvalidArgumentException::entityNotManaged($entity); } $class = $this->em->getClassMetadata(get_class($entity)); switch ($lockMode) { case \Doctrine\DBAL\LockMode::OPTIMISTIC; if ( ! $class->isVersioned) { throw OptimisticLockException::notVersioned($class->name); } if ($lockVersion === null) { return; } $entityVersion = $class->reflFields[$class->versionField]->getValue($entity); if ($entityVersion != $lockVersion) { throw OptimisticLockException::lockFailedVersionMissmatch($entity, $lockVersion, $entityVersion); } break; case \Doctrine\DBAL\LockMode::PESSIMISTIC_READ: case \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE: if (!$this->em->getConnection()->isTransactionActive()) { throw TransactionRequiredException::transactionRequired(); } $oid = spl_object_hash($entity); $this->getEntityPersister($class->name)->lock( array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]), $lockMode ); break; default: // Do nothing } } /** * Gets the CommitOrderCalculator used by the UnitOfWork to order commits. * * @return \Doctrine\ORM\Internal\CommitOrderCalculator */ public function getCommitOrderCalculator() { if ($this->commitOrderCalculator === null) { $this->commitOrderCalculator = new Internal\CommitOrderCalculator; } return $this->commitOrderCalculator; } /** * Clears the UnitOfWork. * * @param string $entityName if given, only entities of this type will get detached */ public function clear($entityName = null) { if ($entityName === null) { $this->identityMap = $this->entityIdentifiers = $this->originalEntityData = $this->entityChangeSets = $this->entityStates = $this->scheduledForDirtyCheck = $this->entityInsertions = $this->entityUpdates = $this->entityDeletions = $this->collectionDeletions = $this->collectionUpdates = $this->extraUpdates = $this->readOnlyObjects = $this->orphanRemovals = array(); if ($this->commitOrderCalculator !== null) { $this->commitOrderCalculator->clear(); } } else { $visited = array(); foreach ($this->identityMap as $className => $entities) { if ($className === $entityName) { foreach ($entities as $entity) { $this->doDetach($entity, $visited, true); } } } } if ($this->evm->hasListeners(Events::onClear)) { $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em, $entityName)); } } /** * INTERNAL: * Schedules an orphaned entity for removal. The remove() operation will be * invoked on that entity at the beginning of the next commit of this * UnitOfWork. * * @ignore * @param object $entity */ public function scheduleOrphanRemoval($entity) { $this->orphanRemovals[spl_object_hash($entity)] = $entity; } /** * INTERNAL: * Schedules a complete collection for removal when this UnitOfWork commits. * * @param PersistentCollection $coll */ public function scheduleCollectionDeletion(PersistentCollection $coll) { $coid = spl_object_hash($coll); //TODO: if $coll is already scheduled for recreation ... what to do? // Just remove $coll from the scheduled recreations? if (isset($this->collectionUpdates[$coid])) { unset($this->collectionUpdates[$coid]); } $this->collectionDeletions[$coid] = $coll; } /** * @param PersistentCollection $coll * * @return bool */ public function isCollectionScheduledForDeletion(PersistentCollection $coll) { return isset($this->collectionDeletions[spl_object_hash($coll)]); } /** * @param ClassMetadata $class * * @return \Doctrine\Common\Persistence\ObjectManagerAware|object */ private function newInstance($class) { $entity = $class->newInstance(); if ($entity instanceof \Doctrine\Common\Persistence\ObjectManagerAware) { $entity->injectObjectManager($this->em, $class); } return $entity; } /** * INTERNAL: * Creates an entity. Used for reconstitution of persistent entities. * * @ignore * @param string $className The name of the entity class. * @param array $data The data for the entity. * @param array $hints Any hints to account for during reconstitution/lookup of the entity. * * @return object The managed entity instance. * @internal Highly performance-sensitive method. * * @todo Rename: getOrCreateEntity */ public function createEntity($className, array $data, &$hints = array()) { $class = $this->em->getClassMetadata($className); //$isReadOnly = isset($hints[Query::HINT_READ_ONLY]); if ($class->isIdentifierComposite) { $id = array(); foreach ($class->identifier as $fieldName) { $id[$fieldName] = isset($class->associationMappings[$fieldName]) ? $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']] : $data[$fieldName]; } $idHash = implode(' ', $id); } else { $idHash = isset($class->associationMappings[$class->identifier[0]]) ? $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']] : $data[$class->identifier[0]]; $id = array($class->identifier[0] => $idHash); } if (isset($this->identityMap[$class->rootEntityName][$idHash])) { $entity = $this->identityMap[$class->rootEntityName][$idHash]; $oid = spl_object_hash($entity); if ($entity instanceof Proxy && ! $entity->__isInitialized__) { $entity->__isInitialized__ = true; $overrideLocalValues = true; if ($entity instanceof NotifyPropertyChanged) { $entity->addPropertyChangedListener($this); } // inject ObjectManager into just loaded proxies. if ($overrideLocalValues && $entity instanceof ObjectManagerAware) { $entity->injectObjectManager($this->em, $class); } } else { $overrideLocalValues = isset($hints[Query::HINT_REFRESH]); // If only a specific entity is set to refresh, check that it's the one if(isset($hints[Query::HINT_REFRESH_ENTITY])) { $overrideLocalValues = $hints[Query::HINT_REFRESH_ENTITY] === $entity; } // inject ObjectManager upon refresh. if ($overrideLocalValues && $entity instanceof ObjectManagerAware) { $entity->injectObjectManager($this->em, $class); } } if ($overrideLocalValues) { $this->originalEntityData[$oid] = $data; } } else { $entity = $this->newInstance($class); $oid = spl_object_hash($entity); $this->entityIdentifiers[$oid] = $id; $this->entityStates[$oid] = self::STATE_MANAGED; $this->originalEntityData[$oid] = $data; $this->identityMap[$class->rootEntityName][$idHash] = $entity; if ($entity instanceof NotifyPropertyChanged) { $entity->addPropertyChangedListener($this); } $overrideLocalValues = true; } if ( ! $overrideLocalValues) { return $entity; } foreach ($data as $field => $value) { if (isset($class->fieldMappings[$field])) { $class->reflFields[$field]->setValue($entity, $value); } } // Loading the entity right here, if its in the eager loading map get rid of it there. unset($this->eagerLoadingEntities[$class->rootEntityName][$idHash]); if (isset($this->eagerLoadingEntities[$class->rootEntityName]) && ! $this->eagerLoadingEntities[$class->rootEntityName]) { unset($this->eagerLoadingEntities[$class->rootEntityName]); } // Properly initialize any unfetched associations, if partial objects are not allowed. if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) { return $entity; } foreach ($class->associationMappings as $field => $assoc) { // Check if the association is not among the fetch-joined associations already. if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) { continue; } $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); switch (true) { case ($assoc['type'] & ClassMetadata::TO_ONE): if ( ! $assoc['isOwningSide']) { // Inverse side of x-to-one can never be lazy $class->reflFields[$field]->setValue($entity, $this->getEntityPersister($assoc['targetEntity'])->loadOneToOneEntity($assoc, $entity)); continue 2; } $associatedId = array(); // TODO: Is this even computed right in all cases of composite keys? foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) { $joinColumnValue = isset($data[$srcColumn]) ? $data[$srcColumn] : null; if ($joinColumnValue !== null) { if ($targetClass->containsForeignIdentifier) { $associatedId[$targetClass->getFieldForColumn($targetColumn)] = $joinColumnValue; } else { $associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue; } } } if ( ! $associatedId) { // Foreign key is NULL $class->reflFields[$field]->setValue($entity, null); $this->originalEntityData[$oid][$field] = null; continue; } if ( ! isset($hints['fetchMode'][$class->name][$field])) { $hints['fetchMode'][$class->name][$field] = $assoc['fetch']; } // Foreign key is set // 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); switch (true) { case (isset($this->identityMap[$targetClass->rootEntityName][$relatedIdHash])): $newValue = $this->identityMap[$targetClass->rootEntityName][$relatedIdHash]; // If this is an uninitialized proxy, we are deferring eager loads, // this association is marked as eager fetch, and its an uninitialized proxy (wtf!) // then we can append this entity for eager loading! if ($hints['fetchMode'][$class->name][$field] == ClassMetadata::FETCH_EAGER && isset($hints['deferEagerLoad']) && !$targetClass->isIdentifierComposite && $newValue instanceof Proxy && $newValue->__isInitialized__ === false) { $this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId); } break; case ($targetClass->subClasses): // If it might be a subtype, it can not be lazy. There isn't even // a way to solve this with deferred eager loading, which means putting // an entity with subclasses at a *-to-one location is really bad! (performance-wise) $newValue = $this->getEntityPersister($assoc['targetEntity'])->loadOneToOneEntity($assoc, $entity, $associatedId); break; default: switch (true) { // We are negating the condition here. Other cases will assume it is valid! case ($hints['fetchMode'][$class->name][$field] !== ClassMetadata::FETCH_EAGER): $newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId); break; // Deferred eager load only works for single identifier classes case (isset($hints['deferEagerLoad']) && ! $targetClass->isIdentifierComposite): // TODO: Is there a faster approach? $this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId); $newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId); break; default: // TODO: This is very imperformant, ignore it? $newValue = $this->em->find($assoc['targetEntity'], $associatedId); break; } // PERF: Inlined & optimized code from UnitOfWork#registerManaged() $newValueOid = spl_object_hash($newValue); $this->entityIdentifiers[$newValueOid] = $associatedId; $this->identityMap[$targetClass->rootEntityName][$relatedIdHash] = $newValue; if ($newValue instanceof NotifyPropertyChanged) { $newValue->addPropertyChangedListener($this); } $this->entityStates[$newValueOid] = self::STATE_MANAGED; // make sure that when an proxy is then finally loaded, $this->originalEntityData is set also! break; } $this->originalEntityData[$oid][$field] = $newValue; $class->reflFields[$field]->setValue($entity, $newValue); if ($assoc['inversedBy'] && $assoc['type'] & ClassMetadata::ONE_TO_ONE) { $inverseAssoc = $targetClass->associationMappings[$assoc['inversedBy']]; $targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($newValue, $entity); } break; default: // Inject collection $pColl = new PersistentCollection($this->em, $targetClass, new ArrayCollection); $pColl->setOwner($entity, $assoc); $pColl->setInitialized(false); $reflField = $class->reflFields[$field]; $reflField->setValue($entity, $pColl); if ($assoc['fetch'] == ClassMetadata::FETCH_EAGER) { $this->loadCollection($pColl); $pColl->takeSnapshot(); } $this->originalEntityData[$oid][$field] = $pColl; break; } } if ($overrideLocalValues) { if (isset($class->lifecycleCallbacks[Events::postLoad])) { $class->invokeLifecycleCallbacks(Events::postLoad, $entity); } if ($this->evm->hasListeners(Events::postLoad)) { $this->evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->em)); } } return $entity; } /** * @return void */ public function triggerEagerLoads() { if ( ! $this->eagerLoadingEntities) { return; } // avoid infinite recursion $eagerLoadingEntities = $this->eagerLoadingEntities; $this->eagerLoadingEntities = array(); foreach ($eagerLoadingEntities as $entityName => $ids) { if ( ! $ids) { continue; } $class = $this->em->getClassMetadata($entityName); $this->getEntityPersister($entityName)->loadAll( array_combine($class->identifier, array(array_values($ids))) ); } } /** * Initializes (loads) an uninitialized persistent collection of an entity. * * @param \Doctrine\ORM\PersistentCollection $collection The collection to initialize. * * @return void * @todo Maybe later move to EntityManager#initialize($proxyOrCollection). See DDC-733. */ public function loadCollection(PersistentCollection $collection) { $assoc = $collection->getMapping(); $persister = $this->getEntityPersister($assoc['targetEntity']); switch ($assoc['type']) { case ClassMetadata::ONE_TO_MANY: $persister->loadOneToManyCollection($assoc, $collection->getOwner(), $collection); break; case ClassMetadata::MANY_TO_MANY: $persister->loadManyToManyCollection($assoc, $collection->getOwner(), $collection); break; } } /** * Gets the identity map of the UnitOfWork. * * @return array */ public function getIdentityMap() { return $this->identityMap; } /** * Gets the original data of an entity. The original data is the data that was * present at the time the entity was reconstituted from the database. * * @param object $entity * * @return array */ public function getOriginalEntityData($entity) { $oid = spl_object_hash($entity); if (isset($this->originalEntityData[$oid])) { return $this->originalEntityData[$oid]; } return array(); } /** * @ignore */ public function setOriginalEntityData($entity, array $data) { $this->originalEntityData[spl_object_hash($entity)] = $data; } /** * INTERNAL: * Sets a property value of the original data array of an entity. * * @ignore * @param string $oid * @param string $property * @param mixed $value */ public function setOriginalEntityProperty($oid, $property, $value) { $this->originalEntityData[$oid][$property] = $value; } /** * Gets the identifier of an entity. * The returned value is always an array of identifier values. If the entity * has a composite identifier then the identifier values are in the same * order as the identifier field names as returned by ClassMetadata#getIdentifierFieldNames(). * * @param object $entity * * @return array The identifier values. */ public function getEntityIdentifier($entity) { return $this->entityIdentifiers[spl_object_hash($entity)]; } /** * Tries to find an entity with the given identifier in the identity map of * this UnitOfWork. * * @param mixed $id The entity identifier to look for. * @param string $rootClassName The name of the root class of the mapped entity hierarchy. * * @return mixed Returns the entity with the specified identifier if it exists in * this UnitOfWork, FALSE otherwise. */ public function tryGetById($id, $rootClassName) { $idHash = implode(' ', (array) $id); if (isset($this->identityMap[$rootClassName][$idHash])) { return $this->identityMap[$rootClassName][$idHash]; } return false; } /** * Schedules an entity for dirty-checking at commit-time. * * @param object $entity The entity to schedule for dirty-checking. * @todo Rename: scheduleForSynchronization */ public function scheduleForDirtyCheck($entity) { $rootClassName = $this->em->getClassMetadata(get_class($entity))->rootEntityName; $this->scheduledForDirtyCheck[$rootClassName][spl_object_hash($entity)] = $entity; } /** * Checks whether the UnitOfWork has any pending insertions. * * @return boolean TRUE if this UnitOfWork has pending insertions, FALSE otherwise. */ public function hasPendingInsertions() { return ! empty($this->entityInsertions); } /** * Calculates the size of the UnitOfWork. The size of the UnitOfWork is the * number of entities in the identity map. * * @return integer */ public function size() { $countArray = array_map(function ($item) { return count($item); }, $this->identityMap); return array_sum($countArray); } /** * Gets the EntityPersister for an Entity. * * @param string $entityName The name of the Entity. * * @return \Doctrine\ORM\Persisters\BasicEntityPersister */ public function getEntityPersister($entityName) { if (isset($this->persisters[$entityName])) { return $this->persisters[$entityName]; } $class = $this->em->getClassMetadata($entityName); switch (true) { case ($class->isInheritanceTypeNone()): $persister = new Persisters\BasicEntityPersister($this->em, $class); break; case ($class->isInheritanceTypeSingleTable()): $persister = new Persisters\SingleTablePersister($this->em, $class); break; case ($class->isInheritanceTypeJoined()): $persister = new Persisters\JoinedSubclassPersister($this->em, $class); break; default: $persister = new Persisters\UnionSubclassPersister($this->em, $class); } $this->persisters[$entityName] = $persister; return $this->persisters[$entityName]; } /** * Gets a collection persister for a collection-valued association. * * @param array $association * * @return \Doctrine\ORM\Persisters\AbstractCollectionPersister */ public function getCollectionPersister(array $association) { $type = $association['type']; if (isset($this->collectionPersisters[$type])) { return $this->collectionPersisters[$type]; } switch ($type) { case ClassMetadata::ONE_TO_MANY: $persister = new Persisters\OneToManyPersister($this->em); break; case ClassMetadata::MANY_TO_MANY: $persister = new Persisters\ManyToManyPersister($this->em); break; } $this->collectionPersisters[$type] = $persister; return $this->collectionPersisters[$type]; } /** * INTERNAL: * Registers an entity as managed. * * @param object $entity The entity. * @param array $id The identifier values. * @param array $data The original entity data. */ public function registerManaged($entity, array $id, array $data) { $oid = spl_object_hash($entity); $this->entityIdentifiers[$oid] = $id; $this->entityStates[$oid] = self::STATE_MANAGED; $this->originalEntityData[$oid] = $data; $this->addToIdentityMap($entity); if ($entity instanceof NotifyPropertyChanged) { $entity->addPropertyChangedListener($this); } } /** * INTERNAL: * Clears the property changeset of the entity with the given OID. * * @param string $oid The entity's OID. */ public function clearEntityChangeSet($oid) { $this->entityChangeSets[$oid] = array(); } /* PropertyChangedListener implementation */ /** * Notifies this UnitOfWork of a property change in an entity. * * @param object $entity The entity that owns the property. * @param string $propertyName The name of the property that changed. * @param mixed $oldValue The old value of the property. * @param mixed $newValue The new value of the property. */ public function propertyChanged($entity, $propertyName, $oldValue, $newValue) { $oid = spl_object_hash($entity); $class = $this->em->getClassMetadata(get_class($entity)); $isAssocField = isset($class->associationMappings[$propertyName]); if ( ! $isAssocField && ! isset($class->fieldMappings[$propertyName])) { return; // ignore non-persistent fields } // Update changeset and mark entity for synchronization $this->entityChangeSets[$oid][$propertyName] = array($oldValue, $newValue); if ( ! isset($this->scheduledForDirtyCheck[$class->rootEntityName][$oid])) { $this->scheduleForDirtyCheck($entity); } } /** * Gets the currently scheduled entity insertions in this UnitOfWork. * * @return array */ public function getScheduledEntityInsertions() { return $this->entityInsertions; } /** * Gets the currently scheduled entity updates in this UnitOfWork. * * @return array */ public function getScheduledEntityUpdates() { return $this->entityUpdates; } /** * Gets the currently scheduled entity deletions in this UnitOfWork. * * @return array */ public function getScheduledEntityDeletions() { return $this->entityDeletions; } /** * Get the currently scheduled complete collection deletions * * @return array */ public function getScheduledCollectionDeletions() { return $this->collectionDeletions; } /** * Gets the currently scheduled collection inserts, updates and deletes. * * @return array */ public function getScheduledCollectionUpdates() { return $this->collectionUpdates; } /** * Helper method to initialize a lazy loading proxy or persistent collection. * * @param object * * @return void */ public function initializeObject($obj) { if ($obj instanceof Proxy) { $obj->__load(); return; } if ($obj instanceof PersistentCollection) { $obj->initialize(); } } /** * Helper method to show an object as string. * * @param object $obj * * @return string */ private static function objToStr($obj) { return method_exists($obj, '__toString') ? (string)$obj : get_class($obj).'@'.spl_object_hash($obj); } /** * Marks an entity as read-only so that it will not be considered for updates during UnitOfWork#commit(). * * This operation cannot be undone as some parts of the UnitOfWork now keep gathering information * on this object that might be necessary to perform a correct update. * * * @param object $object * * @throws ORMInvalidArgumentException * * @return void */ public function markReadOnly($object) { if ( ! is_object($object) || ! $this->isInIdentityMap($object)) { throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object); } $this->readOnlyObjects[spl_object_hash($object)] = true; } /** * Is this entity read only? * * @param object $object * * @throws ORMInvalidArgumentException * * @return bool */ public function isReadOnly($object) { if ( ! is_object($object)) { throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object); } return isset($this->readOnlyObjects[spl_object_hash($object)]); } } DoctrineORM-2.3.3/Doctrine/ORM/Version.php0000644000175100017510000000366712143374607020337 0ustar beberleibeberlei. */ namespace Doctrine\ORM; /** * Class to store and retrieve the version of Doctrine * * * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Version { /** * Current Doctrine Version */ const VERSION = '2.3.3'; /** * Compares a Doctrine version with the current one. * * @param string $version Doctrine version to compare. * @return int Returns -1 if older, 0 if it is the same, 1 if version * passed as argument is newer. */ public static function compare($version) { $currentVersion = str_replace(' ', '', strtolower(self::VERSION)); $version = str_replace(' ', '', $version); return version_compare($version, $currentVersion); } } DoctrineORM-2.3.3/Doctrine/ORM/Event/LifecycleEventArgs.php0000644000175100017510000000410012143374607023470 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Event; use Doctrine\Common\EventArgs; use Doctrine\ORM\EntityManager; /** * Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions * of entities. * * @link www.doctrine-project.org * @since 2.0 * @author Roman Borschel * @author Benjamin Eberlei */ class LifecycleEventArgs extends EventArgs { /** * @var \Doctrine\ORM\EntityManager */ private $em; /** * @var object */ private $entity; /** * Constructor * * @param object $entity * @param \Doctrine\ORM\EntityManager $em */ public function __construct($entity, EntityManager $em) { $this->entity = $entity; $this->em = $em; } /** * Retrieve associated Entity. * * @return object */ public function getEntity() { return $this->entity; } /** * Retrieve associated EntityManager. * * @return \Doctrine\ORM\EntityManager */ public function getEntityManager() { return $this->em; } } DoctrineORM-2.3.3/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php0000644000175100017510000000425412143374607025111 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Event; use Doctrine\Common\EventArgs; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\EntityManager; /** * Class that holds event arguments for a loadMetadata event. * * @author Jonathan H. Wage * @since 2.0 */ class LoadClassMetadataEventArgs extends EventArgs { /** * @var \Doctrine\ORM\Mapping\ClassMetadata */ private $classMetadata; /** * @var \Doctrine\ORM\EntityManager */ private $em; /** * Constructor. * * @param \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata * @param \Doctrine\ORM\EntityManager $em */ public function __construct(ClassMetadataInfo $classMetadata, EntityManager $em) { $this->classMetadata = $classMetadata; $this->em = $em; } /** * Retrieve associated ClassMetadata. * * @return \Doctrine\ORM\Mapping\ClassMetadataInfo */ public function getClassMetadata() { return $this->classMetadata; } /** * Retrieve associated EntityManager. * * @return \Doctrine\ORM\EntityManager */ public function getEntityManager() { return $this->em; } } DoctrineORM-2.3.3/Doctrine/ORM/Event/OnClearEventArgs.php0000644000175100017510000000453312143374607023126 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Event; /** * Provides event arguments for the onClear event. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @author Roman Borschel * @author Benjamin Eberlei */ class OnClearEventArgs extends \Doctrine\Common\EventArgs { /** * @var \Doctrine\ORM\EntityManager */ private $em; /** * @var string */ private $entityClass; /** * Constructor. * * @param \Doctrine\ORM\EntityManager $em * @param string $entityClass Optional entity class */ public function __construct($em, $entityClass = null) { $this->em = $em; $this->entityClass = $entityClass; } /** * Retrieve associated EntityManager. * * @return \Doctrine\ORM\EntityManager */ public function getEntityManager() { return $this->em; } /** * Name of the entity class that is cleared, or empty if all are cleared. * * @return string */ public function getEntityClass() { return $this->entityClass; } /** * Check if event clears all entities. * * @return bool */ public function clearsAllEntities() { return ($this->entityClass === null); } } DoctrineORM-2.3.3/Doctrine/ORM/Event/OnFlushEventArgs.php0000644000175100017510000000430212143374607023153 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Event; use Doctrine\ORM\EntityManager; /** * Provides event arguments for the preFlush event. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @author Roman Borschel * @author Benjamin Eberlei */ class OnFlushEventArgs extends \Doctrine\Common\EventArgs { /** * @var Doctirne\ORM\EntityManager */ private $em; //private $entitiesToPersist = array(); //private $entitiesToRemove = array(); /** * Constructor. * * @param \Doctrine\ORM\EntityManager $em */ public function __construct(EntityManager $em) { $this->em = $em; } /** * Retrieve associated EntityManager. * * @return \Doctrine\ORM\EntityManager */ public function getEntityManager() { return $this->em; } /* public function addEntityToPersist($entity) { } public function addEntityToRemove($entity) { } public function addEntityToUpdate($entity) { } public function getEntitiesToPersist() { return $this->_entitiesToPersist; } */ } DoctrineORM-2.3.3/Doctrine/ORM/Event/PostFlushEventArgs.php0000644000175100017510000000343012143374607023525 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Event; use Doctrine\ORM\EntityManager; use Doctrine\Common\EventArgs; /** * Provides event arguments for the postFlush event. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @author Daniel Freudenberger */ class PostFlushEventArgs extends EventArgs { /** * @var \Doctrine\ORM\EntityManager */ private $em; /** * Constructor. * * @param \Doctrine\ORM\EntityManager $em */ public function __construct(EntityManager $em) { $this->em = $em; } /** * Retrieve associated EntityManager. * * @return \Doctrine\ORM\EntityManager */ public function getEntityManager() { return $this->em; } } DoctrineORM-2.3.3/Doctrine/ORM/Event/PreFlushEventArgs.php0000644000175100017510000000316212143374607023330 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Event; /** * Provides event arguments for the preFlush event. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 2.0 * @author Roman Borschel * @author Benjamin Eberlei */ class PreFlushEventArgs extends \Doctrine\Common\EventArgs { /** * @var EntityManager */ private $_em; public function __construct($em) { $this->_em = $em; } /** * @return EntityManager */ public function getEntityManager() { return $this->_em; } } DoctrineORM-2.3.3/Doctrine/ORM/Event/PreUpdateEventArgs.php0000644000175100017510000000657312143374607023502 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Event; use Doctrine\Common\EventArgs, Doctrine\ORM\EntityManager; /** * Class that holds event arguments for a preInsert/preUpdate event. * * @author Guilherme Blanco * @author Roman Borschel * @author Benjamin Eberlei * @since 2.0 */ class PreUpdateEventArgs extends LifecycleEventArgs { /** * @var array */ private $entityChangeSet; /** * Constructor. * * @param object $entity * @param \Doctrine\ORM\EntityManager $em * @param array $changeSet */ public function __construct($entity, EntityManager $em, array &$changeSet) { parent::__construct($entity, $em); $this->entityChangeSet = &$changeSet; } /** * Retrieve entity changeset. * * @return array */ public function getEntityChangeSet() { return $this->entityChangeSet; } /** * Check if field has a changeset. * * @return boolean */ public function hasChangedField($field) { return isset($this->entityChangeSet[$field]); } /** * Get the old value of the changeset of the changed field. * * @param string $field * @return mixed */ public function getOldValue($field) { $this->assertValidField($field); return $this->entityChangeSet[$field][0]; } /** * Get the new value of the changeset of the changed field. * * @param string $field * @return mixed */ public function getNewValue($field) { $this->assertValidField($field); return $this->entityChangeSet[$field][1]; } /** * Set the new value of this field. * * @param string $field * @param mixed $value */ public function setNewValue($field, $value) { $this->assertValidField($field); $this->entityChangeSet[$field][1] = $value; } /** * Assert the field exists in changeset. * * @param string $field */ private function assertValidField($field) { if ( ! isset($this->entityChangeSet[$field])) { throw new \InvalidArgumentException(sprintf( 'Field "%s" is not a valid field of the entity "%s" in PreUpdateEventArgs.', $field, get_class($this->getEntity()) )); } } } DoctrineORM-2.3.3/Doctrine/ORM/Id/AbstractIdGenerator.php0000644000175100017510000000331012143374607023116 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Id; use Doctrine\ORM\EntityManager; abstract class AbstractIdGenerator { /** * Generates an identifier for an entity. * * @param \Doctrine\ORM\Entity $entity * @return mixed */ abstract public function generate(EntityManager $em, $entity); /** * Gets whether this generator is a post-insert generator which means that * {@link generate()} must be called after the entity has been inserted * into the database. * * By default, this method returns FALSE. Generators that have this requirement * must override this method and return TRUE. * * @return boolean */ public function isPostInsertGenerator() { return false; } } DoctrineORM-2.3.3/Doctrine/ORM/Id/AssignedGenerator.php0000644000175100017510000000510612143374607022640 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Id; use Doctrine\ORM\EntityManager; use Doctrine\ORM\ORMException; /** * Special generator for application-assigned identifiers (doesnt really generate anything). * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class AssignedGenerator extends AbstractIdGenerator { /** * Returns the identifier assigned to the given entity. * * @param object $entity * @return mixed * @override */ public function generate(EntityManager $em, $entity) { $class = $em->getClassMetadata(get_class($entity)); $idFields = $class->getIdentifierFieldNames(); $identifier = array(); foreach ($idFields as $idField) { $value = $class->reflFields[$idField]->getValue($entity); if ( ! isset($value)) { throw ORMException::entityMissingAssignedIdForField($entity, $idField); } if (isset($class->associationMappings[$idField])) { if ( ! $em->getUnitOfWork()->isInIdentityMap($value)) { throw ORMException::entityMissingForeignAssignedId($entity, $value); } // NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced. $value = current($em->getUnitOfWork()->getEntityIdentifier($value)); } $identifier[$idField] = $value; } return $identifier; } } DoctrineORM-2.3.3/Doctrine/ORM/Id/BigIntegerIdentityGenerator.php0000644000175100017510000000424012143374607024632 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Id; use Doctrine\ORM\EntityManager; /** * Id generator that obtains IDs from special "identity" columns. These are columns * that automatically get a database-generated, auto-incremented identifier on INSERT. * This generator obtains the last insert id after such an insert. */ class BigIntegerIdentityGenerator extends AbstractIdGenerator { /** * The name of the sequence to pass to lastInsertId(), if any. * * @var string */ private $sequenceName; /** * Constructor. * * @param string|null $seqName The name of the sequence to pass to lastInsertId() * to obtain the last generated identifier within the current * database session/connection, if any. */ public function __construct($sequenceName = null) { $this->sequenceName = $sequenceName; } /** * {@inheritdoc} */ public function generate(EntityManager $em, $entity) { return (string)$em->getConnection()->lastInsertId($this->sequenceName); } /** * {@inheritdoc} */ public function isPostInsertGenerator() { return true; } } DoctrineORM-2.3.3/Doctrine/ORM/Id/IdentityGenerator.php0000644000175100017510000000415012143374607022672 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Id; use Doctrine\ORM\EntityManager; /** * Id generator that obtains IDs from special "identity" columns. These are columns * that automatically get a database-generated, auto-incremented identifier on INSERT. * This generator obtains the last insert id after such an insert. */ class IdentityGenerator extends AbstractIdGenerator { /** * The name of the sequence to pass to lastInsertId(), if any. * * @var string */ private $sequenceName; /** * @param string $seqName The name of the sequence to pass to lastInsertId() * to obtain the last generated identifier within the current * database session/connection, if any. */ public function __construct($sequenceName = null) { $this->sequenceName = $sequenceName; } /** * {@inheritdoc} */ public function generate(EntityManager $em, $entity) { return (int)$em->getConnection()->lastInsertId($this->sequenceName); } /** * {@inheritdoc} */ public function isPostInsertGenerator() { return true; } } DoctrineORM-2.3.3/Doctrine/ORM/Id/SequenceGenerator.php0000644000175100017510000000642212143374607022655 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Id; use Serializable, Doctrine\ORM\EntityManager; /** * Represents an ID generator that uses a database sequence. * * @since 2.0 * @author Roman Borschel */ class SequenceGenerator extends AbstractIdGenerator implements Serializable { private $_allocationSize; private $_sequenceName; private $_nextValue = 0; private $_maxValue = null; /** * Initializes a new sequence generator. * * @param \Doctrine\ORM\EntityManager $em The EntityManager to use. * @param string $sequenceName The name of the sequence. * @param integer $allocationSize The allocation size of the sequence. */ public function __construct($sequenceName, $allocationSize) { $this->_sequenceName = $sequenceName; $this->_allocationSize = $allocationSize; } /** * Generates an ID for the given entity. * * @param object $entity * @return integer|float The generated value. * @override */ public function generate(EntityManager $em, $entity) { if ($this->_maxValue === null || $this->_nextValue == $this->_maxValue) { // Allocate new values $conn = $em->getConnection(); $sql = $conn->getDatabasePlatform()->getSequenceNextValSQL($this->_sequenceName); $this->_nextValue = (int)$conn->fetchColumn($sql); $this->_maxValue = $this->_nextValue + $this->_allocationSize; } return $this->_nextValue++; } /** * Gets the maximum value of the currently allocated bag of values. * * @return integer|float */ public function getCurrentMaxValue() { return $this->_maxValue; } /** * Gets the next value that will be returned by generate(). * * @return integer|float */ public function getNextValue() { return $this->_nextValue; } public function serialize() { return serialize(array( 'allocationSize' => $this->_allocationSize, 'sequenceName' => $this->_sequenceName )); } public function unserialize($serialized) { $array = unserialize($serialized); $this->_sequenceName = $array['sequenceName']; $this->_allocationSize = $array['allocationSize']; } } DoctrineORM-2.3.3/Doctrine/ORM/Id/TableGenerator.php0000644000175100017510000000633712143374607022141 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Id; use Doctrine\ORM\EntityManager; /** * Id generator that uses a single-row database table and a hi/lo algorithm. * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class TableGenerator extends AbstractIdGenerator { private $_tableName; private $_sequenceName; private $_allocationSize; private $_nextValue; private $_maxValue; public function __construct($tableName, $sequenceName = 'default', $allocationSize = 10) { $this->_tableName = $tableName; $this->_sequenceName = $sequenceName; $this->_allocationSize = $allocationSize; } public function generate(EntityManager $em, $entity) { if ($this->_maxValue === null || $this->_nextValue == $this->_maxValue) { // Allocate new values $conn = $em->getConnection(); if ($conn->getTransactionNestingLevel() === 0) { // use select for update $sql = $conn->getDatabasePlatform()->getTableHiLoCurrentValSql($this->_tableName, $this->_sequenceName); $currentLevel = $conn->fetchColumn($sql); if ($currentLevel != null) { $this->_nextValue = $currentLevel; $this->_maxValue = $this->_nextValue + $this->_allocationSize; $updateSql = $conn->getDatabasePlatform()->getTableHiLoUpdateNextValSql( $this->_tableName, $this->_sequenceName, $this->_allocationSize ); if ($conn->executeUpdate($updateSql, array(1 => $currentLevel, 2 => $currentLevel+1)) !== 1) { // no affected rows, concurrency issue, throw exception } } else { // no current level returned, TableGenerator seems to be broken, throw exception } } else { // only table locks help here, implement this or throw exception? // or do we want to work with table locks exclusively? } } return $this->_nextValue++; } } DoctrineORM-2.3.3/Doctrine/ORM/Id/UuidGenerator.php0000644000175100017510000000331112143374607022005 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Id; use Serializable, Doctrine\ORM\EntityManager; /** * Represents an ID generator that uses the database UUID expression * * @since 2.3 * @author Maarten de Keizer */ class UuidGenerator extends AbstractIdGenerator { /** * Generates an ID for the given entity. * * @param Doctrine\ORM\EntityManager $em The EntityManager to user * @param object $entity * @return string The generated value. * @override */ public function generate(EntityManager $em, $entity) { $conn = $em->getConnection(); $sql = 'SELECT ' . $conn->getDatabasePlatform()->getGuidExpression(); return $conn->query($sql)->fetchColumn(0); } } DoctrineORM-2.3.3/Doctrine/ORM/Internal/CommitOrderCalculator.php0000644000175100017510000000707212143374607024716 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Internal; /** * The CommitOrderCalculator is used by the UnitOfWork to sort out the * correct order in which changes to entities need to be persisted. * * @since 2.0 * @author Roman Borschel * @author Guilherme Blanco */ class CommitOrderCalculator { const NOT_VISITED = 1; const IN_PROGRESS = 2; const VISITED = 3; private $_nodeStates = array(); private $_classes = array(); // The nodes to sort private $_relatedClasses = array(); private $_sorted = array(); /** * Clears the current graph. * * @return void */ public function clear() { $this->_classes = $this->_relatedClasses = array(); } /** * Gets a valid commit order for all current nodes. * * Uses a depth-first search (DFS) to traverse the graph. * The desired topological sorting is the reverse postorder of these searches. * * @return array The list of ordered classes. */ public function getCommitOrder() { // Check whether we need to do anything. 0 or 1 node is easy. $nodeCount = count($this->_classes); if ($nodeCount <= 1) { return ($nodeCount == 1) ? array_values($this->_classes) : array(); } // Init foreach ($this->_classes as $node) { $this->_nodeStates[$node->name] = self::NOT_VISITED; } // Go foreach ($this->_classes as $node) { if ($this->_nodeStates[$node->name] == self::NOT_VISITED) { $this->_visitNode($node); } } $sorted = array_reverse($this->_sorted); $this->_sorted = $this->_nodeStates = array(); return $sorted; } private function _visitNode($node) { $this->_nodeStates[$node->name] = self::IN_PROGRESS; if (isset($this->_relatedClasses[$node->name])) { foreach ($this->_relatedClasses[$node->name] as $relatedNode) { if ($this->_nodeStates[$relatedNode->name] == self::NOT_VISITED) { $this->_visitNode($relatedNode); } } } $this->_nodeStates[$node->name] = self::VISITED; $this->_sorted[] = $node; } public function addDependency($fromClass, $toClass) { $this->_relatedClasses[$fromClass->name][] = $toClass; } public function hasClass($className) { return isset($this->_classes[$className]); } public function addClass($class) { $this->_classes[$class->name] = $class; } } DoctrineORM-2.3.3/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php0000644000175100017510000003466212143374607025706 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Internal\Hydration; use PDO, Doctrine\DBAL\Connection, Doctrine\DBAL\Types\Type, Doctrine\ORM\EntityManager, Doctrine\ORM\Events, Doctrine\ORM\Mapping\ClassMetadata; /** * Base class for all hydrators. A hydrator is a class that provides some form * of transformation of an SQL result set into another structure. * * @since 2.0 * @author Konsta Vesterinen * @author Roman Borschel * @author Guilherme Blanco */ abstract class AbstractHydrator { /** @var \Doctrine\ORM\Query\ResultSetMapping The ResultSetMapping. */ protected $_rsm; /** @var EntityManager The EntityManager instance. */ protected $_em; /** @var \Doctrine\DBAL\Platforms\AbstractPlatform The dbms Platform instance */ protected $_platform; /** @var \Doctrine\ORM\UnitOfWork The UnitOfWork of the associated EntityManager. */ protected $_uow; /** @var array The cache used during row-by-row hydration. */ protected $_cache = array(); /** @var \Doctrine\DBAL\Driver\Statement The statement that provides the data to hydrate. */ protected $_stmt; /** @var array The query hints. */ protected $_hints; /** * Initializes a new instance of a class derived from AbstractHydrator. * * @param \Doctrine\ORM\EntityManager $em The EntityManager to use. */ public function __construct(EntityManager $em) { $this->_em = $em; $this->_platform = $em->getConnection()->getDatabasePlatform(); $this->_uow = $em->getUnitOfWork(); } /** * Initiates a row-by-row hydration. * * @param object $stmt * @param object $resultSetMapping * * @return IterableResult */ public function iterate($stmt, $resultSetMapping, array $hints = array()) { $this->_stmt = $stmt; $this->_rsm = $resultSetMapping; $this->_hints = $hints; $evm = $this->_em->getEventManager(); $evm->addEventListener(array(Events::onClear), $this); $this->prepare(); return new IterableResult($this); } /** * Hydrates all rows returned by the passed statement instance at once. * * @param object $stmt * @param object $resultSetMapping * @param array $hints * @return mixed */ public function hydrateAll($stmt, $resultSetMapping, array $hints = array()) { $this->_stmt = $stmt; $this->_rsm = $resultSetMapping; $this->_hints = $hints; $this->prepare(); $result = $this->hydrateAllData(); $this->cleanup(); return $result; } /** * Hydrates a single row returned by the current statement instance during * row-by-row hydration with {@link iterate()}. * * @return mixed */ public function hydrateRow() { $row = $this->_stmt->fetch(PDO::FETCH_ASSOC); if ( ! $row) { $this->cleanup(); return false; } $result = array(); $this->hydrateRowData($row, $this->_cache, $result); return $result; } /** * Excutes one-time preparation tasks, once each time hydration is started * through {@link hydrateAll} or {@link iterate()}. */ protected function prepare() {} /** * Excutes one-time cleanup tasks at the end of a hydration that was initiated * through {@link hydrateAll} or {@link iterate()}. */ protected function cleanup() { $this->_rsm = null; $this->_stmt->closeCursor(); $this->_stmt = null; } /** * Hydrates a single row from the current statement instance. * * Template method. * * @param array $data The row data. * @param array $cache The cache to use. * @param mixed $result The result to fill. */ protected function hydrateRowData(array $data, array &$cache, array &$result) { throw new HydrationException("hydrateRowData() not implemented by this hydrator."); } /** * Hydrates all rows from the current statement instance at once. */ abstract protected function hydrateAllData(); /** * Processes a row of the result set. * * Used for identity-based hydration (HYDRATE_OBJECT and HYDRATE_ARRAY). * Puts the elements of a result row into a new array, grouped by the dql alias * they belong to. The column names in the result set are mapped to their * field names during this procedure as well as any necessary conversions on * the values applied. Scalar values are kept in a specfic key 'scalars'. * * @param array $data SQL Result Row * @param array &$cache Cache for column to field result information * @param array &$id Dql-Alias => ID-Hash * @param array &$nonemptyComponents Does this DQL-Alias has at least one non NULL value? * * @return array An array with all the fields (name => value) of the data row, * grouped by their component alias. */ protected function gatherRowData(array $data, array &$cache, array &$id, array &$nonemptyComponents) { $rowData = array(); foreach ($data as $key => $value) { // Parse each column name only once. Cache the results. if ( ! isset($cache[$key])) { switch (true) { // NOTE: Most of the times it's a field mapping, so keep it first!!! case (isset($this->_rsm->fieldMappings[$key])): $fieldName = $this->_rsm->fieldMappings[$key]; $classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]); $cache[$key]['fieldName'] = $fieldName; $cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']); $cache[$key]['isIdentifier'] = $classMetadata->isIdentifier($fieldName); $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; break; case (isset($this->_rsm->scalarMappings[$key])): $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key]; $cache[$key]['type'] = Type::getType($this->_rsm->typeMappings[$key]); $cache[$key]['isScalar'] = true; break; case (isset($this->_rsm->metaMappings[$key])): // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). $fieldName = $this->_rsm->metaMappings[$key]; $classMetadata = $this->_em->getClassMetadata($this->_rsm->aliasMap[$this->_rsm->columnOwnerMap[$key]]); $cache[$key]['isMetaColumn'] = true; $cache[$key]['fieldName'] = $fieldName; $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; $cache[$key]['isIdentifier'] = isset($this->_rsm->isIdentifierColumn[$cache[$key]['dqlAlias']][$key]); break; default: // this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2 // maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping. continue 2; } } if (isset($cache[$key]['isScalar'])) { $value = $cache[$key]['type']->convertToPHPValue($value, $this->_platform); $rowData['scalars'][$cache[$key]['fieldName']] = $value; continue; } $dqlAlias = $cache[$key]['dqlAlias']; if ($cache[$key]['isIdentifier']) { $id[$dqlAlias] .= '|' . $value; } if (isset($cache[$key]['isMetaColumn'])) { if ( ! isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) && $value !== null) { $rowData[$dqlAlias][$cache[$key]['fieldName']] = $value; if ($cache[$key]['isIdentifier']) { $nonemptyComponents[$dqlAlias] = true; } } continue; } // in an inheritance hierarchy the same field could be defined several times. // We overwrite this value so long we dont have a non-null value, that value we keep. // Per definition it cannot be that a field is defined several times and has several values. if (isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) && $value === null) { continue; } $rowData[$dqlAlias][$cache[$key]['fieldName']] = $cache[$key]['type']->convertToPHPValue($value, $this->_platform); if ( ! isset($nonemptyComponents[$dqlAlias]) && $value !== null) { $nonemptyComponents[$dqlAlias] = true; } } return $rowData; } /** * Processes a row of the result set. * * Used for HYDRATE_SCALAR. This is a variant of _gatherRowData() that * simply converts column names to field names and properly converts the * values according to their types. The resulting row has the same number * of elements as before. * * @param array $data * @param array $cache * * @return array The processed row. */ protected function gatherScalarRowData(&$data, &$cache) { $rowData = array(); foreach ($data as $key => $value) { // Parse each column name only once. Cache the results. if ( ! isset($cache[$key])) { switch (true) { // NOTE: During scalar hydration, most of the times it's a scalar mapping, keep it first!!! case (isset($this->_rsm->scalarMappings[$key])): $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key]; $cache[$key]['isScalar'] = true; break; case (isset($this->_rsm->fieldMappings[$key])): $fieldName = $this->_rsm->fieldMappings[$key]; $classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]); $cache[$key]['fieldName'] = $fieldName; $cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']); $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; break; case (isset($this->_rsm->metaMappings[$key])): // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). $cache[$key]['isMetaColumn'] = true; $cache[$key]['fieldName'] = $this->_rsm->metaMappings[$key]; $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; break; default: // this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2 // maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping. continue 2; } } $fieldName = $cache[$key]['fieldName']; switch (true) { case (isset($cache[$key]['isScalar'])): $rowData[$fieldName] = $value; break; case (isset($cache[$key]['isMetaColumn'])): $rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value; break; default: $value = $cache[$key]['type']->convertToPHPValue($value, $this->_platform); $rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value; } } return $rowData; } /** * Register entity as managed in UnitOfWork. * * @param \Doctrine\ORM\Mapping\ClassMetadata $class * @param object $entity * @param array $data * * @todo The "$id" generation is the same of UnitOfWork#createEntity. Remove this duplication somehow */ protected function registerManaged(ClassMetadata $class, $entity, array $data) { if ($class->isIdentifierComposite) { $id = array(); foreach ($class->identifier as $fieldName) { if (isset($class->associationMappings[$fieldName])) { $id[$fieldName] = $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']]; } else { $id[$fieldName] = $data[$fieldName]; } } } else { if (isset($class->associationMappings[$class->identifier[0]])) { $id = array($class->identifier[0] => $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']]); } else { $id = array($class->identifier[0] => $data[$class->identifier[0]]); } } $this->_em->getUnitOfWork()->registerManaged($entity, $id, $data); } /** * When executed in a hydrate() loop we have to clear internal state to * decrease memory consumption. */ public function onClear($eventArgs) { } } DoctrineORM-2.3.3/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php0000644000175100017510000002527312143374607025217 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Internal\Hydration; use PDO, Doctrine\DBAL\Connection, Doctrine\ORM\Mapping\ClassMetadata; /** * The ArrayHydrator produces a nested array "graph" that is often (not always) * interchangeable with the corresponding object graph for read-only access. * * @since 2.0 * @author Roman Borschel * @author Guilherme Blanco */ class ArrayHydrator extends AbstractHydrator { private $_ce = array(); private $_rootAliases = array(); private $_isSimpleQuery = false; private $_identifierMap = array(); private $_resultPointers = array(); private $_idTemplate = array(); private $_resultCounter = 0; /** * {@inheritdoc} */ protected function prepare() { $this->_isSimpleQuery = count($this->_rsm->aliasMap) <= 1; $this->_identifierMap = array(); $this->_resultPointers = array(); $this->_idTemplate = array(); $this->_resultCounter = 0; foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { $this->_identifierMap[$dqlAlias] = array(); $this->_resultPointers[$dqlAlias] = array(); $this->_idTemplate[$dqlAlias] = ''; } } /** * {@inheritdoc} */ protected function hydrateAllData() { $result = array(); $cache = array(); while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { $this->hydrateRowData($data, $cache, $result); } return $result; } /** * {@inheritdoc} */ protected function hydrateRowData(array $row, array &$cache, array &$result) { // 1) Initialize $id = $this->_idTemplate; // initialize the id-memory $nonemptyComponents = array(); $rowData = $this->gatherRowData($row, $cache, $id, $nonemptyComponents); // Extract scalar values. They're appended at the end. if (isset($rowData['scalars'])) { $scalars = $rowData['scalars']; unset($rowData['scalars']); if (empty($rowData)) { ++$this->_resultCounter; } } // 2) Now hydrate the data found in the current row. foreach ($rowData as $dqlAlias => $data) { $index = false; if (isset($this->_rsm->parentAliasMap[$dqlAlias])) { // It's a joined result $parent = $this->_rsm->parentAliasMap[$dqlAlias]; $path = $parent . '.' . $dqlAlias; // missing parent data, skipping as RIGHT JOIN hydration is not supported. if ( ! isset($nonemptyComponents[$parent]) ) { continue; } // Get a reference to the right element in the result tree. // This element will get the associated element attached. if ($this->_rsm->isMixed && isset($this->_rootAliases[$parent])) { $first = reset($this->_resultPointers); // TODO: Exception if $key === null ? $baseElement =& $this->_resultPointers[$parent][key($first)]; } else if (isset($this->_resultPointers[$parent])) { $baseElement =& $this->_resultPointers[$parent]; } else { unset($this->_resultPointers[$dqlAlias]); // Ticket #1228 continue; } $relationAlias = $this->_rsm->relationMap[$dqlAlias]; $relation = $this->getClassMetadata($this->_rsm->aliasMap[$parent])->associationMappings[$relationAlias]; // Check the type of the relation (many or single-valued) if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) { $oneToOne = false; if (isset($nonemptyComponents[$dqlAlias])) { if ( ! isset($baseElement[$relationAlias])) { $baseElement[$relationAlias] = array(); } $indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]); $index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false; $indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false; if ( ! $indexExists || ! $indexIsValid) { $element = $data; if (isset($this->_rsm->indexByMap[$dqlAlias])) { $baseElement[$relationAlias][$row[$this->_rsm->indexByMap[$dqlAlias]]] = $element; } else { $baseElement[$relationAlias][] = $element; } end($baseElement[$relationAlias]); $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = key($baseElement[$relationAlias]); } } else if ( ! isset($baseElement[$relationAlias])) { $baseElement[$relationAlias] = array(); } } else { $oneToOne = true; if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($baseElement[$relationAlias])) { $baseElement[$relationAlias] = null; } else if ( ! isset($baseElement[$relationAlias])) { $baseElement[$relationAlias] = $data; } } $coll =& $baseElement[$relationAlias]; if ($coll !== null) { $this->updateResultPointer($coll, $index, $dqlAlias, $oneToOne); } } else { // It's a root result element $this->_rootAliases[$dqlAlias] = true; // Mark as root $entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0; // if this row has a NULL value for the root result id then make it a null result. if ( ! isset($nonemptyComponents[$dqlAlias]) ) { if ($this->_rsm->isMixed) { $result[] = array($entityKey => null); } else { $result[] = null; } $resultKey = $this->_resultCounter; ++$this->_resultCounter; continue; } // Check for an existing element if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { $element = $rowData[$dqlAlias]; if ($this->_rsm->isMixed) { $element = array($entityKey => $element); } if (isset($this->_rsm->indexByMap[$dqlAlias])) { $resultKey = $row[$this->_rsm->indexByMap[$dqlAlias]]; $result[$resultKey] = $element; } else { $resultKey = $this->_resultCounter; $result[] = $element; ++$this->_resultCounter; } $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey; } else { $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]]; $resultKey = $index; /*if ($this->_rsm->isMixed) { $result[] =& $result[$index]; ++$this->_resultCounter; }*/ } $this->updateResultPointer($result, $index, $dqlAlias, false); } } // Append scalar values to mixed result sets if (isset($scalars)) { if ( ! isset($resultKey) ) { // this only ever happens when no object is fetched (scalar result only) if (isset($this->_rsm->indexByMap['scalars'])) { $resultKey = $row[$this->_rsm->indexByMap['scalars']]; } else { $resultKey = $this->_resultCounter - 1; } } foreach ($scalars as $name => $value) { $result[$resultKey][$name] = $value; } } } /** * Updates the result pointer for an Entity. The result pointers point to the * last seen instance of each Entity type. This is used for graph construction. * * @param array $coll The element. * @param boolean|integer $index Index of the element in the collection. * @param string $dqlAlias * @param boolean $oneToOne Whether it is a single-valued association or not. */ private function updateResultPointer(array &$coll, $index, $dqlAlias, $oneToOne) { if ($coll === null) { unset($this->_resultPointers[$dqlAlias]); // Ticket #1228 return; } if ($index !== false) { $this->_resultPointers[$dqlAlias] =& $coll[$index]; return; } if ( ! $coll) { return; } if ($oneToOne) { $this->_resultPointers[$dqlAlias] =& $coll; return; } end($coll); $this->_resultPointers[$dqlAlias] =& $coll[key($coll)]; return; } /** * Retrieve ClassMetadata associated to entity class name. * * @param string $className * * @return \Doctrine\ORM\Mapping\ClassMetadata */ private function getClassMetadata($className) { if ( ! isset($this->_ce[$className])) { $this->_ce[$className] = $this->_em->getClassMetadata($className); } return $this->_ce[$className]; } } DoctrineORM-2.3.3/Doctrine/ORM/Internal/Hydration/HydrationException.php0000644000175100017510000000350512143374607026236 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Internal\Hydration; /** * Represents a result structure that can be iterated over, hydrating row-by-row * during the iteration. An IterableResult is obtained by AbstractHydrator#iterate(). * * @author robo * @since 2.0 */ class IterableResult implements \Iterator { /** * @var \Doctrine\ORM\Internal\Hydration\AbstractHydrator */ private $_hydrator; /** * @var boolean */ private $_rewinded = false; /** * @var integer */ private $_key = -1; /** * @var object */ private $_current = null; /** * @param \Doctrine\ORM\Internal\Hydration\AbstractHydrator $hydrator */ public function __construct($hydrator) { $this->_hydrator = $hydrator; } public function rewind() { if ($this->_rewinded == true) { throw new HydrationException("Can only iterate a Result once."); } else { $this->_current = $this->next(); $this->_rewinded = true; } } /** * Gets the next set of results. * * @return array */ public function next() { $this->_current = $this->_hydrator->hydrateRow(); $this->_key++; return $this->_current; } /** * @return mixed */ public function current() { return $this->_current; } /** * @return int */ public function key() { return $this->_key; } /** * @return bool */ public function valid() { return ($this->_current!=false); } } DoctrineORM-2.3.3/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php0000644000175100017510000005716312143374607025352 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Internal\Hydration; use PDO, Doctrine\ORM\Mapping\ClassMetadata, Doctrine\ORM\PersistentCollection, Doctrine\ORM\Query, Doctrine\ORM\Event\LifecycleEventArgs, Doctrine\ORM\Events, Doctrine\Common\Collections\ArrayCollection, Doctrine\Common\Collections\Collection, Doctrine\ORM\Proxy\Proxy; /** * The ObjectHydrator constructs an object graph out of an SQL result set. * * @since 2.0 * @author Roman Borschel * @author Guilherme Blanco * * @internal Highly performance-sensitive code. */ class ObjectHydrator extends AbstractHydrator { /* Local ClassMetadata cache to avoid going to the EntityManager all the time. * This local cache is maintained between hydration runs and not cleared. */ private $_ce = array(); /* The following parts are reinitialized on every hydration run. */ private $_identifierMap; private $_resultPointers; private $_idTemplate; private $_resultCounter; private $_rootAliases = array(); private $_initializedCollections = array(); private $_existingCollections = array(); /** @override */ protected function prepare() { $this->_identifierMap = $this->_resultPointers = $this->_idTemplate = array(); $this->_resultCounter = 0; if ( ! isset($this->_hints['deferEagerLoad'])) { $this->_hints['deferEagerLoad'] = true; } foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { $this->_identifierMap[$dqlAlias] = array(); $this->_idTemplate[$dqlAlias] = ''; if ( ! isset($this->_ce[$className])) { $this->_ce[$className] = $this->_em->getClassMetadata($className); } // Remember which associations are "fetch joined", so that we know where to inject // collection stubs or proxies and where not. if ( ! isset($this->_rsm->relationMap[$dqlAlias])) { continue; } if ( ! isset($this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]])) { throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $this->_rsm->parentAliasMap[$dqlAlias]); } $sourceClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]]; $sourceClass = $this->_getClassMetadata($sourceClassName); $assoc = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]]; $this->_hints['fetched'][$this->_rsm->parentAliasMap[$dqlAlias]][$assoc['fieldName']] = true; if ($assoc['type'] === ClassMetadata::MANY_TO_MANY) { continue; } // Mark any non-collection opposite sides as fetched, too. if ($assoc['mappedBy']) { $this->_hints['fetched'][$dqlAlias][$assoc['mappedBy']] = true; continue; } // handle fetch-joined owning side bi-directional one-to-one associations if ($assoc['inversedBy']) { $class = $this->_ce[$className]; $inverseAssoc = $class->associationMappings[$assoc['inversedBy']]; if ( ! ($inverseAssoc['type'] & ClassMetadata::TO_ONE)) { continue; } $this->_hints['fetched'][$dqlAlias][$inverseAssoc['fieldName']] = true; } } } /** * {@inheritdoc} */ protected function cleanup() { $eagerLoad = (isset($this->_hints['deferEagerLoad'])) && $this->_hints['deferEagerLoad'] == true; parent::cleanup(); $this->_identifierMap = $this->_initializedCollections = $this->_existingCollections = $this->_resultPointers = array(); if ($eagerLoad) { $this->_em->getUnitOfWork()->triggerEagerLoads(); } } /** * {@inheritdoc} */ protected function hydrateAllData() { $result = array(); $cache = array(); while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { $this->hydrateRowData($row, $cache, $result); } // Take snapshots from all newly initialized collections foreach ($this->_initializedCollections as $coll) { $coll->takeSnapshot(); } return $result; } /** * Initializes a related collection. * * @param object $entity The entity to which the collection belongs. * @param ClassMetadata $class * @param string $fieldName The name of the field on the entity that holds the collection. * @param string $parentDqlAlias Alias of the parent fetch joining this collection. */ private function _initRelatedCollection($entity, $class, $fieldName, $parentDqlAlias) { $oid = spl_object_hash($entity); $relation = $class->associationMappings[$fieldName]; $value = $class->reflFields[$fieldName]->getValue($entity); if ($value === null) { $value = new ArrayCollection; } if ( ! $value instanceof PersistentCollection) { $value = new PersistentCollection( $this->_em, $this->_ce[$relation['targetEntity']], $value ); $value->setOwner($entity, $relation); $class->reflFields[$fieldName]->setValue($entity, $value); $this->_uow->setOriginalEntityProperty($oid, $fieldName, $value); $this->_initializedCollections[$oid . $fieldName] = $value; } else if ( isset($this->_hints[Query::HINT_REFRESH]) || isset($this->_hints['fetched'][$parentDqlAlias][$fieldName]) && ! $value->isInitialized() ) { // Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED! $value->setDirty(false); $value->setInitialized(true); $value->unwrap()->clear(); $this->_initializedCollections[$oid . $fieldName] = $value; } else { // Is already PersistentCollection, and DON'T REFRESH or FETCH-JOIN! $this->_existingCollections[$oid . $fieldName] = $value; } return $value; } /** * Gets an entity instance. * * @param array $data The instance data. * @param string $dqlAlias The DQL alias of the entity's class. * @return object The entity. */ private function _getEntity(array $data, $dqlAlias) { $className = $this->_rsm->aliasMap[$dqlAlias]; if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) { if ( ! isset($this->_rsm->metaMappings[$this->_rsm->discriminatorColumns[$dqlAlias]])) { throw HydrationException::missingDiscriminatorMetaMappingColumn($className, $this->_rsm->discriminatorColumns[$dqlAlias], $dqlAlias); } $discrColumn = $this->_rsm->metaMappings[$this->_rsm->discriminatorColumns[$dqlAlias]]; if ( ! isset($data[$discrColumn])) { throw HydrationException::missingDiscriminatorColumn($className, $discrColumn, $dqlAlias); } if ($data[$discrColumn] === "") { throw HydrationException::emptyDiscriminatorValue($dqlAlias); } $className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]]; unset($data[$discrColumn]); } if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->_rootAliases[$dqlAlias])) { $this->registerManaged($this->_ce[$className], $this->_hints[Query::HINT_REFRESH_ENTITY], $data); } $this->_hints['fetchAlias'] = $dqlAlias; return $this->_uow->createEntity($className, $data, $this->_hints); } /** * @param string $className * @param array $data * @return mixed */ private function _getEntityFromIdentityMap($className, array $data) { // TODO: Abstract this code and UnitOfWork::createEntity() equivalent? $class = $this->_ce[$className]; /* @var $class ClassMetadata */ if ($class->isIdentifierComposite) { $idHash = ''; foreach ($class->identifier as $fieldName) { if (isset($class->associationMappings[$fieldName])) { $idHash .= $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']] . ' '; } else { $idHash .= $data[$fieldName] . ' '; } } return $this->_uow->tryGetByIdHash(rtrim($idHash), $class->rootEntityName); } else if (isset($class->associationMappings[$class->identifier[0]])) { return $this->_uow->tryGetByIdHash($data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']], $class->rootEntityName); } else { return $this->_uow->tryGetByIdHash($data[$class->identifier[0]], $class->rootEntityName); } } /** * Gets a ClassMetadata instance from the local cache. * If the instance is not yet in the local cache, it is loaded into the * local cache. * * @param string $className The name of the class. * @return ClassMetadata */ private function _getClassMetadata($className) { if ( ! isset($this->_ce[$className])) { $this->_ce[$className] = $this->_em->getClassMetadata($className); } return $this->_ce[$className]; } /** * Hydrates a single row in an SQL result set. * * @internal * First, the data of the row is split into chunks where each chunk contains data * that belongs to a particular component/class. Afterwards, all these chunks * are processed, one after the other. For each chunk of class data only one of the * following code paths is executed: * * Path A: The data chunk belongs to a joined/associated object and the association * is collection-valued. * Path B: The data chunk belongs to a joined/associated object and the association * is single-valued. * Path C: The data chunk belongs to a root result element/object that appears in the topmost * level of the hydrated result. A typical example are the objects of the type * specified by the FROM clause in a DQL query. * * @param array $row The data of the row to process. * @param array $cache The cache to use. * @param array $result The result array to fill. */ protected function hydrateRowData(array $row, array &$cache, array &$result) { // Initialize $id = $this->_idTemplate; // initialize the id-memory $nonemptyComponents = array(); // Split the row data into chunks of class data. $rowData = $this->gatherRowData($row, $cache, $id, $nonemptyComponents); // Extract scalar values. They're appended at the end. if (isset($rowData['scalars'])) { $scalars = $rowData['scalars']; unset($rowData['scalars']); if (empty($rowData)) { ++$this->_resultCounter; } } // Hydrate the data chunks foreach ($rowData as $dqlAlias => $data) { $entityName = $this->_rsm->aliasMap[$dqlAlias]; if (isset($this->_rsm->parentAliasMap[$dqlAlias])) { // It's a joined result $parentAlias = $this->_rsm->parentAliasMap[$dqlAlias]; // we need the $path to save into the identifier map which entities were already // seen for this parent-child relationship $path = $parentAlias . '.' . $dqlAlias; // We have a RIGHT JOIN result here. Doctrine cannot hydrate RIGHT JOIN Object-Graphs if ( ! isset($nonemptyComponents[$parentAlias])) { // TODO: Add special case code where we hydrate the right join objects into identity map at least continue; } // Get a reference to the parent object to which the joined element belongs. if ($this->_rsm->isMixed && isset($this->_rootAliases[$parentAlias])) { $first = reset($this->_resultPointers); $parentObject = $first[key($first)]; } else if (isset($this->_resultPointers[$parentAlias])) { $parentObject = $this->_resultPointers[$parentAlias]; } else { // Parent object of relation not found, so skip it. continue; } $parentClass = $this->_ce[$this->_rsm->aliasMap[$parentAlias]]; $oid = spl_object_hash($parentObject); $relationField = $this->_rsm->relationMap[$dqlAlias]; $relation = $parentClass->associationMappings[$relationField]; $reflField = $parentClass->reflFields[$relationField]; // Check the type of the relation (many or single-valued) if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) { $reflFieldValue = $reflField->getValue($parentObject); // PATH A: Collection-valued association if (isset($nonemptyComponents[$dqlAlias])) { $collKey = $oid . $relationField; if (isset($this->_initializedCollections[$collKey])) { $reflFieldValue = $this->_initializedCollections[$collKey]; } else if ( ! isset($this->_existingCollections[$collKey])) { $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias); } $indexExists = isset($this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]); $index = $indexExists ? $this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] : false; $indexIsValid = $index !== false ? isset($reflFieldValue[$index]) : false; if ( ! $indexExists || ! $indexIsValid) { if (isset($this->_existingCollections[$collKey])) { // Collection exists, only look for the element in the identity map. if ($element = $this->_getEntityFromIdentityMap($entityName, $data)) { $this->_resultPointers[$dqlAlias] = $element; } else { unset($this->_resultPointers[$dqlAlias]); } } else { $element = $this->_getEntity($data, $dqlAlias); if (isset($this->_rsm->indexByMap[$dqlAlias])) { $indexValue = $row[$this->_rsm->indexByMap[$dqlAlias]]; $reflFieldValue->hydrateSet($indexValue, $element); $this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue; } else { $reflFieldValue->hydrateAdd($element); $reflFieldValue->last(); $this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $reflFieldValue->key(); } // Update result pointer $this->_resultPointers[$dqlAlias] = $element; } } else { // Update result pointer $this->_resultPointers[$dqlAlias] = $reflFieldValue[$index]; } } else if ( ! $reflFieldValue) { $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias); } else if ($reflFieldValue instanceof PersistentCollection && $reflFieldValue->isInitialized() === false) { $reflFieldValue->setInitialized(true); } } else { // PATH B: Single-valued association $reflFieldValue = $reflField->getValue($parentObject); if ( ! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH]) || ($reflFieldValue instanceof Proxy && !$reflFieldValue->__isInitialized__)) { // we only need to take action if this value is null, // we refresh the entity or its an unitialized proxy. if (isset($nonemptyComponents[$dqlAlias])) { $element = $this->_getEntity($data, $dqlAlias); $reflField->setValue($parentObject, $element); $this->_uow->setOriginalEntityProperty($oid, $relationField, $element); $targetClass = $this->_ce[$relation['targetEntity']]; if ($relation['isOwningSide']) { //TODO: Just check hints['fetched'] here? // If there is an inverse mapping on the target class its bidirectional if ($relation['inversedBy']) { $inverseAssoc = $targetClass->associationMappings[$relation['inversedBy']]; if ($inverseAssoc['type'] & ClassMetadata::TO_ONE) { $targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($element, $parentObject); $this->_uow->setOriginalEntityProperty(spl_object_hash($element), $inverseAssoc['fieldName'], $parentObject); } } else if ($parentClass === $targetClass && $relation['mappedBy']) { // Special case: bi-directional self-referencing one-one on the same class $targetClass->reflFields[$relationField]->setValue($element, $parentObject); } } else { // For sure bidirectional, as there is no inverse side in unidirectional mappings $targetClass->reflFields[$relation['mappedBy']]->setValue($element, $parentObject); $this->_uow->setOriginalEntityProperty(spl_object_hash($element), $relation['mappedBy'], $parentObject); } // Update result pointer $this->_resultPointers[$dqlAlias] = $element; } else { $this->_uow->setOriginalEntityProperty($oid, $relationField, null); $reflField->setValue($parentObject, null); } // else leave $reflFieldValue null for single-valued associations } else { // Update result pointer $this->_resultPointers[$dqlAlias] = $reflFieldValue; } } } else { // PATH C: Its a root result element $this->_rootAliases[$dqlAlias] = true; // Mark as root alias $entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0; // if this row has a NULL value for the root result id then make it a null result. if ( ! isset($nonemptyComponents[$dqlAlias]) ) { if ($this->_rsm->isMixed) { $result[] = array($entityKey => null); } else { $result[] = null; } $resultKey = $this->_resultCounter; ++$this->_resultCounter; continue; } // check for existing result from the iterations before if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { $element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias); if ($this->_rsm->isMixed) { $element = array($entityKey => $element); } if (isset($this->_rsm->indexByMap[$dqlAlias])) { $resultKey = $row[$this->_rsm->indexByMap[$dqlAlias]]; if (isset($this->_hints['collection'])) { $this->_hints['collection']->hydrateSet($resultKey, $element); } $result[$resultKey] = $element; } else { $resultKey = $this->_resultCounter; ++$this->_resultCounter; if (isset($this->_hints['collection'])) { $this->_hints['collection']->hydrateAdd($element); } $result[] = $element; } $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey; // Update result pointer $this->_resultPointers[$dqlAlias] = $element; } else { // Update result pointer $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]]; $this->_resultPointers[$dqlAlias] = $result[$index]; $resultKey = $index; /*if ($this->_rsm->isMixed) { $result[] = $result[$index]; ++$this->_resultCounter; }*/ } } } // Append scalar values to mixed result sets if (isset($scalars)) { if ( ! isset($resultKey) ) { if (isset($this->_rsm->indexByMap['scalars'])) { $resultKey = $row[$this->_rsm->indexByMap['scalars']]; } else { $resultKey = $this->_resultCounter - 1; } } foreach ($scalars as $name => $value) { $result[$resultKey][$name] = $value; } } } /** * When executed in a hydrate() loop we may have to clear internal state to * decrease memory consumption. */ public function onClear($eventArgs) { parent::onClear($eventArgs); $aliases = array_keys($this->_identifierMap); $this->_identifierMap = array(); foreach ($aliases as $alias) { $this->_identifierMap[$alias] = array(); } } } DoctrineORM-2.3.3/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php0000644000175100017510000000363212143374607025341 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Internal\Hydration; /** * Hydrator that produces flat, rectangular results of scalar data. * The created result is almost the same as a regular SQL result set, except * that column names are mapped to field names and data type conversions take place. * * @since 2.0 * @author Roman Borschel * @author Guilherme Blanco */ class ScalarHydrator extends AbstractHydrator { /** * {@inheritdoc} */ protected function hydrateAllData() { $result = array(); $cache = array(); while ($data = $this->_stmt->fetch(\PDO::FETCH_ASSOC)) { $this->hydrateRowData($data, $cache, $result); } return $result; } /** * {@inheritdoc} */ protected function hydrateRowData(array $data, array &$cache, array &$result) { $result[] = $this->gatherScalarRowData($data, $cache); } } DoctrineORM-2.3.3/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php0000644000175100017510000001517612143374607026522 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Internal\Hydration; use \PDO, Doctrine\DBAL\Types\Type, Doctrine\ORM\Mapping\ClassMetadata, Doctrine\ORM\Event\LifecycleEventArgs, Doctrine\ORM\Events, Doctrine\ORM\Query; class SimpleObjectHydrator extends AbstractHydrator { /** * @var ClassMetadata */ private $class; /** * @var array */ private $declaringClasses = array(); /** * {@inheritdoc} */ protected function hydrateAllData() { $result = array(); $cache = array(); while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { $this->hydrateRowData($row, $cache, $result); } $this->_em->getUnitOfWork()->triggerEagerLoads(); return $result; } /** * {@inheritdoc} */ protected function prepare() { if (count($this->_rsm->aliasMap) !== 1) { throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping that contains more than one object result."); } if ($this->_rsm->scalarMappings) { throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping that contains scalar mappings."); } $this->class = $this->_em->getClassMetadata(reset($this->_rsm->aliasMap)); // We only need to add declaring classes if we have inheritance. if ($this->class->inheritanceType === ClassMetadata::INHERITANCE_TYPE_NONE) { return; } foreach ($this->_rsm->declaringClasses as $column => $class) { $this->declaringClasses[$column] = $this->_em->getClassMetadata($class); } } /** * {@inheritdoc} */ protected function hydrateRowData(array $sqlResult, array &$cache, array &$result) { $entityName = $this->class->name; $data = array(); // We need to find the correct entity class name if we have inheritance in resultset if ($this->class->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { $discrColumnName = $this->_platform->getSQLResultCasing($this->class->discriminatorColumn['name']); if ( ! isset($sqlResult[$discrColumnName])) { throw HydrationException::missingDiscriminatorColumn($entityName, $discrColumnName, key($this->_rsm->aliasMap)); } if ($sqlResult[$discrColumnName] === '') { throw HydrationException::emptyDiscriminatorValue(key($this->_rsm->aliasMap)); } $entityName = $this->class->discriminatorMap[$sqlResult[$discrColumnName]]; unset($sqlResult[$discrColumnName]); } foreach ($sqlResult as $column => $value) { // Hydrate column information if not yet present if ( ! isset($cache[$column])) { if (($info = $this->hydrateColumnInfo($entityName, $column)) === null) { continue; } $cache[$column] = $info; } // Convert field to a valid PHP value if (isset($cache[$column]['field'])) { $type = Type::getType($cache[$column]['class']->fieldMappings[$cache[$column]['name']]['type']); $value = $type->convertToPHPValue($value, $this->_platform); } // Prevent overwrite in case of inherit classes using same property name (See AbstractHydrator) if (isset($cache[$column]) && ( ! isset($data[$cache[$column]['name']]) || $value !== null)) { $data[$cache[$column]['name']] = $value; } } if (isset($this->_hints[Query::HINT_REFRESH_ENTITY])) { $this->registerManaged($this->class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data); } $uow = $this->_em->getUnitOfWork(); $entity = $uow->createEntity($entityName, $data, $this->_hints); $result[] = $entity; } /** * Retrieve column information form ResultSetMapping. * * @param string $entityName * @param string $column * * @return array */ protected function hydrateColumnInfo($entityName, $column) { switch (true) { case (isset($this->_rsm->fieldMappings[$column])): $class = isset($this->declaringClasses[$column]) ? $this->declaringClasses[$column] : $this->class; // If class is not part of the inheritance, ignore if ( ! ($class->name === $entityName || is_subclass_of($entityName, $class->name))) { return null; } return array( 'class' => $class, 'name' => $this->_rsm->fieldMappings[$column], 'field' => true, ); case (isset($this->_rsm->relationMap[$column])): $class = isset($this->_rsm->relationMap[$column]) ? $this->_rsm->relationMap[$column] : $this->class; // If class is not self referencing, ignore if ( ! ($class === $entityName || is_subclass_of($entityName, $class))) { return null; } // TODO: Decide what to do with associations. It seems original code is incomplete. // One solution is to load the association, but it might require extra efforts. return array('name' => $column); case (isset($this->_rsm->metaMappings[$column])): return array( 'name' => $this->_rsm->metaMappings[$column] ); default: return null; } } } DoctrineORM-2.3.3/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php0000644000175100017510000000361312143374607026502 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Internal\Hydration; use Doctrine\DBAL\Connection, Doctrine\ORM\NoResultException, Doctrine\ORM\NonUniqueResultException; /** * Hydrator that hydrates a single scalar value from the result set. * * @since 2.0 * @author Roman Borschel * @author Guilherme Blanco */ class SingleScalarHydrator extends AbstractHydrator { /** * {@inheritdoc} */ protected function hydrateAllData() { $data = $this->_stmt->fetchAll(\PDO::FETCH_ASSOC); $numRows = count($data); if ($numRows === 0) { throw new NoResultException(); } if ($numRows > 1 || count($data[key($data)]) > 1) { throw new NonUniqueResultException(); } $cache = array(); $result = $this->gatherScalarRowData($data[key($data)], $cache); return array_shift($result); } } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Annotation.php0000644000175100017510000000202712143374607022404 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; interface Annotation { } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/AssociationOverride.php0000644000175100017510000000334312143374607024250 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * This annotation is used to override association mapping of property for an entity relationship. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("ANNOTATION") */ final class AssociationOverride implements Annotation { /** * The name of the relationship property whose mapping is being overridden * * @var string */ public $name; /** * The join column that is being mapped to the persistent attribute. * * @var array<\Doctrine\ORM\Mapping\JoinColumn> */ public $joinColumns; /** * The join table that maps the relationship. * * @var \Doctrine\ORM\Mapping\JoinTable */ public $joinTable; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/AssociationOverrides.php0000644000175100017510000000264712143374607024441 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * This annotation is used to override association mappings of relationship properties. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("CLASS") */ final class AssociationOverrides implements Annotation { /** * Mapping overrides of relationship properties * * @var array<\Doctrine\ORM\Mapping\AssociationOverride> */ public $value; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/AttributeOverride.php0000644000175100017510000000276112143374607023742 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * This annotation is used to override the mapping of a entity property. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("ANNOTATION") */ final class AttributeOverride implements Annotation { /** * The name of the property whose mapping is being overridden. * * @var string */ public $name; /** * The column definition. * * @var \Doctrine\ORM\Mapping\Column */ public $column; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/AttributeOverrides.php0000644000175100017510000000262712143374607024126 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * This annotation is used to override the mapping of a entity property. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("CLASS") */ final class AttributeOverrides implements Annotation { /** * One or more field or property mapping overrides. * * @var array<\Doctrine\ORM\Mapping\AttributeOverride> */ public $value; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/ChangeTrackingPolicy.php0000644000175100017510000000221612143374607024322 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("CLASS") */ final class ChangeTrackingPolicy implements Annotation { /** @var string */ public $value; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/ClassMetadata.php0000644000175100017510000000221112143374607022773 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * {@inheritDoc} * * @todo remove or rename ClassMetadataInfo to ClassMetadata */ class ClassMetadata extends ClassMetadataInfo { } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/ClassMetadataFactory.php0000644000175100017510000005111412143374607024331 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; use ReflectionException; use Doctrine\ORM\ORMException; use Doctrine\ORM\EntityManager; use Doctrine\DBAL\Platforms; use Doctrine\ORM\Events; use Doctrine\Common\Persistence\Mapping\ReflectionService; use Doctrine\Common\Persistence\Mapping\ClassMetadata as ClassMetadataInterface; use Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory; use Doctrine\ORM\Id\IdentityGenerator; use Doctrine\ORM\Id\BigIntegerIdentityGenerator; use Doctrine\ORM\Event\LoadClassMetadataEventArgs; /** * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the * metadata mapping information of a class which describes how a class should be mapped * to a relational database. * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ClassMetadataFactory extends AbstractClassMetadataFactory { /** * @var EntityManager */ private $em; /** * @var \Doctrine\DBAL\Platforms\AbstractPlatform */ private $targetPlatform; /** * @var \Doctrine\Common\Persistence\Mapping\Driver\MappingDriver */ private $driver; /** * @var \Doctrine\Common\EventManager */ private $evm; /** * @param EntityManager $em */ public function setEntityManager(EntityManager $em) { $this->em = $em; } /** * {@inheritDoc}. */ protected function initialize() { $this->driver = $this->em->getConfiguration()->getMetadataDriverImpl(); $this->targetPlatform = $this->em->getConnection()->getDatabasePlatform(); $this->evm = $this->em->getEventManager(); $this->initialized = true; } /** * {@inheritDoc} */ protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents) { /* @var $class ClassMetadata */ /* @var $parent ClassMetadata */ if ($parent) { $class->setInheritanceType($parent->inheritanceType); $class->setDiscriminatorColumn($parent->discriminatorColumn); $class->setIdGeneratorType($parent->generatorType); $this->addInheritedFields($class, $parent); $this->addInheritedRelations($class, $parent); $class->setIdentifier($parent->identifier); $class->setVersioned($parent->isVersioned); $class->setVersionField($parent->versionField); $class->setDiscriminatorMap($parent->discriminatorMap); $class->setLifecycleCallbacks($parent->lifecycleCallbacks); $class->setChangeTrackingPolicy($parent->changeTrackingPolicy); if ($parent->isMappedSuperclass) { $class->setCustomRepositoryClass($parent->customRepositoryClassName); } } // Invoke driver try { $this->driver->loadMetadataForClass($class->getName(), $class); } catch (ReflectionException $e) { throw MappingException::reflectionFailure($class->getName(), $e); } // If this class has a parent the id generator strategy is inherited. // However this is only true if the hierarchy of parents contains the root entity, // if it consists of mapped superclasses these don't necessarily include the id field. if ($parent && $rootEntityFound) { if ($parent->isIdGeneratorSequence()) { $class->setSequenceGeneratorDefinition($parent->sequenceGeneratorDefinition); } else if ($parent->isIdGeneratorTable()) { $class->tableGeneratorDefinition = $parent->tableGeneratorDefinition; } if ($parent->generatorType) { $class->setIdGeneratorType($parent->generatorType); } if ($parent->idGenerator) { $class->setIdGenerator($parent->idGenerator); } } else { $this->completeIdGeneratorMapping($class); } if ($parent && $parent->isInheritanceTypeSingleTable()) { $class->setPrimaryTable($parent->table); } if ($parent && $parent->containsForeignIdentifier) { $class->containsForeignIdentifier = true; } if ($parent && !empty($parent->namedQueries)) { $this->addInheritedNamedQueries($class, $parent); } if ($parent && !empty($parent->namedNativeQueries)) { $this->addInheritedNamedNativeQueries($class, $parent); } if ($parent && !empty($parent->sqlResultSetMappings)) { $this->addInheritedSqlResultSetMappings($class, $parent); } $class->setParentClasses($nonSuperclassParents); if ( $class->isRootEntity() && ! $class->isInheritanceTypeNone() && ! $class->discriminatorMap) { $this->addDefaultDiscriminatorMap($class); } if ($this->evm->hasListeners(Events::loadClassMetadata)) { $eventArgs = new LoadClassMetadataEventArgs($class, $this->em); $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs); } $this->wakeupReflection($class, $this->getReflectionService()); $this->validateRuntimeMetadata($class, $parent); } /** * Validate runtime metadata is correctly defined. * * @param ClassMetadata $class * @param $parent * @throws MappingException */ protected function validateRuntimeMetadata($class, $parent) { if ( ! $class->reflClass ) { // only validate if there is a reflection class instance return; } $class->validateIdentifier(); $class->validateAssocations(); $class->validateLifecycleCallbacks($this->getReflectionService()); // verify inheritance if ( ! $class->isMappedSuperclass && !$class->isInheritanceTypeNone()) { if ( ! $parent) { if (count($class->discriminatorMap) == 0) { throw MappingException::missingDiscriminatorMap($class->name); } if ( ! $class->discriminatorColumn) { throw MappingException::missingDiscriminatorColumn($class->name); } } else if ($parent && !$class->reflClass->isAbstract() && !in_array($class->name, array_values($class->discriminatorMap))) { // enforce discriminator map for all entities of an inheritance hierarchy, otherwise problems will occur. throw MappingException::mappedClassNotPartOfDiscriminatorMap($class->name, $class->rootEntityName); } } else if ($class->isMappedSuperclass && $class->name == $class->rootEntityName && (count($class->discriminatorMap) || $class->discriminatorColumn)) { // second condition is necessary for mapped superclasses in the middle of an inheritance hierarchy throw MappingException::noInheritanceOnMappedSuperClass($class->name); } } /** * {@inheritDoc} */ protected function newClassMetadataInstance($className) { return new ClassMetadata($className, $this->em->getConfiguration()->getNamingStrategy()); } /** * Adds a default discriminator map if no one is given * * If an entity is of any inheritance type and does not contain a * discriminator map, then the map is generated automatically. This process * is expensive computation wise. * * The automatically generated discriminator map contains the lowercase short name of * each class as key. * * @param \Doctrine\ORM\Mapping\ClassMetadata $class * @throws MappingException */ private function addDefaultDiscriminatorMap(ClassMetadata $class) { $allClasses = $this->driver->getAllClassNames(); $fqcn = $class->getName(); $map = array($this->getShortName($class->name) => $fqcn); $duplicates = array(); foreach ($allClasses as $subClassCandidate) { if (is_subclass_of($subClassCandidate, $fqcn)) { $shortName = $this->getShortName($subClassCandidate); if (isset($map[$shortName])) { $duplicates[] = $shortName; } $map[$shortName] = $subClassCandidate; } } if ($duplicates) { throw MappingException::duplicateDiscriminatorEntry($class->name, $duplicates, $map); } $class->setDiscriminatorMap($map); } /** * Get the lower-case short name of a class. * * @param string $className * @return string */ private function getShortName($className) { if (strpos($className, "\\") === false) { return strtolower($className); } $parts = explode("\\", $className); return strtolower(end($parts)); } /** * Adds inherited fields to the subclass mapping. * * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass */ private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass) { foreach ($parentClass->fieldMappings as $mapping) { if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) { $mapping['inherited'] = $parentClass->name; } if ( ! isset($mapping['declared'])) { $mapping['declared'] = $parentClass->name; } $subClass->addInheritedFieldMapping($mapping); } foreach ($parentClass->reflFields as $name => $field) { $subClass->reflFields[$name] = $field; } } /** * Adds inherited association mappings to the subclass mapping. * * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass * @throws MappingException */ private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass) { foreach ($parentClass->associationMappings as $field => $mapping) { if ($parentClass->isMappedSuperclass) { if ($mapping['type'] & ClassMetadata::TO_MANY && !$mapping['isOwningSide']) { throw MappingException::illegalToManyAssocationOnMappedSuperclass($parentClass->name, $field); } $mapping['sourceEntity'] = $subClass->name; } //$subclassMapping = $mapping; if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) { $mapping['inherited'] = $parentClass->name; } if ( ! isset($mapping['declared'])) { $mapping['declared'] = $parentClass->name; } $subClass->addInheritedAssociationMapping($mapping); } } /** * Adds inherited named queries to the subclass mapping. * * @since 2.2 * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass */ private function addInheritedNamedQueries(ClassMetadata $subClass, ClassMetadata $parentClass) { foreach ($parentClass->namedQueries as $name => $query) { if ( ! isset ($subClass->namedQueries[$name])) { $subClass->addNamedQuery(array( 'name' => $query['name'], 'query' => $query['query'] )); } } } /** * Adds inherited named native queries to the subclass mapping. * * @since 2.3 * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass */ private function addInheritedNamedNativeQueries(ClassMetadata $subClass, ClassMetadata $parentClass) { foreach ($parentClass->namedNativeQueries as $name => $query) { if ( ! isset ($subClass->namedNativeQueries[$name])) { $subClass->addNamedNativeQuery(array( 'name' => $query['name'], 'query' => $query['query'], 'isSelfClass' => $query['isSelfClass'], 'resultSetMapping' => $query['resultSetMapping'], 'resultClass' => $query['isSelfClass'] ? $subClass->name : $query['resultClass'], )); } } } /** * Adds inherited sql result set mappings to the subclass mapping. * * @since 2.3 * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass */ private function addInheritedSqlResultSetMappings(ClassMetadata $subClass, ClassMetadata $parentClass) { foreach ($parentClass->sqlResultSetMappings as $name => $mapping) { if ( ! isset ($subClass->sqlResultSetMappings[$name])) { $entities = array(); foreach ($mapping['entities'] as $entity) { $entities[] = array( 'fields' => $entity['fields'], 'isSelfClass' => $entity['isSelfClass'], 'discriminatorColumn' => $entity['discriminatorColumn'], 'entityClass' => $entity['isSelfClass'] ? $subClass->name : $entity['entityClass'], ); } $subClass->addSqlResultSetMapping(array( 'name' => $mapping['name'], 'columns' => $mapping['columns'], 'entities' => $entities, )); } } } /** * Completes the ID generator mapping. If "auto" is specified we choose the generator * most appropriate for the targeted database platform. * * @param ClassMetadataInfo $class * @throws ORMException */ private function completeIdGeneratorMapping(ClassMetadataInfo $class) { $idGenType = $class->generatorType; if ($idGenType == ClassMetadata::GENERATOR_TYPE_AUTO) { if ($this->targetPlatform->prefersSequences()) { $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_SEQUENCE); } else if ($this->targetPlatform->prefersIdentityColumns()) { $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_IDENTITY); } else { $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_TABLE); } } // Create & assign an appropriate ID generator instance switch ($class->generatorType) { case ClassMetadata::GENERATOR_TYPE_IDENTITY: // For PostgreSQL IDENTITY (SERIAL) we need a sequence name. It defaults to // __seq in PostgreSQL for SERIAL columns. // Not pretty but necessary and the simplest solution that currently works. $sequenceName = null; $fieldName = $class->identifier ? $class->getSingleIdentifierFieldName() : null; if ($this->targetPlatform instanceof Platforms\PostgreSQLPlatform) { $columnName = $class->getSingleIdentifierColumnName(); $quoted = isset($class->fieldMappings[$fieldName]['quoted']) || isset($class->table['quoted']); $sequenceName = $class->getTableName() . '_' . $columnName . '_seq'; $definition = array( 'sequenceName' => $this->targetPlatform->fixSchemaElementName($sequenceName) ); if ($quoted) { $definition['quoted'] = true; } $sequenceName = $this->em->getConfiguration()->getQuoteStrategy()->getSequenceName($definition, $class, $this->targetPlatform); } $generator = ($fieldName && $class->fieldMappings[$fieldName]['type'] === "bigint") ? new BigIntegerIdentityGenerator($sequenceName) : new IdentityGenerator($sequenceName); $class->setIdGenerator($generator); break; case ClassMetadata::GENERATOR_TYPE_SEQUENCE: // If there is no sequence definition yet, create a default definition $definition = $class->sequenceGeneratorDefinition; if ( ! $definition) { $fieldName = $class->getSingleIdentifierFieldName(); $columnName = $class->getSingleIdentifierColumnName(); $quoted = isset($class->fieldMappings[$fieldName]['quoted']) || isset($class->table['quoted']); $sequenceName = $class->getTableName() . '_' . $columnName . '_seq'; $definition = array( 'sequenceName' => $this->targetPlatform->fixSchemaElementName($sequenceName), 'allocationSize' => 1, 'initialValue' => 1, ); if ($quoted) { $definition['quoted'] = true; } $class->setSequenceGeneratorDefinition($definition); } $sequenceGenerator = new \Doctrine\ORM\Id\SequenceGenerator( $this->em->getConfiguration()->getQuoteStrategy()->getSequenceName($definition, $class, $this->targetPlatform), $definition['allocationSize'] ); $class->setIdGenerator($sequenceGenerator); break; case ClassMetadata::GENERATOR_TYPE_NONE: $class->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator()); break; case ClassMetadata::GENERATOR_TYPE_UUID: $class->setIdGenerator(new \Doctrine\ORM\Id\UuidGenerator()); break; case ClassMetadata::GENERATOR_TYPE_TABLE: throw new ORMException("TableGenerator not yet implemented."); break; case ClassMetadata::GENERATOR_TYPE_CUSTOM: $definition = $class->customGeneratorDefinition; if ( ! class_exists($definition['class'])) { throw new ORMException("Can't instantiate custom generator : " . $definition['class']); } $class->setIdGenerator(new $definition['class']); break; default: throw new ORMException("Unknown generator type: " . $class->generatorType); } } /** * {@inheritDoc} */ protected function wakeupReflection(ClassMetadataInterface $class, ReflectionService $reflService) { /* @var $class ClassMetadata */ $class->wakeupReflection($reflService); } /** * {@inheritDoc} */ protected function initializeReflection(ClassMetadataInterface $class, ReflectionService $reflService) { /* @var $class ClassMetadata */ $class->initializeReflection($reflService); } /** * {@inheritDoc} */ protected function getFqcnFromAlias($namespaceAlias, $simpleClassName) { return $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName; } /** * {@inheritDoc} */ protected function getDriver() { return $this->driver; } /** * {@inheritDoc} */ protected function isEntity(ClassMetadataInterface $class) { return isset($class->isMappedSuperclass) && $class->isMappedSuperclass === false; } } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/ClassMetadataInfo.php0000644000175100017510000026527512143374607023634 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; use BadMethodCallException; use InvalidArgumentException; use RuntimeException; use Doctrine\DBAL\Types\Type; use ReflectionClass; use Doctrine\Common\Persistence\Mapping\ClassMetadata; use Doctrine\Common\ClassLoader; /** * A ClassMetadata instance holds all the object-relational mapping metadata * of an entity and it's associations. * * Once populated, ClassMetadata instances are usually cached in a serialized form. * * IMPORTANT NOTE: * * The fields of this class are only public for 2 reasons: * 1) To allow fast READ access. * 2) To drastically reduce the size of a serialized instance (private/protected members * get the whole class name, namespace inclusive, prepended to every property in * the serialized representation). * * @author Roman Borschel * @author Jonathan H. Wage * @since 2.0 */ class ClassMetadataInfo implements ClassMetadata { /* The inheritance mapping types */ /** * NONE means the class does not participate in an inheritance hierarchy * and therefore does not need an inheritance mapping type. */ const INHERITANCE_TYPE_NONE = 1; /** * JOINED means the class will be persisted according to the rules of * Class Table Inheritance. */ const INHERITANCE_TYPE_JOINED = 2; /** * SINGLE_TABLE means the class will be persisted according to the rules of * Single Table Inheritance. */ const INHERITANCE_TYPE_SINGLE_TABLE = 3; /** * TABLE_PER_CLASS means the class will be persisted according to the rules * of Concrete Table Inheritance. */ const INHERITANCE_TYPE_TABLE_PER_CLASS = 4; /* The Id generator types. */ /** * AUTO means the generator type will depend on what the used platform prefers. * Offers full portability. */ const GENERATOR_TYPE_AUTO = 1; /** * SEQUENCE means a separate sequence object will be used. Platforms that do * not have native sequence support may emulate it. Full portability is currently * not guaranteed. */ const GENERATOR_TYPE_SEQUENCE = 2; /** * TABLE means a separate table is used for id generation. * Offers full portability. */ const GENERATOR_TYPE_TABLE = 3; /** * IDENTITY means an identity column is used for id generation. The database * will fill in the id column on insertion. Platforms that do not support * native identity columns may emulate them. Full portability is currently * not guaranteed. */ const GENERATOR_TYPE_IDENTITY = 4; /** * NONE means the class does not have a generated id. That means the class * must have a natural, manually assigned id. */ const GENERATOR_TYPE_NONE = 5; /** * UUID means that a UUID/GUID expression is used for id generation. Full * portability is currently not guaranteed. */ const GENERATOR_TYPE_UUID = 6; /** * CUSTOM means that customer will use own ID generator that supposedly work */ const GENERATOR_TYPE_CUSTOM = 7; /** * DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time * by doing a property-by-property comparison with the original data. This will * be done for all entities that are in MANAGED state at commit-time. * * This is the default change tracking policy. */ const CHANGETRACKING_DEFERRED_IMPLICIT = 1; /** * DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time * by doing a property-by-property comparison with the original data. This will * be done only for entities that were explicitly saved (through persist() or a cascade). */ const CHANGETRACKING_DEFERRED_EXPLICIT = 2; /** * NOTIFY means that Doctrine relies on the entities sending out notifications * when their properties change. Such entity classes must implement * the NotifyPropertyChanged interface. */ const CHANGETRACKING_NOTIFY = 3; /** * Specifies that an association is to be fetched when it is first accessed. */ const FETCH_LAZY = 2; /** * Specifies that an association is to be fetched when the owner of the * association is fetched. */ const FETCH_EAGER = 3; /** * Specifies that an association is to be fetched lazy (on first access) and that * commands such as Collection#count, Collection#slice are issued directly against * the database if the collection is not yet initialized. */ const FETCH_EXTRA_LAZY = 4; /** * Identifies a one-to-one association. */ const ONE_TO_ONE = 1; /** * Identifies a many-to-one association. */ const MANY_TO_ONE = 2; /** * Identifies a one-to-many association. */ const ONE_TO_MANY = 4; /** * Identifies a many-to-many association. */ const MANY_TO_MANY = 8; /** * Combined bitmask for to-one (single-valued) associations. */ const TO_ONE = 3; /** * Combined bitmask for to-many (collection-valued) associations. */ const TO_MANY = 12; /** * READ-ONLY: The name of the entity class. */ public $name; /** * READ-ONLY: The namespace the entity class is contained in. * * @var string * @todo Not really needed. Usage could be localized. */ public $namespace; /** * READ-ONLY: The name of the entity class that is at the root of the mapped entity inheritance * hierarchy. If the entity is not part of a mapped inheritance hierarchy this is the same * as {@link $entityName}. * * @var string */ public $rootEntityName; /** * READ-ONLY: The definition of custom generator. Only used for CUSTOM * generator type * * The definition has the following structure: * * array( * 'class' => 'ClassName', * ) * * * @var array * @todo Merge with tableGeneratorDefinition into generic generatorDefinition */ public $customGeneratorDefinition; /** * The name of the custom repository class used for the entity class. * (Optional). * * @var string */ public $customRepositoryClassName; /** * READ-ONLY: Whether this class describes the mapping of a mapped superclass. * * @var boolean */ public $isMappedSuperclass = false; /** * READ-ONLY: The names of the parent classes (ancestors). * * @var array */ public $parentClasses = array(); /** * READ-ONLY: The names of all subclasses (descendants). * * @var array */ public $subClasses = array(); /** * READ-ONLY: The named queries allowed to be called directly from Repository. * * @var array */ public $namedQueries = array(); /** * READ-ONLY: The named native queries allowed to be called directly from Repository. * * A native SQL named query definition has the following structure: *
     * array(
     *     'name'               => ,
     *     'query'              => ,
     *     'resultClass'        => ,
     *     'resultSetMapping'   => 
     * )
     * 
*/ public $namedNativeQueries = array(); /** * READ-ONLY: The mappings of the results of native SQL queries. * * A native result mapping definition has the following structure: *
     * array(
     *     'name'               => ,
     *     'entities'           => array(),
     *     'columns'            => array()
     * )
     * 
*/ public $sqlResultSetMappings = array(); /** * READ-ONLY: The field names of all fields that are part of the identifier/primary key * of the mapped entity class. * * @var array */ public $identifier = array(); /** * READ-ONLY: The inheritance mapping type used by the class. * * @var integer */ public $inheritanceType = self::INHERITANCE_TYPE_NONE; /** * READ-ONLY: The Id generator type used by the class. * * @var string */ public $generatorType = self::GENERATOR_TYPE_NONE; /** * READ-ONLY: The field mappings of the class. * Keys are field names and values are mapping definitions. * * The mapping definition array has the following values: * * - fieldName (string) * The name of the field in the Entity. * * - type (string) * The type name of the mapped field. Can be one of Doctrine's mapping types * or a custom mapping type. * * - columnName (string, optional) * The column name. Optional. Defaults to the field name. * * - length (integer, optional) * The database length of the column. Optional. Default value taken from * the type. * * - id (boolean, optional) * Marks the field as the primary key of the entity. Multiple fields of an * entity can have the id attribute, forming a composite key. * * - nullable (boolean, optional) * Whether the column is nullable. Defaults to FALSE. * * - columnDefinition (string, optional, schema-only) * The SQL fragment that is used when generating the DDL for the column. * * - precision (integer, optional, schema-only) * The precision of a decimal column. Only valid if the column type is decimal. * * - scale (integer, optional, schema-only) * The scale of a decimal column. Only valid if the column type is decimal. * [* - 'unique'] (string, optional, schema-only) * Whether a unique constraint should be generated for the column. * * @var array */ public $fieldMappings = array(); /** * READ-ONLY: An array of field names. Used to look up field names from column names. * Keys are column names and values are field names. * This is the reverse lookup map of $_columnNames. * * @var array */ public $fieldNames = array(); /** * READ-ONLY: A map of field names to column names. Keys are field names and values column names. * Used to look up column names from field names. * This is the reverse lookup map of $_fieldNames. * * @var array * @todo We could get rid of this array by just using $fieldMappings[$fieldName]['columnName']. */ public $columnNames = array(); /** * READ-ONLY: The discriminator value of this class. * * This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies * where a discriminator column is used. * * @var mixed * @see discriminatorColumn */ public $discriminatorValue; /** * READ-ONLY: The discriminator map of all mapped classes in the hierarchy. * * This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies * where a discriminator column is used. * * @var mixed * @see discriminatorColumn */ public $discriminatorMap = array(); /** * READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE * inheritance mappings. * * @var array */ public $discriminatorColumn; /** * READ-ONLY: The primary table definition. The definition is an array with the * following entries: * * name => * schema => * indexes => array * uniqueConstraints => array * * @var array */ public $table; /** * READ-ONLY: The registered lifecycle callbacks for entities of this class. * * @var array */ public $lifecycleCallbacks = array(); /** * READ-ONLY: The association mappings of this class. * * The mapping definition array supports the following keys: * * - fieldName (string) * The name of the field in the entity the association is mapped to. * * - targetEntity (string) * The class name of the target entity. If it is fully-qualified it is used as is. * If it is a simple, unqualified class name the namespace is assumed to be the same * as the namespace of the source entity. * * - mappedBy (string, required for bidirectional associations) * The name of the field that completes the bidirectional association on the owning side. * This key must be specified on the inverse side of a bidirectional association. * * - inversedBy (string, required for bidirectional associations) * The name of the field that completes the bidirectional association on the inverse side. * This key must be specified on the owning side of a bidirectional association. * * - cascade (array, optional) * The names of persistence operations to cascade on the association. The set of possible * values are: "persist", "remove", "detach", "merge", "refresh", "all" (implies all others). * * - orderBy (array, one-to-many/many-to-many only) * A map of field names (of the target entity) to sorting directions (ASC/DESC). * Example: array('priority' => 'desc') * * - fetch (integer, optional) * The fetching strategy to use for the association, usually defaults to FETCH_LAZY. * Possible values are: ClassMetadata::FETCH_EAGER, ClassMetadata::FETCH_LAZY. * * - joinTable (array, optional, many-to-many only) * Specification of the join table and its join columns (foreign keys). * Only valid for many-to-many mappings. Note that one-to-many associations can be mapped * through a join table by simply mapping the association as many-to-many with a unique * constraint on the join table. * * - indexBy (string, optional, to-many only) * Specification of a field on target-entity that is used to index the collection by. * This field HAS to be either the primary key or a unique column. Otherwise the collection * does not contain all the entities that are actually related. * * A join table definition has the following structure: *
     * array(
     *     'name' => ,
     *      'joinColumns' => array(),
     *      'inverseJoinColumns' => array()
     * )
     * 
* * * @var array */ public $associationMappings = array(); /** * READ-ONLY: Flag indicating whether the identifier/primary key of the class is composite. * * @var boolean */ public $isIdentifierComposite = false; /** * READ-ONLY: Flag indicating wheather the identifier/primary key contains at least one foreign key association. * * This flag is necessary because some code blocks require special treatment of this cases. * * @var boolean */ public $containsForeignIdentifier = false; /** * READ-ONLY: The ID generator used for generating IDs for this class. * * @var \Doctrine\ORM\Id\AbstractIdGenerator * @todo Remove! */ public $idGenerator; /** * READ-ONLY: The definition of the sequence generator of this class. Only used for the * SEQUENCE generation strategy. * * The definition has the following structure: * * array( * 'sequenceName' => 'name', * 'allocationSize' => 20, * 'initialValue' => 1 * ) * * * @var array * @todo Merge with tableGeneratorDefinition into generic generatorDefinition */ public $sequenceGeneratorDefinition; /** * READ-ONLY: The definition of the table generator of this class. Only used for the * TABLE generation strategy. * * @var array * @todo Merge with tableGeneratorDefinition into generic generatorDefinition */ public $tableGeneratorDefinition; /** * READ-ONLY: The policy used for change-tracking on entities of this class. * * @var integer */ public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT; /** * READ-ONLY: A flag for whether or not instances of this class are to be versioned * with optimistic locking. * * @var boolean $isVersioned */ public $isVersioned; /** * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any). * * @var mixed $versionField */ public $versionField; /** * The ReflectionClass instance of the mapped class. * * @var ReflectionClass */ public $reflClass; /** * Is this entity marked as "read-only"? * * That means it is never considered for change-tracking in the UnitOfWork. It is a very helpful performance * optimization for entities that are immutable, either in your domain or through the relation database * (coming from a view, or a history table for example). * * @var bool */ public $isReadOnly = false; /** * NamingStrategy determining the default column and table names * * @var \Doctrine\ORM\Mapping\NamingStrategy */ protected $namingStrategy; /** * The ReflectionProperty instances of the mapped class. * * @var array */ public $reflFields = array(); /** * The prototype from which new instances of the mapped class are created. * * @var object */ private $_prototype; /** * Initializes a new ClassMetadata instance that will hold the object-relational mapping * metadata of the class with the given name. * * @param string $entityName The name of the entity class the new instance is used for. * @param NamingStrategy $namingStrategy */ public function __construct($entityName, NamingStrategy $namingStrategy = null) { $this->name = $entityName; $this->rootEntityName = $entityName; $this->namingStrategy = $namingStrategy ?: new DefaultNamingStrategy(); } /** * Gets the ReflectionPropertys of the mapped class. * * @return array An array of ReflectionProperty instances. */ public function getReflectionProperties() { return $this->reflFields; } /** * Gets a ReflectionProperty for a specific field of the mapped class. * * @param string $name * @return \ReflectionProperty */ public function getReflectionProperty($name) { return $this->reflFields[$name]; } /** * Gets the ReflectionProperty for the single identifier field. * * @return \ReflectionProperty * @throws BadMethodCallException If the class has a composite identifier. */ public function getSingleIdReflectionProperty() { if ($this->isIdentifierComposite) { throw new BadMethodCallException("Class " . $this->name . " has a composite identifier."); } return $this->reflFields[$this->identifier[0]]; } /** * Extracts the identifier values of an entity of this class. * * For composite identifiers, the identifier values are returned as an array * with the same order as the field order in {@link identifier}. * * @param object $entity * @return array */ public function getIdentifierValues($entity) { if ($this->isIdentifierComposite) { $id = array(); foreach ($this->identifier as $idField) { $value = $this->reflFields[$idField]->getValue($entity); if ($value !== null) { $id[$idField] = $value; } } return $id; } $value = $this->reflFields[$this->identifier[0]]->getValue($entity); if ($value !== null) { return array($this->identifier[0] => $value); } return array(); } /** * Populates the entity identifier of an entity. * * @param object $entity * @param mixed $id * @todo Rename to assignIdentifier() */ public function setIdentifierValues($entity, array $id) { foreach ($id as $idField => $idValue) { $this->reflFields[$idField]->setValue($entity, $idValue); } } /** * Sets the specified field to the specified value on the given entity. * * @param object $entity * @param string $field * @param mixed $value */ public function setFieldValue($entity, $field, $value) { $this->reflFields[$field]->setValue($entity, $value); } /** * Gets the specified field's value off the given entity. * * @param object $entity * @param string $field */ public function getFieldValue($entity, $field) { return $this->reflFields[$field]->getValue($entity); } /** * Creates a string representation of this instance. * * @return string The string representation of this instance. * @todo Construct meaningful string representation. */ public function __toString() { return __CLASS__ . '@' . spl_object_hash($this); } /** * Determines which fields get serialized. * * It is only serialized what is necessary for best unserialization performance. * That means any metadata properties that are not set or empty or simply have * their default value are NOT serialized. * * Parts that are also NOT serialized because they can not be properly unserialized: * - reflClass (ReflectionClass) * - reflFields (ReflectionProperty array) * * @return array The names of all the fields that should be serialized. */ public function __sleep() { // This metadata is always serialized/cached. $serialized = array( 'associationMappings', 'columnNames', //TODO: Not really needed. Can use fieldMappings[$fieldName]['columnName'] 'fieldMappings', 'fieldNames', 'identifier', 'isIdentifierComposite', // TODO: REMOVE 'name', 'namespace', // TODO: REMOVE 'table', 'rootEntityName', 'idGenerator', //TODO: Does not really need to be serialized. Could be moved to runtime. ); // The rest of the metadata is only serialized if necessary. if ($this->changeTrackingPolicy != self::CHANGETRACKING_DEFERRED_IMPLICIT) { $serialized[] = 'changeTrackingPolicy'; } if ($this->customRepositoryClassName) { $serialized[] = 'customRepositoryClassName'; } if ($this->inheritanceType != self::INHERITANCE_TYPE_NONE) { $serialized[] = 'inheritanceType'; $serialized[] = 'discriminatorColumn'; $serialized[] = 'discriminatorValue'; $serialized[] = 'discriminatorMap'; $serialized[] = 'parentClasses'; $serialized[] = 'subClasses'; } if ($this->generatorType != self::GENERATOR_TYPE_NONE) { $serialized[] = 'generatorType'; if ($this->generatorType == self::GENERATOR_TYPE_SEQUENCE) { $serialized[] = 'sequenceGeneratorDefinition'; } } if ($this->isMappedSuperclass) { $serialized[] = 'isMappedSuperclass'; } if ($this->containsForeignIdentifier) { $serialized[] = 'containsForeignIdentifier'; } if ($this->isVersioned) { $serialized[] = 'isVersioned'; $serialized[] = 'versionField'; } if ($this->lifecycleCallbacks) { $serialized[] = 'lifecycleCallbacks'; } if ($this->namedQueries) { $serialized[] = 'namedQueries'; } if ($this->namedNativeQueries) { $serialized[] = 'namedNativeQueries'; } if ($this->sqlResultSetMappings) { $serialized[] = 'sqlResultSetMappings'; } if ($this->isReadOnly) { $serialized[] = 'isReadOnly'; } if ($this->customGeneratorDefinition) { $serialized[] = "customGeneratorDefinition"; } return $serialized; } /** * Creates a new instance of the mapped class, without invoking the constructor. * * @return object */ public function newInstance() { if ($this->_prototype === null) { $this->_prototype = unserialize(sprintf('O:%d:"%s":0:{}', strlen($this->name), $this->name)); } return clone $this->_prototype; } /** * Restores some state that can not be serialized/unserialized. * * @param \Doctrine\Common\Persistence\Mapping\ReflectionService $reflService * @return void */ public function wakeupReflection($reflService) { // Restore ReflectionClass and properties $this->reflClass = $reflService->getClass($this->name); foreach ($this->fieldMappings as $field => $mapping) { $this->reflFields[$field] = isset($mapping['declared']) ? $reflService->getAccessibleProperty($mapping['declared'], $field) : $reflService->getAccessibleProperty($this->name, $field); } foreach ($this->associationMappings as $field => $mapping) { $this->reflFields[$field] = isset($mapping['declared']) ? $reflService->getAccessibleProperty($mapping['declared'], $field) : $reflService->getAccessibleProperty($this->name, $field); } } /** * Initializes a new ClassMetadata instance that will hold the object-relational mapping * metadata of the class with the given name. * * @param \Doctrine\Common\Persistence\Mapping\ReflectionService $reflService The reflection service. */ public function initializeReflection($reflService) { $this->reflClass = $reflService->getClass($this->name); $this->namespace = $reflService->getClassNamespace($this->name); if ($this->reflClass) { $this->name = $this->rootEntityName = $this->reflClass->getName(); } $this->table['name'] = $this->namingStrategy->classToTableName($this->name); } /** * Validate Identifier * * @throws MappingException * @return void */ public function validateIdentifier() { // Verify & complete identifier mapping if ( ! $this->identifier && ! $this->isMappedSuperclass) { throw MappingException::identifierRequired($this->name); } if ($this->usesIdGenerator() && $this->isIdentifierComposite) { throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->name); } } /** * Validate association targets actually exist. * * @throws MappingException * @return void */ public function validateAssocations() { foreach ($this->associationMappings as $mapping) { if ( ! ClassLoader::classExists($mapping['targetEntity']) ) { throw MappingException::invalidTargetEntityClass($mapping['targetEntity'], $this->name, $mapping['fieldName']); } } } /** * Validate lifecycle callbacks * * @param \Doctrine\Common\Persistence\Mapping\ReflectionService $reflService * @throws MappingException * @return void */ public function validateLifecycleCallbacks($reflService) { foreach ($this->lifecycleCallbacks as $callbacks) { foreach ($callbacks as $callbackFuncName) { if ( ! $reflService->hasPublicMethod($this->name, $callbackFuncName)) { throw MappingException::lifecycleCallbackMethodNotFound($this->name, $callbackFuncName); } } } } /** * {@inheritDoc} */ public function getReflectionClass() { return $this->reflClass; } /** * Sets the change tracking policy used by this class. * * @param integer $policy */ public function setChangeTrackingPolicy($policy) { $this->changeTrackingPolicy = $policy; } /** * Whether the change tracking policy of this class is "deferred explicit". * * @return boolean */ public function isChangeTrackingDeferredExplicit() { return $this->changeTrackingPolicy == self::CHANGETRACKING_DEFERRED_EXPLICIT; } /** * Whether the change tracking policy of this class is "deferred implicit". * * @return boolean */ public function isChangeTrackingDeferredImplicit() { return $this->changeTrackingPolicy == self::CHANGETRACKING_DEFERRED_IMPLICIT; } /** * Whether the change tracking policy of this class is "notify". * * @return boolean */ public function isChangeTrackingNotify() { return $this->changeTrackingPolicy == self::CHANGETRACKING_NOTIFY; } /** * Checks whether a field is part of the identifier/primary key field(s). * * @param string $fieldName The field name * @return boolean TRUE if the field is part of the table identifier/primary key field(s), * FALSE otherwise. */ public function isIdentifier($fieldName) { if ( ! $this->isIdentifierComposite) { return $fieldName === $this->identifier[0]; } return in_array($fieldName, $this->identifier); } /** * Check if the field is unique. * * @param string $fieldName The field name * @return boolean TRUE if the field is unique, FALSE otherwise. */ public function isUniqueField($fieldName) { $mapping = $this->getFieldMapping($fieldName); if ($mapping !== false) { return isset($mapping['unique']) && $mapping['unique'] == true; } return false; } /** * Check if the field is not null. * * @param string $fieldName The field name * @return boolean TRUE if the field is not null, FALSE otherwise. */ public function isNullable($fieldName) { $mapping = $this->getFieldMapping($fieldName); if ($mapping !== false) { return isset($mapping['nullable']) && $mapping['nullable'] == true; } return false; } /** * Gets a column name for a field name. * If the column name for the field cannot be found, the given field name * is returned. * * @param string $fieldName The field name. * @return string The column name. */ public function getColumnName($fieldName) { return isset($this->columnNames[$fieldName]) ? $this->columnNames[$fieldName] : $fieldName; } /** * Gets the mapping of a (regular) field that holds some data but not a * reference to another object. * * @param string $fieldName The field name. * @throws MappingException * @return array The field mapping. */ public function getFieldMapping($fieldName) { if ( ! isset($this->fieldMappings[$fieldName])) { throw MappingException::mappingNotFound($this->name, $fieldName); } return $this->fieldMappings[$fieldName]; } /** * Gets the mapping of an association. * * @see ClassMetadataInfo::$associationMappings * @param string $fieldName The field name that represents the association in * the object model. * @throws MappingException * @return array The mapping. */ public function getAssociationMapping($fieldName) { if ( ! isset($this->associationMappings[$fieldName])) { throw MappingException::mappingNotFound($this->name, $fieldName); } return $this->associationMappings[$fieldName]; } /** * Gets all association mappings of the class. * * @return array */ public function getAssociationMappings() { return $this->associationMappings; } /** * Gets the field name for a column name. * If no field name can be found the column name is returned. * * @param string $columnName column name * @return string column alias */ public function getFieldName($columnName) { return isset($this->fieldNames[$columnName]) ? $this->fieldNames[$columnName] : $columnName; } /** * Gets the named query. * * @see ClassMetadataInfo::$namedQueries * @throws MappingException * @param string $queryName The query name * @return string */ public function getNamedQuery($queryName) { if ( ! isset($this->namedQueries[$queryName])) { throw MappingException::queryNotFound($this->name, $queryName); } return $this->namedQueries[$queryName]['dql']; } /** * Gets all named queries of the class. * * @return array */ public function getNamedQueries() { return $this->namedQueries; } /** * Gets the named native query. * * @see ClassMetadataInfo::$namedNativeQueries * @throws MappingException * @param string $queryName The query name * @return array */ public function getNamedNativeQuery($queryName) { if ( ! isset($this->namedNativeQueries[$queryName])) { throw MappingException::queryNotFound($this->name, $queryName); } return $this->namedNativeQueries[$queryName]; } /** * Gets all named native queries of the class. * * @return array */ public function getNamedNativeQueries() { return $this->namedNativeQueries; } /** * Gets the result set mapping. * * @see ClassMetadataInfo::$sqlResultSetMappings * @throws MappingException * @param string $name The result set mapping name * @return array */ public function getSqlResultSetMapping($name) { if ( ! isset($this->sqlResultSetMappings[$name])) { throw MappingException::resultMappingNotFound($this->name, $name); } return $this->sqlResultSetMappings[$name]; } /** * Gets all sql result set mappings of the class. * * @return array */ public function getSqlResultSetMappings() { return $this->sqlResultSetMappings; } /** * Validates & completes the given field mapping. * * @param array $mapping The field mapping to validated & complete. * @throws MappingException * @return array The validated and completed field mapping. */ protected function _validateAndCompleteFieldMapping(array &$mapping) { // Check mandatory fields if ( ! isset($mapping['fieldName']) || strlen($mapping['fieldName']) == 0) { throw MappingException::missingFieldName($this->name); } if ( ! isset($mapping['type'])) { // Default to string $mapping['type'] = 'string'; } // Complete fieldName and columnName mapping if ( ! isset($mapping['columnName'])) { $mapping['columnName'] = $this->namingStrategy->propertyToColumnName($mapping['fieldName']); } if ($mapping['columnName'][0] === '`') { $mapping['columnName'] = trim($mapping['columnName'], '`'); $mapping['quoted'] = true; } $this->columnNames[$mapping['fieldName']] = $mapping['columnName']; if (isset($this->fieldNames[$mapping['columnName']]) || ($this->discriminatorColumn != null && $this->discriminatorColumn['name'] == $mapping['columnName'])) { throw MappingException::duplicateColumnName($this->name, $mapping['columnName']); } $this->fieldNames[$mapping['columnName']] = $mapping['fieldName']; // Complete id mapping if (isset($mapping['id']) && $mapping['id'] === true) { if ($this->versionField == $mapping['fieldName']) { throw MappingException::cannotVersionIdField($this->name, $mapping['fieldName']); } if ( ! in_array($mapping['fieldName'], $this->identifier)) { $this->identifier[] = $mapping['fieldName']; } // Check for composite key if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) { $this->isIdentifierComposite = true; } } if (Type::hasType($mapping['type']) && Type::getType($mapping['type'])->canRequireSQLConversion()) { if (isset($mapping['id']) && $mapping['id'] === true) { throw MappingException::sqlConversionNotAllowedForIdentifiers($this->name, $mapping['fieldName'], $mapping['type']); } $mapping['requireSQLConversion'] = true; } } /** * Validates & completes the basic mapping information that is common to all * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many). * * @param array $mapping The mapping. * @return array The updated mapping. * @throws MappingException If something is wrong with the mapping. */ protected function _validateAndCompleteAssociationMapping(array $mapping) { if ( ! isset($mapping['mappedBy'])) { $mapping['mappedBy'] = null; } if ( ! isset($mapping['inversedBy'])) { $mapping['inversedBy'] = null; } $mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy // unset optional indexBy attribute if its empty if ( ! isset($mapping['indexBy']) || !$mapping['indexBy']) { unset($mapping['indexBy']); } // If targetEntity is unqualified, assume it is in the same namespace as // the sourceEntity. $mapping['sourceEntity'] = $this->name; if (isset($mapping['targetEntity'])) { if (strlen($this->namespace) > 0 && strpos($mapping['targetEntity'], '\\') === false) { $mapping['targetEntity'] = $this->namespace . '\\' . $mapping['targetEntity']; } $mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\'); } if ( ($mapping['type'] & self::MANY_TO_ONE) > 0 && isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'] == true) { throw MappingException::illegalOrphanRemoval($this->name, $mapping['fieldName']); } // Complete id mapping if (isset($mapping['id']) && $mapping['id'] === true) { if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'] == true) { throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->name, $mapping['fieldName']); } if ( ! in_array($mapping['fieldName'], $this->identifier)) { if (count($mapping['joinColumns']) >= 2) { throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId( $mapping['targetEntity'], $this->name, $mapping['fieldName'] ); } $this->identifier[] = $mapping['fieldName']; $this->containsForeignIdentifier = true; } // Check for composite key if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) { $this->isIdentifierComposite = true; } } // Mandatory attributes for both sides // Mandatory: fieldName, targetEntity if ( ! isset($mapping['fieldName']) || strlen($mapping['fieldName']) == 0) { throw MappingException::missingFieldName($this->name); } if ( ! isset($mapping['targetEntity'])) { throw MappingException::missingTargetEntity($mapping['fieldName']); } // Mandatory and optional attributes for either side if ( ! $mapping['mappedBy']) { if (isset($mapping['joinTable']) && $mapping['joinTable']) { if (isset($mapping['joinTable']['name']) && $mapping['joinTable']['name'][0] === '`') { $mapping['joinTable']['name'] = trim($mapping['joinTable']['name'], '`'); $mapping['joinTable']['quoted'] = true; } } } else { $mapping['isOwningSide'] = false; } if (isset($mapping['id']) && $mapping['id'] === true && $mapping['type'] & self::TO_MANY) { throw MappingException::illegalToManyIdentifierAssoaction($this->name, $mapping['fieldName']); } // Fetch mode. Default fetch mode to LAZY, if not set. if ( ! isset($mapping['fetch'])) { $mapping['fetch'] = self::FETCH_LAZY; } // Cascades $cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : array(); if (in_array('all', $cascades)) { $cascades = array('remove', 'persist', 'refresh', 'merge', 'detach'); } if (count($cascades) !== count(array_intersect($cascades, array('remove', 'persist', 'refresh', 'merge', 'detach')))) { throw MappingException::invalidCascadeOption( array_diff($cascades, array_intersect($cascades, array('remove', 'persist', 'refresh', 'merge', 'detach'))), $this->name, $mapping['fieldName'] ); } $mapping['cascade'] = $cascades; $mapping['isCascadeRemove'] = in_array('remove', $cascades); $mapping['isCascadePersist'] = in_array('persist', $cascades); $mapping['isCascadeRefresh'] = in_array('refresh', $cascades); $mapping['isCascadeMerge'] = in_array('merge', $cascades); $mapping['isCascadeDetach'] = in_array('detach', $cascades); return $mapping; } /** * Validates & completes a one-to-one association mapping. * * @param array $mapping The mapping to validate & complete. * @throws RuntimeException * @throws MappingException * @return array The validated & completed mapping.@override */ protected function _validateAndCompleteOneToOneMapping(array $mapping) { $mapping = $this->_validateAndCompleteAssociationMapping($mapping); if (isset($mapping['joinColumns']) && $mapping['joinColumns']) { $mapping['isOwningSide'] = true; } if ($mapping['isOwningSide']) { if ( ! isset($mapping['joinColumns']) || ! $mapping['joinColumns']) { // Apply default join column $mapping['joinColumns'] = array(array( 'name' => $this->namingStrategy->joinColumnName($mapping['fieldName']), 'referencedColumnName' => $this->namingStrategy->referenceColumnName() )); } $uniqueContraintColumns = array(); foreach ($mapping['joinColumns'] as &$joinColumn) { if ($mapping['type'] === self::ONE_TO_ONE && ! $this->isInheritanceTypeSingleTable()) { if (count($mapping['joinColumns']) == 1) { if ( ! isset($mapping['id']) || ! $mapping['id']) { $joinColumn['unique'] = true; } } else { $uniqueContraintColumns[] = $joinColumn['name']; } } if (empty($joinColumn['name'])) { $joinColumn['name'] = $this->namingStrategy->joinColumnName($mapping['fieldName']); } if (empty($joinColumn['referencedColumnName'])) { $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName(); } if ($joinColumn['name'][0] === '`') { $joinColumn['name'] = trim($joinColumn['name'], '`'); $joinColumn['quoted'] = true; } if ($joinColumn['referencedColumnName'][0] === '`') { $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`'); $joinColumn['quoted'] = true; } $mapping['sourceToTargetKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName']; $mapping['joinColumnFieldNames'][$joinColumn['name']] = isset($joinColumn['fieldName']) ? $joinColumn['fieldName'] : $joinColumn['name']; } if ($uniqueContraintColumns) { if ( ! $this->table) { throw new RuntimeException("ClassMetadataInfo::setTable() has to be called before defining a one to one relationship."); } $this->table['uniqueConstraints'][$mapping['fieldName']."_uniq"] = array( 'columns' => $uniqueContraintColumns ); } $mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']); } $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false; $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] ? true : $mapping['isCascadeRemove']; if ($mapping['orphanRemoval']) { unset($mapping['unique']); } if (isset($mapping['id']) && $mapping['id'] === true && !$mapping['isOwningSide']) { throw MappingException::illegalInverseIdentifierAssocation($this->name, $mapping['fieldName']); } return $mapping; } /** * Validates and completes the mapping. * * @param array $mapping The mapping to validate and complete. * @throws MappingException * @throws InvalidArgumentException * @return array The validated and completed mapping.@override */ protected function _validateAndCompleteOneToManyMapping(array $mapping) { $mapping = $this->_validateAndCompleteAssociationMapping($mapping); // OneToMany-side MUST be inverse (must have mappedBy) if ( ! isset($mapping['mappedBy'])) { throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']); } $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false; $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] ? true : $mapping['isCascadeRemove']; if (isset($mapping['orderBy'])) { if ( ! is_array($mapping['orderBy'])) { throw new InvalidArgumentException("'orderBy' is expected to be an array, not ".gettype($mapping['orderBy'])); } } return $mapping; } protected function _validateAndCompleteManyToManyMapping(array $mapping) { $mapping = $this->_validateAndCompleteAssociationMapping($mapping); if ($mapping['isOwningSide']) { // owning side MUST have a join table if ( ! isset($mapping['joinTable']['name'])) { $mapping['joinTable']['name'] = $this->namingStrategy->joinTableName($mapping['sourceEntity'], $mapping['targetEntity'], $mapping['fieldName']); } if ( ! isset($mapping['joinTable']['joinColumns'])) { $mapping['joinTable']['joinColumns'] = array(array( 'name' => $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity']), 'referencedColumnName' => $this->namingStrategy->referenceColumnName(), 'onDelete' => 'CASCADE')); } if ( ! isset($mapping['joinTable']['inverseJoinColumns'])) { $mapping['joinTable']['inverseJoinColumns'] = array(array( 'name' => $this->namingStrategy->joinKeyColumnName($mapping['targetEntity']), 'referencedColumnName' => $this->namingStrategy->referenceColumnName(), 'onDelete' => 'CASCADE')); } $mapping['joinTableColumns'] = array(); foreach ($mapping['joinTable']['joinColumns'] as &$joinColumn) { if (empty($joinColumn['name'])) { $joinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $joinColumn['referencedColumnName']); } if (empty($joinColumn['referencedColumnName'])) { $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName(); } if ($joinColumn['name'][0] === '`') { $joinColumn['name'] = trim($joinColumn['name'], '`'); $joinColumn['quoted'] = true; } if ($joinColumn['referencedColumnName'][0] === '`') { $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`'); $joinColumn['quoted'] = true; } if (isset($joinColumn['onDelete']) && strtolower($joinColumn['onDelete']) == 'cascade') { $mapping['isOnDeleteCascade'] = true; } $mapping['relationToSourceKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName']; $mapping['joinTableColumns'][] = $joinColumn['name']; } foreach ($mapping['joinTable']['inverseJoinColumns'] as &$inverseJoinColumn) { if (empty($inverseJoinColumn['name'])) { $inverseJoinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $inverseJoinColumn['referencedColumnName']); } if (empty($inverseJoinColumn['referencedColumnName'])) { $inverseJoinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName(); } if ($inverseJoinColumn['name'][0] === '`') { $inverseJoinColumn['name'] = trim($inverseJoinColumn['name'], '`'); $inverseJoinColumn['quoted'] = true; } if ($inverseJoinColumn['referencedColumnName'][0] === '`') { $inverseJoinColumn['referencedColumnName'] = trim($inverseJoinColumn['referencedColumnName'], '`'); $inverseJoinColumn['quoted'] = true; } if (isset($inverseJoinColumn['onDelete']) && strtolower($inverseJoinColumn['onDelete']) == 'cascade') { $mapping['isOnDeleteCascade'] = true; } $mapping['relationToTargetKeyColumns'][$inverseJoinColumn['name']] = $inverseJoinColumn['referencedColumnName']; $mapping['joinTableColumns'][] = $inverseJoinColumn['name']; } } $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false; if (isset($mapping['orderBy'])) { if ( ! is_array($mapping['orderBy'])) { throw new InvalidArgumentException("'orderBy' is expected to be an array, not ".gettype($mapping['orderBy'])); } } return $mapping; } /** * {@inheritDoc} */ public function getIdentifierFieldNames() { return $this->identifier; } /** * Gets the name of the single id field. Note that this only works on * entity classes that have a single-field pk. * * @return string * @throws MappingException If the class has a composite primary key. */ public function getSingleIdentifierFieldName() { if ($this->isIdentifierComposite) { throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->name); } return $this->identifier[0]; } /** * Gets the column name of the single id column. Note that this only works on * entity classes that have a single-field pk. * * @return string * @throws MappingException If the class has a composite primary key. */ public function getSingleIdentifierColumnName() { return $this->getColumnName($this->getSingleIdentifierFieldName()); } /** * INTERNAL: * Sets the mapped identifier/primary key fields of this class. * Mainly used by the ClassMetadataFactory to assign inherited identifiers. * * @param array $identifier */ public function setIdentifier(array $identifier) { $this->identifier = $identifier; $this->isIdentifierComposite = (count($this->identifier) > 1); } /** * Gets the mapped identifier field of this class. * * @return array|string $identifier */ public function getIdentifier() { return $this->identifier; } /** * {@inheritDoc} */ public function hasField($fieldName) { return isset($this->fieldMappings[$fieldName]); } /** * Gets an array containing all the column names. * * @param array $fieldNames * @return array */ public function getColumnNames(array $fieldNames = null) { if ($fieldNames === null) { return array_keys($this->fieldNames); } else { $columnNames = array(); foreach ($fieldNames as $fieldName) { $columnNames[] = $this->getColumnName($fieldName); } return $columnNames; } } /** * Returns an array with all the identifier column names. * * @return array */ public function getIdentifierColumnNames() { $columnNames = array(); foreach ($this->identifier as $idProperty) { if (isset($this->fieldMappings[$idProperty])) { $columnNames[] = $this->fieldMappings[$idProperty]['columnName']; continue; } // Association defined as Id field $joinColumns = $this->associationMappings[$idProperty]['joinColumns']; $assocColumnNames = array_map(function ($joinColumn) { return $joinColumn['name']; }, $joinColumns); $columnNames = array_merge($columnNames, $assocColumnNames); } return $columnNames; } /** * Sets the type of Id generator to use for the mapped class. */ public function setIdGeneratorType($generatorType) { $this->generatorType = $generatorType; } /** * Checks whether the mapped class uses an Id generator. * * @return boolean TRUE if the mapped class uses an Id generator, FALSE otherwise. */ public function usesIdGenerator() { return $this->generatorType != self::GENERATOR_TYPE_NONE; } /** * @return boolean */ public function isInheritanceTypeNone() { return $this->inheritanceType == self::INHERITANCE_TYPE_NONE; } /** * Checks whether the mapped class uses the JOINED inheritance mapping strategy. * * @return boolean TRUE if the class participates in a JOINED inheritance mapping, * FALSE otherwise. */ public function isInheritanceTypeJoined() { return $this->inheritanceType == self::INHERITANCE_TYPE_JOINED; } /** * Checks whether the mapped class uses the SINGLE_TABLE inheritance mapping strategy. * * @return boolean TRUE if the class participates in a SINGLE_TABLE inheritance mapping, * FALSE otherwise. */ public function isInheritanceTypeSingleTable() { return $this->inheritanceType == self::INHERITANCE_TYPE_SINGLE_TABLE; } /** * Checks whether the mapped class uses the TABLE_PER_CLASS inheritance mapping strategy. * * @return boolean TRUE if the class participates in a TABLE_PER_CLASS inheritance mapping, * FALSE otherwise. */ public function isInheritanceTypeTablePerClass() { return $this->inheritanceType == self::INHERITANCE_TYPE_TABLE_PER_CLASS; } /** * Checks whether the class uses an identity column for the Id generation. * * @return boolean TRUE if the class uses the IDENTITY generator, FALSE otherwise. */ public function isIdGeneratorIdentity() { return $this->generatorType == self::GENERATOR_TYPE_IDENTITY; } /** * Checks whether the class uses a sequence for id generation. * * @return boolean TRUE if the class uses the SEQUENCE generator, FALSE otherwise. */ public function isIdGeneratorSequence() { return $this->generatorType == self::GENERATOR_TYPE_SEQUENCE; } /** * Checks whether the class uses a table for id generation. * * @return boolean TRUE if the class uses the TABLE generator, FALSE otherwise. */ public function isIdGeneratorTable() { return $this->generatorType == self::GENERATOR_TYPE_TABLE; } /** * Checks whether the class has a natural identifier/pk (which means it does * not use any Id generator. * * @return boolean */ public function isIdentifierNatural() { return $this->generatorType == self::GENERATOR_TYPE_NONE; } /** * Checks whether the class use a UUID for id generation * * @return boolean */ public function isIdentifierUuid() { return $this->generatorType == self::GENERATOR_TYPE_UUID; } /** * Gets the type of a field. * * @param string $fieldName * @return \Doctrine\DBAL\Types\Type|string */ public function getTypeOfField($fieldName) { return isset($this->fieldMappings[$fieldName]) ? $this->fieldMappings[$fieldName]['type'] : null; } /** * Gets the type of a column. * * @param string $columnName * @return \Doctrine\DBAL\Types\Type */ public function getTypeOfColumn($columnName) { return $this->getTypeOfField($this->getFieldName($columnName)); } /** * Gets the name of the primary table. * * @return string */ public function getTableName() { return $this->table['name']; } /** * Gets the table name to use for temporary identifier tables of this class. * * @return string */ public function getTemporaryIdTableName() { // replace dots with underscores because PostgreSQL creates temporary tables in a special schema return str_replace('.', '_', $this->getTableName() . '_id_tmp'); } /** * Sets the mapped subclasses of this class. * * @param array $subclasses The names of all mapped subclasses. */ public function setSubclasses(array $subclasses) { foreach ($subclasses as $subclass) { if (strpos($subclass, '\\') === false && strlen($this->namespace)) { $this->subClasses[] = $this->namespace . '\\' . $subclass; } else { $this->subClasses[] = $subclass; } } } /** * Sets the parent class names. * Assumes that the class names in the passed array are in the order: * directParent -> directParentParent -> directParentParentParent ... -> root. */ public function setParentClasses(array $classNames) { $this->parentClasses = $classNames; if (count($classNames) > 0) { $this->rootEntityName = array_pop($classNames); } } /** * Sets the inheritance type used by the class and it's subclasses. * * @param integer $type * @throws MappingException * @return void */ public function setInheritanceType($type) { if ( ! $this->_isInheritanceType($type)) { throw MappingException::invalidInheritanceType($this->name, $type); } $this->inheritanceType = $type; } /** * Sets the association to override association mapping of property for an entity relationship. * * @param string $fieldName * @param array $overrideMapping * @throws MappingException * @return void */ public function setAssociationOverride($fieldName, array $overrideMapping) { if ( ! isset($this->associationMappings[$fieldName])) { throw MappingException::invalidOverrideFieldName($this->name, $fieldName); } $mapping = $this->associationMappings[$fieldName]; if (isset($overrideMapping['joinColumns'])) { $mapping['joinColumns'] = $overrideMapping['joinColumns']; } if (isset($overrideMapping['joinTable'])) { $mapping['joinTable'] = $overrideMapping['joinTable']; } $mapping['joinColumnFieldNames'] = null; $mapping['joinTableColumns'] = null; $mapping['sourceToTargetKeyColumns'] = null; $mapping['relationToSourceKeyColumns'] = null; $mapping['relationToTargetKeyColumns'] = null; switch ($mapping['type']) { case self::ONE_TO_ONE: $mapping = $this->_validateAndCompleteOneToOneMapping($mapping); break; case self::ONE_TO_MANY: $mapping = $this->_validateAndCompleteOneToManyMapping($mapping); break; case self::MANY_TO_ONE: $mapping = $this->_validateAndCompleteOneToOneMapping($mapping); break; case self::MANY_TO_MANY: $mapping = $this->_validateAndCompleteManyToManyMapping($mapping); break; } $this->associationMappings[$fieldName] = $mapping; } /** * Sets the override for a mapped field. * * @param string $fieldName * @param array $overrideMapping * @throws MappingException * @param array $overrideMapping * @return void */ public function setAttributeOverride($fieldName, array $overrideMapping) { if ( ! isset($this->fieldMappings[$fieldName])) { throw MappingException::invalidOverrideFieldName($this->name, $fieldName); } $mapping = $this->fieldMappings[$fieldName]; if (isset($mapping['id'])) { $overrideMapping['id'] = $mapping['id']; } if ( ! isset($overrideMapping['type']) || $overrideMapping['type'] === null) { $overrideMapping['type'] = $mapping['type']; } if ( ! isset($overrideMapping['fieldName']) || $overrideMapping['fieldName'] === null) { $overrideMapping['fieldName'] = $mapping['fieldName']; } if ($overrideMapping['type'] !== $mapping['type']) { throw MappingException::invalidOverrideFieldType($this->name, $fieldName); } unset($this->fieldMappings[$fieldName]); unset($this->fieldNames[$mapping['columnName']]); unset($this->columnNames[$mapping['fieldName']]); $this->_validateAndCompleteFieldMapping($overrideMapping); $this->fieldMappings[$fieldName] = $overrideMapping; } /** * Checks whether a mapped field is inherited from an entity superclass. * * @param string $fieldName * @return bool TRUE if the field is inherited, FALSE otherwise. */ public function isInheritedField($fieldName) { return isset($this->fieldMappings[$fieldName]['inherited']); } /** * Check if this entity is the root in any entity-inheritance-hierachy. * * @return bool */ public function isRootEntity() { return $this->name == $this->rootEntityName; } /** * Checks whether a mapped association field is inherited from a superclass. * * @param string $fieldName * @return boolean TRUE if the field is inherited, FALSE otherwise. */ public function isInheritedAssociation($fieldName) { return isset($this->associationMappings[$fieldName]['inherited']); } /** * Sets the name of the primary table the class is mapped to. * * @param string $tableName The table name. * @deprecated Use {@link setPrimaryTable}. */ public function setTableName($tableName) { $this->table['name'] = $tableName; } /** * Sets the primary table definition. The provided array supports the * following structure: * * name => (optional, defaults to class name) * indexes => array of indexes (optional) * uniqueConstraints => array of constraints (optional) * * If a key is omitted, the current value is kept. * * @param array $table The table description. */ public function setPrimaryTable(array $table) { if (isset($table['name'])) { if ($table['name'][0] === '`') { $table['name'] = trim($table['name'], '`'); $this->table['quoted'] = true; } $this->table['name'] = $table['name']; } if (isset($table['indexes'])) { $this->table['indexes'] = $table['indexes']; } if (isset($table['uniqueConstraints'])) { $this->table['uniqueConstraints'] = $table['uniqueConstraints']; } if (isset($table['options'])) { $this->table['options'] = $table['options']; } } /** * Checks whether the given type identifies an inheritance type. * * @param integer $type * @return boolean TRUE if the given type identifies an inheritance type, FALSe otherwise. */ private function _isInheritanceType($type) { return $type == self::INHERITANCE_TYPE_NONE || $type == self::INHERITANCE_TYPE_SINGLE_TABLE || $type == self::INHERITANCE_TYPE_JOINED || $type == self::INHERITANCE_TYPE_TABLE_PER_CLASS; } /** * Adds a mapped field to the class. * * @param array $mapping The field mapping. * @throws MappingException * @return void */ public function mapField(array $mapping) { $this->_validateAndCompleteFieldMapping($mapping); if (isset($this->fieldMappings[$mapping['fieldName']]) || isset($this->associationMappings[$mapping['fieldName']])) { throw MappingException::duplicateFieldMapping($this->name, $mapping['fieldName']); } $this->fieldMappings[$mapping['fieldName']] = $mapping; } /** * INTERNAL: * Adds an association mapping without completing/validating it. * This is mainly used to add inherited association mappings to derived classes. * * @param array $mapping * @throws MappingException * @return void */ public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/) { if (isset($this->associationMappings[$mapping['fieldName']])) { throw MappingException::duplicateAssociationMapping($this->name, $mapping['fieldName']); } $this->associationMappings[$mapping['fieldName']] = $mapping; } /** * INTERNAL: * Adds a field mapping without completing/validating it. * This is mainly used to add inherited field mappings to derived classes. * * @param array $fieldMapping * @return void */ public function addInheritedFieldMapping(array $fieldMapping) { $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping; $this->columnNames[$fieldMapping['fieldName']] = $fieldMapping['columnName']; $this->fieldNames[$fieldMapping['columnName']] = $fieldMapping['fieldName']; } /** * INTERNAL: * Adds a named query to this class. * * @throws MappingException * @param array $queryMapping */ public function addNamedQuery(array $queryMapping) { if (!isset($queryMapping['name'])) { throw MappingException::nameIsMandatoryForQueryMapping($this->name); } if (isset($this->namedQueries[$queryMapping['name']])) { throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']); } if (!isset($queryMapping['query'])) { throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']); } $name = $queryMapping['name']; $query = $queryMapping['query']; $dql = str_replace('__CLASS__', $this->name, $query); $this->namedQueries[$name] = array( 'name' => $name, 'query' => $query, 'dql' => $dql ); } /** * INTERNAL: * Adds a named native query to this class. * * @throws MappingException * @param array $queryMapping */ public function addNamedNativeQuery(array $queryMapping) { if (!isset($queryMapping['name'])) { throw MappingException::nameIsMandatoryForQueryMapping($this->name); } if (isset($this->namedNativeQueries[$queryMapping['name']])) { throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']); } if (!isset($queryMapping['query'])) { throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']); } if (!isset($queryMapping['resultClass']) && !isset($queryMapping['resultSetMapping'])) { throw MappingException::missingQueryMapping($this->name, $queryMapping['name']); } $queryMapping['isSelfClass'] = false; if (isset($queryMapping['resultClass'])) { if($queryMapping['resultClass'] === '__CLASS__') { $queryMapping['isSelfClass'] = true; $queryMapping['resultClass'] = $this->name; } else if (strlen($this->namespace) > 0 && strpos($queryMapping['resultClass'], '\\') === false) { $queryMapping['resultClass'] = $this->namespace . '\\' . $queryMapping['resultClass']; } $queryMapping['resultClass'] = ltrim($queryMapping['resultClass'], '\\'); } $this->namedNativeQueries[$queryMapping['name']] = $queryMapping; } /** * INTERNAL: * Adds a sql result set mapping to this class. * * @throws MappingException * @param array $resultMapping */ public function addSqlResultSetMapping(array $resultMapping) { if (!isset($resultMapping['name'])) { throw MappingException::nameIsMandatoryForSqlResultSetMapping($this->name); } if (isset($this->sqlResultSetMappings[$resultMapping['name']])) { throw MappingException::duplicateResultSetMapping($this->name, $resultMapping['name']); } if (isset($resultMapping['entities'])) { foreach ($resultMapping['entities'] as $key => $entityResult) { if (!isset($entityResult['entityClass'])) { throw MappingException::missingResultSetMappingEntity($this->name, $resultMapping['name']); } $entityResult['isSelfClass'] = false; if($entityResult['entityClass'] === '__CLASS__') { $entityResult['isSelfClass'] = true; $entityResult['entityClass'] = $this->name; } else if (strlen($this->namespace) > 0 && strpos($entityResult['entityClass'], '\\') === false) { $entityResult['entityClass'] = $this->namespace . '\\' . $entityResult['entityClass']; } $resultMapping['entities'][$key]['entityClass'] = ltrim($entityResult['entityClass'], '\\'); $resultMapping['entities'][$key]['isSelfClass'] = $entityResult['isSelfClass']; if (isset($entityResult['fields'])) { foreach ($entityResult['fields'] as $k => $field) { if (!isset($field['name'])) { throw MappingException::missingResultSetMappingFieldName($this->name, $resultMapping['name']); } if (!isset($field['column'])) { $fieldName = $field['name']; if(strpos($fieldName, '.')){ list(, $fieldName) = explode('.', $fieldName); } $resultMapping['entities'][$key]['fields'][$k]['column'] = $fieldName; } } } } } $this->sqlResultSetMappings[$resultMapping['name']] = $resultMapping; } /** * Adds a one-to-one mapping. * * @param array $mapping The mapping. */ public function mapOneToOne(array $mapping) { $mapping['type'] = self::ONE_TO_ONE; $mapping = $this->_validateAndCompleteOneToOneMapping($mapping); $this->_storeAssociationMapping($mapping); } /** * Adds a one-to-many mapping. * * @param array $mapping The mapping. */ public function mapOneToMany(array $mapping) { $mapping['type'] = self::ONE_TO_MANY; $mapping = $this->_validateAndCompleteOneToManyMapping($mapping); $this->_storeAssociationMapping($mapping); } /** * Adds a many-to-one mapping. * * @param array $mapping The mapping. */ public function mapManyToOne(array $mapping) { $mapping['type'] = self::MANY_TO_ONE; // A many-to-one mapping is essentially a one-one backreference $mapping = $this->_validateAndCompleteOneToOneMapping($mapping); $this->_storeAssociationMapping($mapping); } /** * Adds a many-to-many mapping. * * @param array $mapping The mapping. */ public function mapManyToMany(array $mapping) { $mapping['type'] = self::MANY_TO_MANY; $mapping = $this->_validateAndCompleteManyToManyMapping($mapping); $this->_storeAssociationMapping($mapping); } /** * Stores the association mapping. * * @param array $assocMapping * @throws MappingException * @return void */ protected function _storeAssociationMapping(array $assocMapping) { $sourceFieldName = $assocMapping['fieldName']; if (isset($this->fieldMappings[$sourceFieldName]) || isset($this->associationMappings[$sourceFieldName])) { throw MappingException::duplicateFieldMapping($this->name, $sourceFieldName); } $this->associationMappings[$sourceFieldName] = $assocMapping; } /** * Registers a custom repository class for the entity class. * * @param string $repositoryClassName The class name of the custom mapper. * @return void */ public function setCustomRepositoryClass($repositoryClassName) { if ($repositoryClassName !== null && strpos($repositoryClassName, '\\') === false && strlen($this->namespace) > 0) { $repositoryClassName = $this->namespace . '\\' . $repositoryClassName; } $this->customRepositoryClassName = $repositoryClassName; } /** * Dispatches the lifecycle event of the given entity to the registered * lifecycle callbacks and lifecycle listeners. * * @param string $lifecycleEvent The lifecycle event. * @param \Object $entity The Entity on which the event occured. */ public function invokeLifecycleCallbacks($lifecycleEvent, $entity) { foreach ($this->lifecycleCallbacks[$lifecycleEvent] as $callback) { $entity->$callback(); } } /** * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event. * * @param string $lifecycleEvent * @return boolean */ public function hasLifecycleCallbacks($lifecycleEvent) { return isset($this->lifecycleCallbacks[$lifecycleEvent]); } /** * Gets the registered lifecycle callbacks for an event. * * @param string $event * @return array */ public function getLifecycleCallbacks($event) { return isset($this->lifecycleCallbacks[$event]) ? $this->lifecycleCallbacks[$event] : array(); } /** * Adds a lifecycle callback for entities of this class. * * @param string $callback * @param string $event */ public function addLifecycleCallback($callback, $event) { $this->lifecycleCallbacks[$event][] = $callback; } /** * Sets the lifecycle callbacks for entities of this class. * Any previously registered callbacks are overwritten. * * @param array $callbacks */ public function setLifecycleCallbacks(array $callbacks) { $this->lifecycleCallbacks = $callbacks; } /** * Sets the discriminator column definition. * * @param array $columnDef * * @param $columnDef * @throws MappingException * @return void * @see getDiscriminatorColumn() */ public function setDiscriminatorColumn($columnDef) { if ($columnDef !== null) { if ( ! isset($columnDef['name'])) { throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name); } if (isset($this->fieldNames[$columnDef['name']])) { throw MappingException::duplicateColumnName($this->name, $columnDef['name']); } if ( ! isset($columnDef['fieldName'])) { $columnDef['fieldName'] = $columnDef['name']; } if ( ! isset($columnDef['type'])) { $columnDef['type'] = "string"; } if (in_array($columnDef['type'], array("boolean", "array", "object", "datetime", "time", "date"))) { throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']); } $this->discriminatorColumn = $columnDef; } } /** * Sets the discriminator values used by this class. * Used for JOINED and SINGLE_TABLE inheritance mapping strategies. * * @param array $map */ public function setDiscriminatorMap(array $map) { foreach ($map as $value => $className) { $this->addDiscriminatorMapClass($value, $className); } } /** * Add one entry of the discriminator map with a new class and corresponding name. * * @param string $name * @param string $className * @throws MappingException * @return void */ public function addDiscriminatorMapClass($name, $className) { if (strlen($this->namespace) > 0 && strpos($className, '\\') === false) { $className = $this->namespace . '\\' . $className; } $className = ltrim($className, '\\'); $this->discriminatorMap[$name] = $className; if ($this->name == $className) { $this->discriminatorValue = $name; } else { if ( ! class_exists($className)) { throw MappingException::invalidClassInDiscriminatorMap($className, $this->name); } if (is_subclass_of($className, $this->name) && ! in_array($className, $this->subClasses)) { $this->subClasses[] = $className; } } } /** * Checks whether the class has a named query with the given query name. * * @param string $queryName * @return boolean */ public function hasNamedQuery($queryName) { return isset($this->namedQueries[$queryName]); } /** * Checks whether the class has a named native query with the given query name. * * @param string $queryName * @return boolean */ public function hasNamedNativeQuery($queryName) { return isset($this->namedNativeQueries[$queryName]); } /** * Checks whether the class has a named native query with the given query name. * * @param string $name * @return boolean */ public function hasSqlResultSetMapping($name) { return isset($this->sqlResultSetMappings[$name]); } /** * {@inheritDoc} */ public function hasAssociation($fieldName) { return isset($this->associationMappings[$fieldName]); } /** * {@inheritDoc} */ public function isSingleValuedAssociation($fieldName) { return isset($this->associationMappings[$fieldName]) && ($this->associationMappings[$fieldName]['type'] & self::TO_ONE); } /** * {@inheritDoc} */ public function isCollectionValuedAssociation($fieldName) { return isset($this->associationMappings[$fieldName]) && ! ($this->associationMappings[$fieldName]['type'] & self::TO_ONE); } /** * Is this an association that only has a single join column? * * @param string $fieldName * @return bool */ public function isAssociationWithSingleJoinColumn($fieldName) { return ( isset($this->associationMappings[$fieldName]) && isset($this->associationMappings[$fieldName]['joinColumns'][0]) && !isset($this->associationMappings[$fieldName]['joinColumns'][1]) ); } /** * Return the single association join column (if any). * * @param string $fieldName * @throws MappingException * @return string */ public function getSingleAssociationJoinColumnName($fieldName) { if ( ! $this->isAssociationWithSingleJoinColumn($fieldName)) { throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName); } return $this->associationMappings[$fieldName]['joinColumns'][0]['name']; } /** * Return the single association referenced join column name (if any). * * @param string $fieldName * @throws MappingException * @return string */ public function getSingleAssociationReferencedJoinColumnName($fieldName) { if ( ! $this->isAssociationWithSingleJoinColumn($fieldName)) { throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName); } return $this->associationMappings[$fieldName]['joinColumns'][0]['referencedColumnName']; } /** * Used to retrieve a fieldname for either field or association from a given column, * * This method is used in foreign-key as primary-key contexts. * * @param string $columnName * @throws MappingException * @return string */ public function getFieldForColumn($columnName) { if (isset($this->fieldNames[$columnName])) { return $this->fieldNames[$columnName]; } else { foreach ($this->associationMappings as $assocName => $mapping) { if ($this->isAssociationWithSingleJoinColumn($assocName) && $this->associationMappings[$assocName]['joinColumns'][0]['name'] == $columnName) { return $assocName; } } throw MappingException::noFieldNameFoundForColumn($this->name, $columnName); } } /** * Sets the ID generator used to generate IDs for instances of this class. * * @param \Doctrine\ORM\Id\AbstractIdGenerator $generator */ public function setIdGenerator($generator) { $this->idGenerator = $generator; } /** * Sets definition * @param array $definition */ public function setCustomGeneratorDefinition(array $definition) { $this->customGeneratorDefinition = $definition; } /** * Sets the definition of the sequence ID generator for this class. * * The definition must have the following structure: * * array( * 'sequenceName' => 'name', * 'allocationSize' => 20, * 'initialValue' => 1 * 'quoted' => 1 * ) * * * @param array $definition */ public function setSequenceGeneratorDefinition(array $definition) { if (isset($definition['name']) && $definition['name'] == '`') { $definition['name'] = trim($definition['name'], '`'); $definition['quoted'] = true; } $this->sequenceGeneratorDefinition = $definition; } /** * Sets the version field mapping used for versioning. Sets the default * value to use depending on the column type. * * @param array $mapping The version field mapping array * @throws MappingException * @return void */ public function setVersionMapping(array &$mapping) { $this->isVersioned = true; $this->versionField = $mapping['fieldName']; if ( ! isset($mapping['default'])) { if (in_array($mapping['type'], array('integer', 'bigint', 'smallint'))) { $mapping['default'] = 1; } else if ($mapping['type'] == 'datetime') { $mapping['default'] = 'CURRENT_TIMESTAMP'; } else { throw MappingException::unsupportedOptimisticLockingType($this->name, $mapping['fieldName'], $mapping['type']); } } } /** * Sets whether this class is to be versioned for optimistic locking. * * @param boolean $bool */ public function setVersioned($bool) { $this->isVersioned = $bool; } /** * Sets the name of the field that is to be used for versioning if this class is * versioned for optimistic locking. * * @param string $versionField */ public function setVersionField($versionField) { $this->versionField = $versionField; } /** * Mark this class as read only, no change tracking is applied to it. * * @return void */ public function markReadOnly() { $this->isReadOnly = true; } /** * {@inheritDoc} */ public function getFieldNames() { return array_keys($this->fieldMappings); } /** * {@inheritDoc} */ public function getAssociationNames() { return array_keys($this->associationMappings); } /** * {@inheritDoc} * @throws InvalidArgumentException */ public function getAssociationTargetClass($assocName) { if ( ! isset($this->associationMappings[$assocName])) { throw new InvalidArgumentException("Association name expected, '" . $assocName ."' is not an association."); } return $this->associationMappings[$assocName]['targetEntity']; } /** * {@inheritDoc} */ public function getName() { return $this->name; } /** * Gets the (possibly quoted) identifier column names for safe use in an SQL statement. * * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy * * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform * @return array */ public function getQuotedIdentifierColumnNames($platform) { $quotedColumnNames = array(); foreach ($this->identifier as $idProperty) { if (isset($this->fieldMappings[$idProperty])) { $quotedColumnNames[] = isset($this->fieldMappings[$idProperty]['quoted']) ? $platform->quoteIdentifier($this->fieldMappings[$idProperty]['columnName']) : $this->fieldMappings[$idProperty]['columnName']; continue; } // Association defined as Id field $joinColumns = $this->associationMappings[$idProperty]['joinColumns']; $assocQuotedColumnNames = array_map( function ($joinColumn) use ($platform) { return isset($joinColumn['quoted']) ? $platform->quoteIdentifier($joinColumn['name']) : $joinColumn['name']; }, $joinColumns ); $quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames); } return $quotedColumnNames; } /** * Gets the (possibly quoted) column name of a mapped field for safe use in an SQL statement. * * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy * * @param string $field * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform * @return string */ public function getQuotedColumnName($field, $platform) { return isset($this->fieldMappings[$field]['quoted']) ? $platform->quoteIdentifier($this->fieldMappings[$field]['columnName']) : $this->fieldMappings[$field]['columnName']; } /** * Gets the (possibly quoted) primary table name of this class for safe use in an SQL statement. * * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy * * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform * @return string */ public function getQuotedTableName($platform) { return isset($this->table['quoted']) ? $platform->quoteIdentifier($this->table['name']) : $this->table['name']; } /** * Gets the (possibly quoted) name of the join table. * * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy * * @param array $assoc * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform * @return string */ public function getQuotedJoinTableName(array $assoc, $platform) { return isset($assoc['joinTable']['quoted']) ? $platform->quoteIdentifier($assoc['joinTable']['name']) : $assoc['joinTable']['name']; } /** * {@inheritDoc} */ public function isAssociationInverseSide($fieldName) { return isset($this->associationMappings[$fieldName]) && ! $this->associationMappings[$fieldName]['isOwningSide']; } /** * {@inheritDoc} */ public function getAssociationMappedByTargetField($fieldName) { return $this->associationMappings[$fieldName]['mappedBy']; } /** * @param string $targetClass * @return array */ public function getAssociationsByTargetClass($targetClass) { $relations = array(); foreach ($this->associationMappings as $mapping) { if ($mapping['targetEntity'] == $targetClass) { $relations[$mapping['fieldName']] = $mapping; } } return $relations; } } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Column.php0000644000175100017510000000332212143374607021526 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target({"PROPERTY","ANNOTATION"}) */ final class Column implements Annotation { /** @var string */ public $name; /** @var mixed */ public $type = 'string'; /** @var integer */ public $length; /** @var integer */ public $precision = 0; // The precision for a decimal (exact numeric) column (Applies only for decimal column) /** @var integer */ public $scale = 0; // The scale for a decimal (exact numeric) column (Applies only for decimal column) /** @var boolean */ public $unique = false; /** @var boolean */ public $nullable = false; /** @var array */ public $options = array(); /** @var string */ public $columnDefinition; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/ColumnResult.php0000644000175100017510000000273212143374607022731 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * References name of a column in the SELECT clause of a SQL query. * Scalar result types can be included in the query result by specifying this annotation in the metadata. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("ANNOTATION") */ final class ColumnResult implements Annotation { /** * The name of a column in the SELECT clause of a SQL query * * @var string */ public $name; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/CustomIdGenerator.php0000644000175100017510000000221612143374607023670 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class CustomIdGenerator implements Annotation { /** @var string */ public $class; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/DefaultNamingStrategy.php0000644000175100017510000000462412143374607024540 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * The default NamingStrategy * * * @link www.doctrine-project.org * @since 2.3 * @author Fabio B. Silva */ class DefaultNamingStrategy implements NamingStrategy { /** * {@inheritdoc} */ public function classToTableName($className) { if (strpos($className, '\\') !== false) { return substr($className, strrpos($className, '\\') + 1); } return $className; } /** * {@inheritdoc} */ public function propertyToColumnName($propertyName) { return $propertyName; } /** * {@inheritdoc} */ public function referenceColumnName() { return 'id'; } /** * {@inheritdoc} */ public function joinColumnName($propertyName) { return $propertyName . '_' . $this->referenceColumnName(); } /** * {@inheritdoc} */ public function joinTableName($sourceEntity, $targetEntity, $propertyName = null) { return strtolower($this->classToTableName($sourceEntity) . '_' . $this->classToTableName($targetEntity)); } /** * {@inheritdoc} */ public function joinKeyColumnName($entityName, $referencedColumnName = null) { return strtolower($this->classToTableName($entityName) . '_' . ($referencedColumnName ?: $this->referenceColumnName())); } } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/DefaultQuoteStrategy.php0000644000175100017510000001154712143374607024426 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * A set of rules for determining the physical column, alias and table quotes * * @since 2.3 * @author Fabio B. Silva */ class DefaultQuoteStrategy implements QuoteStrategy { /** * {@inheritdoc} */ public function getColumnName($fieldName, ClassMetadata $class, AbstractPlatform $platform) { return isset($class->fieldMappings[$fieldName]['quoted']) ? $platform->quoteIdentifier($class->fieldMappings[$fieldName]['columnName']) : $class->fieldMappings[$fieldName]['columnName']; } /** * {@inheritdoc} */ public function getTableName(ClassMetadata $class, AbstractPlatform $platform) { return isset($class->table['quoted']) ? $platform->quoteIdentifier($class->table['name']) : $class->table['name']; } /** * {@inheritdoc} */ public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform) { return isset($definition['quoted']) ? $platform->quoteIdentifier($definition['sequenceName']) : $definition['sequenceName']; } /** * {@inheritdoc} */ public function getJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform) { return isset($joinColumn['quoted']) ? $platform->quoteIdentifier($joinColumn['name']) : $joinColumn['name']; } /** * {@inheritdoc} */ public function getReferencedJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform) { return isset($joinColumn['quoted']) ? $platform->quoteIdentifier($joinColumn['referencedColumnName']) : $joinColumn['referencedColumnName']; } /** * {@inheritdoc} */ public function getJoinTableName(array $association, ClassMetadata $class, AbstractPlatform $platform) { return isset($association['joinTable']['quoted']) ? $platform->quoteIdentifier($association['joinTable']['name']) : $association['joinTable']['name']; } /** * {@inheritdoc} */ public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform) { $quotedColumnNames = array(); foreach ($class->identifier as $fieldName) { if (isset($class->fieldMappings[$fieldName])) { $quotedColumnNames[] = $this->getColumnName($fieldName, $class, $platform); continue; } // Association defined as Id field $joinColumns = $class->associationMappings[$fieldName]['joinColumns']; $assocQuotedColumnNames = array_map( function ($joinColumn) use ($platform) { return isset($joinColumn['quoted']) ? $platform->quoteIdentifier($joinColumn['name']) : $joinColumn['name']; }, $joinColumns ); $quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames); } return $quotedColumnNames; } /** * {@inheritdoc} */ public function getColumnAlias($columnName, $counter, AbstractPlatform $platform, ClassMetadata $class = null) { // Trim the column alias to the maximum identifier length of the platform. // If the alias is to long, characters are cut off from the beginning. // And strip non alphanumeric characters $columnName = $columnName . $counter; $columnName = substr($columnName, -$platform->getMaxIdentifierLength()); $columnName = preg_replace('/[^A-Za-z0-9_]/', '', $columnName); return $platform->getSQLResultCasing($columnName); } }DoctrineORM-2.3.3/Doctrine/ORM/Mapping/DiscriminatorColumn.php0000644000175100017510000000257512143374607024267 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("CLASS") */ final class DiscriminatorColumn implements Annotation { /** @var string */ public $name; /** @var string */ public $type; /** @var integer */ public $length; /** @var mixed */ public $fieldName; // field name used in non-object hydration (array/scalar) /** @var string */ public $columnDefinition; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/DiscriminatorMap.php0000644000175100017510000000222112143374607023533 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("CLASS") */ final class DiscriminatorMap implements Annotation { /** @var array */ public $value; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/ElementCollection.php0000644000175100017510000000225612143374607023703 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("ALL") * @todo check available targets */ final class ElementCollection implements Annotation { /** @var string */ public $tableName; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Entity.php0000644000175100017510000000230012143374607021540 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("CLASS") */ final class Entity implements Annotation { /** @var string */ public $repositoryClass; /** @var boolean */ public $readOnly = false; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/EntityResult.php0000644000175100017510000000377112143374607022754 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * References an entity in the SELECT clause of a SQL query. * If this annotation is used, the SQL statement should select all of the columns that are mapped to the entity object. * This should include foreign key columns to related entities. * The results obtained when insufficient data is available are undefined. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("ANNOTATION") */ final class EntityResult implements Annotation { /** * The class of the result * * @var string */ public $entityClass; /** * Maps the columns specified in the SELECT list of the query to the properties or fields of the entity class. * * @var array<\Doctrine\ORM\Mapping\FieldResult> */ public $fields = array(); /** * Specifies the column name of the column in the SELECT list that is used to determine the type of the entity instance. * * @var string */ public $discriminatorColumn; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/FieldResult.php0000644000175100017510000000302212143374607022510 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * Is used to map the columns specified in the SELECT list of the query to the properties or fields of the entity class. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("ANNOTATION") */ final class FieldResult implements Annotation { /** * Name of the column in the SELECT clause. * * @var string */ public $name; /** * Name of the persistent field or property of the class. * * @var string */ public $column; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/GeneratedValue.php0000644000175100017510000000223012143374607023161 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class GeneratedValue implements Annotation { /** @var string */ public $strategy = 'AUTO'; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/HasLifecycleCallbacks.php0000644000175100017510000000214512143374607024426 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("CLASS") */ final class HasLifecycleCallbacks implements Annotation { } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Id.php0000644000175100017510000000212512143374607020625 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class Id implements Annotation { } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Index.php0000644000175100017510000000226612143374607021346 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("ANNOTATION") */ final class Index implements Annotation { /** @var string */ public $name; /** @var array */ public $columns; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/InheritanceType.php0000644000175100017510000000221112143374607023360 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("CLASS") */ final class InheritanceType implements Annotation { /** @var string */ public $value; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/JoinColumn.php0000644000175100017510000000300712143374607022346 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target({"PROPERTY","ANNOTATION"}) */ final class JoinColumn implements Annotation { /** @var string */ public $name; /** @var string */ public $referencedColumnName = 'id'; /** @var boolean */ public $unique = false; /** @var boolean */ public $nullable = true; /** @var mixed */ public $onDelete; /** @var string */ public $columnDefinition; /** @var string */ public $fieldName; // field name used in non-object hydration (array/scalar) } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/JoinColumns.php0000644000175100017510000000225112143374607022531 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class JoinColumns implements Annotation { /** @var array<\Doctrine\ORM\Mapping\JoinColumn> */ public $value; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/JoinTable.php0000644000175100017510000000257412143374607022150 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target({"PROPERTY","ANNOTATION"}) */ final class JoinTable implements Annotation { /** @var string */ public $name; /** @var string */ public $schema; /** @var array<\Doctrine\ORM\Mapping\JoinColumn> */ public $joinColumns = array(); /** @var array<\Doctrine\ORM\Mapping\JoinColumn> */ public $inverseJoinColumns = array(); } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/ManyToMany.php0000644000175100017510000000266712143374607022340 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class ManyToMany implements Annotation { /** @var string */ public $targetEntity; /** @var string */ public $mappedBy; /** @var string */ public $inversedBy; /** @var array */ public $cascade; /** @var string */ public $fetch = 'LAZY'; /** @var boolean */ public $orphanRemoval = false; /** @var string */ public $indexBy; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/ManyToOne.php0000644000175100017510000000244212143374607022144 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class ManyToOne implements Annotation { /** @var string */ public $targetEntity; /** @var array */ public $cascade; /** @var string */ public $fetch = 'LAZY'; /** @var string */ public $inversedBy; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/MappedSuperclass.php0000644000175100017510000000222412143374607023544 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("CLASS") */ final class MappedSuperclass implements Annotation { /** @var string */ public $repositoryClass; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/MappingException.php0000644000175100017510000004166012143374607023552 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * A MappingException indicates that something is wrong with the mapping setup. * * @since 2.0 */ class MappingException extends \Doctrine\ORM\ORMException { public static function pathRequired() { return new self("Specifying the paths to your entities is required ". "in the AnnotationDriver to retrieve all class names."); } public static function identifierRequired($entityName) { if (false !== ($parent = get_parent_class($entityName))) { return new self(sprintf( 'No identifier/primary key specified for Entity "%s" sub class of "%s". Every Entity must have an identifier/primary key.', $entityName, $parent )); } return new self(sprintf( 'No identifier/primary key specified for Entity "%s". Every Entity must have an identifier/primary key.', $entityName )); } public static function invalidInheritanceType($entityName, $type) { return new self("The inheritance type '$type' specified for '$entityName' does not exist."); } public static function generatorNotAllowedWithCompositeId() { return new self("Id generators can't be used with a composite id."); } public static function missingFieldName($entity) { return new self("The field or association mapping misses the 'fieldName' attribute in entity '$entity'."); } public static function missingTargetEntity($fieldName) { return new self("The association mapping '$fieldName' misses the 'targetEntity' attribute."); } public static function missingSourceEntity($fieldName) { return new self("The association mapping '$fieldName' misses the 'sourceEntity' attribute."); } public static function mappingFileNotFound($entityName, $fileName) { return new self("No mapping file found named '$fileName' for class '$entityName'."); } /** * Exception for invalid property name override. * * @param string $className The entity's name * @param string $fieldName */ public static function invalidOverrideFieldName($className, $fieldName) { return new self("Invalid field override named '$fieldName' for class '$className'."); } /** * Exception for invalid property type override. * * @param string $className The entity's name * @param string $fieldName */ public static function invalidOverrideFieldType($className, $fieldName) { return new self("The column type of attribute '$fieldName' on class '$className' could not be changed."); } public static function mappingNotFound($className, $fieldName) { return new self("No mapping found for field '$fieldName' on class '$className'."); } public static function queryNotFound($className, $queryName) { return new self("No query found named '$queryName' on class '$className'."); } public static function resultMappingNotFound($className, $resultName) { return new self("No result set mapping found named '$resultName' on class '$className'."); } public static function emptyQueryMapping($entity, $queryName) { return new self('Query named "'.$queryName.'" in "'.$entity.'" could not be empty.'); } public static function nameIsMandatoryForQueryMapping($className) { return new self("Query name on entity class '$className' is not defined."); } public static function missingQueryMapping($entity, $queryName) { return new self('Query named "'.$queryName.'" in "'.$entity.' requires a result class or result set mapping.'); } public static function missingResultSetMappingEntity($entity, $resultName) { return new self('Result set mapping named "'.$resultName.'" in "'.$entity.' requires a entity class name.'); } public static function missingResultSetMappingFieldName($entity, $resultName) { return new self('Result set mapping named "'.$resultName.'" in "'.$entity.' requires a field name.'); } public static function nameIsMandatoryForSqlResultSetMapping($className) { return new self("Result set mapping name on entity class '$className' is not defined."); } public static function oneToManyRequiresMappedBy($fieldName) { return new self("OneToMany mapping on field '$fieldName' requires the 'mappedBy' attribute."); } public static function joinTableRequired($fieldName) { return new self("The mapping of field '$fieldName' requires an the 'joinTable' attribute."); } /** * Called if a required option was not found but is required * * @param string $field which field cannot be processed? * @param string $expectedOption which option is required * @param string $hint Can optionally be used to supply a tip for common mistakes, * e.g. "Did you think of the plural s?" * @return MappingException */ static function missingRequiredOption($field, $expectedOption, $hint = '') { $message = "The mapping of field '{$field}' is invalid: The option '{$expectedOption}' is required."; if ( ! empty($hint)) { $message .= ' (Hint: ' . $hint . ')'; } return new self($message); } /** * Generic exception for invalid mappings. * * @param string $fieldName */ public static function invalidMapping($fieldName) { return new self("The mapping of field '$fieldName' is invalid."); } /** * Exception for reflection exceptions - adds the entity name, * because there might be long classnames that will be shortened * within the stacktrace * * @param string $entity The entity's name * @param \ReflectionException $previousException */ public static function reflectionFailure($entity, \ReflectionException $previousException) { return new self('An error occurred in ' . $entity, 0, $previousException); } public static function joinColumnMustPointToMappedField($className, $joinColumn) { return new self('The column ' . $joinColumn . ' must be mapped to a field in class ' . $className . ' since it is referenced by a join column of another class.'); } public static function classIsNotAValidEntityOrMappedSuperClass($className) { if (false !== ($parent = get_parent_class($className))) { return new self(sprintf( 'Class "%s" sub class of "%s" is not a valid entity or mapped super class.', $className, $parent )); } return new self(sprintf( 'Class "%s" is not a valid entity or mapped super class.', $className )); } public static function propertyTypeIsRequired($className, $propertyName) { return new self("The attribute 'type' is required for the column description of property ".$className."::\$".$propertyName."."); } public static function tableIdGeneratorNotImplemented($className) { return new self("TableIdGenerator is not yet implemented for use with class ".$className); } /** * @param string $entity The entity's name * @param string $fieldName The name of the field that was already declared */ public static function duplicateFieldMapping($entity, $fieldName) { return new self('Property "'.$fieldName.'" in "'.$entity.'" was already declared, but it must be declared only once'); } public static function duplicateAssociationMapping($entity, $fieldName) { return new self('Property "'.$fieldName.'" in "'.$entity.'" was already declared, but it must be declared only once'); } public static function duplicateQueryMapping($entity, $queryName) { return new self('Query named "'.$queryName.'" in "'.$entity.'" was already declared, but it must be declared only once'); } public static function duplicateResultSetMapping($entity, $resultName) { return new self('Result set mapping named "'.$resultName.'" in "'.$entity.'" was already declared, but it must be declared only once'); } public static function singleIdNotAllowedOnCompositePrimaryKey($entity) { return new self('Single id is not allowed on composite primary key in entity '.$entity); } public static function unsupportedOptimisticLockingType($entity, $fieldName, $unsupportedType) { return new self('Locking type "'.$unsupportedType.'" (specified in "'.$entity.'", field "'.$fieldName.'") ' .'is not supported by Doctrine.' ); } public static function fileMappingDriversRequireConfiguredDirectoryPath($path = null) { if ( ! empty($path)) { $path = '[' . $path . ']'; } return new self( 'File mapping drivers must have a valid directory path, ' . 'however the given path ' . $path . ' seems to be incorrect!' ); } /** * Throws an exception that indicates that a class used in a discriminator map does not exist. * An example would be an outdated (maybe renamed) classname. * * @param string $className The class that could not be found * @param string $owningClass The class that declares the discriminator map. * @return self */ public static function invalidClassInDiscriminatorMap($className, $owningClass) { return new self( "Entity class '$className' used in the discriminator map of class '$owningClass' ". "does not exist." ); } public static function duplicateDiscriminatorEntry($className, array $entries, array $map) { return new self( "The entries " . implode(', ', $entries) . " in discriminator map of class '" . $className . "' is duplicated. " . "If the discriminator map is automatically generated you have to convert it to an explicit discriminator map now. " . "The entries of the current map are: @DiscriminatorMap({" . implode(', ', array_map( function($a, $b) { return "'$a': '$b'"; }, array_keys($map), array_values($map) )) . "})" ); } public static function missingDiscriminatorMap($className) { return new self("Entity class '$className' is using inheritance but no discriminator map was defined."); } public static function missingDiscriminatorColumn($className) { return new self("Entity class '$className' is using inheritance but no discriminator column was defined."); } public static function invalidDiscriminatorColumnType($className, $type) { return new self("Discriminator column type on entity class '$className' is not allowed to be '$type'. 'string' or 'integer' type variables are suggested!"); } public static function nameIsMandatoryForDiscriminatorColumns($className) { return new self("Discriminator column name on entity class '$className' is not defined."); } public static function cannotVersionIdField($className, $fieldName) { return new self("Setting Id field '$fieldName' as versionale in entity class '$className' is not supported."); } public static function sqlConversionNotAllowedForIdentifiers($className, $fieldName, $type) { return new self("It is not possible to set id field '$fieldName' to type '$type' in entity class '$className'. The type '$type' requires conversion SQL which is not allowed for identifiers."); } /** * @param string $className * @param string $columnName * @return self */ public static function duplicateColumnName($className, $columnName) { return new self("Duplicate definition of column '".$columnName."' on entity '".$className."' in a field or discriminator column mapping."); } public static function illegalToManyAssocationOnMappedSuperclass($className, $field) { return new self("It is illegal to put an inverse side one-to-many or many-to-many association on mapped superclass '".$className."#".$field."'."); } /** * @param string $className * @param string $targetEntity * @param string $targetField * @return self */ public static function cannotMapCompositePrimaryKeyEntitiesAsForeignId($className, $targetEntity, $targetField) { return new self("It is not possible to map entity '".$className."' with a composite primary key ". "as part of the primary key of another entity '".$targetEntity."#".$targetField."'."); } public static function noSingleAssociationJoinColumnFound($className, $field) { return new self("'$className#$field' is not an association with a single join column."); } public static function noFieldNameFoundForColumn($className, $column) { return new self("Cannot find a field on '$className' that is mapped to column '$column'. Either the ". "field does not exist or an association exists but it has multiple join columns."); } public static function illegalOrphanRemovalOnIdentifierAssociation($className, $field) { return new self("The orphan removal option is not allowed on an association that is ". "part of the identifier in '$className#$field'."); } public static function illegalOrphanRemoval($className, $field) { return new self("Orphan removal is only allowed on one-to-one and one-to-many ". "associations, but " . $className."#" .$field . " is not."); } public static function illegalInverseIdentifierAssocation($className, $field) { return new self("An inverse association is not allowed to be identifier in '$className#$field'."); } public static function illegalToManyIdentifierAssoaction($className, $field) { return new self("Many-to-many or one-to-many associations are not allowed to be identifier in '$className#$field'."); } public static function noInheritanceOnMappedSuperClass($className) { return new self("Its not supported to define inheritance information on a mapped superclass '" . $className . "'."); } public static function mappedClassNotPartOfDiscriminatorMap($className, $rootClassName) { return new self( "Entity '" . $className . "' has to be part of the discriminator map of '" . $rootClassName . "' " . "to be properly mapped in the inheritance hierachy. Alternatively you can make '".$className."' an abstract class " . "to avoid this exception from occuring." ); } public static function lifecycleCallbackMethodNotFound($className, $methodName) { return new self("Entity '" . $className . "' has no method '" . $methodName . "' to be registered as lifecycle callback."); } public static function invalidFetchMode($className, $annotation) { return new self("Entity '" . $className . "' has a mapping with invalid fetch mode '" . $annotation . "'"); } public static function compositeKeyAssignedIdGeneratorRequired($className) { return new self("Entity '". $className . "' has a composite identifier but uses an ID generator other than manually assigning (Identity, Sequence). This is not supported."); } public static function invalidTargetEntityClass($targetEntity, $sourceEntity, $associationName) { return new self("The target-entity " . $targetEntity . " cannot be found in '" . $sourceEntity."#".$associationName."'."); } public static function invalidCascadeOption(array $cascades, $className, $propertyName) { $cascades = implode(", ", array_map(function ($e) { return "'" . $e . "'"; }, $cascades)); return new self(sprintf( "You have specified invalid cascade options for %s::$%s: %s; available options: 'remove', 'persist', 'refresh', 'merge', and 'detach'", $className, $propertyName, $cascades )); } } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/NamedNativeQueries.php0000644000175100017510000000274212143374607024027 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * Is used to specify an array of native SQL named queries. * The NamedNativeQueries annotation can be applied to an entity or mapped superclass. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("CLASS") */ final class NamedNativeQueries implements Annotation { /** * One or more NamedNativeQuery annotations. * * @var array<\Doctrine\ORM\Mapping\NamedNativeQuery> */ public $value = array(); } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/NamedNativeQuery.php0000644000175100017510000000344612143374607023521 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * Is used to specify a native SQL named query. * The NamedNativeQuery annotation can be applied to an entity or mapped superclass. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("ANNOTATION") */ final class NamedNativeQuery implements Annotation { /** * The name used to refer to the query with the EntityManager methods that create query objects. * * @var string */ public $name; /** * The SQL query string. * * @var string */ public $query; /** * The class of the result. * * @var string */ public $resultClass; /** * The name of a SqlResultSetMapping, as defined in metadata. * * @var string */ public $resultSetMapping; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/NamedQueries.php0000644000175100017510000000224712143374607022660 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("CLASS") */ final class NamedQueries implements Annotation { /** @var array<\Doctrine\ORM\Mapping\NamedQuery> */ public $value; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/NamedQuery.php0000644000175100017510000000226212143374607022345 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("ANNOTATION") */ final class NamedQuery implements Annotation { /** @var string */ public $name; /** @var string */ public $query; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/NamingStrategy.php0000644000175100017510000000511112143374607023223 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * A set of rules for determining the physical column and table names * * * @link www.doctrine-project.org * @since 2.3 * @author Fabio B. Silva */ interface NamingStrategy { /** * Return a table name for an entity class * * @param string $className The fully-qualified class name * @return string A table name */ function classToTableName($className); /** * Return a column name for a property * * @param string $propertyName A property * @return string A column name */ function propertyToColumnName($propertyName); /** * Return the default reference column name * * @return string A column name */ function referenceColumnName(); /** * Return a join column name for a property * * @param string $propertyName A property * @return string A join column name */ function joinColumnName($propertyName); /** * Return a join table name * * @param string $sourceEntity The source entity * @param string $targetEntity The target entity * @param string $propertyName A property * @return string A join table name */ function joinTableName($sourceEntity, $targetEntity, $propertyName = null); /** * Return the foreign key column name for the given parameters * * @param string $entityName A entity * @param string $referencedColumnName A property * @return string A join column name */ function joinKeyColumnName($entityName, $referencedColumnName = null); } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/OneToMany.php0000644000175100017510000000260712143374607022147 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class OneToMany implements Annotation { /** @var string */ public $mappedBy; /** @var string */ public $targetEntity; /** @var array */ public $cascade; /** @var string */ public $fetch = 'LAZY'; /** @var boolean */ public $orphanRemoval = false; /** @var string */ public $indexBy; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/OneToOne.php0000644000175100017510000000261112143374607021757 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class OneToOne implements Annotation { /** @var string */ public $targetEntity; /** @var string */ public $mappedBy; /** @var string */ public $inversedBy; /** @var array */ public $cascade; /** @var string */ public $fetch = 'LAZY'; /** @var boolean */ public $orphanRemoval = false; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/OrderBy.php0000644000175100017510000000221312143374607021635 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class OrderBy implements Annotation { /** @var array */ public $value; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/PostLoad.php0000644000175100017510000000213112143374607022013 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("METHOD") */ final class PostLoad implements Annotation { } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/PostPersist.php0000644000175100017510000000213412143374607022570 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("METHOD") */ final class PostPersist implements Annotation { } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/PostRemove.php0000644000175100017510000000213312143374607022373 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("METHOD") */ final class PostRemove implements Annotation { } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/PostUpdate.php0000644000175100017510000000213312143374607022360 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("METHOD") */ final class PostUpdate implements Annotation { } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/PreFlush.php0000644000175100017510000000213112143374607022016 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("METHOD") */ final class PreFlush implements Annotation { } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/PrePersist.php0000644000175100017510000000213312143374607022370 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("METHOD") */ final class PrePersist implements Annotation { } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/PreRemove.php0000644000175100017510000000213212143374607022173 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("METHOD") */ final class PreRemove implements Annotation { } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/PreUpdate.php0000644000175100017510000000213212143374607022160 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("METHOD") */ final class PreUpdate implements Annotation { } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/QuoteStrategy.php0000644000175100017510000000741412143374607023117 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * A set of rules for determining the column, alias and table quotes * * @since 2.3 * @author Fabio B. Silva */ interface QuoteStrategy { /** * Gets the (possibly quoted) column name for safe use in an SQL statement. * * @param string $fieldName * @param ClassMetadata $class * @param AbstractPlatform $platform * @return string */ function getColumnName($fieldName, ClassMetadata $class, AbstractPlatform $platform); /** * Gets the (possibly quoted) primary table name for safe use in an SQL statement. * * @param ClassMetadata $class * @param AbstractPlatform $platform * @return string */ function getTableName(ClassMetadata $class, AbstractPlatform $platform); /** * Gets the (possibly quoted) sequence name for safe use in an SQL statement. * * @param array $definition * @param ClassMetadata $class * @param AbstractPlatform $platform * @return string */ function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform); /** * Gets the (possibly quoted) name of the join table. * * @param array $association * @param ClassMetadata $class * @param AbstractPlatform $platform * @return string */ function getJoinTableName(array $association, ClassMetadata $class, AbstractPlatform $platform); /** * Gets the (possibly quoted) join column name. * * @param array $joinColumn * @param ClassMetadata $class * @param AbstractPlatform $platform * @return string */ function getJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform); /** * Gets the (possibly quoted) join column name. * * @param array $joinColumn * @param ClassMetadata $class * @param AbstractPlatform $platform * @return string */ function getReferencedJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform); /** * Gets the (possibly quoted) identifier column names for safe use in an SQL statement. * * @param ClassMetadata $class * @param AbstractPlatform $platform * @return array */ function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform); /** * Gets the column alias. * * @param string $columnName * @param integer $counter * @param AbstractPlatform $platform * @param ClassMetadata $class * @return string */ function getColumnAlias($columnName, $counter, AbstractPlatform $platform, ClassMetadata $class = null); }DoctrineORM-2.3.3/Doctrine/ORM/Mapping/SequenceGenerator.php0000644000175100017510000000240312143374607023707 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class SequenceGenerator implements Annotation { /** @var string */ public $sequenceName; /** @var integer */ public $allocationSize = 1; /** @var integer */ public $initialValue = 1; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/SqlResultSetMapping.php0000644000175100017510000000355312143374607024225 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * The SqlResultSetMapping annotation is used to specify the mapping of the result of a native SQL query. * The SqlResultSetMapping annotation can be applied to an entity or mapped superclass. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("ANNOTATION") */ final class SqlResultSetMapping implements Annotation { /** * The name given to the result set mapping, and used to refer to it in the methods of the Query API. * * @var string */ public $name; /** * Specifies the result set mapping to entities. * * @var array<\Doctrine\ORM\Mapping\EntityResult> */ public $entities = array(); /** * Specifies the result set mapping to scalar values. * * @var array<\Doctrine\ORM\Mapping\ColumnResult> */ public $columns = array(); } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/SqlResultSetMappings.php0000644000175100017510000000273412143374607024410 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * Is used to specify an array of mappings. * The SqlResultSetMappings annotation can be applied to an entity or mapped superclass. * * @author Fabio B. Silva * @since 2.3 * * @Annotation * @Target("CLASS") */ final class SqlResultSetMappings implements Annotation { /** * One or more SqlResultSetMapping annotations. * * @var array<\Doctrine\ORM\Mapping\SqlResultSetMapping> */ public $value = array(); } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Table.php0000644000175100017510000000260312143374607021321 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("CLASS") */ final class Table implements Annotation { /** @var string */ public $name; /** @var string */ public $schema; /** @var array<\Doctrine\ORM\Mapping\Index> */ public $indexes; /** @var array<\Doctrine\ORM\Mapping\UniqueConstraint> */ public $uniqueConstraints; /** @var array */ public $options = array(); } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/UnderscoreNamingStrategy.php0000644000175100017510000000674012143374607025266 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * Naming strategy implementing the underscore naming convention. * Converts 'MyEntity' to 'my_entity' or 'MY_ENTITY'. * * * @link www.doctrine-project.org * @since 2.3 * @author Fabio B. Silva */ class UnderscoreNamingStrategy implements NamingStrategy { /** * @var integer */ private $case; /** * Underscore naming strategy construct * * @param integer $case CASE_LOWER | CASE_UPPER */ public function __construct($case = CASE_LOWER) { $this->case = $case; } /** * @return integer */ public function getCase() { return $this->case; } /** * Sets string case CASE_LOWER | CASE_UPPER * Alphabetic characters converted to lowercase or uppercase * * @param integer $case */ public function setCase($case) { $this->case = $case; } /** * {@inheritdoc} */ public function classToTableName($className) { if (strpos($className, '\\') !== false) { $className = substr($className, strrpos($className, '\\') + 1); } return $this->underscore($className); } /** * {@inheritdoc} */ public function propertyToColumnName($propertyName) { return $this->underscore($propertyName); } /** * {@inheritdoc} */ public function referenceColumnName() { return $this->case === CASE_UPPER ? 'ID' : 'id'; } /** * {@inheritdoc} */ public function joinColumnName($propertyName) { return $this->underscore($propertyName) . '_' . $this->referenceColumnName(); } /** * {@inheritdoc} */ public function joinTableName($sourceEntity, $targetEntity, $propertyName = null) { return $this->classToTableName($sourceEntity) . '_' . $this->classToTableName($targetEntity); } /** * {@inheritdoc} */ public function joinKeyColumnName($entityName, $referencedColumnName = null) { return $this->classToTableName($entityName) . '_' . ($referencedColumnName ?: $this->referenceColumnName()); } /** * @param string $string * @return string */ private function underscore($string) { $string = preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $string); if ($this->case === CASE_UPPER) { return strtoupper($string); } return strtolower($string); } } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/UniqueConstraint.php0000644000175100017510000000230112143374607023600 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("ANNOTATION") */ final class UniqueConstraint implements Annotation { /** @var string */ public $name; /** @var array */ public $columns; } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Version.php0000644000175100017510000000213212143374607021714 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class Version implements Annotation { } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Builder/AssociationBuilder.php0000644000175100017510000001040512143374607025442 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping\Builder; use Doctrine\ORM\Mapping\ClassMetadata; class AssociationBuilder { /** * @var ClassMetadataBuilder */ protected $builder; /** * @var array */ protected $mapping; /** * @var array */ protected $joinColumns; /** * * @var int */ protected $type; /** * @param ClassMetadataBuilder $builder * @param array $mapping */ public function __construct(ClassMetadataBuilder $builder, array $mapping, $type) { $this->builder = $builder; $this->mapping = $mapping; $this->type = $type; } public function mappedBy($fieldName) { $this->mapping['mappedBy'] = $fieldName; return $this; } public function inversedBy($fieldName) { $this->mapping['inversedBy'] = $fieldName; return $this; } public function cascadeAll() { $this->mapping['cascade'] = array("ALL"); return $this; } public function cascadePersist() { $this->mapping['cascade'][] = "persist"; return $this; } public function cascadeRemove() { $this->mapping['cascade'][] = "remove"; return $this; } public function cascadeMerge() { $this->mapping['cascade'][] = "merge"; return $this; } public function cascadeDetach() { $this->mapping['cascade'][] = "detach"; return $this; } public function cascadeRefresh() { $this->mapping['cascade'][] = "refresh"; return $this; } public function fetchExtraLazy() { $this->mapping['fetch'] = ClassMetadata::FETCH_EXTRA_LAZY; return $this; } public function fetchEager() { $this->mapping['fetch'] = ClassMetadata::FETCH_EAGER; return $this; } public function fetchLazy() { $this->mapping['fetch'] = ClassMetadata::FETCH_LAZY; return $this; } /** * Add Join Columns * * @param string $columnName * @param string $referencedColumnName * @param bool $nullable * @param bool $unique * @param string $onDelete * @param string $columnDef */ public function addJoinColumn($columnName, $referencedColumnName, $nullable = true, $unique = false, $onDelete = null, $columnDef = null) { $this->joinColumns[] = array( 'name' => $columnName, 'referencedColumnName' => $referencedColumnName, 'nullable' => $nullable, 'unique' => $unique, 'onDelete' => $onDelete, 'columnDefinition' => $columnDef, ); return $this; } /** * @return ClassMetadataBuilder */ public function build() { $mapping = $this->mapping; if ($this->joinColumns) { $mapping['joinColumns'] = $this->joinColumns; } $cm = $this->builder->getClassMetadata(); if ($this->type == ClassMetadata::MANY_TO_ONE) { $cm->mapManyToOne($mapping); } else if ($this->type == ClassMetadata::ONE_TO_ONE) { $cm->mapOneToOne($mapping); } else { throw new \InvalidArgumentException("Type should be a ToOne Assocation here"); } return $this->builder; } } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Builder/ClassMetadataBuilder.php0000644000175100017510000002661012143374607025701 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping\Builder; use Doctrine\ORM\Mapping\ClassMetadata, Doctrine\ORM\Mapping\ClassMetadataInfo; /** * Builder Object for ClassMetadata * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 2.2 * @author Benjamin Eberlei * @author Guilherme Blanco */ class ClassMetadataBuilder { /** * @var \Doctrine\ORM\Mapping\ClassMetadataInfo */ private $cm; /** * @param \Doctrine\ORM\Mapping\ClassMetadataInfo $cm */ public function __construct(ClassMetadataInfo $cm) { $this->cm = $cm; } /** * @return ClassMetadata */ public function getClassMetadata() { return $this->cm; } /** * Mark the class as mapped superclass. * * @return ClassMetadataBuilder */ public function setMappedSuperClass() { $this->cm->isMappedSuperclass = true; return $this; } /** * Set custom Repository class name * * @param string $repositoryClassName * @return ClassMetadataBuilder */ public function setCustomRepositoryClass($repositoryClassName) { $this->cm->setCustomRepositoryClass($repositoryClassName); return $this; } /** * Mark class read only * * @return ClassMetadataBuilder */ public function setReadOnly() { $this->cm->markReadOnly(); return $this; } /** * Set the table name * * @param string $name * @return ClassMetadataBuilder */ public function setTable($name) { $this->cm->setPrimaryTable(array('name' => $name)); return $this; } /** * Add Index * * @param array $columns * @param string $name * @return ClassMetadataBuilder */ public function addIndex(array $columns, $name) { if (!isset($this->cm->table['indexes'])) { $this->cm->table['indexes'] = array(); } $this->cm->table['indexes'][$name] = array('columns' => $columns); return $this; } /** * Add Unique Constraint * * @param array $columns * @param string $name * @return ClassMetadataBuilder */ public function addUniqueConstraint(array $columns, $name) { if ( ! isset($this->cm->table['uniqueConstraints'])) { $this->cm->table['uniqueConstraints'] = array(); } $this->cm->table['uniqueConstraints'][$name] = array('columns' => $columns); return $this; } /** * Add named query * * @param string $name * @param string $dqlQuery * @return ClassMetadataBuilder */ public function addNamedQuery($name, $dqlQuery) { $this->cm->addNamedQuery(array( 'name' => $name, 'query' => $dqlQuery, )); return $this; } /** * Set class as root of a joined table inheritance hierachy. * * @return ClassMetadataBuilder */ public function setJoinedTableInheritance() { $this->cm->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_JOINED); return $this; } /** * Set class as root of a single table inheritance hierachy. * * @return ClassMetadataBuilder */ public function setSingleTableInheritance() { $this->cm->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE); return $this; } /** * Set the discriminator column details. * * @param string $name * @param string $type */ public function setDiscriminatorColumn($name, $type = 'string', $length = 255) { $this->cm->setDiscriminatorColumn(array( 'name' => $name, 'type' => $type, 'length' => $length, )); return $this; } /** * Add a subclass to this inheritance hierachy. * * @param string $name * @param string $class * @return ClassMetadataBuilder */ public function addDiscriminatorMapClass($name, $class) { $this->cm->addDiscriminatorMapClass($name, $class); return $this; } /** * Set deferred explicit change tracking policy. * * @return ClassMetadataBuilder */ public function setChangeTrackingPolicyDeferredExplicit() { $this->cm->setChangeTrackingPolicy(ClassMetadata::CHANGETRACKING_DEFERRED_EXPLICIT); return $this; } /** * Set notify change tracking policy. * * @return ClassMetadataBuilder */ public function setChangeTrackingPolicyNotify() { $this->cm->setChangeTrackingPolicy(ClassMetadata::CHANGETRACKING_NOTIFY); return $this; } /** * Add lifecycle event * * @param string $methodName * @param string $event * @return ClassMetadataBuilder */ public function addLifecycleEvent($methodName, $event) { $this->cm->addLifecycleCallback($methodName, $event); return $this; } /** * Add Field * * @param string $name * @param string $type * @param array $mapping */ public function addField($name, $type, array $mapping = array()) { $mapping['fieldName'] = $name; $mapping['type'] = $type; $this->cm->mapField($mapping); return $this; } /** * Create a field builder. * * @param string $name * @param string $type * @return FieldBuilder */ public function createField($name, $type) { return new FieldBuilder( $this, array( 'fieldName' => $name, 'type' => $type ) ); } /** * Add a simple many to one association, optionally with the inversed by field. * * @param string $name * @param string $targetEntity * @param string|null $inversedBy * @return ClassMetadataBuilder */ public function addManyToOne($name, $targetEntity, $inversedBy = null) { $builder = $this->createManyToOne($name, $targetEntity); if ($inversedBy) { $builder->inversedBy($inversedBy); } return $builder->build(); } /** * Create a ManyToOne Assocation Builder. * * Note: This method does not add the association, you have to call build() on the AssociationBuilder. * * @param string $name * @param string $targetEntity * @return AssociationBuilder */ public function createManyToOne($name, $targetEntity) { return new AssociationBuilder( $this, array( 'fieldName' => $name, 'targetEntity' => $targetEntity ), ClassMetadata::MANY_TO_ONE ); } /** * Create OneToOne Assocation Builder * * @param string $name * @param string $targetEntity * @return AssociationBuilder */ public function createOneToOne($name, $targetEntity) { return new AssociationBuilder( $this, array( 'fieldName' => $name, 'targetEntity' => $targetEntity ), ClassMetadata::ONE_TO_ONE ); } /** * Add simple inverse one-to-one assocation. * * @param string $name * @param string $targetEntity * @param string $mappedBy * @return ClassMetadataBuilder */ public function addInverseOneToOne($name, $targetEntity, $mappedBy) { $builder = $this->createOneToOne($name, $targetEntity); $builder->mappedBy($mappedBy); return $builder->build(); } /** * Add simple owning one-to-one assocation. * * @param string $name * @param string $targetEntity * @param string $inversedBy * @return ClassMetadataBuilder */ public function addOwningOneToOne($name, $targetEntity, $inversedBy = null) { $builder = $this->createOneToOne($name, $targetEntity); if ($inversedBy) { $builder->inversedBy($inversedBy); } return $builder->build(); } /** * Create ManyToMany Assocation Builder * * @param string $name * @param string $targetEntity * @return ManyToManyAssociationBuilder */ public function createManyToMany($name, $targetEntity) { return new ManyToManyAssociationBuilder( $this, array( 'fieldName' => $name, 'targetEntity' => $targetEntity ), ClassMetadata::MANY_TO_MANY ); } /** * Add a simple owning many to many assocation. * * @param string $name * @param string $targetEntity * @param string|null $inversedBy * @return ClassMetadataBuilder */ public function addOwningManyToMany($name, $targetEntity, $inversedBy = null) { $builder = $this->createManyToMany($name, $targetEntity); if ($inversedBy) { $builder->inversedBy($inversedBy); } return $builder->build(); } /** * Add a simple inverse many to many assocation. * * @param string $name * @param string $targetEntity * @param string $mappedBy * @return ClassMetadataBuilder */ public function addInverseManyToMany($name, $targetEntity, $mappedBy) { $builder = $this->createManyToMany($name, $targetEntity); $builder->mappedBy($mappedBy); return $builder->build(); } /** * Create a one to many assocation builder * * @param string $name * @param string $targetEntity * @return OneToManyAssociationBuilder */ public function createOneToMany($name, $targetEntity) { return new OneToManyAssociationBuilder( $this, array( 'fieldName' => $name, 'targetEntity' => $targetEntity ), ClassMetadata::ONE_TO_MANY ); } /** * Add simple OneToMany assocation. * * @param string $name * @param string $targetEntity * @param string $mappedBy * @return ClassMetadataBuilder */ public function addOneToMany($name, $targetEntity, $mappedBy) { $builder = $this->createOneToMany($name, $targetEntity); $builder->mappedBy($mappedBy); return $builder->build(); } } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Builder/FieldBuilder.php0000644000175100017510000001204112143374607024207 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping\Builder; /** * Field Builder * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 2.2 * @author Benjamin Eberlei */ class FieldBuilder { /** * @var ClassMetadataBuilder */ private $builder; /** * @var array */ private $mapping; /** * @var bool */ private $version; /** * @var string */ private $generatedValue; /** * @var array */ private $sequenceDef; /** * * @param ClassMetadataBuilder $builder * @param array $mapping */ public function __construct(ClassMetadataBuilder $builder, array $mapping) { $this->builder = $builder; $this->mapping = $mapping; } /** * Set length. * * @param int $length * @return FieldBuilder */ public function length($length) { $this->mapping['length'] = $length; return $this; } /** * Set nullable * * @param bool * @return FieldBuilder */ public function nullable($flag = true) { $this->mapping['nullable'] = (bool)$flag; return $this; } /** * Set Unique * * @param bool * @return FieldBuilder */ public function unique($flag = true) { $this->mapping['unique'] = (bool)$flag; return $this; } /** * Set column name * * @param string $name * @return FieldBuilder */ public function columnName($name) { $this->mapping['columnName'] = $name; return $this; } /** * Set Precision * * @param int $p * @return FieldBuilder */ public function precision($p) { $this->mapping['precision'] = $p; return $this; } /** * Set scale. * * @param int $s * @return FieldBuilder */ public function scale($s) { $this->mapping['scale'] = $s; return $this; } /** * Set field as primary key. * * @return FieldBuilder */ public function isPrimaryKey() { $this->mapping['id'] = true; return $this; } /** * @param int $strategy * @return FieldBuilder */ public function generatedValue($strategy = 'AUTO') { $this->generatedValue = $strategy; return $this; } /** * Set field versioned * * @return FieldBuilder */ public function isVersionField() { $this->version = true; return $this; } /** * Set Sequence Generator * * @param string $sequenceName * @param int $allocationSize * @param int $initialValue * @return FieldBuilder */ public function setSequenceGenerator($sequenceName, $allocationSize = 1, $initialValue = 1) { $this->sequenceDef = array( 'sequenceName' => $sequenceName, 'allocationSize' => $allocationSize, 'initialValue' => $initialValue, ); return $this; } /** * Set column definition. * * @param string $def * @return FieldBuilder */ public function columnDefinition($def) { $this->mapping['columnDefinition'] = $def; return $this; } /** * Finalize this field and attach it to the ClassMetadata. * * Without this call a FieldBuilder has no effect on the ClassMetadata. * * @return ClassMetadataBuilder */ public function build() { $cm = $this->builder->getClassMetadata(); if ($this->generatedValue) { $cm->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $this->generatedValue)); } if ($this->version) { $cm->setVersionMapping($this->mapping); } $cm->mapField($this->mapping); if ($this->sequenceDef) { $cm->setSequenceGeneratorDefinition($this->sequenceDef); } return $this->builder; } } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Builder/ManyToManyAssociationBuilder.php0000644000175100017510000000554312143374607027426 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping\Builder; /** * ManyToMany Association Builder * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 2.0 * @author Benjamin Eberlei */ class ManyToManyAssociationBuilder extends OneToManyAssociationBuilder { private $joinTableName; private $inverseJoinColumns = array(); public function setJoinTable($name) { $this->joinTableName = $name; return $this; } /** * Add Inverse Join Columns * * @param string $columnName * @param string $referencedColumnName * @param bool $nullable * @param bool $unique * @param string $onDelete * @param string $columnDef */ public function addInverseJoinColumn($columnName, $referencedColumnName, $nullable = true, $unique = false, $onDelete = null, $columnDef = null) { $this->inverseJoinColumns[] = array( 'name' => $columnName, 'referencedColumnName' => $referencedColumnName, 'nullable' => $nullable, 'unique' => $unique, 'onDelete' => $onDelete, 'columnDefinition' => $columnDef, ); return $this; } /** * @return ClassMetadataBuilder */ public function build() { $mapping = $this->mapping; $mapping['joinTable'] = array(); if ($this->joinColumns) { $mapping['joinTable']['joinColumns'] = $this->joinColumns; } if ($this->inverseJoinColumns) { $mapping['joinTable']['inverseJoinColumns'] = $this->inverseJoinColumns; } if ($this->joinTableName) { $mapping['joinTable']['name'] = $this->joinTableName; } $cm = $this->builder->getClassMetadata(); $cm->mapManyToMany($mapping); return $this->builder; } } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Builder/OneToManyAssociationBuilder.php0000644000175100017510000000375312143374607027244 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping\Builder; /** * OneToMany Association Builder * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 2.0 * @author Benjamin Eberlei */ class OneToManyAssociationBuilder extends AssociationBuilder { /** * @param array $fieldNames * @return OneToManyAssociationBuilder */ public function setOrderBy(array $fieldNames) { $this->mapping['orderBy'] = $fieldNames; return $this; } public function setIndexBy($fieldName) { $this->mapping['indexBy'] = $fieldName; return $this; } /** * @return ClassMetadataBuilder */ public function build() { $mapping = $this->mapping; if ($this->joinColumns) { $mapping['joinColumns'] = $this->joinColumns; } $cm = $this->builder->getClassMetadata(); $cm->mapOneToMany($mapping); return $this->builder; } } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php0000644000175100017510000006156312143374607025025 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Common\Annotations\AnnotationReader, Doctrine\ORM\Mapping\MappingException, Doctrine\ORM\Mapping\JoinColumn, Doctrine\ORM\Mapping\Column, Doctrine\Common\Persistence\Mapping\ClassMetadata, Doctrine\Common\Persistence\Mapping\Driver\AnnotationDriver as AbstractAnnotationDriver; /** * The AnnotationDriver reads the mapping metadata from docblock annotations. * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan H. Wage * @author Roman Borschel */ class AnnotationDriver extends AbstractAnnotationDriver { /** * {@inheritDoc} */ protected $entityAnnotationClasses = array( 'Doctrine\ORM\Mapping\Entity' => 1, 'Doctrine\ORM\Mapping\MappedSuperclass' => 2, ); /** * {@inheritDoc} */ public function loadMetadataForClass($className, ClassMetadata $metadata) { /* @var $metadata \Doctrine\ORM\Mapping\ClassMetadataInfo */ $class = $metadata->getReflectionClass(); if ( ! $class) { // this happens when running annotation driver in combination with // static reflection services. This is not the nicest fix $class = new \ReflectionClass($metadata->name); } $classAnnotations = $this->reader->getClassAnnotations($class); if ($classAnnotations) { foreach ($classAnnotations as $key => $annot) { if ( ! is_numeric($key)) { continue; } $classAnnotations[get_class($annot)] = $annot; } } // Evaluate Entity annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\Entity'])) { $entityAnnot = $classAnnotations['Doctrine\ORM\Mapping\Entity']; if ($entityAnnot->repositoryClass !== null) { $metadata->setCustomRepositoryClass($entityAnnot->repositoryClass); } if ($entityAnnot->readOnly) { $metadata->markReadOnly(); } } else if (isset($classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass'])) { $mappedSuperclassAnnot = $classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass']; $metadata->setCustomRepositoryClass($mappedSuperclassAnnot->repositoryClass); $metadata->isMappedSuperclass = true; } else { throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); } // Evaluate Table annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\Table'])) { $tableAnnot = $classAnnotations['Doctrine\ORM\Mapping\Table']; $primaryTable = array( 'name' => $tableAnnot->name, 'schema' => $tableAnnot->schema ); if ($tableAnnot->indexes !== null) { foreach ($tableAnnot->indexes as $indexAnnot) { $index = array('columns' => $indexAnnot->columns); if ( ! empty($indexAnnot->name)) { $primaryTable['indexes'][$indexAnnot->name] = $index; } else { $primaryTable['indexes'][] = $index; } } } if ($tableAnnot->uniqueConstraints !== null) { foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) { $uniqueConstraint = array('columns' => $uniqueConstraintAnnot->columns); if ( ! empty($uniqueConstraintAnnot->name)) { $primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint; } else { $primaryTable['uniqueConstraints'][] = $uniqueConstraint; } } } if ($tableAnnot->options !== null) { $primaryTable['options'] = $tableAnnot->options; } $metadata->setPrimaryTable($primaryTable); } // Evaluate NamedNativeQueries annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\NamedNativeQueries'])) { $namedNativeQueriesAnnot = $classAnnotations['Doctrine\ORM\Mapping\NamedNativeQueries']; foreach ($namedNativeQueriesAnnot->value as $namedNativeQuery) { $metadata->addNamedNativeQuery(array( 'name' => $namedNativeQuery->name, 'query' => $namedNativeQuery->query, 'resultClass' => $namedNativeQuery->resultClass, 'resultSetMapping' => $namedNativeQuery->resultSetMapping, )); } } // Evaluate SqlResultSetMappings annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\SqlResultSetMappings'])) { $sqlResultSetMappingsAnnot = $classAnnotations['Doctrine\ORM\Mapping\SqlResultSetMappings']; foreach ($sqlResultSetMappingsAnnot->value as $resultSetMapping) { $entities = array(); $columns = array(); foreach ($resultSetMapping->entities as $entityResultAnnot) { $entityResult = array( 'fields' => array(), 'entityClass' => $entityResultAnnot->entityClass, 'discriminatorColumn' => $entityResultAnnot->discriminatorColumn, ); foreach ($entityResultAnnot->fields as $fieldResultAnnot) { $entityResult['fields'][] = array( 'name' => $fieldResultAnnot->name, 'column' => $fieldResultAnnot->column ); } $entities[] = $entityResult; } foreach ($resultSetMapping->columns as $columnResultAnnot) { $columns[] = array( 'name' => $columnResultAnnot->name, ); } $metadata->addSqlResultSetMapping(array( 'name' => $resultSetMapping->name, 'entities' => $entities, 'columns' => $columns )); } } // Evaluate NamedQueries annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\NamedQueries'])) { $namedQueriesAnnot = $classAnnotations['Doctrine\ORM\Mapping\NamedQueries']; if ( ! is_array($namedQueriesAnnot->value)) { throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations."); } foreach ($namedQueriesAnnot->value as $namedQuery) { if ( ! ($namedQuery instanceof \Doctrine\ORM\Mapping\NamedQuery)) { throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations."); } $metadata->addNamedQuery(array( 'name' => $namedQuery->name, 'query' => $namedQuery->query )); } } // Evaluate InheritanceType annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\InheritanceType'])) { $inheritanceTypeAnnot = $classAnnotations['Doctrine\ORM\Mapping\InheritanceType']; $metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceTypeAnnot->value)); if ($metadata->inheritanceType != \Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_NONE) { // Evaluate DiscriminatorColumn annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\DiscriminatorColumn'])) { $discrColumnAnnot = $classAnnotations['Doctrine\ORM\Mapping\DiscriminatorColumn']; $metadata->setDiscriminatorColumn(array( 'name' => $discrColumnAnnot->name, 'type' => $discrColumnAnnot->type, 'length' => $discrColumnAnnot->length, 'columnDefinition' => $discrColumnAnnot->columnDefinition )); } else { $metadata->setDiscriminatorColumn(array('name' => 'dtype', 'type' => 'string', 'length' => 255)); } // Evaluate DiscriminatorMap annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\DiscriminatorMap'])) { $discrMapAnnot = $classAnnotations['Doctrine\ORM\Mapping\DiscriminatorMap']; $metadata->setDiscriminatorMap($discrMapAnnot->value); } } } // Evaluate DoctrineChangeTrackingPolicy annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\ChangeTrackingPolicy'])) { $changeTrackingAnnot = $classAnnotations['Doctrine\ORM\Mapping\ChangeTrackingPolicy']; $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . $changeTrackingAnnot->value)); } // Evaluate annotations on properties/fields /* @var $property \ReflectionProperty */ foreach ($class->getProperties() as $property) { if ($metadata->isMappedSuperclass && ! $property->isPrivate() || $metadata->isInheritedField($property->name) || $metadata->isInheritedAssociation($property->name)) { continue; } $mapping = array(); $mapping['fieldName'] = $property->getName(); // Check for JoinColummn/JoinColumns annotations $joinColumns = array(); if ($joinColumnAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinColumn')) { $joinColumns[] = $this->joinColumnToArray($joinColumnAnnot); } else if ($joinColumnsAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinColumns')) { foreach ($joinColumnsAnnot->value as $joinColumn) { $joinColumns[] = $this->joinColumnToArray($joinColumn); } } // Field can only be annotated with one of: // @Column, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany if ($columnAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Column')) { if ($columnAnnot->type == null) { throw MappingException::propertyTypeIsRequired($className, $property->getName()); } $mapping = $this->columnToArray($property->getName(), $columnAnnot); if ($idAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Id')) { $mapping['id'] = true; } if ($generatedValueAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\GeneratedValue')) { $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $generatedValueAnnot->strategy)); } if ($this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Version')) { $metadata->setVersionMapping($mapping); } $metadata->mapField($mapping); // Check for SequenceGenerator/TableGenerator definition if ($seqGeneratorAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\SequenceGenerator')) { $metadata->setSequenceGeneratorDefinition(array( 'sequenceName' => $seqGeneratorAnnot->sequenceName, 'allocationSize' => $seqGeneratorAnnot->allocationSize, 'initialValue' => $seqGeneratorAnnot->initialValue )); } else if ($this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\TableGenerator')) { throw MappingException::tableIdGeneratorNotImplemented($className); } else if ($customGeneratorAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\CustomIdGenerator')) { $metadata->setCustomGeneratorDefinition(array( 'class' => $customGeneratorAnnot->class )); } } else if ($oneToOneAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToOne')) { if ($idAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Id')) { $mapping['id'] = true; } $mapping['targetEntity'] = $oneToOneAnnot->targetEntity; $mapping['joinColumns'] = $joinColumns; $mapping['mappedBy'] = $oneToOneAnnot->mappedBy; $mapping['inversedBy'] = $oneToOneAnnot->inversedBy; $mapping['cascade'] = $oneToOneAnnot->cascade; $mapping['orphanRemoval'] = $oneToOneAnnot->orphanRemoval; $mapping['fetch'] = $this->getFetchMode($className, $oneToOneAnnot->fetch); $metadata->mapOneToOne($mapping); } else if ($oneToManyAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToMany')) { $mapping['mappedBy'] = $oneToManyAnnot->mappedBy; $mapping['targetEntity'] = $oneToManyAnnot->targetEntity; $mapping['cascade'] = $oneToManyAnnot->cascade; $mapping['indexBy'] = $oneToManyAnnot->indexBy; $mapping['orphanRemoval'] = $oneToManyAnnot->orphanRemoval; $mapping['fetch'] = $this->getFetchMode($className, $oneToManyAnnot->fetch); if ($orderByAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) { $mapping['orderBy'] = $orderByAnnot->value; } $metadata->mapOneToMany($mapping); } else if ($manyToOneAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToOne')) { if ($idAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Id')) { $mapping['id'] = true; } $mapping['joinColumns'] = $joinColumns; $mapping['cascade'] = $manyToOneAnnot->cascade; $mapping['inversedBy'] = $manyToOneAnnot->inversedBy; $mapping['targetEntity'] = $manyToOneAnnot->targetEntity; $mapping['fetch'] = $this->getFetchMode($className, $manyToOneAnnot->fetch); $metadata->mapManyToOne($mapping); } else if ($manyToManyAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToMany')) { $joinTable = array(); if ($joinTableAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinTable')) { $joinTable = array( 'name' => $joinTableAnnot->name, 'schema' => $joinTableAnnot->schema ); foreach ($joinTableAnnot->joinColumns as $joinColumn) { $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn); } foreach ($joinTableAnnot->inverseJoinColumns as $joinColumn) { $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn); } } $mapping['joinTable'] = $joinTable; $mapping['targetEntity'] = $manyToManyAnnot->targetEntity; $mapping['mappedBy'] = $manyToManyAnnot->mappedBy; $mapping['inversedBy'] = $manyToManyAnnot->inversedBy; $mapping['cascade'] = $manyToManyAnnot->cascade; $mapping['indexBy'] = $manyToManyAnnot->indexBy; $mapping['orphanRemoval'] = $manyToManyAnnot->orphanRemoval; $mapping['fetch'] = $this->getFetchMode($className, $manyToManyAnnot->fetch); if ($orderByAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) { $mapping['orderBy'] = $orderByAnnot->value; } $metadata->mapManyToMany($mapping); } } // Evaluate AssociationOverrides annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\AssociationOverrides'])) { $associationOverridesAnnot = $classAnnotations['Doctrine\ORM\Mapping\AssociationOverrides']; foreach ($associationOverridesAnnot->value as $associationOverride) { $override = array(); $fieldName = $associationOverride->name; // Check for JoinColummn/JoinColumns annotations if ($associationOverride->joinColumns) { $joinColumns = array(); foreach ($associationOverride->joinColumns as $joinColumn) { $joinColumns[] = $this->joinColumnToArray($joinColumn); } $override['joinColumns'] = $joinColumns; } // Check for JoinTable annotations if ($associationOverride->joinTable) { $joinTable = null; $joinTableAnnot = $associationOverride->joinTable; $joinTable = array( 'name' => $joinTableAnnot->name, 'schema' => $joinTableAnnot->schema ); foreach ($joinTableAnnot->joinColumns as $joinColumn) { $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn); } foreach ($joinTableAnnot->inverseJoinColumns as $joinColumn) { $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn); } $override['joinTable'] = $joinTable; } $metadata->setAssociationOverride($fieldName, $override); } } // Evaluate AttributeOverrides annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\AttributeOverrides'])) { $attributeOverridesAnnot = $classAnnotations['Doctrine\ORM\Mapping\AttributeOverrides']; foreach ($attributeOverridesAnnot->value as $attributeOverrideAnnot) { $attributeOverride = $this->columnToArray($attributeOverrideAnnot->name, $attributeOverrideAnnot->column); $metadata->setAttributeOverride($attributeOverrideAnnot->name, $attributeOverride); } } // Evaluate @HasLifecycleCallbacks annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\HasLifecycleCallbacks'])) { /* @var $method \ReflectionMethod */ foreach ($class->getMethods() as $method) { // filter for the declaring class only, callbacks from parents will already be registered. if ($method->isPublic() && $method->getDeclaringClass()->getName() == $class->name) { $annotations = $this->reader->getMethodAnnotations($method); if ($annotations) { foreach ($annotations as $key => $annot) { if ( ! is_numeric($key)) { continue; } $annotations[get_class($annot)] = $annot; } } if (isset($annotations['Doctrine\ORM\Mapping\PrePersist'])) { $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::prePersist); } if (isset($annotations['Doctrine\ORM\Mapping\PostPersist'])) { $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postPersist); } if (isset($annotations['Doctrine\ORM\Mapping\PreUpdate'])) { $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::preUpdate); } if (isset($annotations['Doctrine\ORM\Mapping\PostUpdate'])) { $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postUpdate); } if (isset($annotations['Doctrine\ORM\Mapping\PreRemove'])) { $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::preRemove); } if (isset($annotations['Doctrine\ORM\Mapping\PostRemove'])) { $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postRemove); } if (isset($annotations['Doctrine\ORM\Mapping\PostLoad'])) { $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postLoad); } if (isset($annotations['Doctrine\ORM\Mapping\PreFlush'])) { $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::preFlush); } } } } } /** * Attempts to resolve the fetch mode. * * @param string $className The class name * @param string $fetchMode The fetch mode * @return integer The fetch mode as defined in ClassMetadata * @throws MappingException If the fetch mode is not valid */ private function getFetchMode($className, $fetchMode) { if( ! defined('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode)) { throw MappingException::invalidFetchMode($className, $fetchMode); } return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode); } /** * Parse the given JoinColumn as array * * @param JoinColumn $joinColumn * @return array */ private function joinColumnToArray(JoinColumn $joinColumn) { return array( 'name' => $joinColumn->name, 'unique' => $joinColumn->unique, 'nullable' => $joinColumn->nullable, 'onDelete' => $joinColumn->onDelete, 'columnDefinition' => $joinColumn->columnDefinition, 'referencedColumnName' => $joinColumn->referencedColumnName, ); } /** * Parse the given Column as array * * @param string $fieldName * @param Column $column * @return array */ private function columnToArray($fieldName, Column $column) { $mapping = array( 'fieldName' => $fieldName, 'type' => $column->type, 'scale' => $column->scale, 'length' => $column->length, 'unique' => $column->unique, 'nullable' => $column->nullable, 'precision' => $column->precision ); if ($column->options) { $mapping['options'] = $column->options; } if (isset($column->name)) { $mapping['columnName'] = $column->name; } if (isset($column->columnDefinition)) { $mapping['columnDefinition'] = $column->columnDefinition; } return $mapping; } /** * Factory method for the Annotation Driver * * @param array|string $paths * @param AnnotationReader $reader * @return AnnotationDriver */ static public function create($paths = array(), AnnotationReader $reader = null) { if ($reader == null) { $reader = new AnnotationReader(); } return new self($reader, $paths); } } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php0000644000175100017510000003515312143374607024413 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping\Driver; use Doctrine\DBAL\Schema\AbstractSchemaManager, Doctrine\DBAL\Schema\SchemaException, Doctrine\Common\Persistence\Mapping\Driver\MappingDriver, Doctrine\Common\Persistence\Mapping\ClassMetadata, Doctrine\ORM\Mapping\ClassMetadataInfo, Doctrine\Common\Util\Inflector, Doctrine\ORM\Mapping\MappingException; /** * The DatabaseDriver reverse engineers the mapping metadata from a database. * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Benjamin Eberlei */ class DatabaseDriver implements MappingDriver { /** * @var AbstractSchemaManager */ private $_sm; /** * @var array */ private $tables = null; private $classToTableNames = array(); /** * @var array */ private $manyToManyTables = array(); /** * @var array */ private $classNamesForTables = array(); /** * @var array */ private $fieldNamesForColumns = array(); /** * The namespace for the generated entities. * * @var string */ private $namespace; /** * * @param AbstractSchemaManager $schemaManager */ public function __construct(AbstractSchemaManager $schemaManager) { $this->_sm = $schemaManager; } /** * Set tables manually instead of relying on the reverse engeneering capabilities of SchemaManager. * * @param array $entityTables * @param array $manyToManyTables * @return void */ public function setTables($entityTables, $manyToManyTables) { $this->tables = $this->manyToManyTables = $this->classToTableNames = array(); foreach ($entityTables as $table) { $className = $this->getClassNameForTable($table->getName()); $this->classToTableNames[$className] = $table->getName(); $this->tables[$table->getName()] = $table; } foreach ($manyToManyTables as $table) { $this->manyToManyTables[$table->getName()] = $table; } } private function reverseEngineerMappingFromDatabase() { if ($this->tables !== null) { return; } $tables = array(); foreach ($this->_sm->listTableNames() as $tableName) { $tables[$tableName] = $this->_sm->listTableDetails($tableName); } $this->tables = $this->manyToManyTables = $this->classToTableNames = array(); foreach ($tables as $tableName => $table) { /* @var $table \Doctrine\DBAL\Schema\Table */ if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) { $foreignKeys = $table->getForeignKeys(); } else { $foreignKeys = array(); } $allForeignKeyColumns = array(); foreach ($foreignKeys as $foreignKey) { $allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns()); } if ( ! $table->hasPrimaryKey()) { throw new MappingException( "Table " . $table->getName() . " has no primary key. Doctrine does not ". "support reverse engineering from tables that don't have a primary key." ); } $pkColumns = $table->getPrimaryKey()->getColumns(); sort($pkColumns); sort($allForeignKeyColumns); if ($pkColumns == $allForeignKeyColumns && count($foreignKeys) == 2) { $this->manyToManyTables[$tableName] = $table; } else { // lower-casing is necessary because of Oracle Uppercase Tablenames, // assumption is lower-case + underscore separated. $className = $this->getClassNameForTable($tableName); $this->tables[$tableName] = $table; $this->classToTableNames[$className] = $tableName; } } } /** * {@inheritDoc} */ public function loadMetadataForClass($className, ClassMetadata $metadata) { $this->reverseEngineerMappingFromDatabase(); if (!isset($this->classToTableNames[$className])) { throw new \InvalidArgumentException("Unknown class " . $className); } $tableName = $this->classToTableNames[$className]; $metadata->name = $className; $metadata->table['name'] = $tableName; $columns = $this->tables[$tableName]->getColumns(); $indexes = $this->tables[$tableName]->getIndexes(); try { $primaryKeyColumns = $this->tables[$tableName]->getPrimaryKey()->getColumns(); } catch(SchemaException $e) { $primaryKeyColumns = array(); } if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) { $foreignKeys = $this->tables[$tableName]->getForeignKeys(); } else { $foreignKeys = array(); } $allForeignKeyColumns = array(); foreach ($foreignKeys as $foreignKey) { $allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns()); } $ids = array(); $fieldMappings = array(); foreach ($columns as $column) { $fieldMapping = array(); if (in_array($column->getName(), $allForeignKeyColumns)) { continue; } else if ($primaryKeyColumns && in_array($column->getName(), $primaryKeyColumns)) { $fieldMapping['id'] = true; } $fieldMapping['fieldName'] = $this->getFieldNameForColumn($tableName, $column->getName(), false); $fieldMapping['columnName'] = $column->getName(); $fieldMapping['type'] = strtolower((string) $column->getType()); if ($column->getType() instanceof \Doctrine\DBAL\Types\StringType) { $fieldMapping['length'] = $column->getLength(); $fieldMapping['fixed'] = $column->getFixed(); } else if ($column->getType() instanceof \Doctrine\DBAL\Types\IntegerType) { $fieldMapping['unsigned'] = $column->getUnsigned(); } $fieldMapping['nullable'] = $column->getNotNull() ? false : true; if (isset($fieldMapping['id'])) { $ids[] = $fieldMapping; } else { $fieldMappings[] = $fieldMapping; } } if ($ids) { if (count($ids) == 1) { $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO); } foreach ($ids as $id) { $metadata->mapField($id); } } foreach ($fieldMappings as $fieldMapping) { $metadata->mapField($fieldMapping); } foreach ($this->manyToManyTables as $manyTable) { foreach ($manyTable->getForeignKeys() as $foreignKey) { // foreign key maps to the table of the current entity, many to many association probably exists if (strtolower($tableName) == strtolower($foreignKey->getForeignTableName())) { $myFk = $foreignKey; $otherFk = null; foreach ($manyTable->getForeignKeys() as $foreignKey) { if ($foreignKey != $myFk) { $otherFk = $foreignKey; break; } } if (!$otherFk) { // the definition of this many to many table does not contain // enough foreign key information to continue reverse engeneering. continue; } $localColumn = current($myFk->getColumns()); $associationMapping = array(); $associationMapping['fieldName'] = $this->getFieldNameForColumn($manyTable->getName(), current($otherFk->getColumns()), true); $associationMapping['targetEntity'] = $this->getClassNameForTable($otherFk->getForeignTableName()); if (current($manyTable->getColumns())->getName() == $localColumn) { $associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true); $associationMapping['joinTable'] = array( 'name' => strtolower($manyTable->getName()), 'joinColumns' => array(), 'inverseJoinColumns' => array(), ); $fkCols = $myFk->getForeignColumns(); $cols = $myFk->getColumns(); for ($i = 0; $i < count($cols); $i++) { $associationMapping['joinTable']['joinColumns'][] = array( 'name' => $cols[$i], 'referencedColumnName' => $fkCols[$i], ); } $fkCols = $otherFk->getForeignColumns(); $cols = $otherFk->getColumns(); for ($i = 0; $i < count($cols); $i++) { $associationMapping['joinTable']['inverseJoinColumns'][] = array( 'name' => $cols[$i], 'referencedColumnName' => $fkCols[$i], ); } } else { $associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true); } $metadata->mapManyToMany($associationMapping); break; } } } foreach ($foreignKeys as $foreignKey) { $foreignTable = $foreignKey->getForeignTableName(); $cols = $foreignKey->getColumns(); $fkCols = $foreignKey->getForeignColumns(); $localColumn = current($cols); $associationMapping = array(); $associationMapping['fieldName'] = $this->getFieldNameForColumn($tableName, $localColumn, true); $associationMapping['targetEntity'] = $this->getClassNameForTable($foreignTable); if (isset($metadata->fieldMappings[$associationMapping['fieldName']])) { $associationMapping['fieldName'] = $associationMapping['fieldName'] . "2"; } if ($primaryKeyColumns && in_array($localColumn, $primaryKeyColumns)) { $associationMapping['id'] = true; } for ($i = 0; $i < count($cols); $i++) { $associationMapping['joinColumns'][] = array( 'name' => $cols[$i], 'referencedColumnName' => $fkCols[$i], ); } //Here we need to check if $cols are the same as $primaryKeyColums if (!array_diff($cols,$primaryKeyColumns)) { $metadata->mapOneToOne($associationMapping); } else { $metadata->mapManyToOne($associationMapping); } } } /** * {@inheritDoc} */ public function isTransient($className) { return true; } /** * {@inheritDoc} */ public function getAllClassNames() { $this->reverseEngineerMappingFromDatabase(); return array_keys($this->classToTableNames); } /** * Set class name for a table. * * @param string $tableName * @param string $className * @return void */ public function setClassNameForTable($tableName, $className) { $this->classNamesForTables[$tableName] = $className; } /** * Set field name for a column on a specific table. * * @param string $tableName * @param string $columnName * @param string $fieldName * @return void */ public function setFieldNameForColumn($tableName, $columnName, $fieldName) { $this->fieldNamesForColumns[$tableName][$columnName] = $fieldName; } /** * Return the mapped class name for a table if it exists. Otherwise return "classified" version. * * @param string $tableName * @return string */ private function getClassNameForTable($tableName) { if (isset($this->classNamesForTables[$tableName])) { return $this->namespace . $this->classNamesForTables[$tableName]; } return $this->namespace . Inflector::classify(strtolower($tableName)); } /** * Return the mapped field name for a column, if it exists. Otherwise return camelized version. * * @param string $tableName * @param string $columnName * @param boolean $fk Whether the column is a foreignkey or not. * @return string */ private function getFieldNameForColumn($tableName, $columnName, $fk = false) { if (isset($this->fieldNamesForColumns[$tableName]) && isset($this->fieldNamesForColumns[$tableName][$columnName])) { return $this->fieldNamesForColumns[$tableName][$columnName]; } $columnName = strtolower($columnName); // Replace _id if it is a foreignkey column if ($fk) { $columnName = str_replace('_id', '', $columnName); } return Inflector::camelize($columnName); } /** * Set the namespace for the generated entities. * * @param string $namespace * @return void */ public function setNamespace($namespace) { $this->namespace = $namespace; } } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php0000644000175100017510000000606112143374607025514 0ustar beberleibeberlei. */ require_once __DIR__.'/../Annotation.php'; require_once __DIR__.'/../Entity.php'; require_once __DIR__.'/../MappedSuperclass.php'; require_once __DIR__.'/../InheritanceType.php'; require_once __DIR__.'/../DiscriminatorColumn.php'; require_once __DIR__.'/../DiscriminatorMap.php'; require_once __DIR__.'/../Id.php'; require_once __DIR__.'/../GeneratedValue.php'; require_once __DIR__.'/../Version.php'; require_once __DIR__.'/../JoinColumn.php'; require_once __DIR__.'/../JoinColumns.php'; require_once __DIR__.'/../Column.php'; require_once __DIR__.'/../OneToOne.php'; require_once __DIR__.'/../OneToMany.php'; require_once __DIR__.'/../ManyToOne.php'; require_once __DIR__.'/../ManyToMany.php'; require_once __DIR__.'/../ElementCollection.php'; require_once __DIR__.'/../Table.php'; require_once __DIR__.'/../UniqueConstraint.php'; require_once __DIR__.'/../Index.php'; require_once __DIR__.'/../JoinTable.php'; require_once __DIR__.'/../SequenceGenerator.php'; require_once __DIR__.'/../CustomIdGenerator.php'; require_once __DIR__.'/../ChangeTrackingPolicy.php'; require_once __DIR__.'/../OrderBy.php'; require_once __DIR__.'/../NamedQueries.php'; require_once __DIR__.'/../NamedQuery.php'; require_once __DIR__.'/../HasLifecycleCallbacks.php'; require_once __DIR__.'/../PrePersist.php'; require_once __DIR__.'/../PostPersist.php'; require_once __DIR__.'/../PreUpdate.php'; require_once __DIR__.'/../PostUpdate.php'; require_once __DIR__.'/../PreRemove.php'; require_once __DIR__.'/../PostRemove.php'; require_once __DIR__.'/../PostLoad.php'; require_once __DIR__.'/../PreFlush.php'; require_once __DIR__.'/../FieldResult.php'; require_once __DIR__.'/../ColumnResult.php'; require_once __DIR__.'/../EntityResult.php'; require_once __DIR__.'/../NamedNativeQuery.php'; require_once __DIR__.'/../NamedNativeQueries.php'; require_once __DIR__.'/../SqlResultSetMapping.php'; require_once __DIR__.'/../SqlResultSetMappings.php'; require_once __DIR__.'/../AssociationOverride.php'; require_once __DIR__.'/../AssociationOverrides.php'; require_once __DIR__.'/../AttributeOverride.php'; require_once __DIR__.'/../AttributeOverrides.php'; DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Driver/DriverChain.php0000644000175100017510000000241312143374607023722 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain; /** * {@inheritDoc} * * @deprecated this driver will be removed. Use Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain instead */ class DriverChain extends MappingDriverChain { }DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Driver/PHPDriver.php0000644000175100017510000000240712143374607023332 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Common\Persistence\Mapping\Driver\PHPDriver as CommonPHPDriver; /** * {@inheritDoc} * * @deprecated this driver will be removed. Use Doctrine\Common\Persistence\Mapping\Driver\PHPDriver instead */ class PHPDriver extends CommonPHPDriver { }DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Driver/SimplifiedXmlDriver.php0000644000175100017510000000320512143374607025446 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Common\Persistence\Mapping\Driver\SymfonyFileLocator; /** * XmlDriver that additionally looks for mapping information in a global file. * * @author Fabien Potencier * @author Benjamin Eberlei * @license MIT */ class SimplifiedXmlDriver extends XmlDriver { const DEFAULT_FILE_EXTENSION = '.orm.xml'; /** * {@inheritDoc} */ public function __construct($prefixes, $fileExtension = self::DEFAULT_FILE_EXTENSION) { $locator = new SymfonyFileLocator((array) $prefixes, $fileExtension); parent::__construct($locator, $fileExtension); } } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Driver/SimplifiedYamlDriver.php0000644000175100017510000000321012143374607025604 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Common\Persistence\Mapping\Driver\SymfonyFileLocator; /** * YamlDriver that additionally looks for mapping information in a global file. * * @author Fabien Potencier * @author Benjamin Eberlei * @license MIT */ class SimplifiedYamlDriver extends YamlDriver { const DEFAULT_FILE_EXTENSION = '.orm.yml'; /** * {@inheritDoc} */ public function __construct($prefixes, $fileExtension = self::DEFAULT_FILE_EXTENSION) { $locator = new SymfonyFileLocator((array) $prefixes, $fileExtension); parent::__construct($locator, $fileExtension); } } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Driver/StaticPHPDriver.php0000644000175100017510000000244512143374607024504 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Common\Persistence\Mapping\Driver\StaticPHPDriver as CommonStaticPHPDriver; /** * {@inheritDoc} * * @deprecated this driver will be removed. Use Doctrine\Common\Persistence\Mapping\Driver\StaticPHPDriver instead */ class StaticPHPDriver extends CommonStaticPHPDriver { }DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Driver/XmlDriver.php0000644000175100017510000007347312143374607023456 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping\Driver; use SimpleXMLElement, Doctrine\Common\Persistence\Mapping\Driver\FileDriver, Doctrine\Common\Persistence\Mapping\ClassMetadata, Doctrine\ORM\Mapping\MappingException; /** * XmlDriver is a metadata driver that enables mapping through XML files. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan H. Wage * @author Roman Borschel */ class XmlDriver extends FileDriver { const DEFAULT_FILE_EXTENSION = '.dcm.xml'; /** * {@inheritDoc} */ public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION) { parent::__construct($locator, $fileExtension); } /** * {@inheritDoc} */ public function loadMetadataForClass($className, ClassMetadata $metadata) { /* @var $metadata \Doctrine\ORM\Mapping\ClassMetadataInfo */ /* @var $xmlRoot SimpleXMLElement */ $xmlRoot = $this->getElement($className); if ($xmlRoot->getName() == 'entity') { if (isset($xmlRoot['repository-class'])) { $metadata->setCustomRepositoryClass((string)$xmlRoot['repository-class']); } if (isset($xmlRoot['read-only']) && $this->evaluateBoolean($xmlRoot['read-only'])) { $metadata->markReadOnly(); } } else if ($xmlRoot->getName() == 'mapped-superclass') { $metadata->setCustomRepositoryClass( isset($xmlRoot['repository-class']) ? (string)$xmlRoot['repository-class'] : null ); $metadata->isMappedSuperclass = true; } else { throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); } // Evaluate attributes $table = array(); if (isset($xmlRoot['table'])) { $table['name'] = (string)$xmlRoot['table']; } $metadata->setPrimaryTable($table); // Evaluate named queries if (isset($xmlRoot->{'named-queries'})) { foreach ($xmlRoot->{'named-queries'}->{'named-query'} as $namedQueryElement) { $metadata->addNamedQuery(array( 'name' => (string)$namedQueryElement['name'], 'query' => (string)$namedQueryElement['query'] )); } } // Evaluate native named queries if (isset($xmlRoot->{'named-native-queries'})) { foreach ($xmlRoot->{'named-native-queries'}->{'named-native-query'} as $nativeQueryElement) { $metadata->addNamedNativeQuery(array( 'name' => isset($nativeQueryElement['name']) ? (string)$nativeQueryElement['name'] : null, 'query' => isset($nativeQueryElement->query) ? (string)$nativeQueryElement->query : null, 'resultClass' => isset($nativeQueryElement['result-class']) ? (string)$nativeQueryElement['result-class'] : null, 'resultSetMapping' => isset($nativeQueryElement['result-set-mapping']) ? (string)$nativeQueryElement['result-set-mapping'] : null, )); } } // Evaluate sql result set mapping if (isset($xmlRoot->{'sql-result-set-mappings'})) { foreach ($xmlRoot->{'sql-result-set-mappings'}->{'sql-result-set-mapping'} as $rsmElement) { $entities = array(); $columns = array(); foreach ($rsmElement as $entityElement) { // if (isset($entityElement['entity-class'])) { $entityResult = array( 'fields' => array(), 'entityClass' => (string)$entityElement['entity-class'], 'discriminatorColumn' => isset($entityElement['discriminator-column']) ? (string)$entityElement['discriminator-column'] : null, ); foreach ($entityElement as $fieldElement) { $entityResult['fields'][] = array( 'name' => isset($fieldElement['name']) ? (string)$fieldElement['name'] : null, 'column' => isset($fieldElement['column']) ? (string)$fieldElement['column'] : null, ); } $entities[] = $entityResult; } // if (isset($entityElement['name'])) { $columns[] = array( 'name' => (string)$entityElement['name'], ); } } $metadata->addSqlResultSetMapping(array( 'name' => (string)$rsmElement['name'], 'entities' => $entities, 'columns' => $columns )); } } /* not implemented specially anyway. use table = schema.table if (isset($xmlRoot['schema'])) { $metadata->table['schema'] = (string)$xmlRoot['schema']; }*/ if (isset($xmlRoot['inheritance-type'])) { $inheritanceType = (string)$xmlRoot['inheritance-type']; $metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceType)); if ($metadata->inheritanceType != \Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_NONE) { // Evaluate if (isset($xmlRoot->{'discriminator-column'})) { $discrColumn = $xmlRoot->{'discriminator-column'}; $metadata->setDiscriminatorColumn(array( 'name' => isset($discrColumn['name']) ? (string)$discrColumn['name'] : null, 'type' => isset($discrColumn['type']) ? (string)$discrColumn['type'] : null, 'length' => isset($discrColumn['length']) ? (string)$discrColumn['length'] : null, 'columnDefinition' => isset($discrColumn['column-definition']) ? (string)$discrColumn['column-definition'] : null )); } else { $metadata->setDiscriminatorColumn(array('name' => 'dtype', 'type' => 'string', 'length' => 255)); } // Evaluate if (isset($xmlRoot->{'discriminator-map'})) { $map = array(); foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} as $discrMapElement) { $map[(string)$discrMapElement['value']] = (string)$discrMapElement['class']; } $metadata->setDiscriminatorMap($map); } } } // Evaluate if (isset($xmlRoot['change-tracking-policy'])) { $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . strtoupper((string)$xmlRoot['change-tracking-policy']))); } // Evaluate if (isset($xmlRoot->indexes)) { $metadata->table['indexes'] = array(); foreach ($xmlRoot->indexes->index as $index) { $columns = explode(',', (string)$index['columns']); if (isset($index['name'])) { $metadata->table['indexes'][(string)$index['name']] = array( 'columns' => $columns ); } else { $metadata->table['indexes'][] = array( 'columns' => $columns ); } } } // Evaluate if (isset($xmlRoot->{'unique-constraints'})) { $metadata->table['uniqueConstraints'] = array(); foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} as $unique) { $columns = explode(',', (string)$unique['columns']); if (isset($unique['name'])) { $metadata->table['uniqueConstraints'][(string)$unique['name']] = array( 'columns' => $columns ); } else { $metadata->table['uniqueConstraints'][] = array( 'columns' => $columns ); } } } if (isset($xmlRoot->options)) { $metadata->table['options'] = $this->_parseOptions($xmlRoot->options->children()); } // The mapping assignement is done in 2 times as a bug might occurs on some php/xml lib versions // The internal SimpleXmlIterator get resetted, to this generate a duplicate field exception $mappings = array(); // Evaluate mappings if (isset($xmlRoot->field)) { foreach ($xmlRoot->field as $fieldMapping) { $mapping = $this->columnToArray($fieldMapping); if (isset($mapping['version'])) { $metadata->setVersionMapping($mapping); unset($mapping['version']); } $metadata->mapField($mapping); } } foreach ($mappings as $mapping) { if (isset($mapping['version'])) { $metadata->setVersionMapping($mapping); } $metadata->mapField($mapping); } // Evaluate mappings $associationIds = array(); foreach ($xmlRoot->id as $idElement) { if (isset($idElement['association-key']) && $this->evaluateBoolean($idElement['association-key'])) { $associationIds[(string)$idElement['name']] = true; continue; } $mapping = array( 'id' => true, 'fieldName' => (string)$idElement['name'] ); if (isset($idElement['type'])) { $mapping['type'] = (string)$idElement['type']; } if (isset($idElement['length'])) { $mapping['length'] = (string)$idElement['length']; } if (isset($idElement['column'])) { $mapping['columnName'] = (string)$idElement['column']; } if (isset($idElement['column-definition'])) { $mapping['columnDefinition'] = (string)$idElement['column-definition']; } $metadata->mapField($mapping); if (isset($idElement->generator)) { $strategy = isset($idElement->generator['strategy']) ? (string)$idElement->generator['strategy'] : 'AUTO'; $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $strategy)); } // Check for SequenceGenerator/TableGenerator definition if (isset($idElement->{'sequence-generator'})) { $seqGenerator = $idElement->{'sequence-generator'}; $metadata->setSequenceGeneratorDefinition(array( 'sequenceName' => (string)$seqGenerator['sequence-name'], 'allocationSize' => (string)$seqGenerator['allocation-size'], 'initialValue' => (string)$seqGenerator['initial-value'] )); } else if (isset($idElement->{'custom-id-generator'})) { $customGenerator = $idElement->{'custom-id-generator'}; $metadata->setCustomGeneratorDefinition(array( 'class' => (string) $customGenerator['class'] )); } else if (isset($idElement->{'table-generator'})) { throw MappingException::tableIdGeneratorNotImplemented($className); } } // Evaluate mappings if (isset($xmlRoot->{'one-to-one'})) { foreach ($xmlRoot->{'one-to-one'} as $oneToOneElement) { $mapping = array( 'fieldName' => (string)$oneToOneElement['field'], 'targetEntity' => (string)$oneToOneElement['target-entity'] ); if (isset($associationIds[$mapping['fieldName']])) { $mapping['id'] = true; } if (isset($oneToOneElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$oneToOneElement['fetch']); } if (isset($oneToOneElement['mapped-by'])) { $mapping['mappedBy'] = (string)$oneToOneElement['mapped-by']; } else { if (isset($oneToOneElement['inversed-by'])) { $mapping['inversedBy'] = (string)$oneToOneElement['inversed-by']; } $joinColumns = array(); if (isset($oneToOneElement->{'join-column'})) { $joinColumns[] = $this->joinColumnToArray($oneToOneElement->{'join-column'}); } else if (isset($oneToOneElement->{'join-columns'})) { foreach ($oneToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) { $joinColumns[] = $this->joinColumnToArray($joinColumnElement); } } $mapping['joinColumns'] = $joinColumns; } if (isset($oneToOneElement->cascade)) { $mapping['cascade'] = $this->_getCascadeMappings($oneToOneElement->cascade); } if (isset($oneToOneElement['orphan-removal'])) { $mapping['orphanRemoval'] = $this->evaluateBoolean($oneToOneElement['orphan-removal']); } $metadata->mapOneToOne($mapping); } } // Evaluate mappings if (isset($xmlRoot->{'one-to-many'})) { foreach ($xmlRoot->{'one-to-many'} as $oneToManyElement) { $mapping = array( 'fieldName' => (string)$oneToManyElement['field'], 'targetEntity' => (string)$oneToManyElement['target-entity'], 'mappedBy' => (string)$oneToManyElement['mapped-by'] ); if (isset($oneToManyElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$oneToManyElement['fetch']); } if (isset($oneToManyElement->cascade)) { $mapping['cascade'] = $this->_getCascadeMappings($oneToManyElement->cascade); } if (isset($oneToManyElement['orphan-removal'])) { $mapping['orphanRemoval'] = $this->evaluateBoolean($oneToManyElement['orphan-removal']); } if (isset($oneToManyElement->{'order-by'})) { $orderBy = array(); foreach ($oneToManyElement->{'order-by'}->{'order-by-field'} as $orderByField) { $orderBy[(string)$orderByField['name']] = (string)$orderByField['direction']; } $mapping['orderBy'] = $orderBy; } if (isset($oneToManyElement['index-by'])) { $mapping['indexBy'] = (string)$oneToManyElement['index-by']; } else if (isset($oneToManyElement->{'index-by'})) { throw new \InvalidArgumentException(" is not a valid tag"); } $metadata->mapOneToMany($mapping); } } // Evaluate mappings if (isset($xmlRoot->{'many-to-one'})) { foreach ($xmlRoot->{'many-to-one'} as $manyToOneElement) { $mapping = array( 'fieldName' => (string)$manyToOneElement['field'], 'targetEntity' => (string)$manyToOneElement['target-entity'] ); if (isset($associationIds[$mapping['fieldName']])) { $mapping['id'] = true; } if (isset($manyToOneElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$manyToOneElement['fetch']); } if (isset($manyToOneElement['inversed-by'])) { $mapping['inversedBy'] = (string)$manyToOneElement['inversed-by']; } $joinColumns = array(); if (isset($manyToOneElement->{'join-column'})) { $joinColumns[] = $this->joinColumnToArray($manyToOneElement->{'join-column'}); } else if (isset($manyToOneElement->{'join-columns'})) { foreach ($manyToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) { $joinColumns[] = $this->joinColumnToArray($joinColumnElement); } } $mapping['joinColumns'] = $joinColumns; if (isset($manyToOneElement->cascade)) { $mapping['cascade'] = $this->_getCascadeMappings($manyToOneElement->cascade); } $metadata->mapManyToOne($mapping); } } // Evaluate mappings if (isset($xmlRoot->{'many-to-many'})) { foreach ($xmlRoot->{'many-to-many'} as $manyToManyElement) { $mapping = array( 'fieldName' => (string)$manyToManyElement['field'], 'targetEntity' => (string)$manyToManyElement['target-entity'] ); if (isset($manyToManyElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$manyToManyElement['fetch']); } if (isset($manyToManyElement['orphan-removal'])) { $mapping['orphanRemoval'] = $this->evaluateBoolean($manyToManyElement['orphan-removal']); } if (isset($manyToManyElement['mapped-by'])) { $mapping['mappedBy'] = (string)$manyToManyElement['mapped-by']; } else if (isset($manyToManyElement->{'join-table'})) { if (isset($manyToManyElement['inversed-by'])) { $mapping['inversedBy'] = (string)$manyToManyElement['inversed-by']; } $joinTableElement = $manyToManyElement->{'join-table'}; $joinTable = array( 'name' => (string)$joinTableElement['name'] ); if (isset($joinTableElement['schema'])) { $joinTable['schema'] = (string)$joinTableElement['schema']; } foreach ($joinTableElement->{'join-columns'}->{'join-column'} as $joinColumnElement) { $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement); } foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} as $joinColumnElement) { $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement); } $mapping['joinTable'] = $joinTable; } if (isset($manyToManyElement->cascade)) { $mapping['cascade'] = $this->_getCascadeMappings($manyToManyElement->cascade); } if (isset($manyToManyElement->{'order-by'})) { $orderBy = array(); foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} as $orderByField) { $orderBy[(string)$orderByField['name']] = (string)$orderByField['direction']; } $mapping['orderBy'] = $orderBy; } if (isset($manyToManyElement['index-by'])) { $mapping['indexBy'] = (string)$manyToManyElement['index-by']; } else if (isset($manyToManyElement->{'index-by'})) { throw new \InvalidArgumentException(" is not a valid tag"); } $metadata->mapManyToMany($mapping); } } // Evaluate association-overrides if (isset($xmlRoot->{'attribute-overrides'})) { foreach ($xmlRoot->{'attribute-overrides'}->{'attribute-override'} as $overrideElement) { $fieldName = (string) $overrideElement['name']; foreach ($overrideElement->field as $field) { $mapping = $this->columnToArray($field); $mapping['fieldName'] = $fieldName; $metadata->setAttributeOverride($fieldName, $mapping); } } } // Evaluate association-overrides if (isset($xmlRoot->{'association-overrides'})) { foreach ($xmlRoot->{'association-overrides'}->{'association-override'} as $overrideElement) { $fieldName = (string) $overrideElement['name']; $override = array(); // Check for join-columns if (isset($overrideElement->{'join-columns'})) { $joinColumns = array(); foreach ($overrideElement->{'join-columns'}->{'join-column'} as $joinColumnElement) { $joinColumns[] = $this->joinColumnToArray($joinColumnElement); } $override['joinColumns'] = $joinColumns; } // Check for join-table if ($overrideElement->{'join-table'}) { $joinTable = null; $joinTableElement = $overrideElement->{'join-table'}; $joinTable = array( 'name' => (string) $joinTableElement['name'], 'schema' => (string) $joinTableElement['schema'] ); if (isset($joinTableElement->{'join-columns'})) { foreach ($joinTableElement->{'join-columns'}->{'join-column'} as $joinColumnElement) { $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement); } } if (isset($joinTableElement->{'inverse-join-columns'})) { foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} as $joinColumnElement) { $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement); } } $override['joinTable'] = $joinTable; } $metadata->setAssociationOverride($fieldName, $override); } } // Evaluate if (isset($xmlRoot->{'lifecycle-callbacks'})) { foreach ($xmlRoot->{'lifecycle-callbacks'}->{'lifecycle-callback'} as $lifecycleCallback) { $metadata->addLifecycleCallback((string)$lifecycleCallback['method'], constant('Doctrine\ORM\Events::' . (string)$lifecycleCallback['type'])); } } } /** * Parses (nested) option elements. * * @param SimpleXMLElement $options the XML element. * @return array The options array. */ private function _parseOptions(SimpleXMLElement $options) { $array = array(); /* @var $option SimpleXMLElement */ foreach ($options as $option) { if ($option->count()) { $value = $this->_parseOptions($option->children()); } else { $value = (string) $option; } $attr = $option->attributes(); if (isset($attr->name)) { $array[(string) $attr->name] = $value; } else { $array[] = $value; } } return $array; } /** * Constructs a joinColumn mapping array based on the information * found in the given SimpleXMLElement. * * @param SimpleXMLElement $joinColumnElement the XML element. * @return array The mapping array. */ private function joinColumnToArray(SimpleXMLElement $joinColumnElement) { $joinColumn = array( 'name' => (string)$joinColumnElement['name'], 'referencedColumnName' => (string)$joinColumnElement['referenced-column-name'] ); if (isset($joinColumnElement['unique'])) { $joinColumn['unique'] = $this->evaluateBoolean($joinColumnElement['unique']); } if (isset($joinColumnElement['nullable'])) { $joinColumn['nullable'] = $this->evaluateBoolean($joinColumnElement['nullable']); } if (isset($joinColumnElement['on-delete'])) { $joinColumn['onDelete'] = (string)$joinColumnElement['on-delete']; } if (isset($joinColumnElement['column-definition'])) { $joinColumn['columnDefinition'] = (string)$joinColumnElement['column-definition']; } return $joinColumn; } /** * Parse the given field as array * * @param SimpleXMLElement $fieldMapping * @return array */ private function columnToArray(SimpleXMLElement $fieldMapping) { $mapping = array( 'fieldName' => (string) $fieldMapping['name'], ); if (isset($fieldMapping['type'])) { $mapping['type'] = (string) $fieldMapping['type']; } if (isset($fieldMapping['column'])) { $mapping['columnName'] = (string) $fieldMapping['column']; } if (isset($fieldMapping['length'])) { $mapping['length'] = (int) $fieldMapping['length']; } if (isset($fieldMapping['precision'])) { $mapping['precision'] = (int) $fieldMapping['precision']; } if (isset($fieldMapping['scale'])) { $mapping['scale'] = (int) $fieldMapping['scale']; } if (isset($fieldMapping['unique'])) { $mapping['unique'] = $this->evaluateBoolean($fieldMapping['unique']); } if (isset($fieldMapping['nullable'])) { $mapping['nullable'] = $this->evaluateBoolean($fieldMapping['nullable']); } if (isset($fieldMapping['version']) && $fieldMapping['version']) { $mapping['version'] = $this->evaluateBoolean($fieldMapping['version']); } if (isset($fieldMapping['column-definition'])) { $mapping['columnDefinition'] = (string) $fieldMapping['column-definition']; } if (isset($fieldMapping->options)) { $mapping['options'] = $this->_parseOptions($fieldMapping->options->children()); } return $mapping; } /** * Gathers a list of cascade options found in the given cascade element. * * @param SimpleXMLElement $cascadeElement the cascade element. * @return array The list of cascade options. */ private function _getCascadeMappings($cascadeElement) { $cascades = array(); /* @var $action SimpleXmlElement */ foreach ($cascadeElement->children() as $action) { // According to the JPA specifications, XML uses "cascade-persist" // instead of "persist". Here, both variations // are supported because both YAML and Annotation use "persist" // and we want to make sure that this driver doesn't need to know // anything about the supported cascading actions $cascades[] = str_replace('cascade-', '', $action->getName()); } return $cascades; } /** * {@inheritDoc} */ protected function loadMappingFile($file) { $result = array(); $xmlElement = simplexml_load_file($file); if (isset($xmlElement->entity)) { foreach ($xmlElement->entity as $entityElement) { $entityName = (string)$entityElement['name']; $result[$entityName] = $entityElement; } } else if (isset($xmlElement->{'mapped-superclass'})) { foreach ($xmlElement->{'mapped-superclass'} as $mappedSuperClass) { $className = (string)$mappedSuperClass['name']; $result[$className] = $mappedSuperClass; } } return $result; } protected function evaluateBoolean($element) { $flag = (string)$element; return ($flag === true || $flag == "true" || $flag == "1"); } } DoctrineORM-2.3.3/Doctrine/ORM/Mapping/Driver/YamlDriver.php0000644000175100017510000006537112143374607023616 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Common\Persistence\Mapping\ClassMetadata, Doctrine\Common\Persistence\Mapping\Driver\FileDriver, Doctrine\ORM\Mapping\MappingException, Symfony\Component\Yaml\Yaml; /** * The YamlDriver reads the mapping metadata from yaml schema files. * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan H. Wage * @author Roman Borschel */ class YamlDriver extends FileDriver { const DEFAULT_FILE_EXTENSION = '.dcm.yml'; /** * {@inheritDoc} */ public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION) { parent::__construct($locator, $fileExtension); } /** * {@inheritDoc} */ public function loadMetadataForClass($className, ClassMetadata $metadata) { /* @var $metadata \Doctrine\ORM\Mapping\ClassMetadataInfo */ $element = $this->getElement($className); if ($element['type'] == 'entity') { if (isset($element['repositoryClass'])) { $metadata->setCustomRepositoryClass($element['repositoryClass']); } if (isset($element['readOnly']) && $element['readOnly'] == true) { $metadata->markReadOnly(); } } else if ($element['type'] == 'mappedSuperclass') { $metadata->setCustomRepositoryClass( isset($element['repositoryClass']) ? $element['repositoryClass'] : null ); $metadata->isMappedSuperclass = true; } else { throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); } // Evaluate root level properties $table = array(); if (isset($element['table'])) { $table['name'] = $element['table']; } $metadata->setPrimaryTable($table); // Evaluate named queries if (isset($element['namedQueries'])) { foreach ($element['namedQueries'] as $name => $queryMapping) { if (is_string($queryMapping)) { $queryMapping = array('query' => $queryMapping); } if ( ! isset($queryMapping['name'])) { $queryMapping['name'] = $name; } $metadata->addNamedQuery($queryMapping); } } // Evaluate named native queries if (isset($element['namedNativeQueries'])) { foreach ($element['namedNativeQueries'] as $name => $mappingElement) { if (!isset($mappingElement['name'])) { $mappingElement['name'] = $name; } $metadata->addNamedNativeQuery(array( 'name' => $mappingElement['name'], 'query' => isset($mappingElement['query']) ? $mappingElement['query'] : null, 'resultClass' => isset($mappingElement['resultClass']) ? $mappingElement['resultClass'] : null, 'resultSetMapping' => isset($mappingElement['resultSetMapping']) ? $mappingElement['resultSetMapping'] : null, )); } } // Evaluate sql result set mappings if (isset($element['sqlResultSetMappings'])) { foreach ($element['sqlResultSetMappings'] as $name => $resultSetMapping) { if (!isset($resultSetMapping['name'])) { $resultSetMapping['name'] = $name; } $entities = array(); $columns = array(); if (isset($resultSetMapping['entityResult'])) { foreach ($resultSetMapping['entityResult'] as $entityResultElement) { $entityResult = array( 'fields' => array(), 'entityClass' => isset($entityResultElement['entityClass']) ? $entityResultElement['entityClass'] : null, 'discriminatorColumn' => isset($entityResultElement['discriminatorColumn']) ? $entityResultElement['discriminatorColumn'] : null, ); if (isset($entityResultElement['fieldResult'])) { foreach ($entityResultElement['fieldResult'] as $fieldResultElement) { $entityResult['fields'][] = array( 'name' => isset($fieldResultElement['name']) ? $fieldResultElement['name'] : null, 'column' => isset($fieldResultElement['column']) ? $fieldResultElement['column'] : null, ); } } $entities[] = $entityResult; } } if (isset($resultSetMapping['columnResult'])) { foreach ($resultSetMapping['columnResult'] as $columnResultAnnot) { $columns[] = array( 'name' => isset($columnResultAnnot['name']) ? $columnResultAnnot['name'] : null, ); } } $metadata->addSqlResultSetMapping(array( 'name' => $resultSetMapping['name'], 'entities' => $entities, 'columns' => $columns )); } } /* not implemented specially anyway. use table = schema.table if (isset($element['schema'])) { $metadata->table['schema'] = $element['schema']; }*/ if (isset($element['inheritanceType'])) { $metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . strtoupper($element['inheritanceType']))); if ($metadata->inheritanceType != \Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_NONE) { // Evaluate discriminatorColumn if (isset($element['discriminatorColumn'])) { $discrColumn = $element['discriminatorColumn']; $metadata->setDiscriminatorColumn(array( 'name' => isset($discrColumn['name']) ? (string)$discrColumn['name'] : null, 'type' => isset($discrColumn['type']) ? (string)$discrColumn['type'] : null, 'length' => isset($discrColumn['length']) ? (string)$discrColumn['length'] : null, 'columnDefinition' => isset($discrColumn['columnDefinition']) ? (string)$discrColumn['columnDefinition'] : null )); } else { $metadata->setDiscriminatorColumn(array('name' => 'dtype', 'type' => 'string', 'length' => 255)); } // Evaluate discriminatorMap if (isset($element['discriminatorMap'])) { $metadata->setDiscriminatorMap($element['discriminatorMap']); } } } // Evaluate changeTrackingPolicy if (isset($element['changeTrackingPolicy'])) { $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . strtoupper($element['changeTrackingPolicy']))); } // Evaluate indexes if (isset($element['indexes'])) { foreach ($element['indexes'] as $name => $index) { if ( ! isset($index['name'])) { $index['name'] = $name; } if (is_string($index['columns'])) { $columns = explode(',', $index['columns']); $columns = array_map('trim', $columns); } else { $columns = $index['columns']; } $metadata->table['indexes'][$index['name']] = array( 'columns' => $columns ); } } // Evaluate uniqueConstraints if (isset($element['uniqueConstraints'])) { foreach ($element['uniqueConstraints'] as $name => $unique) { if ( ! isset($unique['name'])) { $unique['name'] = $name; } if (is_string($unique['columns'])) { $columns = explode(',', $unique['columns']); $columns = array_map('trim', $columns); } else { $columns = $unique['columns']; } $metadata->table['uniqueConstraints'][$unique['name']] = array( 'columns' => $columns ); } } if (isset($element['options'])) { $metadata->table['options'] = $element['options']; } $associationIds = array(); if (isset($element['id'])) { // Evaluate identifier settings foreach ($element['id'] as $name => $idElement) { if (isset($idElement['associationKey']) && $idElement['associationKey'] == true) { $associationIds[$name] = true; continue; } $mapping = array( 'id' => true, 'fieldName' => $name ); if (isset($idElement['type'])) { $mapping['type'] = $idElement['type']; } if (isset($idElement['column'])) { $mapping['columnName'] = $idElement['column']; } if (isset($idElement['length'])) { $mapping['length'] = $idElement['length']; } if (isset($idElement['columnDefinition'])) { $mapping['columnDefinition'] = $idElement['columnDefinition']; } $metadata->mapField($mapping); if (isset($idElement['generator'])) { $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . strtoupper($idElement['generator']['strategy']))); } // Check for SequenceGenerator/TableGenerator definition if (isset($idElement['sequenceGenerator'])) { $metadata->setSequenceGeneratorDefinition($idElement['sequenceGenerator']); } else if (isset($idElement['customIdGenerator'])) { $customGenerator = $idElement['customIdGenerator']; $metadata->setCustomGeneratorDefinition(array( 'class' => (string) $customGenerator['class'] )); } else if (isset($idElement['tableGenerator'])) { throw MappingException::tableIdGeneratorNotImplemented($className); } } } // Evaluate fields if (isset($element['fields'])) { foreach ($element['fields'] as $name => $fieldMapping) { $mapping = $this->columnToArray($name, $fieldMapping); if (isset($fieldMapping['id'])) { $mapping['id'] = true; if (isset($fieldMapping['generator']['strategy'])) { $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . strtoupper($fieldMapping['generator']['strategy']))); } } if (isset($mapping['version'])) { $metadata->setVersionMapping($mapping); unset($mapping['version']); } $metadata->mapField($mapping); } } // Evaluate oneToOne relationships if (isset($element['oneToOne'])) { foreach ($element['oneToOne'] as $name => $oneToOneElement) { $mapping = array( 'fieldName' => $name, 'targetEntity' => $oneToOneElement['targetEntity'] ); if (isset($associationIds[$mapping['fieldName']])) { $mapping['id'] = true; } if (isset($oneToOneElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToOneElement['fetch']); } if (isset($oneToOneElement['mappedBy'])) { $mapping['mappedBy'] = $oneToOneElement['mappedBy']; } else { if (isset($oneToOneElement['inversedBy'])) { $mapping['inversedBy'] = $oneToOneElement['inversedBy']; } $joinColumns = array(); if (isset($oneToOneElement['joinColumn'])) { $joinColumns[] = $this->joinColumnToArray($oneToOneElement['joinColumn']); } else if (isset($oneToOneElement['joinColumns'])) { foreach ($oneToOneElement['joinColumns'] as $joinColumnName => $joinColumnElement) { if ( ! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $joinColumnName; } $joinColumns[] = $this->joinColumnToArray($joinColumnElement); } } $mapping['joinColumns'] = $joinColumns; } if (isset($oneToOneElement['cascade'])) { $mapping['cascade'] = $oneToOneElement['cascade']; } if (isset($oneToOneElement['orphanRemoval'])) { $mapping['orphanRemoval'] = (bool)$oneToOneElement['orphanRemoval']; } $metadata->mapOneToOne($mapping); } } // Evaluate oneToMany relationships if (isset($element['oneToMany'])) { foreach ($element['oneToMany'] as $name => $oneToManyElement) { $mapping = array( 'fieldName' => $name, 'targetEntity' => $oneToManyElement['targetEntity'], 'mappedBy' => $oneToManyElement['mappedBy'] ); if (isset($oneToManyElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToManyElement['fetch']); } if (isset($oneToManyElement['cascade'])) { $mapping['cascade'] = $oneToManyElement['cascade']; } if (isset($oneToManyElement['orphanRemoval'])) { $mapping['orphanRemoval'] = (bool)$oneToManyElement['orphanRemoval']; } if (isset($oneToManyElement['orderBy'])) { $mapping['orderBy'] = $oneToManyElement['orderBy']; } if (isset($oneToManyElement['indexBy'])) { $mapping['indexBy'] = $oneToManyElement['indexBy']; } $metadata->mapOneToMany($mapping); } } // Evaluate manyToOne relationships if (isset($element['manyToOne'])) { foreach ($element['manyToOne'] as $name => $manyToOneElement) { $mapping = array( 'fieldName' => $name, 'targetEntity' => $manyToOneElement['targetEntity'] ); if (isset($associationIds[$mapping['fieldName']])) { $mapping['id'] = true; } if (isset($manyToOneElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToOneElement['fetch']); } if (isset($manyToOneElement['inversedBy'])) { $mapping['inversedBy'] = $manyToOneElement['inversedBy']; } $joinColumns = array(); if (isset($manyToOneElement['joinColumn'])) { $joinColumns[] = $this->joinColumnToArray($manyToOneElement['joinColumn']); } else if (isset($manyToOneElement['joinColumns'])) { foreach ($manyToOneElement['joinColumns'] as $joinColumnName => $joinColumnElement) { if ( ! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $joinColumnName; } $joinColumns[] = $this->joinColumnToArray($joinColumnElement); } } $mapping['joinColumns'] = $joinColumns; if (isset($manyToOneElement['cascade'])) { $mapping['cascade'] = $manyToOneElement['cascade']; } $metadata->mapManyToOne($mapping); } } // Evaluate manyToMany relationships if (isset($element['manyToMany'])) { foreach ($element['manyToMany'] as $name => $manyToManyElement) { $mapping = array( 'fieldName' => $name, 'targetEntity' => $manyToManyElement['targetEntity'] ); if (isset($manyToManyElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToManyElement['fetch']); } if (isset($manyToManyElement['mappedBy'])) { $mapping['mappedBy'] = $manyToManyElement['mappedBy']; } else if (isset($manyToManyElement['joinTable'])) { $joinTableElement = $manyToManyElement['joinTable']; $joinTable = array( 'name' => $joinTableElement['name'] ); if (isset($joinTableElement['schema'])) { $joinTable['schema'] = $joinTableElement['schema']; } foreach ($joinTableElement['joinColumns'] as $joinColumnName => $joinColumnElement) { if ( ! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $joinColumnName; } $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement); } foreach ($joinTableElement['inverseJoinColumns'] as $joinColumnName => $joinColumnElement) { if ( ! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $joinColumnName; } $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement); } $mapping['joinTable'] = $joinTable; } if (isset($manyToManyElement['inversedBy'])) { $mapping['inversedBy'] = $manyToManyElement['inversedBy']; } if (isset($manyToManyElement['cascade'])) { $mapping['cascade'] = $manyToManyElement['cascade']; } if (isset($manyToManyElement['orderBy'])) { $mapping['orderBy'] = $manyToManyElement['orderBy']; } if (isset($manyToManyElement['indexBy'])) { $mapping['indexBy'] = $manyToManyElement['indexBy']; } if (isset($manyToManyElement['orphanRemoval'])) { $mapping['orphanRemoval'] = (bool)$manyToManyElement['orphanRemoval']; } $metadata->mapManyToMany($mapping); } } // Evaluate associationOverride if (isset($element['associationOverride']) && is_array($element['associationOverride'])) { foreach ($element['associationOverride'] as $fieldName => $associationOverrideElement) { $override = array(); // Check for joinColumn if (isset($associationOverrideElement['joinColumn'])) { $joinColumns = array(); foreach ($associationOverrideElement['joinColumn'] as $name => $joinColumnElement) { if ( ! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $name; } $joinColumns[] = $this->joinColumnToArray($joinColumnElement); } $override['joinColumns'] = $joinColumns; } // Check for joinTable if (isset($associationOverrideElement['joinTable'])) { $joinTableElement = $associationOverrideElement['joinTable']; $joinTable = array( 'name' => $joinTableElement['name'] ); if (isset($joinTableElement['schema'])) { $joinTable['schema'] = $joinTableElement['schema']; } foreach ($joinTableElement['joinColumns'] as $name => $joinColumnElement) { if ( ! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $name; } $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement); } foreach ($joinTableElement['inverseJoinColumns'] as $name => $joinColumnElement) { if ( ! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $name; } $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement); } $override['joinTable'] = $joinTable; } $metadata->setAssociationOverride($fieldName, $override); } } // Evaluate associationOverride if (isset($element['attributeOverride']) && is_array($element['attributeOverride'])) { foreach ($element['attributeOverride'] as $fieldName => $attributeOverrideElement) { $mapping = $this->columnToArray($fieldName, $attributeOverrideElement); $metadata->setAttributeOverride($fieldName, $mapping); } } // Evaluate lifeCycleCallbacks if (isset($element['lifecycleCallbacks'])) { foreach ($element['lifecycleCallbacks'] as $type => $methods) { foreach ($methods as $method) { $metadata->addLifecycleCallback($method, constant('Doctrine\ORM\Events::' . $type)); } } } } /** * Constructs a joinColumn mapping array based on the information * found in the given join column element. * * @param array $joinColumnElement The array join column element * @return array The mapping array. */ private function joinColumnToArray($joinColumnElement) { $joinColumn = array(); if (isset($joinColumnElement['referencedColumnName'])) { $joinColumn['referencedColumnName'] = (string) $joinColumnElement['referencedColumnName']; } if (isset($joinColumnElement['name'])) { $joinColumn['name'] = (string) $joinColumnElement['name']; } if (isset($joinColumnElement['fieldName'])) { $joinColumn['fieldName'] = (string) $joinColumnElement['fieldName']; } if (isset($joinColumnElement['unique'])) { $joinColumn['unique'] = (bool) $joinColumnElement['unique']; } if (isset($joinColumnElement['nullable'])) { $joinColumn['nullable'] = (bool) $joinColumnElement['nullable']; } if (isset($joinColumnElement['onDelete'])) { $joinColumn['onDelete'] = $joinColumnElement['onDelete']; } if (isset($joinColumnElement['columnDefinition'])) { $joinColumn['columnDefinition'] = $joinColumnElement['columnDefinition']; } return $joinColumn; } /** * Parse the given column as array * * @param string $fieldName * @param array $column * @return array */ private function columnToArray($fieldName, $column) { $mapping = array( 'fieldName' => $fieldName ); if (isset($column['type'])) { $params = explode('(', $column['type']); $column['type'] = $params[0]; $mapping['type'] = $column['type']; if (isset($params[1])) { $column['length'] = (integer) substr($params[1], 0, strlen($params[1]) - 1); } } if (isset($column['column'])) { $mapping['columnName'] = $column['column']; } if (isset($column['length'])) { $mapping['length'] = $column['length']; } if (isset($column['precision'])) { $mapping['precision'] = $column['precision']; } if (isset($column['scale'])) { $mapping['scale'] = $column['scale']; } if (isset($column['unique'])) { $mapping['unique'] = (bool)$column['unique']; } if (isset($column['options'])) { $mapping['options'] = $column['options']; } if (isset($column['nullable'])) { $mapping['nullable'] = $column['nullable']; } if (isset($column['version']) && $column['version']) { $mapping['version'] = $column['version']; } if (isset($column['columnDefinition'])) { $mapping['columnDefinition'] = $column['columnDefinition']; } return $mapping; } /** * {@inheritDoc} */ protected function loadMappingFile($file) { return Yaml::parse($file); } } DoctrineORM-2.3.3/Doctrine/ORM/Persisters/AbstractCollectionPersister.php0000644000175100017510000001573212143374607026531 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Persisters; use Doctrine\ORM\EntityManager, Doctrine\ORM\PersistentCollection; /** * Base class for all collection persisters. * * @since 2.0 * @author Roman Borschel */ abstract class AbstractCollectionPersister { /** * @var EntityManager */ protected $_em; /** * @var \Doctrine\DBAL\Connection */ protected $_conn; /** * @var \Doctrine\ORM\UnitOfWork */ protected $_uow; /** * The database platform. * * @var \Doctrine\DBAL\Platforms\AbstractPlatform */ protected $platform; /** * The quote strategy. * * @var \Doctrine\ORM\Mapping\QuoteStrategy */ protected $quoteStrategy; /** * Initializes a new instance of a class derived from AbstractCollectionPersister. * * @param \Doctrine\ORM\EntityManager $em */ public function __construct(EntityManager $em) { $this->_em = $em; $this->_uow = $em->getUnitOfWork(); $this->_conn = $em->getConnection(); $this->platform = $this->_conn->getDatabasePlatform(); $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); } /** * Deletes the persistent state represented by the given collection. * * @param PersistentCollection $coll */ public function delete(PersistentCollection $coll) { $mapping = $coll->getMapping(); if ( ! $mapping['isOwningSide']) { return; // ignore inverse side } $sql = $this->_getDeleteSQL($coll); $this->_conn->executeUpdate($sql, $this->_getDeleteSQLParameters($coll)); } /** * Gets the SQL statement for deleting the given collection. * * @param PersistentCollection $coll */ abstract protected function _getDeleteSQL(PersistentCollection $coll); /** * Gets the SQL parameters for the corresponding SQL statement to delete * the given collection. * * @param PersistentCollection $coll */ abstract protected function _getDeleteSQLParameters(PersistentCollection $coll); /** * Updates the given collection, synchronizing it's state with the database * by inserting, updating and deleting individual elements. * * @param PersistentCollection $coll */ public function update(PersistentCollection $coll) { $mapping = $coll->getMapping(); if ( ! $mapping['isOwningSide']) { return; // ignore inverse side } $this->deleteRows($coll); //$this->updateRows($coll); $this->insertRows($coll); } public function deleteRows(PersistentCollection $coll) { $deleteDiff = $coll->getDeleteDiff(); $sql = $this->_getDeleteRowSQL($coll); foreach ($deleteDiff as $element) { $this->_conn->executeUpdate($sql, $this->_getDeleteRowSQLParameters($coll, $element)); } } //public function updateRows(PersistentCollection $coll) //{} public function insertRows(PersistentCollection $coll) { $insertDiff = $coll->getInsertDiff(); $sql = $this->_getInsertRowSQL($coll); foreach ($insertDiff as $element) { $this->_conn->executeUpdate($sql, $this->_getInsertRowSQLParameters($coll, $element)); } } public function count(PersistentCollection $coll) { throw new \BadMethodCallException("Counting the size of this persistent collection is not supported by this CollectionPersister."); } public function slice(PersistentCollection $coll, $offset, $length = null) { throw new \BadMethodCallException("Slicing elements is not supported by this CollectionPersister."); } public function contains(PersistentCollection $coll, $element) { throw new \BadMethodCallException("Checking for existance of an element is not supported by this CollectionPersister."); } public function containsKey(PersistentCollection $coll, $key) { throw new \BadMethodCallException("Checking for existance of a key is not supported by this CollectionPersister."); } public function removeElement(PersistentCollection $coll, $element) { throw new \BadMethodCallException("Removing an element is not supported by this CollectionPersister."); } public function removeKey(PersistentCollection $coll, $key) { throw new \BadMethodCallException("Removing a key is not supported by this CollectionPersister."); } public function get(PersistentCollection $coll, $index) { throw new \BadMethodCallException("Selecting a collection by index is not supported by this CollectionPersister."); } /** * Gets the SQL statement used for deleting a row from the collection. * * @param PersistentCollection $coll */ abstract protected function _getDeleteRowSQL(PersistentCollection $coll); /** * Gets the SQL parameters for the corresponding SQL statement to delete the given * element from the given collection. * * @param PersistentCollection $coll * @param mixed $element */ abstract protected function _getDeleteRowSQLParameters(PersistentCollection $coll, $element); /** * Gets the SQL statement used for updating a row in the collection. * * @param PersistentCollection $coll */ abstract protected function _getUpdateRowSQL(PersistentCollection $coll); /** * Gets the SQL statement used for inserting a row in the collection. * * @param PersistentCollection $coll */ abstract protected function _getInsertRowSQL(PersistentCollection $coll); /** * Gets the SQL parameters for the corresponding SQL statement to insert the given * element of the given collection into the database. * * @param PersistentCollection $coll * @param mixed $element */ abstract protected function _getInsertRowSQLParameters(PersistentCollection $coll, $element); } DoctrineORM-2.3.3/Doctrine/ORM/Persisters/AbstractEntityInheritancePersister.php0000644000175100017510000000630012143374607030053 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Persisters; use Doctrine\ORM\Mapping\ClassMetadata, Doctrine\DBAL\Types\Type; /** * Base class for entity persisters that implement a certain inheritance mapping strategy. * All these persisters are assumed to use a discriminator column to discriminate entity * types in the hierarchy. * * @author Roman Borschel * @author Benjamin Eberlei * @since 2.0 */ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister { /** * {@inheritdoc} */ protected function _prepareInsertData($entity) { $data = parent::_prepareInsertData($entity); // Populate the discriminator column $discColumn = $this->_class->discriminatorColumn; $this->_columnTypes[$discColumn['name']] = $discColumn['type']; $data[$this->_getDiscriminatorColumnTableName()][$discColumn['name']] = $this->_class->discriminatorValue; return $data; } /** * Gets the name of the table that contains the discriminator column. * * @return string The table name. */ abstract protected function _getDiscriminatorColumnTableName(); /** * {@inheritdoc} */ protected function _getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r') { $columnName = $class->columnNames[$field]; $sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) . '.' . $this->quoteStrategy->getColumnName($field, $class, $this->_platform); $columnAlias = $this->getSQLColumnAlias($columnName); $this->_rsm->addFieldResult($alias, $columnAlias, $field, $class->name); if (isset($class->fieldMappings[$field]['requireSQLConversion'])) { $type = Type::getType($class->getTypeOfField($field)); $sql = $type->convertToPHPValueSQL($sql, $this->_platform); } return $sql . ' AS ' . $columnAlias; } protected function getSelectJoinColumnSQL($tableAlias, $joinColumnName, $className) { $columnAlias = $this->getSQLColumnAlias($joinColumnName); $this->_rsm->addMetaResult('r', $columnAlias, $joinColumnName); return $tableAlias . '.' . $joinColumnName . ' AS ' . $columnAlias; } } DoctrineORM-2.3.3/Doctrine/ORM/Persisters/BasicEntityPersister.php0000644000175100017510000021070212143374607025162 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Persisters; use PDO; use Doctrine\DBAL\LockMode; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Connection; use Doctrine\ORM\ORMException; use Doctrine\ORM\OptimisticLockException; use Doctrine\ORM\EntityManager; use Doctrine\ORM\UnitOfWork; use Doctrine\ORM\Query; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Mapping\MappingException; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Events; use Doctrine\ORM\Event\LifecycleEventArgs; use Doctrine\Common\Util\ClassUtils; use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Collections\Expr\Comparison; /** * A BasicEntityPersiter maps an entity to a single table in a relational database. * * A persister is always responsible for a single entity type. * * EntityPersisters are used during a UnitOfWork to apply any changes to the persistent * state of entities onto a relational database when the UnitOfWork is committed, * as well as for basic querying of entities and their associations (not DQL). * * The persisting operations that are invoked during a commit of a UnitOfWork to * persist the persistent entity state are: * * - {@link addInsert} : To schedule an entity for insertion. * - {@link executeInserts} : To execute all scheduled insertions. * - {@link update} : To update the persistent state of an entity. * - {@link delete} : To delete the persistent state of an entity. * * As can be seen from the above list, insertions are batched and executed all at once * for increased efficiency. * * The querying operations invoked during a UnitOfWork, either through direct find * requests or lazy-loading, are the following: * * - {@link load} : Loads (the state of) a single, managed entity. * - {@link loadAll} : Loads multiple, managed entities. * - {@link loadOneToOneEntity} : Loads a one/many-to-one entity association (lazy-loading). * - {@link loadOneToManyCollection} : Loads a one-to-many entity association (lazy-loading). * - {@link loadManyToManyCollection} : Loads a many-to-many entity association (lazy-loading). * * The BasicEntityPersister implementation provides the default behavior for * persisting and querying entities that are mapped to a single database table. * * Subclasses can be created to provide custom persisting and querying strategies, * i.e. spanning multiple tables. * * @author Roman Borschel * @author Giorgio Sironi * @author Benjamin Eberlei * @author Alexander * @since 2.0 */ class BasicEntityPersister { /** * @var array */ static private $comparisonMap = array( Comparison::EQ => '= %s', Comparison::IS => 'IS %s', Comparison::NEQ => '!= %s', Comparison::GT => '> %s', Comparison::GTE => '>= %s', Comparison::LT => '< %s', Comparison::LTE => '<= %s', Comparison::IN => 'IN (%s)', Comparison::NIN => 'NOT IN (%s)', ); /** * Metadata object that describes the mapping of the mapped entity class. * * @var \Doctrine\ORM\Mapping\ClassMetadata */ protected $_class; /** * The underlying DBAL Connection of the used EntityManager. * * @var \Doctrine\DBAL\Connection $conn */ protected $_conn; /** * The database platform. * * @var \Doctrine\DBAL\Platforms\AbstractPlatform */ protected $_platform; /** * The EntityManager instance. * * @var \Doctrine\ORM\EntityManager */ protected $_em; /** * Queued inserts. * * @var array */ protected $_queuedInserts = array(); /** * ResultSetMapping that is used for all queries. Is generated lazily once per request. * * TODO: Evaluate Caching in combination with the other cached SQL snippets. * * @var Query\ResultSetMapping */ protected $_rsm; /** * The map of column names to DBAL mapping types of all prepared columns used * when INSERTing or UPDATEing an entity. * * @var array * @see _prepareInsertData($entity) * @see _prepareUpdateData($entity) */ protected $_columnTypes = array(); /** * The map of quoted column names. * * @var array * @see _prepareInsertData($entity) * @see _prepareUpdateData($entity) */ protected $quotedColumns = array(); /** * The INSERT SQL statement used for entities handled by this persister. * This SQL is only generated once per request, if at all. * * @var string */ private $_insertSql; /** * The SELECT column list SQL fragment used for querying entities by this persister. * This SQL fragment is only generated once per request, if at all. * * @var string */ protected $_selectColumnListSql; /** * The JOIN SQL fragement used to eagerly load all many-to-one and one-to-one * associations configured as FETCH_EAGER, aswell as all inverse one-to-one associations. * * @var string */ protected $_selectJoinSql; /** * Counter for creating unique SQL table and column aliases. * * @var integer */ protected $_sqlAliasCounter = 0; /** * Map from class names (FQCN) to the corresponding generated SQL table aliases. * * @var array */ protected $_sqlTableAliases = array(); /** * The quote strategy. * * @var \Doctrine\ORM\Mapping\QuoteStrategy */ protected $quoteStrategy; /** * Initializes a new BasicEntityPersister that uses the given EntityManager * and persists instances of the class described by the given ClassMetadata descriptor. * * @param \Doctrine\ORM\EntityManager $em * @param \Doctrine\ORM\Mapping\ClassMetadata $class */ public function __construct(EntityManager $em, ClassMetadata $class) { $this->_em = $em; $this->_class = $class; $this->_conn = $em->getConnection(); $this->_platform = $this->_conn->getDatabasePlatform(); $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); } /** * @return \Doctrine\ORM\Mapping\ClassMetadata */ public function getClassMetadata() { return $this->_class; } /** * Adds an entity to the queued insertions. * The entity remains queued until {@link executeInserts} is invoked. * * @param object $entity The entity to queue for insertion. */ public function addInsert($entity) { $this->_queuedInserts[spl_object_hash($entity)] = $entity; } /** * Executes all queued entity insertions and returns any generated post-insert * identifiers that were created as a result of the insertions. * * If no inserts are queued, invoking this method is a NOOP. * * @return array An array of any generated post-insert IDs. This will be an empty array * if the entity class does not use the IDENTITY generation strategy. */ public function executeInserts() { if ( ! $this->_queuedInserts) { return; } $postInsertIds = array(); $idGen = $this->_class->idGenerator; $isPostInsertId = $idGen->isPostInsertGenerator(); $stmt = $this->_conn->prepare($this->_getInsertSQL()); $tableName = $this->_class->getTableName(); foreach ($this->_queuedInserts as $entity) { $insertData = $this->_prepareInsertData($entity); if (isset($insertData[$tableName])) { $paramIndex = 1; foreach ($insertData[$tableName] as $column => $value) { $stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$column]); } } $stmt->execute(); if ($isPostInsertId) { $id = $idGen->generate($this->_em, $entity); $postInsertIds[$id] = $entity; } else { $id = $this->_class->getIdentifierValues($entity); } if ($this->_class->isVersioned) { $this->assignDefaultVersionValue($entity, $id); } } $stmt->closeCursor(); $this->_queuedInserts = array(); return $postInsertIds; } /** * Retrieves the default version value which was created * by the preceding INSERT statement and assigns it back in to the * entities version field. * * @param object $entity * @param mixed $id */ protected function assignDefaultVersionValue($entity, $id) { $value = $this->fetchVersionValue($this->_class, $id); $this->_class->setFieldValue($entity, $this->_class->versionField, $value); } /** * Fetch the current version value of a versioned entity. * * @param \Doctrine\ORM\Mapping\ClassMetadata $versionedClass * @param mixed $id * @return mixed */ protected function fetchVersionValue($versionedClass, $id) { $versionField = $versionedClass->versionField; $identifier = $this->quoteStrategy->getIdentifierColumnNames($versionedClass, $this->_platform); $versionFieldColumnName = $this->quoteStrategy->getColumnName($versionField, $versionedClass, $this->_platform); //FIXME: Order with composite keys might not be correct $sql = 'SELECT ' . $versionFieldColumnName . ' FROM ' . $this->quoteStrategy->getTableName($versionedClass, $this->_platform) . ' WHERE ' . implode(' = ? AND ', $identifier) . ' = ?'; $value = $this->_conn->fetchColumn($sql, array_values((array)$id)); return Type::getType($versionedClass->fieldMappings[$versionField]['type'])->convertToPHPValue($value, $this->_platform); } /** * Updates a managed entity. The entity is updated according to its current changeset * in the running UnitOfWork. If there is no changeset, nothing is updated. * * The data to update is retrieved through {@link _prepareUpdateData}. * Subclasses that override this method are supposed to obtain the update data * in the same way, through {@link _prepareUpdateData}. * * Subclasses are also supposed to take care of versioning when overriding this method, * if necessary. The {@link _updateTable} method can be used to apply the data retrieved * from {@_prepareUpdateData} on the target tables, thereby optionally applying versioning. * * @param object $entity The entity to update. */ public function update($entity) { $updateData = $this->_prepareUpdateData($entity); $tableName = $this->_class->getTableName(); if (isset($updateData[$tableName]) && $updateData[$tableName]) { $this->_updateTable( $entity, $this->quoteStrategy->getTableName($this->_class, $this->_platform), $updateData[$tableName], $this->_class->isVersioned ); if ($this->_class->isVersioned) { $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); $this->assignDefaultVersionValue($entity, $id); } } } /** * Performs an UPDATE statement for an entity on a specific table. * The UPDATE can optionally be versioned, which requires the entity to have a version field. * * @param object $entity The entity object being updated. * @param string $quotedTableName The quoted name of the table to apply the UPDATE on. * @param array $updateData The map of columns to update (column => value). * @param boolean $versioned Whether the UPDATE should be versioned. */ protected final function _updateTable($entity, $quotedTableName, array $updateData, $versioned = false) { $set = $params = $types = array(); foreach ($updateData as $columnName => $value) { $column = $columnName; $placeholder = '?'; if (isset($this->_class->fieldNames[$columnName])) { $column = $this->quoteStrategy->getColumnName($this->_class->fieldNames[$columnName], $this->_class, $this->_platform); if (isset($this->_class->fieldMappings[$this->_class->fieldNames[$columnName]]['requireSQLConversion'])) { $type = Type::getType($this->_columnTypes[$columnName]); $placeholder = $type->convertToDatabaseValueSQL('?', $this->_platform); } } else if (isset($this->quotedColumns[$columnName])) { $column = $this->quotedColumns[$columnName]; } $set[] = $column . ' = ' . $placeholder; $params[] = $value; $types[] = $this->_columnTypes[$columnName]; } $where = array(); $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); foreach ($this->_class->identifier as $idField) { if (isset($this->_class->associationMappings[$idField])) { $targetMapping = $this->_em->getClassMetadata($this->_class->associationMappings[$idField]['targetEntity']); $where[] = $this->_class->associationMappings[$idField]['joinColumns'][0]['name']; $params[] = $id[$idField]; switch (true) { case (isset($targetMapping->fieldMappings[$targetMapping->identifier[0]])): $types[] = $targetMapping->fieldMappings[$targetMapping->identifier[0]]['type']; break; case (isset($targetMapping->associationMappings[$targetMapping->identifier[0]])): $types[] = $targetMapping->associationMappings[$targetMapping->identifier[0]]['type']; break; default: throw ORMException::unrecognizedField($targetMapping->identifier[0]); } } else { $where[] = $this->quoteStrategy->getColumnName($idField, $this->_class, $this->_platform); $params[] = $id[$idField]; $types[] = $this->_class->fieldMappings[$idField]['type']; } } if ($versioned) { $versionField = $this->_class->versionField; $versionFieldType = $this->_class->fieldMappings[$versionField]['type']; $versionColumn = $this->quoteStrategy->getColumnName($versionField, $this->_class, $this->_platform); if ($versionFieldType == Type::INTEGER) { $set[] = $versionColumn . ' = ' . $versionColumn . ' + 1'; } else if ($versionFieldType == Type::DATETIME) { $set[] = $versionColumn . ' = CURRENT_TIMESTAMP'; } $where[] = $versionColumn; $params[] = $this->_class->reflFields[$versionField]->getValue($entity); $types[] = $this->_class->fieldMappings[$versionField]['type']; } $sql = 'UPDATE ' . $quotedTableName . ' SET ' . implode(', ', $set) . ' WHERE ' . implode(' = ? AND ', $where) . ' = ?'; $result = $this->_conn->executeUpdate($sql, $params, $types); if ($versioned && ! $result) { throw OptimisticLockException::lockFailed($entity); } } /** * @todo Add check for platform if it supports foreign keys/cascading. * @param array $identifier * @return void */ protected function deleteJoinTableRecords($identifier) { foreach ($this->_class->associationMappings as $mapping) { if ($mapping['type'] == ClassMetadata::MANY_TO_MANY) { // @Todo this only covers scenarios with no inheritance or of the same level. Is there something // like self-referential relationship between different levels of an inheritance hierachy? I hope not! $selfReferential = ($mapping['targetEntity'] == $mapping['sourceEntity']); $otherKeys = array(); $keys = array(); if ( ! $mapping['isOwningSide']) { $relatedClass = $this->_em->getClassMetadata($mapping['targetEntity']); $mapping = $relatedClass->associationMappings[$mapping['mappedBy']]; foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) { $keys[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $relatedClass, $this->_platform); } if ($selfReferential) { foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { $otherKeys[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $relatedClass, $this->_platform); } } } else { foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { $keys[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->_class, $this->_platform); } if ($selfReferential) { foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) { $otherKeys[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->_class, $this->_platform); } } } if ( ! isset($mapping['isOnDeleteCascade'])) { $joinTableName = $this->quoteStrategy->getJoinTableName($mapping, $this->_class, $this->_platform); $this->_conn->delete($joinTableName, array_combine($keys, $identifier)); if ($selfReferential) { $this->_conn->delete($joinTableName, array_combine($otherKeys, $identifier)); } } } } } /** * Deletes a managed entity. * * The entity to delete must be managed and have a persistent identifier. * The deletion happens instantaneously. * * Subclasses may override this method to customize the semantics of entity deletion. * * @param object $entity The entity to delete. */ public function delete($entity) { $identifier = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); $this->deleteJoinTableRecords($identifier); $id = array_combine($this->quoteStrategy->getIdentifierColumnNames($this->_class, $this->_platform), $identifier); $this->_conn->delete($this->quoteStrategy->getTableName($this->_class, $this->_platform), $id); } /** * Prepares the changeset of an entity for database insertion (UPDATE). * * The changeset is obtained from the currently running UnitOfWork. * * During this preparation the array that is passed as the second parameter is filled with * => pairs, grouped by table name. * * Example: * * array( * 'foo_table' => array('column1' => 'value1', 'column2' => 'value2', ...), * 'bar_table' => array('columnX' => 'valueX', 'columnY' => 'valueY', ...), * ... * ) * * * @param object $entity The entity for which to prepare the data. * @return array The prepared data. */ protected function _prepareUpdateData($entity) { $result = array(); $uow = $this->_em->getUnitOfWork(); if (($versioned = $this->_class->isVersioned) != false) { $versionField = $this->_class->versionField; } foreach ($uow->getEntityChangeSet($entity) as $field => $change) { if ($versioned && $versionField == $field) { continue; } $newVal = $change[1]; if (isset($this->_class->associationMappings[$field])) { $assoc = $this->_class->associationMappings[$field]; // Only owning side of x-1 associations can have a FK column. if ( ! $assoc['isOwningSide'] || ! ($assoc['type'] & ClassMetadata::TO_ONE)) { continue; } if ($newVal !== null) { $oid = spl_object_hash($newVal); if (isset($this->_queuedInserts[$oid]) || $uow->isScheduledForInsert($newVal)) { // The associated entity $newVal is not yet persisted, so we must // set $newVal = null, in order to insert a null value and schedule an // extra update on the UnitOfWork. $uow->scheduleExtraUpdate($entity, array( $field => array(null, $newVal) )); $newVal = null; } } if ($newVal !== null) { $newValId = $uow->getEntityIdentifier($newVal); } $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); $owningTable = $this->getOwningTable($field); foreach ($assoc['joinColumns'] as $joinColumn) { $sourceColumn = $joinColumn['name']; $targetColumn = $joinColumn['referencedColumnName']; $quotedColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->_class, $this->_platform); $this->quotedColumns[$sourceColumn] = $quotedColumn; if ($newVal === null) { $result[$owningTable][$sourceColumn] = null; } else if ($targetClass->containsForeignIdentifier) { $result[$owningTable][$sourceColumn] = $newValId[$targetClass->getFieldForColumn($targetColumn)]; } else { $result[$owningTable][$sourceColumn] = $newValId[$targetClass->fieldNames[$targetColumn]]; } $this->_columnTypes[$sourceColumn] = $targetClass->getTypeOfColumn($targetColumn); } } else { $columnName = $this->_class->columnNames[$field]; $this->_columnTypes[$columnName] = $this->_class->fieldMappings[$field]['type']; $result[$this->getOwningTable($field)][$columnName] = $newVal; } } return $result; } /** * Prepares the data changeset of a managed entity for database insertion (initial INSERT). * The changeset of the entity is obtained from the currently running UnitOfWork. * * The default insert data preparation is the same as for updates. * * @param object $entity The entity for which to prepare the data. * @return array The prepared data for the tables to update. * @see _prepareUpdateData */ protected function _prepareInsertData($entity) { return $this->_prepareUpdateData($entity); } /** * Gets the name of the table that owns the column the given field is mapped to. * * The default implementation in BasicEntityPersister always returns the name * of the table the entity type of this persister is mapped to, since an entity * is always persisted to a single table with a BasicEntityPersister. * * @param string $fieldName The field name. * @return string The table name. */ public function getOwningTable($fieldName) { return $this->_class->getTableName(); } /** * Loads an entity by a list of field criteria. * * @param array $criteria The criteria by which to load the entity. * @param object $entity The entity to load the data into. If not specified, * a new entity is created. * @param $assoc The association that connects the entity to load to another entity, if any. * @param array $hints Hints for entity creation. * @param int $lockMode * @param int $limit Limit number of results * @param array $orderBy Criteria to order by * @return object The loaded and managed entity instance or NULL if the entity can not be found. * @todo Check identity map? loadById method? Try to guess whether $criteria is the id? */ public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = 0, $limit = null, array $orderBy = null) { $sql = $this->_getSelectEntitiesSQL($criteria, $assoc, $lockMode, $limit, null, $orderBy); list($params, $types) = $this->expandParameters($criteria); $stmt = $this->_conn->executeQuery($sql, $params, $types); if ($entity !== null) { $hints[Query::HINT_REFRESH] = true; $hints[Query::HINT_REFRESH_ENTITY] = $entity; } $hydrator = $this->_em->newHydrator($this->_selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); $entities = $hydrator->hydrateAll($stmt, $this->_rsm, $hints); return $entities ? $entities[0] : null; } /** * Loads an entity of this persister's mapped class as part of a single-valued * association from another entity. * * @param array $assoc The association to load. * @param object $sourceEntity The entity that owns the association (not necessarily the "owning side"). * @param array $identifier The identifier of the entity to load. Must be provided if * the association to load represents the owning side, otherwise * the identifier is derived from the $sourceEntity. * @return object The loaded and managed entity instance or NULL if the entity can not be found. */ public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = array()) { if (($foundEntity = $this->_em->getUnitOfWork()->tryGetById($identifier, $assoc['targetEntity'])) != false) { return $foundEntity; } $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); if ($assoc['isOwningSide']) { $isInverseSingleValued = $assoc['inversedBy'] && ! $targetClass->isCollectionValuedAssociation($assoc['inversedBy']); // Mark inverse side as fetched in the hints, otherwise the UoW would // try to load it in a separate query (remember: to-one inverse sides can not be lazy). $hints = array(); if ($isInverseSingleValued) { $hints['fetched']["r"][$assoc['inversedBy']] = true; } /* cascade read-only status if ($this->_em->getUnitOfWork()->isReadOnly($sourceEntity)) { $hints[Query::HINT_READ_ONLY] = true; } */ $targetEntity = $this->load($identifier, null, $assoc, $hints); // Complete bidirectional association, if necessary if ($targetEntity !== null && $isInverseSingleValued) { $targetClass->reflFields[$assoc['inversedBy']]->setValue($targetEntity, $sourceEntity); } } else { $sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']); $owningAssoc = $targetClass->getAssociationMapping($assoc['mappedBy']); // TRICKY: since the association is specular source and target are flipped foreach ($owningAssoc['targetToSourceKeyColumns'] as $sourceKeyColumn => $targetKeyColumn) { if ( ! isset($sourceClass->fieldNames[$sourceKeyColumn])) { throw MappingException::joinColumnMustPointToMappedField( $sourceClass->name, $sourceKeyColumn ); } // unset the old value and set the new sql aliased value here. By definition // unset($identifier[$targetKeyColumn] works here with how UnitOfWork::createEntity() calls this method. $identifier[$this->_getSQLTableAlias($targetClass->name) . "." . $targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); unset($identifier[$targetKeyColumn]); } $targetEntity = $this->load($identifier, null, $assoc); if ($targetEntity !== null) { $targetClass->setFieldValue($targetEntity, $assoc['mappedBy'], $sourceEntity); } } return $targetEntity; } /** * Refreshes a managed entity. * * @param array $id The identifier of the entity as an associative array from * column or field names to values. * @param object $entity The entity to refresh. */ public function refresh(array $id, $entity, $lockMode = 0) { $sql = $this->_getSelectEntitiesSQL($id, null, $lockMode); list($params, $types) = $this->expandParameters($id); $stmt = $this->_conn->executeQuery($sql, $params, $types); $hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT); $hydrator->hydrateAll($stmt, $this->_rsm, array(Query::HINT_REFRESH => true)); } /** * Load Entities matching the given Criteria object * * @param \Doctrine\Common\Collections\Criteria $criteria * * @return array */ public function loadCriteria(Criteria $criteria) { $orderBy = $criteria->getOrderings(); $limit = $criteria->getMaxResults(); $offset = $criteria->getFirstResult(); $sql = $this->_getSelectEntitiesSQL($criteria, null, 0, $limit, $offset, $orderBy); list($params, $types) = $this->expandCriteriaParameters($criteria); $stmt = $this->_conn->executeQuery($sql, $params, $types); $hydrator = $this->_em->newHydrator(($this->_selectJoinSql) ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); return $hydrator->hydrateAll($stmt, $this->_rsm, array('deferEagerLoads' => true)); } /** * Expand Criteria Parameters by walking the expressions and grabbing all * parameters and types from it. * * @param \Doctrine\Common\Collections\Criteria $criteria * * @return array(array(), array()) */ private function expandCriteriaParameters(Criteria $criteria) { $expression = $criteria->getWhereExpression(); if ($expression === null) { return array(array(), array()); } $valueVisitor = new SqlValueVisitor(); $valueVisitor->dispatch($expression); list($values, $types) = $valueVisitor->getParamsAndTypes(); $sqlValues = array(); foreach ($values as $value) { $sqlValues[] = $this->getValue($value); } $sqlTypes = array(); foreach ($types as $type) { list($field, $value) = $type; $sqlTypes[] = $this->getType($field, $value); } return array($sqlValues, $sqlTypes); } /** * Loads a list of entities by a list of field criteria. * * @param array $criteria * @param array $orderBy * @param int $limit * @param int $offset * @return array */ public function loadAll(array $criteria = array(), array $orderBy = null, $limit = null, $offset = null) { $sql = $this->_getSelectEntitiesSQL($criteria, null, 0, $limit, $offset, $orderBy); list($params, $types) = $this->expandParameters($criteria); $stmt = $this->_conn->executeQuery($sql, $params, $types); $hydrator = $this->_em->newHydrator(($this->_selectJoinSql) ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); return $hydrator->hydrateAll($stmt, $this->_rsm, array('deferEagerLoads' => true)); } /** * Get (sliced or full) elements of the given collection. * * @param array $assoc * @param object $sourceEntity * @param int|null $offset * @param int|null $limit * @return array */ public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) { $stmt = $this->getManyToManyStatement($assoc, $sourceEntity, $offset, $limit); return $this->loadArrayFromStatement($assoc, $stmt); } /** * Load an array of entities from a given dbal statement. * * @param array $assoc * @param \Doctrine\DBAL\Statement $stmt * * @return array */ private function loadArrayFromStatement($assoc, $stmt) { $hints = array('deferEagerLoads' => true); if (isset($assoc['indexBy'])) { $rsm = clone ($this->_rsm); // this is necessary because the "default rsm" should be changed. $rsm->addIndexBy('r', $assoc['indexBy']); } else { $rsm = $this->_rsm; } $hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT); return $hydrator->hydrateAll($stmt, $rsm, $hints); } /** * Hydrate a collection from a given dbal statement. * * @param array $assoc * @param \Doctrine\DBAL\Statement $stmt * @param PersistentCollection $coll * * @return array */ private function loadCollectionFromStatement($assoc, $stmt, $coll) { $hints = array('deferEagerLoads' => true, 'collection' => $coll); if (isset($assoc['indexBy'])) { $rsm = clone ($this->_rsm); // this is necessary because the "default rsm" should be changed. $rsm->addIndexBy('r', $assoc['indexBy']); } else { $rsm = $this->_rsm; } $hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT); return $hydrator->hydrateAll($stmt, $rsm, $hints); } /** * Loads a collection of entities of a many-to-many association. * * @param ManyToManyMapping $assoc The association mapping of the association being loaded. * @param object $sourceEntity The entity that owns the collection. * @param PersistentCollection $coll The collection to fill. * @param int|null $offset * @param int|null $limit * @return array */ public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { $stmt = $this->getManyToManyStatement($assoc, $sourceEntity); return $this->loadCollectionFromStatement($assoc, $stmt, $coll); } private function getManyToManyStatement(array $assoc, $sourceEntity, $offset = null, $limit = null) { $criteria = array(); $sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']); if ($assoc['isOwningSide']) { $quotedJoinTable = $this->quoteStrategy->getJoinTableName($assoc, $sourceClass, $this->_platform); foreach ($assoc['joinTable']['joinColumns'] as $joinColumn) { $relationKeyColumn = $joinColumn['name']; $sourceKeyColumn = $joinColumn['referencedColumnName']; $quotedKeyColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $sourceClass, $this->_platform); if ($sourceClass->containsForeignIdentifier) { $field = $sourceClass->getFieldForColumn($sourceKeyColumn); $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); if (isset($sourceClass->associationMappings[$field])) { $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; } $criteria[$quotedJoinTable . "." . $quotedKeyColumn] = $value; } else if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { $criteria[$quotedJoinTable . "." . $quotedKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); } else { throw MappingException::joinColumnMustPointToMappedField( $sourceClass->name, $sourceKeyColumn ); } } } else { $owningAssoc = $this->_em->getClassMetadata($assoc['targetEntity'])->associationMappings[$assoc['mappedBy']]; $quotedJoinTable = $this->quoteStrategy->getJoinTableName($owningAssoc, $sourceClass, $this->_platform); // TRICKY: since the association is inverted source and target are flipped foreach ($owningAssoc['joinTable']['inverseJoinColumns'] as $joinColumn) { $relationKeyColumn = $joinColumn['name']; $sourceKeyColumn = $joinColumn['referencedColumnName']; $quotedKeyColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $sourceClass, $this->_platform); if ($sourceClass->containsForeignIdentifier) { $field = $sourceClass->getFieldForColumn($sourceKeyColumn); $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); if (isset($sourceClass->associationMappings[$field])) { $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; } $criteria[$quotedJoinTable . "." . $quotedKeyColumn] = $value; } else if (isset($sourceClass->fieldNames[$sourceKeyColumn])) { $criteria[$quotedJoinTable . "." . $quotedKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); } else { throw MappingException::joinColumnMustPointToMappedField( $sourceClass->name, $sourceKeyColumn ); } } } $sql = $this->_getSelectEntitiesSQL($criteria, $assoc, 0, $limit, $offset); list($params, $types) = $this->expandParameters($criteria); return $this->_conn->executeQuery($sql, $params, $types); } /** * Gets the SELECT SQL to select one or more entities by a set of field criteria. * * @param array|\Doctrine\Common\Collections\Criteria $criteria * @param AssociationMapping $assoc * @param string $orderBy * @param int $lockMode * @param int $limit * @param int $offset * @param array $orderBy * @return string * @todo Refactor: _getSelectSQL(...) */ protected function _getSelectEntitiesSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null) { $joinSql = ($assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY) ? $this->_getSelectManyToManyJoinSQL($assoc) : ''; $conditionSql = ($criteria instanceof Criteria) ? $this->_getSelectConditionCriteriaSQL($criteria) : $this->_getSelectConditionSQL($criteria, $assoc); $orderBy = ($assoc !== null && isset($assoc['orderBy'])) ? $assoc['orderBy'] : $orderBy; $orderBySql = $orderBy ? $this->_getOrderBySQL($orderBy, $this->_getSQLTableAlias($this->_class->name)) : ''; $lockSql = ''; if ($lockMode == LockMode::PESSIMISTIC_READ) { $lockSql = ' ' . $this->_platform->getReadLockSql(); } else if ($lockMode == LockMode::PESSIMISTIC_WRITE) { $lockSql = ' ' . $this->_platform->getWriteLockSql(); } $alias = $this->_getSQLTableAlias($this->_class->name); if ($filterSql = $this->generateFilterConditionSQL($this->_class, $alias)) { if ($conditionSql) { $conditionSql .= ' AND '; } $conditionSql .= $filterSql; } return $this->_platform->modifyLimitQuery('SELECT ' . $this->_getSelectColumnListSQL() . $this->_platform->appendLockHint(' FROM ' . $this->quoteStrategy->getTableName($this->_class, $this->_platform) . ' ' . $alias, $lockMode) . $this->_selectJoinSql . $joinSql . ($conditionSql ? ' WHERE ' . $conditionSql : '') . $orderBySql, $limit, $offset) . $lockSql; } /** * Gets the ORDER BY SQL snippet for ordered collections. * * @param array $orderBy * @param string $baseTableAlias * @return string */ protected final function _getOrderBySQL(array $orderBy, $baseTableAlias) { $orderBySql = ''; foreach ($orderBy as $fieldName => $orientation) { if ( ! isset($this->_class->fieldMappings[$fieldName])) { throw ORMException::unrecognizedField($fieldName); } $orientation = strtoupper(trim($orientation)); if ($orientation != 'ASC' && $orientation != 'DESC') { throw ORMException::invalidOrientation($this->_class->name, $fieldName); } $tableAlias = isset($this->_class->fieldMappings[$fieldName]['inherited']) ? $this->_getSQLTableAlias($this->_class->fieldMappings[$fieldName]['inherited']) : $baseTableAlias; $columnName = $this->quoteStrategy->getColumnName($fieldName, $this->_class, $this->_platform); $orderBySql .= $orderBySql ? ', ' : ' ORDER BY '; $orderBySql .= $tableAlias . '.' . $columnName . ' ' . $orientation; } return $orderBySql; } /** * Gets the SQL fragment with the list of columns to select when querying for * an entity in this persister. * * Subclasses should override this method to alter or change the select column * list SQL fragment. Note that in the implementation of BasicEntityPersister * the resulting SQL fragment is generated only once and cached in {@link _selectColumnListSql}. * Subclasses may or may not do the same. * * @return string The SQL fragment. * @todo Rename: _getSelectColumnsSQL() */ protected function _getSelectColumnListSQL() { if ($this->_selectColumnListSql !== null) { return $this->_selectColumnListSql; } $columnList = ''; $this->_rsm = new Query\ResultSetMapping(); $this->_rsm->addEntityResult($this->_class->name, 'r'); // r for root // Add regular columns to select list foreach ($this->_class->fieldNames as $field) { if ($columnList) $columnList .= ', '; $columnList .= $this->_getSelectColumnSQL($field, $this->_class); } $this->_selectJoinSql = ''; $eagerAliasCounter = 0; foreach ($this->_class->associationMappings as $assocField => $assoc) { $assocColumnSQL = $this->_getSelectColumnAssociationSQL($assocField, $assoc, $this->_class); if ($assocColumnSQL) { if ($columnList) $columnList .= ', '; $columnList .= $assocColumnSQL; } if ($assoc['type'] & ClassMetadata::TO_ONE && ($assoc['fetch'] == ClassMetadata::FETCH_EAGER || !$assoc['isOwningSide'])) { $eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']); if ($eagerEntity->inheritanceType != ClassMetadata::INHERITANCE_TYPE_NONE) { continue; // now this is why you shouldn't use inheritance } $assocAlias = 'e' . ($eagerAliasCounter++); $this->_rsm->addJoinedEntityResult($assoc['targetEntity'], $assocAlias, 'r', $assocField); foreach ($eagerEntity->fieldNames as $field) { if ($columnList) $columnList .= ', '; $columnList .= $this->_getSelectColumnSQL($field, $eagerEntity, $assocAlias); } foreach ($eagerEntity->associationMappings as $assoc2Field => $assoc2) { $assoc2ColumnSQL = $this->_getSelectColumnAssociationSQL($assoc2Field, $assoc2, $eagerEntity, $assocAlias); if ($assoc2ColumnSQL) { if ($columnList) $columnList .= ', '; $columnList .= $assoc2ColumnSQL; } } $first = true; if ($assoc['isOwningSide']) { $this->_selectJoinSql .= ' ' . $this->getJoinSQLForJoinColumns($assoc['joinColumns']); $this->_selectJoinSql .= ' ' . $this->quoteStrategy->getTableName($eagerEntity, $this->_platform) . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) .' ON '; $tableAlias = $this->_getSQLTableAlias($assoc['targetEntity'], $assocAlias); foreach ($assoc['joinColumns'] as $joinColumn) { $sourceCol = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->_class, $this->_platform); $targetCol = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->_class, $this->_platform); if ( ! $first) { $this->_selectJoinSql .= ' AND '; } $this->_selectJoinSql .= $this->_getSQLTableAlias($assoc['sourceEntity']) . '.' . $sourceCol . ' = ' . $tableAlias . '.' . $targetCol; $first = false; } // Add filter SQL if ($filterSql = $this->generateFilterConditionSQL($eagerEntity, $tableAlias)) { $this->_selectJoinSql .= ' AND ' . $filterSql; } } else { $eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']); $owningAssoc = $eagerEntity->getAssociationMapping($assoc['mappedBy']); $this->_selectJoinSql .= ' LEFT JOIN'; $this->_selectJoinSql .= ' ' . $this->quoteStrategy->getTableName($eagerEntity, $this->_platform) . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) . ' ON '; foreach ($owningAssoc['sourceToTargetKeyColumns'] as $sourceCol => $targetCol) { if ( ! $first) { $this->_selectJoinSql .= ' AND '; } $this->_selectJoinSql .= $this->_getSQLTableAlias($owningAssoc['sourceEntity'], $assocAlias) . '.' . $sourceCol . ' = ' . $this->_getSQLTableAlias($owningAssoc['targetEntity']) . '.' . $targetCol; $first = false; } } } } $this->_selectColumnListSql = $columnList; return $this->_selectColumnListSql; } /** * Gets the SQL join fragment used when selecting entities from an association. * * @param string $field * @param array $assoc * @param ClassMetadata $class * @param string $alias * * @return string */ protected function _getSelectColumnAssociationSQL($field, $assoc, ClassMetadata $class, $alias = 'r') { $columnList = array(); if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { foreach ($assoc['joinColumns'] as $joinColumn) { $quotedColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->_class, $this->_platform); $resultColumnName = $this->getSQLColumnAlias($joinColumn['name']); $columnList[] = $this->_getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias) ) . '.' . $quotedColumn . ' AS ' . $resultColumnName; $this->_rsm->addMetaResult($alias, $resultColumnName, $quotedColumn, isset($assoc['id']) && $assoc['id'] === true); } } return implode(', ', $columnList); } /** * Gets the SQL join fragment used when selecting entities from a * many-to-many association. * * @param ManyToManyMapping $manyToMany * @return string */ protected function _getSelectManyToManyJoinSQL(array $manyToMany) { $conditions = array(); $association = $manyToMany; $sourceTableAlias = $this->_getSQLTableAlias($this->_class->name); if ( ! $manyToMany['isOwningSide']) { $targetEntity = $this->_em->getClassMetadata($manyToMany['targetEntity']); $association = $targetEntity->associationMappings[$manyToMany['mappedBy']]; } $joinTableName = $this->quoteStrategy->getJoinTableName($association, $this->_class, $this->_platform); $joinColumns = ($manyToMany['isOwningSide']) ? $association['joinTable']['inverseJoinColumns'] : $association['joinTable']['joinColumns']; foreach ($joinColumns as $joinColumn) { $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->_class, $this->_platform); $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->_class, $this->_platform); $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableName . '.' . $quotedSourceColumn; } return ' INNER JOIN ' . $joinTableName . ' ON ' . implode(' AND ', $conditions); } /** * Gets the INSERT SQL used by the persister to persist a new entity. * * @return string */ protected function _getInsertSQL() { if ($this->_insertSql === null) { $insertSql = ''; $columns = $this->_getInsertColumnList(); if (empty($columns)) { $insertSql = $this->_platform->getEmptyIdentityInsertSQL( $this->quoteStrategy->getTableName($this->_class, $this->_platform), $this->quoteStrategy->getColumnName($this->_class->identifier[0], $this->_class, $this->_platform) ); } else { $columns = array_unique($columns); $values = array(); foreach ($columns as $column) { $placeholder = '?'; if (isset($this->_class->fieldNames[$column]) && isset($this->_columnTypes[$this->_class->fieldNames[$column]]) && isset($this->_class->fieldMappings[$this->_class->fieldNames[$column]]['requireSQLConversion'])) { $type = Type::getType($this->_columnTypes[$this->_class->fieldNames[$column]]); $placeholder = $type->convertToDatabaseValueSQL('?', $this->_platform); } $values[] = $placeholder; } $insertSql = 'INSERT INTO ' . $this->quoteStrategy->getTableName($this->_class, $this->_platform) . ' (' . implode(', ', $columns) . ') VALUES (' . implode(', ', $values) . ')'; } $this->_insertSql = $insertSql; } return $this->_insertSql; } /** * Gets the list of columns to put in the INSERT SQL statement. * * Subclasses should override this method to alter or change the list of * columns placed in the INSERT statements used by the persister. * * @return array The list of columns. */ protected function _getInsertColumnList() { $columns = array(); foreach ($this->_class->reflFields as $name => $field) { if ($this->_class->isVersioned && $this->_class->versionField == $name) { continue; } if (isset($this->_class->associationMappings[$name])) { $assoc = $this->_class->associationMappings[$name]; if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { foreach ($assoc['joinColumns'] as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->_class, $this->_platform); } } } else if ($this->_class->generatorType != ClassMetadata::GENERATOR_TYPE_IDENTITY || $this->_class->identifier[0] != $name) { $columns[] = $this->quoteStrategy->getColumnName($name, $this->_class, $this->_platform); $this->_columnTypes[$name] = $this->_class->fieldMappings[$name]['type']; } } return $columns; } /** * Gets the SQL snippet of a qualified column name for the given field name. * * @param string $field The field name. * @param ClassMetadata $class The class that declares this field. The table this class is * mapped to must own the column for the given field. * @param string $alias */ protected function _getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r') { $sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) . '.' . $this->quoteStrategy->getColumnName($field, $class, $this->_platform); $columnAlias = $this->getSQLColumnAlias($class->columnNames[$field]); $this->_rsm->addFieldResult($alias, $columnAlias, $field); if (isset($class->fieldMappings[$field]['requireSQLConversion'])) { $type = Type::getType($class->getTypeOfField($field)); $sql = $type->convertToPHPValueSQL($sql, $this->_platform); } return $sql . ' AS ' . $columnAlias; } /** * Gets the SQL table alias for the given class name. * * @param string $className * @return string The SQL table alias. * @todo Reconsider. Binding table aliases to class names is not such a good idea. */ protected function _getSQLTableAlias($className, $assocName = '') { if ($assocName) { $className .= '#' . $assocName; } if (isset($this->_sqlTableAliases[$className])) { return $this->_sqlTableAliases[$className]; } $tableAlias = 't' . $this->_sqlAliasCounter++; $this->_sqlTableAliases[$className] = $tableAlias; return $tableAlias; } /** * Lock all rows of this entity matching the given criteria with the specified pessimistic lock mode * * @param array $criteria * @param int $lockMode * @return void */ public function lock(array $criteria, $lockMode) { $conditionSql = $this->_getSelectConditionSQL($criteria); if ($lockMode == LockMode::PESSIMISTIC_READ) { $lockSql = $this->_platform->getReadLockSql(); } else if ($lockMode == LockMode::PESSIMISTIC_WRITE) { $lockSql = $this->_platform->getWriteLockSql(); } $sql = 'SELECT 1 ' . $this->_platform->appendLockHint($this->getLockTablesSql(), $lockMode) . ($conditionSql ? ' WHERE ' . $conditionSql : '') . ' ' . $lockSql; list($params, $types) = $this->expandParameters($criteria); $this->_conn->executeQuery($sql, $params, $types); } /** * Get the FROM and optionally JOIN conditions to lock the entity managed by this persister. * * @return string */ protected function getLockTablesSql() { return 'FROM ' . $this->quoteStrategy->getTableName($this->_class, $this->_platform) . ' ' . $this->_getSQLTableAlias($this->_class->name); } /** * Get the Select Where Condition from a Criteria object. * * @param \Doctrine\Common\Collections\Criteria $criteria * @return string */ protected function _getSelectConditionCriteriaSQL(Criteria $criteria) { $expression = $criteria->getWhereExpression(); if ($expression === null) { return ''; } $visitor = new SqlExpressionVisitor($this); return $visitor->dispatch($expression); } /** * Get the SQL WHERE condition for matching a field with a given value. * * @param string $field * @param mixed $value * @param array|null $assoc * @param string $comparison * * @return string */ public function getSelectConditionStatementSQL($field, $value, $assoc = null, $comparison = null) { $conditionSql = $this->getSelectConditionStatementColumnSQL($field, $assoc); $placeholder = '?'; if (isset($this->_class->fieldMappings[$field]['requireSQLConversion'])) { $type = Type::getType($this->_class->getTypeOfField($field)); $placeholder = $type->convertToDatabaseValueSQL($placeholder, $this->_platform); } $conditionSql .= ($comparison === null) ? ((is_array($value)) ? ' IN (?)' : (($value === null) ? ' IS NULL' : ' = ' . $placeholder)) : ' ' . sprintf(self::$comparisonMap[$comparison], $placeholder); return $conditionSql; } /** * Build the left-hand-side of a where condition statement. * * @param string $field * @param array $assoc * * @return string */ protected function getSelectConditionStatementColumnSQL($field, $assoc = null) { switch (true) { case (isset($this->_class->columnNames[$field])): $className = (isset($this->_class->fieldMappings[$field]['inherited'])) ? $this->_class->fieldMappings[$field]['inherited'] : $this->_class->name; return $this->_getSQLTableAlias($className) . '.' . $this->quoteStrategy->getColumnName($field, $this->_class, $this->_platform); case (isset($this->_class->associationMappings[$field])): if ( ! $this->_class->associationMappings[$field]['isOwningSide']) { throw ORMException::invalidFindByInverseAssociation($this->_class->name, $field); } $className = (isset($this->_class->associationMappings[$field]['inherited'])) ? $this->_class->associationMappings[$field]['inherited'] : $this->_class->name; return $this->_getSQLTableAlias($className) . '.' . $this->_class->associationMappings[$field]['joinColumns'][0]['name']; case ($assoc !== null && strpos($field, " ") === false && strpos($field, "(") === false): // very careless developers could potentially open up this normally hidden api for userland attacks, // therefore checking for spaces and function calls which are not allowed. // found a join column condition, not really a "field" return $field; } throw ORMException::unrecognizedField($field); } /** * Gets the conditional SQL fragment used in the WHERE clause when selecting * entities in this persister. * * Subclasses are supposed to override this method if they intend to change * or alter the criteria by which entities are selected. * * @param array $criteria * @param AssociationMapping $assoc * @return string */ protected function _getSelectConditionSQL(array $criteria, $assoc = null) { $conditionSql = ''; foreach ($criteria as $field => $value) { $conditionSql .= $conditionSql ? ' AND ' : ''; $conditionSql .= $this->getSelectConditionStatementSQL($field, $value, $assoc); } return $conditionSql; } /** * Return an array with (sliced or full list) of elements in the specified collection. * * @param array $assoc * @param object $sourceEntity * @param int $offset * @param int $limit * @return array */ public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) { $stmt = $this->getOneToManyStatement($assoc, $sourceEntity, $offset, $limit); return $this->loadArrayFromStatement($assoc, $stmt); } /** * Loads a collection of entities in a one-to-many association. * * @param array $assoc * @param object $sourceEntity * @param PersistentCollection $coll The collection to load/fill. * @param int|null $offset * @param int|null $limit */ public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { $stmt = $this->getOneToManyStatement($assoc, $sourceEntity); return $this->loadCollectionFromStatement($assoc, $stmt, $coll); } /** * Build criteria and execute SQL statement to fetch the one to many entities from. * * @param array $assoc * @param object $sourceEntity * @param int|null $offset * @param int|null $limit * @return \Doctrine\DBAL\Statement */ private function getOneToManyStatement(array $assoc, $sourceEntity, $offset = null, $limit = null) { $criteria = array(); $owningAssoc = $this->_class->associationMappings[$assoc['mappedBy']]; $sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']); $tableAlias = $this->_getSQLTableAlias(isset($owningAssoc['inherited']) ? $owningAssoc['inherited'] : $this->_class->name); foreach ($owningAssoc['targetToSourceKeyColumns'] as $sourceKeyColumn => $targetKeyColumn) { if ($sourceClass->containsForeignIdentifier) { $field = $sourceClass->getFieldForColumn($sourceKeyColumn); $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); if (isset($sourceClass->associationMappings[$field])) { $value = $this->_em->getUnitOfWork()->getEntityIdentifier($value); $value = $value[$this->_em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; } $criteria[$tableAlias . "." . $targetKeyColumn] = $value; } else { $criteria[$tableAlias . "." . $targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); } } $sql = $this->_getSelectEntitiesSQL($criteria, $assoc, 0, $limit, $offset); list($params, $types) = $this->expandParameters($criteria); return $this->_conn->executeQuery($sql, $params, $types); } /** * Expand the parameters from the given criteria and use the correct binding types if found. * * @param array $criteria * @return array */ private function expandParameters($criteria) { $params = $types = array(); foreach ($criteria as $field => $value) { if ($value === null) { continue; // skip null values. } $types[] = $this->getType($field, $value); $params[] = $this->getValue($value); } return array($params, $types); } /** * Infer field type to be used by parameter type casting. * * @param string $field * @param mixed $value * @return integer */ private function getType($field, $value) { switch (true) { case (isset($this->_class->fieldMappings[$field])): $type = $this->_class->fieldMappings[$field]['type']; break; case (isset($this->_class->associationMappings[$field])): $assoc = $this->_class->associationMappings[$field]; if (count($assoc['sourceToTargetKeyColumns']) > 1) { throw Query\QueryException::associationPathCompositeKeyNotSupported(); } $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); $targetColumn = $assoc['joinColumns'][0]['referencedColumnName']; $type = null; if (isset($targetClass->fieldNames[$targetColumn])) { $type = $targetClass->fieldMappings[$targetClass->fieldNames[$targetColumn]]['type']; } break; default: $type = null; } if (is_array($value)) { $type = Type::getType( $type )->getBindingType(); $type += Connection::ARRAY_PARAM_OFFSET; } return $type; } /** * Retrieve parameter value * * @param mixed $value * @return mixed */ private function getValue($value) { if (is_array($value)) { $newValue = array(); foreach ($value as $itemValue) { $newValue[] = $this->getIndividualValue($itemValue); } return $newValue; } return $this->getIndividualValue($value); } /** * Retrieve an invidiual parameter value * * @param mixed $value * @return mixed */ private function getIndividualValue($value) { if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) { if ($this->_em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED) { $idValues = $this->_em->getUnitOfWork()->getEntityIdentifier($value); } else { $class = $this->_em->getClassMetadata(get_class($value)); $idValues = $class->getIdentifierValues($value); } $key = key($idValues); if (null !== $key){ $value = $idValues[$key]; } } return $value; } /** * Checks whether the given managed entity exists in the database. * * @param object $entity * @return boolean TRUE if the entity exists in the database, FALSE otherwise. */ public function exists($entity, array $extraConditions = array()) { $criteria = $this->_class->getIdentifierValues($entity); if ( ! $criteria) { return false; } if ($extraConditions) { $criteria = array_merge($criteria, $extraConditions); } $alias = $this->_getSQLTableAlias($this->_class->name); $sql = 'SELECT 1 ' . $this->getLockTablesSql() . ' WHERE ' . $this->_getSelectConditionSQL($criteria); if ($filterSql = $this->generateFilterConditionSQL($this->_class, $alias)) { $sql .= ' AND ' . $filterSql; } list($params) = $this->expandParameters($criteria); return (bool) $this->_conn->fetchColumn($sql, $params); } /** * Generates the appropriate join SQL for the given join column. * * @param array $joinColumns The join columns definition of an association. * @return string LEFT JOIN if one of the columns is nullable, INNER JOIN otherwise. */ protected function getJoinSQLForJoinColumns($joinColumns) { // if one of the join columns is nullable, return left join foreach ($joinColumns as $joinColumn) { if ( ! isset($joinColumn['nullable']) || $joinColumn['nullable']) { return 'LEFT JOIN'; } } return 'INNER JOIN'; } /** * Gets an SQL column alias for a column name. * * @param string $columnName * @return string */ public function getSQLColumnAlias($columnName) { return $this->quoteStrategy->getColumnAlias($columnName, $this->_sqlAliasCounter++, $this->_platform); } /** * Generates the filter SQL for a given entity and table alias. * * @param ClassMetadata $targetEntity Metadata of the target entity. * @param string $targetTableAlias The table alias of the joined/selected table. * * @return string The SQL query part to add to a query. */ protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias) { $filterClauses = array(); foreach ($this->_em->getFilters()->getEnabledFilters() as $filter) { if ('' !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) { $filterClauses[] = '(' . $filterExpr . ')'; } } $sql = implode(' AND ', $filterClauses); return $sql ? "(" . $sql . ")" : ""; // Wrap again to avoid "X or Y and FilterConditionSQL" } } DoctrineORM-2.3.3/Doctrine/ORM/Persisters/ElementCollectionPersister.php0000644000175100017510000000244412143374607026353 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Persisters; /** * Persister for collections of basic elements / value types. * * @author robo * @todo Implementation once support for collections of basic elements (i.e. strings) is added. */ abstract class ElementCollectionPersister extends AbstractCollectionPersister { //put your code here } DoctrineORM-2.3.3/Doctrine/ORM/Persisters/JoinedSubclassPersister.php0000644000175100017510000004656612143374607025673 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Persisters; use Doctrine\ORM\ORMException; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\DBAL\LockMode; use Doctrine\DBAL\Types\Type; use Doctrine\Common\Collections\Criteria; /** * The joined subclass persister maps a single entity instance to several tables in the * database as it is defined by the Class Table Inheritance strategy. * * @author Roman Borschel * @author Benjamin Eberlei * @author Alexander * @since 2.0 * @see http://martinfowler.com/eaaCatalog/classTableInheritance.html */ class JoinedSubclassPersister extends AbstractEntityInheritancePersister { /** * Map that maps column names to the table names that own them. * This is mainly a temporary cache, used during a single request. * * @var array */ private $_owningTableMap = array(); /** * Map of table to quoted table names. * * @var array */ private $_quotedTableMap = array(); /** * {@inheritdoc} */ protected function _getDiscriminatorColumnTableName() { $class = ($this->_class->name !== $this->_class->rootEntityName) ? $this->_em->getClassMetadata($this->_class->rootEntityName) : $this->_class; return $class->getTableName(); } /** * This function finds the ClassMetadata instance in an inheritance hierarchy * that is responsible for enabling versioning. * * @return \Doctrine\ORM\Mapping\ClassMetadata */ private function _getVersionedClassMetadata() { if (isset($this->_class->fieldMappings[$this->_class->versionField]['inherited'])) { $definingClassName = $this->_class->fieldMappings[$this->_class->versionField]['inherited']; return $this->_em->getClassMetadata($definingClassName); } return $this->_class; } /** * Gets the name of the table that owns the column the given field is mapped to. * * @param string $fieldName * @return string * @override */ public function getOwningTable($fieldName) { if (isset($this->_owningTableMap[$fieldName])) { return $this->_owningTableMap[$fieldName]; } if (isset($this->_class->associationMappings[$fieldName]['inherited'])) { $cm = $this->_em->getClassMetadata($this->_class->associationMappings[$fieldName]['inherited']); } else if (isset($this->_class->fieldMappings[$fieldName]['inherited'])) { $cm = $this->_em->getClassMetadata($this->_class->fieldMappings[$fieldName]['inherited']); } else { $cm = $this->_class; } $tableName = $cm->getTableName(); $this->_owningTableMap[$fieldName] = $tableName; $this->_quotedTableMap[$tableName] = $this->quoteStrategy->getTableName($cm, $this->_platform); return $tableName; } /** * {@inheritdoc} */ public function executeInserts() { if ( ! $this->_queuedInserts) { return; } $postInsertIds = array(); $idGen = $this->_class->idGenerator; $isPostInsertId = $idGen->isPostInsertGenerator(); // Prepare statement for the root table $rootClass = ($this->_class->name !== $this->_class->rootEntityName) ? $this->_em->getClassMetadata($this->_class->rootEntityName) : $this->_class; $rootPersister = $this->_em->getUnitOfWork()->getEntityPersister($rootClass->name); $rootTableName = $rootClass->getTableName(); $rootTableStmt = $this->_conn->prepare($rootPersister->_getInsertSQL()); // Prepare statements for sub tables. $subTableStmts = array(); if ($rootClass !== $this->_class) { $subTableStmts[$this->_class->getTableName()] = $this->_conn->prepare($this->_getInsertSQL()); } foreach ($this->_class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); $parentTableName = $parentClass->getTableName(); if ($parentClass !== $rootClass) { $parentPersister = $this->_em->getUnitOfWork()->getEntityPersister($parentClassName); $subTableStmts[$parentTableName] = $this->_conn->prepare($parentPersister->_getInsertSQL()); } } // Execute all inserts. For each entity: // 1) Insert on root table // 2) Insert on sub tables foreach ($this->_queuedInserts as $entity) { $insertData = $this->_prepareInsertData($entity); // Execute insert on root table $paramIndex = 1; foreach ($insertData[$rootTableName] as $columnName => $value) { $rootTableStmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]); } $rootTableStmt->execute(); if ($isPostInsertId) { $id = $idGen->generate($this->_em, $entity); $postInsertIds[$id] = $entity; } else { $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); } // Execute inserts on subtables. // The order doesn't matter because all child tables link to the root table via FK. foreach ($subTableStmts as $tableName => $stmt) { $data = isset($insertData[$tableName]) ? $insertData[$tableName] : array(); $paramIndex = 1; foreach ((array) $id as $idName => $idVal) { $type = isset($this->_columnTypes[$idName]) ? $this->_columnTypes[$idName] : Type::STRING; $stmt->bindValue($paramIndex++, $idVal, $type); } foreach ($data as $columnName => $value) { $stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]); } $stmt->execute(); } } $rootTableStmt->closeCursor(); foreach ($subTableStmts as $stmt) { $stmt->closeCursor(); } if ($this->_class->isVersioned) { $this->assignDefaultVersionValue($entity, $id); } $this->_queuedInserts = array(); return $postInsertIds; } /** * {@inheritdoc} */ public function update($entity) { $updateData = $this->_prepareUpdateData($entity); if (($isVersioned = $this->_class->isVersioned) != false) { $versionedClass = $this->_getVersionedClassMetadata(); $versionedTable = $versionedClass->getTableName(); } if ($updateData) { foreach ($updateData as $tableName => $data) { $this->_updateTable( $entity, $this->_quotedTableMap[$tableName], $data, $isVersioned && $versionedTable == $tableName ); } // Make sure the table with the version column is updated even if no columns on that // table were affected. if ($isVersioned) { if ( ! isset($updateData[$versionedTable])) { $this->_updateTable($entity, $this->quoteStrategy->getTableName($versionedClass, $this->_platform), array(), true); } $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); $this->assignDefaultVersionValue($entity, $id); } } } /** * {@inheritdoc} */ public function delete($entity) { $identifier = $this->_em->getUnitOfWork()->getEntityIdentifier($entity); $this->deleteJoinTableRecords($identifier); $id = array_combine($this->_class->getIdentifierColumnNames(), $identifier); // If the database platform supports FKs, just // delete the row from the root table. Cascades do the rest. if ($this->_platform->supportsForeignKeyConstraints()) { $this->_conn->delete( $this->quoteStrategy->getTableName($this->_em->getClassMetadata($this->_class->rootEntityName), $this->_platform), $id ); } else { // Delete from all tables individually, starting from this class' table up to the root table. $this->_conn->delete($this->quoteStrategy->getTableName($this->_class, $this->_platform), $id); foreach ($this->_class->parentClasses as $parentClass) { $this->_conn->delete( $this->quoteStrategy->getTableName($this->_em->getClassMetadata($parentClass), $this->_platform), $id ); } } } /** * {@inheritdoc} */ protected function _getSelectEntitiesSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null) { $idColumns = $this->_class->getIdentifierColumnNames(); $baseTableAlias = $this->_getSQLTableAlias($this->_class->name); // Create the column list fragment only once if ($this->_selectColumnListSql === null) { $this->_rsm = new ResultSetMapping(); $this->_rsm->addEntityResult($this->_class->name, 'r'); // Add regular columns $columnList = ''; foreach ($this->_class->fieldMappings as $fieldName => $mapping) { if ($columnList != '') $columnList .= ', '; $columnList .= $this->_getSelectColumnSQL( $fieldName, isset($mapping['inherited']) ? $this->_em->getClassMetadata($mapping['inherited']) : $this->_class ); } // Add foreign key columns foreach ($this->_class->associationMappings as $assoc2) { if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE) { $tableAlias = isset($assoc2['inherited']) ? $this->_getSQLTableAlias($assoc2['inherited']) : $baseTableAlias; foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) { if ($columnList != '') $columnList .= ', '; $columnList .= $this->getSelectJoinColumnSQL( $tableAlias, $srcColumn, isset($assoc2['inherited']) ? $assoc2['inherited'] : $this->_class->name ); } } } // Add discriminator column (DO NOT ALIAS, see AbstractEntityInheritancePersister#_processSQLResult). $discrColumn = $this->_class->discriminatorColumn['name']; $tableAlias = ($this->_class->rootEntityName == $this->_class->name) ? $baseTableAlias : $this->_getSQLTableAlias($this->_class->rootEntityName); $columnList .= ', ' . $tableAlias . '.' . $discrColumn; $resultColumnName = $this->_platform->getSQLResultCasing($discrColumn); $this->_rsm->setDiscriminatorColumn('r', $resultColumnName); $this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn); } // INNER JOIN parent tables $joinSql = ''; foreach ($this->_class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); $tableAlias = $this->_getSQLTableAlias($parentClassName); $joinSql .= ' INNER JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; foreach ($idColumns as $idColumn) { if ($first) $first = false; else $joinSql .= ' AND '; $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } } // OUTER JOIN sub tables foreach ($this->_class->subClasses as $subClassName) { $subClass = $this->_em->getClassMetadata($subClassName); $tableAlias = $this->_getSQLTableAlias($subClassName); if ($this->_selectColumnListSql === null) { // Add subclass columns foreach ($subClass->fieldMappings as $fieldName => $mapping) { if (isset($mapping['inherited'])) continue; $columnList .= ', ' . $this->_getSelectColumnSQL($fieldName, $subClass); } // Add join columns (foreign keys) foreach ($subClass->associationMappings as $assoc2) { if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE && ! isset($assoc2['inherited'])) { foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) { if ($columnList != '') $columnList .= ', '; $columnList .= $this->getSelectJoinColumnSQL( $tableAlias, $srcColumn, isset($assoc2['inherited']) ? $assoc2['inherited'] : $subClass->name ); } } } } // Add LEFT JOIN $joinSql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; foreach ($idColumns as $idColumn) { if ($first) $first = false; else $joinSql .= ' AND '; $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } } $joinSql .= ($assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY) ? $this->_getSelectManyToManyJoinSQL($assoc) : ''; $conditionSql = ($criteria instanceof Criteria) ? $this->_getSelectConditionCriteriaSQL($criteria) : $this->_getSelectConditionSQL($criteria, $assoc); // If the current class in the root entity, add the filters if ($filterSql = $this->generateFilterConditionSQL($this->_em->getClassMetadata($this->_class->rootEntityName), $this->_getSQLTableAlias($this->_class->rootEntityName))) { if ($conditionSql) { $conditionSql .= ' AND '; } $conditionSql .= $filterSql; } $orderBy = ($assoc !== null && isset($assoc['orderBy'])) ? $assoc['orderBy'] : $orderBy; $orderBySql = $orderBy ? $this->_getOrderBySQL($orderBy, $baseTableAlias) : ''; if ($this->_selectColumnListSql === null) { $this->_selectColumnListSql = $columnList; } $lockSql = ''; if ($lockMode == LockMode::PESSIMISTIC_READ) { $lockSql = ' ' . $this->_platform->getReadLockSql(); } else if ($lockMode == LockMode::PESSIMISTIC_WRITE) { $lockSql = ' ' . $this->_platform->getWriteLockSql(); } return $this->_platform->modifyLimitQuery('SELECT ' . $this->_selectColumnListSql . ' FROM ' . $this->quoteStrategy->getTableName($this->_class, $this->_platform) . ' ' . $baseTableAlias . $joinSql . ($conditionSql != '' ? ' WHERE ' . $conditionSql : '') . $orderBySql, $limit, $offset) . $lockSql; } /** * Get the FROM and optionally JOIN conditions to lock the entity managed by this persister. * * @return string */ public function getLockTablesSql() { $idColumns = $this->_class->getIdentifierColumnNames(); $baseTableAlias = $this->_getSQLTableAlias($this->_class->name); // INNER JOIN parent tables $joinSql = ''; foreach ($this->_class->parentClasses as $parentClassName) { $parentClass = $this->_em->getClassMetadata($parentClassName); $tableAlias = $this->_getSQLTableAlias($parentClassName); $joinSql .= ' INNER JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->_platform) . ' ' . $tableAlias . ' ON '; $first = true; foreach ($idColumns as $idColumn) { if ($first) $first = false; else $joinSql .= ' AND '; $joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } } return 'FROM ' .$this->quoteStrategy->getTableName($this->_class, $this->_platform) . ' ' . $baseTableAlias . $joinSql; } /* Ensure this method is never called. This persister overrides _getSelectEntitiesSQL directly. */ protected function _getSelectColumnListSQL() { throw new \BadMethodCallException("Illegal invocation of ".__METHOD__."."); } /** {@inheritdoc} */ protected function _getInsertColumnList() { // Identifier columns must always come first in the column list of subclasses. $columns = $this->_class->parentClasses ? $this->_class->getIdentifierColumnNames() : array(); foreach ($this->_class->reflFields as $name => $field) { if (isset($this->_class->fieldMappings[$name]['inherited']) && ! isset($this->_class->fieldMappings[$name]['id']) || isset($this->_class->associationMappings[$name]['inherited']) || ($this->_class->isVersioned && $this->_class->versionField == $name)) { continue; } if (isset($this->_class->associationMappings[$name])) { $assoc = $this->_class->associationMappings[$name]; if ($assoc['type'] & ClassMetadata::TO_ONE && $assoc['isOwningSide']) { foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) { $columns[] = $sourceCol; } } } else if ($this->_class->name != $this->_class->rootEntityName || ! $this->_class->isIdGeneratorIdentity() || $this->_class->identifier[0] != $name) { $columns[] = $this->quoteStrategy->getColumnName($name, $this->_class, $this->_platform); $this->_columnTypes[$name] = $this->_class->fieldMappings[$name]['type']; } } // Add discriminator column if it is the topmost class. if ($this->_class->name == $this->_class->rootEntityName) { $columns[] = $this->_class->discriminatorColumn['name']; } return $columns; } /** * {@inheritdoc} */ protected function assignDefaultVersionValue($entity, $id) { $value = $this->fetchVersionValue($this->_getVersionedClassMetadata(), $id); $this->_class->setFieldValue($entity, $this->_class->versionField, $value); } } DoctrineORM-2.3.3/Doctrine/ORM/Persisters/ManyToManyPersister.php0000644000175100017510000004047412143374607025007 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Persisters; use Doctrine\ORM\Mapping\ClassMetadata, Doctrine\ORM\PersistentCollection, Doctrine\ORM\UnitOfWork; /** * Persister for many-to-many collections. * * @author Roman Borschel * @author Guilherme Blanco * @author Alexander * @since 2.0 */ class ManyToManyPersister extends AbstractCollectionPersister { /** * {@inheritdoc} * * @override */ protected function _getDeleteRowSQL(PersistentCollection $coll) { $columns = array(); $mapping = $coll->getMapping(); $class = $this->_em->getClassMetadata(get_class($coll->getOwner())); foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } return 'DELETE FROM ' . $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform) . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?'; } /** * {@inheritdoc} * * @override * @internal Order of the parameters must be the same as the order of the columns in * _getDeleteRowSql. */ protected function _getDeleteRowSQLParameters(PersistentCollection $coll, $element) { return $this->_collectJoinTableColumnParameters($coll, $element); } /** * {@inheritdoc} * * @override */ protected function _getUpdateRowSQL(PersistentCollection $coll) {} /** * {@inheritdoc} * * @override * @internal Order of the parameters must be the same as the order of the columns in * _getInsertRowSql. */ protected function _getInsertRowSQL(PersistentCollection $coll) { $columns = array(); $mapping = $coll->getMapping(); $class = $this->_em->getClassMetadata(get_class($coll->getOwner())); $joinTable = $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform); foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } return 'INSERT INTO ' . $joinTable . ' (' . implode(', ', $columns) . ')' . ' VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')'; } /** * {@inheritdoc} * * @override * @internal Order of the parameters must be the same as the order of the columns in * _getInsertRowSql. */ protected function _getInsertRowSQLParameters(PersistentCollection $coll, $element) { return $this->_collectJoinTableColumnParameters($coll, $element); } /** * Collects the parameters for inserting/deleting on the join table in the order * of the join table columns as specified in ManyToManyMapping#joinTableColumns. * * @param $coll * @param $element * @return array */ private function _collectJoinTableColumnParameters(PersistentCollection $coll, $element) { $params = array(); $mapping = $coll->getMapping(); $isComposite = count($mapping['joinTableColumns']) > 2; $identifier1 = $this->_uow->getEntityIdentifier($coll->getOwner()); $identifier2 = $this->_uow->getEntityIdentifier($element); if ($isComposite) { $class1 = $this->_em->getClassMetadata(get_class($coll->getOwner())); $class2 = $coll->getTypeClass(); } foreach ($mapping['joinTableColumns'] as $joinTableColumn) { $isRelationToSource = isset($mapping['relationToSourceKeyColumns'][$joinTableColumn]); if ( ! $isComposite) { $params[] = $isRelationToSource ? array_pop($identifier1) : array_pop($identifier2); continue; } if ($isRelationToSource) { $params[] = $identifier1[$class1->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])]; continue; } $params[] = $identifier2[$class2->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])]; } return $params; } /** * {@inheritdoc} * * @override */ protected function _getDeleteSQL(PersistentCollection $coll) { $columns = array(); $mapping = $coll->getMapping(); $class = $this->_em->getClassMetadata(get_class($coll->getOwner())); $joinTable = $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform); foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } return 'DELETE FROM ' . $joinTable . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?'; } /** * {@inheritdoc} * * @override * @internal Order of the parameters must be the same as the order of the columns in * _getDeleteSql. */ protected function _getDeleteSQLParameters(PersistentCollection $coll) { $identifier = $this->_uow->getEntityIdentifier($coll->getOwner()); $mapping = $coll->getMapping(); $params = array(); // Optimization for single column identifier if (count($mapping['relationToSourceKeyColumns']) === 1) { $params[] = array_pop($identifier); return $params; } // Composite identifier $sourceClass = $this->_em->getClassMetadata(get_class($coll->getOwner())); foreach ($mapping['relationToSourceKeyColumns'] as $srcColumn) { $params[] = $identifier[$sourceClass->fieldNames[$srcColumn]]; } return $params; } /** * {@inheritdoc} */ public function count(PersistentCollection $coll) { $conditions = array(); $params = array(); $mapping = $coll->getMapping(); $association = $mapping; $class = $this->_em->getClassMetadata($mapping['sourceEntity']); $id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); if ( ! $mapping['isOwningSide']) { $targetEntity = $this->_em->getClassMetadata($mapping['targetEntity']); $association = $targetEntity->associationMappings[$mapping['mappedBy']]; } $joinColumns = ( ! $mapping['isOwningSide']) ? $association['joinTable']['inverseJoinColumns'] : $association['joinTable']['joinColumns']; foreach ($joinColumns as $joinColumn) { $columnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); $referencedName = $joinColumn['referencedColumnName']; $conditions[] = $columnName . ' = ?'; $params[] = ($class->containsForeignIdentifier) ? $id[$class->getFieldForColumn($referencedName)] : $id[$class->fieldNames[$referencedName]]; } $joinTableName = $this->quoteStrategy->getJoinTableName($association, $class, $this->platform); list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($mapping); if ($filterSql) { $conditions[] = $filterSql; } $sql = 'SELECT COUNT(*)' . ' FROM ' . $joinTableName . ' t' . $joinTargetEntitySQL . ' WHERE ' . implode(' AND ', $conditions); return $this->_conn->fetchColumn($sql, $params); } /** * @param PersistentCollection $coll * @param int $offset * @param int $length * @return array */ public function slice(PersistentCollection $coll, $offset, $length = null) { $mapping = $coll->getMapping(); return $this->_em->getUnitOfWork()->getEntityPersister($mapping['targetEntity'])->getManyToManyCollection($mapping, $coll->getOwner(), $offset, $length); } /** * @param PersistentCollection $coll * @param object $element * @return boolean */ public function contains(PersistentCollection $coll, $element) { $uow = $this->_em->getUnitOfWork(); // Shortcut for new entities $entityState = $uow->getEntityState($element, UnitOfWork::STATE_NEW); if ($entityState === UnitOfWork::STATE_NEW) { return false; } // Entity is scheduled for inclusion if ($entityState === UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element)) { return false; } list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, $element, true); $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses); return (bool) $this->_conn->fetchColumn($sql, $params); } /** * @param PersistentCollection $coll * @param object $element * @return boolean */ public function removeElement(PersistentCollection $coll, $element) { $uow = $this->_em->getUnitOfWork(); // shortcut for new entities $entityState = $uow->getEntityState($element, UnitOfWork::STATE_NEW); if ($entityState === UnitOfWork::STATE_NEW) { return false; } // If Entity is scheduled for inclusion, it is not in this collection. // We can assure that because it would have return true before on array check if ($entityState === UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element)) { return false; } list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, $element, false); $sql = 'DELETE FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses); return (bool) $this->_conn->executeUpdate($sql, $params); } /** * @param \Doctrine\ORM\PersistentCollection $coll * @param object $element * @param boolean $addFilters Whether the filter SQL should be included or not. * @return array */ private function getJoinTableRestrictions(PersistentCollection $coll, $element, $addFilters) { $uow = $this->_em->getUnitOfWork(); $mapping = $filterMapping = $coll->getMapping(); if ( ! $mapping['isOwningSide']) { $sourceClass = $this->_em->getClassMetadata($mapping['targetEntity']); $targetClass = $this->_em->getClassMetadata($mapping['sourceEntity']); $sourceId = $uow->getEntityIdentifier($element); $targetId = $uow->getEntityIdentifier($coll->getOwner()); $mapping = $sourceClass->associationMappings[$mapping['mappedBy']]; } else { $sourceClass = $this->_em->getClassMetadata($mapping['sourceEntity']); $targetClass = $this->_em->getClassMetadata($mapping['targetEntity']); $sourceId = $uow->getEntityIdentifier($coll->getOwner()); $targetId = $uow->getEntityIdentifier($element); } $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $sourceClass, $this->platform); $whereClauses = array(); $params = array(); foreach ($mapping['joinTableColumns'] as $joinTableColumn) { $whereClauses[] = $joinTableColumn . ' = ?'; if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) { $params[] = ($targetClass->containsForeignIdentifier) ? $targetId[$targetClass->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])] : $targetId[$targetClass->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]]; continue; } // relationToSourceKeyColumns $params[] = ($sourceClass->containsForeignIdentifier) ? $sourceId[$sourceClass->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])] : $sourceId[$sourceClass->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]]; } if ($addFilters) { list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($filterMapping); if ($filterSql) { $quotedJoinTable .= ' t ' . $joinTargetEntitySQL; $whereClauses[] = $filterSql; } } return array($quotedJoinTable, $whereClauses, $params); } /** * Generates the filter SQL for a given mapping. * * This method is not used for actually grabbing the related entities * but when the extra-lazy collection methods are called on a filtered * association. This is why besides the many to many table we also * have to join in the actual entities table leading to additional * JOIN. * * @param array $mapping Array containing mapping information. * * @return string The SQL query part to add to a query. */ public function getFilterSql($mapping) { $targetClass = $this->_em->getClassMetadata($mapping['targetEntity']); if ($mapping['isOwningSide']) { $joinColumns = $mapping['relationToTargetKeyColumns']; } else { $mapping = $targetClass->associationMappings[$mapping['mappedBy']]; $joinColumns = $mapping['relationToSourceKeyColumns']; } $targetClass = $this->_em->getClassMetadata($targetClass->rootEntityName); // A join is needed if there is filtering on the target entity $joinTargetEntitySQL = ''; if ($filterSql = $this->generateFilterConditionSQL($targetClass, 'te')) { $joinTargetEntitySQL = ' JOIN ' . $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' te' . ' ON'; $joinTargetEntitySQLClauses = array(); foreach ($joinColumns as $joinTableColumn => $targetTableColumn) { $joinTargetEntitySQLClauses[] = ' t.' . $joinTableColumn . ' = ' . 'te.' . $targetTableColumn; } $joinTargetEntitySQL .= implode(' AND ', $joinTargetEntitySQLClauses); } return array($joinTargetEntitySQL, $filterSql); } /** * Generates the filter SQL for a given entity and table alias. * * @param ClassMetadata $targetEntity Metadata of the target entity. * @param string $targetTableAlias The table alias of the joined/selected table. * * @return string The SQL query part to add to a query. */ protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias) { $filterClauses = array(); foreach ($this->_em->getFilters()->getEnabledFilters() as $filter) { if ($filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) { $filterClauses[] = '(' . $filterExpr . ')'; } } $sql = implode(' AND ', $filterClauses); return $sql ? "(" . $sql . ")" : ""; } } DoctrineORM-2.3.3/Doctrine/ORM/Persisters/OneToManyPersister.php0000644000175100017510000001652512143374607024624 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Persisters; use Doctrine\ORM\PersistentCollection, Doctrine\ORM\UnitOfWork; /** * Persister for one-to-many collections. * * @author Roman Borschel * @author Guilherme Blanco * @author Alexander * @since 2.0 */ class OneToManyPersister extends AbstractCollectionPersister { /** * Generates the SQL UPDATE that updates a particular row's foreign * key to null. * * @param PersistentCollection $coll * @return string * @override */ protected function _getDeleteRowSQL(PersistentCollection $coll) { $mapping = $coll->getMapping(); $class = $this->_em->getClassMetadata($mapping['targetEntity']); return 'DELETE FROM ' . $this->quoteStrategy->getTableName($class, $this->platform) . ' WHERE ' . implode('= ? AND ', $class->getIdentifierColumnNames()) . ' = ?'; } /** * {@inheritdoc} * */ protected function _getDeleteRowSQLParameters(PersistentCollection $coll, $element) { return array_values($this->_uow->getEntityIdentifier($element)); } protected function _getInsertRowSQL(PersistentCollection $coll) { return "UPDATE xxx SET foreign_key = yyy WHERE foreign_key = zzz"; } /** * Gets the SQL parameters for the corresponding SQL statement to insert the given * element of the given collection into the database. * * @param PersistentCollection $coll * @param mixed $element */ protected function _getInsertRowSQLParameters(PersistentCollection $coll, $element) {} /* Not used for OneToManyPersister */ protected function _getUpdateRowSQL(PersistentCollection $coll) { return; } /** * Generates the SQL UPDATE that updates all the foreign keys to null. * * @param PersistentCollection $coll */ protected function _getDeleteSQL(PersistentCollection $coll) { } /** * Gets the SQL parameters for the corresponding SQL statement to delete * the given collection. * * @param PersistentCollection $coll */ protected function _getDeleteSQLParameters(PersistentCollection $coll) {} /** * {@inheritdoc} */ public function count(PersistentCollection $coll) { $mapping = $coll->getMapping(); $targetClass = $this->_em->getClassMetadata($mapping['targetEntity']); $sourceClass = $this->_em->getClassMetadata($mapping['sourceEntity']); $id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); $whereClauses = array(); $params = array(); foreach ($targetClass->associationMappings[$mapping['mappedBy']]['joinColumns'] as $joinColumn) { $whereClauses[] = $joinColumn['name'] . ' = ?'; $params[] = ($targetClass->containsForeignIdentifier) ? $id[$sourceClass->getFieldForColumn($joinColumn['referencedColumnName'])] : $id[$sourceClass->fieldNames[$joinColumn['referencedColumnName']]]; } $filterTargetClass = $this->_em->getClassMetadata($targetClass->rootEntityName); foreach ($this->_em->getFilters()->getEnabledFilters() as $filter) { if ($filterExpr = $filter->addFilterConstraint($filterTargetClass, 't')) { $whereClauses[] = '(' . $filterExpr . ')'; } } $sql = 'SELECT count(*)' . ' FROM ' . $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' t' . ' WHERE ' . implode(' AND ', $whereClauses); return $this->_conn->fetchColumn($sql, $params); } /** * @param PersistentCollection $coll * @param int $offset * @param int $length * @return \Doctrine\Common\Collections\ArrayCollection */ public function slice(PersistentCollection $coll, $offset, $length = null) { $mapping = $coll->getMapping(); $uow = $this->_em->getUnitOfWork(); $persister = $uow->getEntityPersister($mapping['targetEntity']); return $persister->getOneToManyCollection($mapping, $coll->getOwner(), $offset, $length); } /** * @param PersistentCollection $coll * @param object $element * @return boolean */ public function contains(PersistentCollection $coll, $element) { $mapping = $coll->getMapping(); $uow = $this->_em->getUnitOfWork(); // shortcut for new entities $entityState = $uow->getEntityState($element, UnitOfWork::STATE_NEW); if ($entityState === UnitOfWork::STATE_NEW) { return false; } // Entity is scheduled for inclusion if ($entityState === UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element)) { return false; } $persister = $uow->getEntityPersister($mapping['targetEntity']); // only works with single id identifier entities. Will throw an // exception in Entity Persisters if that is not the case for the // 'mappedBy' field. $id = current( $uow->getEntityIdentifier($coll->getOwner())); return $persister->exists($element, array($mapping['mappedBy'] => $id)); } /** * @param PersistentCollection $coll * @param object $element * @return boolean */ public function removeElement(PersistentCollection $coll, $element) { $uow = $this->_em->getUnitOfWork(); // shortcut for new entities $entityState = $uow->getEntityState($element, UnitOfWork::STATE_NEW); if ($entityState === UnitOfWork::STATE_NEW) { return false; } // If Entity is scheduled for inclusion, it is not in this collection. // We can assure that because it would have return true before on array check if ($entityState === UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element)) { return false; } $mapping = $coll->getMapping(); $class = $this->_em->getClassMetadata($mapping['targetEntity']); $sql = 'DELETE FROM ' . $this->quoteStrategy->getTableName($class, $this->platform) . ' WHERE ' . implode('= ? AND ', $class->getIdentifierColumnNames()) . ' = ?'; return (bool) $this->_conn->executeUpdate($sql, $this->_getDeleteRowSQLParameters($coll, $element)); } } DoctrineORM-2.3.3/Doctrine/ORM/Persisters/SingleTablePersister.php0000644000175100017510000001365112143374607025141 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Persisters; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\Common\Collections\Criteria; /** * Persister for entities that participate in a hierarchy mapped with the * SINGLE_TABLE strategy. * * @author Roman Borschel * @author Benjamin Eberlei * @author Alexander * @since 2.0 * @link http://martinfowler.com/eaaCatalog/singleTableInheritance.html */ class SingleTablePersister extends AbstractEntityInheritancePersister { /** {@inheritdoc} */ protected function _getDiscriminatorColumnTableName() { return $this->_class->getTableName(); } /** {@inheritdoc} */ protected function _getSelectColumnListSQL() { if ($this->_selectColumnListSql !== null) { return $this->_selectColumnListSql; } $columnList = parent::_getSelectColumnListSQL(); $rootClass = $this->_em->getClassMetadata($this->_class->rootEntityName); $tableAlias = $this->_getSQLTableAlias($rootClass->name); // Append discriminator column $discrColumn = $this->_class->discriminatorColumn['name']; $columnList .= ', ' . $tableAlias . '.' . $discrColumn; $resultColumnName = $this->_platform->getSQLResultCasing($discrColumn); $this->_rsm->setDiscriminatorColumn('r', $resultColumnName); $this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn); // Append subclass columns foreach ($this->_class->subClasses as $subClassName) { $subClass = $this->_em->getClassMetadata($subClassName); // Regular columns foreach ($subClass->fieldMappings as $fieldName => $mapping) { if ( ! isset($mapping['inherited'])) { $columnList .= ', ' . $this->_getSelectColumnSQL($fieldName, $subClass); } } // Foreign key columns foreach ($subClass->associationMappings as $assoc) { if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) { foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { if ($columnList != '') $columnList .= ', '; $columnList .= $this->getSelectJoinColumnSQL( $tableAlias, $srcColumn, isset($assoc['inherited']) ? $assoc['inherited'] : $this->_class->name ); } } } } $this->_selectColumnListSql = $columnList; return $this->_selectColumnListSql; } /** {@inheritdoc} */ protected function _getInsertColumnList() { $columns = parent::_getInsertColumnList(); // Add discriminator column to the INSERT SQL $columns[] = $this->_class->discriminatorColumn['name']; return $columns; } /** {@inheritdoc} */ protected function _getSQLTableAlias($className, $assocName = '') { return parent::_getSQLTableAlias($this->_class->rootEntityName, $assocName); } /** {@inheritdoc} */ protected function _getSelectConditionSQL(array $criteria, $assoc = null) { $conditionSql = parent::_getSelectConditionSQL($criteria, $assoc); if ($conditionSql) { $conditionSql .= ' AND '; } return $conditionSql . $this->_getSelectConditionDiscriminatorValueSQL(); } /** {@inheritdoc} */ protected function _getSelectConditionCriteriaSQL(Criteria $criteria) { $conditionSql = parent::_getSelectConditionCriteriaSQL($criteria); if ($conditionSql) { $conditionSql .= ' AND '; } return $conditionSql . $this->_getSelectConditionDiscriminatorValueSQL(); } protected function _getSelectConditionDiscriminatorValueSQL() { $values = array(); if ($this->_class->discriminatorValue !== null) { // discriminators can be 0 $values[] = $this->_conn->quote($this->_class->discriminatorValue); } $discrValues = array_flip($this->_class->discriminatorMap); foreach ($this->_class->subClasses as $subclassName) { $values[] = $this->_conn->quote($discrValues[$subclassName]); } return $this->_getSQLTableAlias($this->_class->name) . '.' . $this->_class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')'; } /** {@inheritdoc} */ protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias) { // Ensure that the filters are applied to the root entity of the inheritance tree $targetEntity = $this->_em->getClassMetadata($targetEntity->rootEntityName); // we dont care about the $targetTableAlias, in a STI there is only one table. return parent::generateFilterConditionSQL($targetEntity, $targetTableAlias); } } DoctrineORM-2.3.3/Doctrine/ORM/Persisters/SqlExpressionVisitor.php0000644000175100017510000000641312143374607025244 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Persisters; use Doctrine\Common\Collections\Expr\ExpressionVisitor; use Doctrine\Common\Collections\Expr\Comparison; use Doctrine\Common\Collections\Expr\Value; use Doctrine\Common\Collections\Expr\CompositeExpression; /** * Visit Expressions and generate SQL WHERE conditions from them. * * @author Benjamin Eberlei * @since 2.3 */ class SqlExpressionVisitor extends ExpressionVisitor { /** * @var \Doctrine\ORM\Persisters\BasicEntityPersister */ private $persister; /** * @param \Doctrine\ORM\Persisters\BasicEntityPersister $persister */ public function __construct(BasicEntityPersister $persister) { $this->persister = $persister; } /** * Convert a comparison expression into the target query language output * * @param \Doctrine\Common\Collections\Expr\Comparison $comparison * * @return mixed */ public function walkComparison(Comparison $comparison) { $field = $comparison->getField(); $value = $comparison->getValue()->getValue(); // shortcut for walkValue() return $this->persister->getSelectConditionStatementSQL($field, $value, null, $comparison->getOperator()); } /** * Convert a composite expression into the target query language output * * @param \Doctrine\Common\Collections\Expr\CompositeExpression $expr * * @return mixed */ public function walkCompositeExpression(CompositeExpression $expr) { $expressionList = array(); foreach ($expr->getExpressionList() as $child) { $expressionList[] = $this->dispatch($child); } switch($expr->getType()) { case CompositeExpression::TYPE_AND: return '(' . implode(' AND ', $expressionList) . ')'; case CompositeExpression::TYPE_OR: return '(' . implode(' OR ', $expressionList) . ')'; default: throw new \RuntimeException("Unknown composite " . $expr->getType()); } } /** * Convert a value expression into the target query language part. * * @param \Doctrine\Common\Collections\Expr\Value $value * * @return mixed */ public function walkValue(Value $value) { return '?'; } } DoctrineORM-2.3.3/Doctrine/ORM/Persisters/SqlValueVisitor.php0000644000175100017510000000565512143374607024170 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Persisters; use Doctrine\Common\Collections\Expr\ExpressionVisitor; use Doctrine\Common\Collections\Expr\Comparison; use Doctrine\Common\Collections\Expr\Value; use Doctrine\Common\Collections\Expr\CompositeExpression; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Connection; /** * Extract the values from a criteria/expression * * @author Benjamin Eberlei */ class SqlValueVisitor extends ExpressionVisitor { /** * @var array */ private $values = array(); /** * @var array */ private $types = array(); /** * Convert a comparison expression into the target query language output * * @param \Doctrine\Common\Collections\Expr\Comparison $comparison * * @return mixed */ public function walkComparison(Comparison $comparison) { $value = $comparison->getValue()->getValue(); $field = $comparison->getField(); $this->values[] = $value; $this->types[] = array($field, $value); } /** * Convert a composite expression into the target query language output * * @param \Doctrine\Common\Collections\Expr\CompositeExpression $expr * * @return mixed */ public function walkCompositeExpression(CompositeExpression $expr) { foreach ($expr->getExpressionList() as $child) { $this->dispatch($child); } } /** * Convert a value expression into the target query language part. * * @param \Doctrine\Common\Collections\Expr\Value $value * * @return mixed */ public function walkValue(Value $value) { return; } /** * Return the Parameters and Types necessary for matching the last visited expression. * * @return array */ public function getParamsAndTypes() { return array($this->values, $this->types); } } DoctrineORM-2.3.3/Doctrine/ORM/Persisters/UnionSubclassPersister.php0000644000175100017510000000015112143374607025527 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Proxy; /** * Special Autoloader for Proxy classes because them not being PSR-0 compatible. * * @author Benjamin Eberlei */ class Autoloader { /** * Resolve proxy class name to a filename based on the following pattern. * * 1. Remove Proxy namespace from class name * 2. Remove namespace seperators from remaining class name. * 3. Return PHP filename from proxy-dir with the result from 2. * * @param string $proxyDir * @param string $proxyNamespace * @param string $className * @return string */ static public function resolveFile($proxyDir, $proxyNamespace, $className) { if (0 !== strpos($className, $proxyNamespace)) { throw ProxyException::notProxyClass($className, $proxyNamespace); } $className = str_replace('\\', '', substr($className, strlen($proxyNamespace) +1)); return $proxyDir . DIRECTORY_SEPARATOR . $className.'.php'; } /** * Register and return autoloader callback for the given proxy dir and * namespace. * * @param string $proxyDir * @param string $proxyNamespace * @param Closure $notFoundCallback Invoked when the proxy file is not found. * @return Closure */ static public function register($proxyDir, $proxyNamespace, \Closure $notFoundCallback = null) { $proxyNamespace = ltrim($proxyNamespace, "\\"); $autoloader = function($className) use ($proxyDir, $proxyNamespace, $notFoundCallback) { if (0 === strpos($className, $proxyNamespace)) { $file = Autoloader::resolveFile($proxyDir, $proxyNamespace, $className); if ($notFoundCallback && ! file_exists($file)) { $notFoundCallback($proxyDir, $proxyNamespace, $className); } require $file; } }; spl_autoload_register($autoloader); return $autoloader; } } DoctrineORM-2.3.3/Doctrine/ORM/Proxy/Proxy.php0000644000175100017510000000230212143374607021135 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Proxy; use Doctrine\Common\Persistence\Proxy as BaseProxy; /** * Interface for proxy classes. * * @author Roman Borschel * @since 2.0 */ interface Proxy extends BaseProxy {} DoctrineORM-2.3.3/Doctrine/ORM/Proxy/ProxyException.php0000644000175100017510000000365512143374607023030 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Proxy; /** * ORM Proxy Exception * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @author Benjamin Eberlei */ class ProxyException extends \Doctrine\ORM\ORMException { public static function proxyDirectoryRequired() { return new self("You must configure a proxy directory. See docs for details"); } public static function proxyDirectoryNotWritable() { return new self("Your proxy directory must be writable."); } public static function proxyNamespaceRequired() { return new self("You must configure a proxy namespace. See docs for details"); } public static function notProxyClass($className, $proxyNamespace) { return new self(sprintf( "The class %s is not part of the proxy namespace %s", $className, $proxyNamespace )); } } DoctrineORM-2.3.3/Doctrine/ORM/Proxy/ProxyFactory.php0000644000175100017510000003523012143374607022473 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Proxy; use Doctrine\ORM\EntityManager, Doctrine\ORM\Mapping\ClassMetadata, Doctrine\Common\Util\ClassUtils; /** * This factory is used to create proxy objects for entities at runtime. * * @author Roman Borschel * @author Giorgio Sironi * @since 2.0 */ class ProxyFactory { /** The EntityManager this factory is bound to. */ private $_em; /** Whether to automatically (re)generate proxy classes. */ private $_autoGenerate; /** The namespace that contains all proxy classes. */ private $_proxyNamespace; /** The directory that contains all proxy classes. */ private $_proxyDir; /** * Used to match very simple id methods that don't need * to be proxied since the identifier is known. * * @var string */ const PATTERN_MATCH_ID_METHOD = '((public\s)?(function\s{1,}%s\s?\(\)\s{1,})\s{0,}{\s{0,}return\s{0,}\$this->%s;\s{0,}})i'; /** * Initializes a new instance of the ProxyFactory class that is * connected to the given EntityManager. * * @param EntityManager $em The EntityManager the new factory works for. * @param string $proxyDir The directory to use for the proxy classes. It must exist. * @param string $proxyNs The namespace to use for the proxy classes. * @param boolean $autoGenerate Whether to automatically generate proxy classes. */ public function __construct(EntityManager $em, $proxyDir, $proxyNs, $autoGenerate = false) { if ( ! $proxyDir) { throw ProxyException::proxyDirectoryRequired(); } if ( ! $proxyNs) { throw ProxyException::proxyNamespaceRequired(); } $this->_em = $em; $this->_proxyDir = $proxyDir; $this->_autoGenerate = $autoGenerate; $this->_proxyNamespace = $proxyNs; } /** * Gets a reference proxy instance for the entity of the given type and identified by * the given identifier. * * @param string $className * @param mixed $identifier * @return object */ public function getProxy($className, $identifier) { $fqn = ClassUtils::generateProxyClassName($className, $this->_proxyNamespace); if (! class_exists($fqn, false)) { $fileName = $this->getProxyFileName($className); if ($this->_autoGenerate) { $this->_generateProxyClass($this->_em->getClassMetadata($className), $fileName, self::$_proxyClassTemplate); } require $fileName; } $entityPersister = $this->_em->getUnitOfWork()->getEntityPersister($className); return new $fqn($entityPersister, $identifier); } /** * Generate the Proxy file name * * @param string $className * @param string $baseDir Optional base directory for proxy file name generation. * If not specified, the directory configured on the Configuration of the * EntityManager will be used by this factory. * @return string */ private function getProxyFileName($className, $baseDir = null) { $proxyDir = $baseDir ?: $this->_proxyDir; return $proxyDir . DIRECTORY_SEPARATOR . '__CG__' . str_replace('\\', '', $className) . '.php'; } /** * Generates proxy classes for all given classes. * * @param array $classes The classes (ClassMetadata instances) for which to generate proxies. * @param string $toDir The target directory of the proxy classes. If not specified, the * directory configured on the Configuration of the EntityManager used * by this factory is used. * @return int Number of generated proxies. */ public function generateProxyClasses(array $classes, $toDir = null) { $proxyDir = $toDir ?: $this->_proxyDir; $proxyDir = rtrim($proxyDir, DIRECTORY_SEPARATOR); $num = 0; foreach ($classes as $class) { /* @var $class ClassMetadata */ if ($class->isMappedSuperclass || $class->reflClass->isAbstract()) { continue; } $proxyFileName = $this->getProxyFileName($class->name, $proxyDir); $this->_generateProxyClass($class, $proxyFileName, self::$_proxyClassTemplate); $num++; } return $num; } /** * Generates a proxy class file. * * @param ClassMetadata $class Metadata for the original class * @param string $fileName Filename (full path) for the generated class * @param string $file The proxy class template data */ private function _generateProxyClass(ClassMetadata $class, $fileName, $file) { $methods = $this->_generateMethods($class); $sleepImpl = $this->_generateSleep($class); $cloneImpl = $class->reflClass->hasMethod('__clone') ? 'parent::__clone();' : ''; // hasMethod() checks case-insensitive $placeholders = array( '', '', '', '', '', '' ); $className = ltrim($class->name, '\\'); $proxyClassName = ClassUtils::generateProxyClassName($class->name, $this->_proxyNamespace); $parts = explode('\\', strrev($proxyClassName), 2); $proxyClassNamespace = strrev($parts[1]); $proxyClassName = strrev($parts[0]); $replacements = array( $proxyClassNamespace, $proxyClassName, $className, $methods, $sleepImpl, $cloneImpl ); $file = str_replace($placeholders, $replacements, $file); $parentDirectory = dirname($fileName); if ( ! is_dir($parentDirectory)) { if (false === @mkdir($parentDirectory, 0775, true)) { throw ProxyException::proxyDirectoryNotWritable(); } } else if ( ! is_writable($parentDirectory)) { throw ProxyException::proxyDirectoryNotWritable(); } $tmpFileName = $fileName . '.' . uniqid("", true); file_put_contents($tmpFileName, $file); rename($tmpFileName, $fileName); } /** * Generates the methods of a proxy class. * * @param ClassMetadata $class * @return string The code of the generated methods. */ private function _generateMethods(ClassMetadata $class) { $methods = ''; $methodNames = array(); foreach ($class->reflClass->getMethods() as $method) { /* @var $method ReflectionMethod */ if ($method->isConstructor() || in_array(strtolower($method->getName()), array("__sleep", "__clone")) || isset($methodNames[$method->getName()])) { continue; } $methodNames[$method->getName()] = true; if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) { $methods .= "\n" . ' public function '; if ($method->returnsReference()) { $methods .= '&'; } $methods .= $method->getName() . '('; $firstParam = true; $parameterString = $argumentString = ''; foreach ($method->getParameters() as $param) { if ($firstParam) { $firstParam = false; } else { $parameterString .= ', '; $argumentString .= ', '; } // We need to pick the type hint class too if (($paramClass = $param->getClass()) !== null) { $parameterString .= '\\' . $paramClass->getName() . ' '; } else if ($param->isArray()) { $parameterString .= 'array '; } if ($param->isPassedByReference()) { $parameterString .= '&'; } $parameterString .= '$' . $param->getName(); $argumentString .= '$' . $param->getName(); if ($param->isDefaultValueAvailable()) { $parameterString .= ' = ' . var_export($param->getDefaultValue(), true); } } $methods .= $parameterString . ')'; $methods .= "\n" . ' {' . "\n"; if ($this->isShortIdentifierGetter($method, $class)) { $identifier = lcfirst(substr($method->getName(), 3)); $cast = in_array($class->fieldMappings[$identifier]['type'], array('integer', 'smallint')) ? '(int) ' : ''; $methods .= ' if ($this->__isInitialized__ === false) {' . "\n"; $methods .= ' return ' . $cast . '$this->_identifier["' . $identifier . '"];' . "\n"; $methods .= ' }' . "\n"; } $methods .= ' $this->__load();' . "\n"; $methods .= ' return parent::' . $method->getName() . '(' . $argumentString . ');'; $methods .= "\n" . ' }' . "\n"; } } return $methods; } /** * Check if the method is a short identifier getter. * * What does this mean? For proxy objects the identifier is already known, * however accessing the getter for this identifier usually triggers the * lazy loading, leading to a query that may not be necessary if only the * ID is interesting for the userland code (for example in views that * generate links to the entity, but do not display anything else). * * @param ReflectionMethod $method * @param ClassMetadata $class * @return bool */ private function isShortIdentifierGetter($method, ClassMetadata $class) { $identifier = lcfirst(substr($method->getName(), 3)); $cheapCheck = ( $method->getNumberOfParameters() == 0 && substr($method->getName(), 0, 3) == "get" && in_array($identifier, $class->identifier, true) && $class->hasField($identifier) && (($method->getEndLine() - $method->getStartLine()) <= 4) && in_array($class->fieldMappings[$identifier]['type'], array('integer', 'bigint', 'smallint', 'string')) ); if ($cheapCheck) { $code = file($method->getDeclaringClass()->getFileName()); $code = trim(implode(" ", array_slice($code, $method->getStartLine() - 1, $method->getEndLine() - $method->getStartLine() + 1))); $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier); if (preg_match($pattern, $code)) { return true; } } return false; } /** * Generates the code for the __sleep method for a proxy class. * * @param $class * @return string */ private function _generateSleep(ClassMetadata $class) { $sleepImpl = ''; if ($class->reflClass->hasMethod('__sleep')) { $sleepImpl .= "return array_merge(array('__isInitialized__'), parent::__sleep());"; } else { $sleepImpl .= "return array('__isInitialized__', "; $first = true; foreach ($class->getReflectionProperties() as $name => $prop) { if ($first) { $first = false; } else { $sleepImpl .= ', '; } $sleepImpl .= "'" . $name . "'"; } $sleepImpl .= ');'; } return $sleepImpl; } /** Proxy class code template */ private static $_proxyClassTemplate = '; /** * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE. */ class extends \ implements \Doctrine\ORM\Proxy\Proxy { private $_entityPersister; private $_identifier; public $__isInitialized__ = false; public function __construct($entityPersister, $identifier) { $this->_entityPersister = $entityPersister; $this->_identifier = $identifier; } /** @private */ public function __load() { if (!$this->__isInitialized__ && $this->_entityPersister) { $this->__isInitialized__ = true; if (method_exists($this, "__wakeup")) { // call this after __isInitialized__to avoid infinite recursion // but before loading to emulate what ClassMetadata::newInstance() // provides. $this->__wakeup(); } if ($this->_entityPersister->load($this->_identifier, $this) === null) { throw new \Doctrine\ORM\EntityNotFoundException(); } unset($this->_entityPersister, $this->_identifier); } } /** @private */ public function __isInitialized() { return $this->__isInitialized__; } public function __sleep() { } public function __clone() { if (!$this->__isInitialized__ && $this->_entityPersister) { $this->__isInitialized__ = true; $class = $this->_entityPersister->getClassMetadata(); $original = $this->_entityPersister->load($this->_identifier); if ($original === null) { throw new \Doctrine\ORM\EntityNotFoundException(); } foreach ($class->reflFields as $field => $reflProperty) { $reflProperty->setValue($this, $reflProperty->getValue($original)); } unset($this->_entityPersister, $this->_identifier); } } }'; } DoctrineORM-2.3.3/Doctrine/ORM/Query/Expr.php0000644000175100017510000004314312143374607020726 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query; /** * This class is used to generate DQL expressions via a set of PHP static functions * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei * @todo Rename: ExpressionBuilder */ class Expr { /** * Creates a conjunction of the given boolean expressions. * * Example: * * [php] * // (u.type = ?1) AND (u.role = ?2) * $expr->andX($expr->eq('u.type', ':1'), $expr->eq('u.role', ':2')); * * @param \Doctrine\ORM\Query\Expr\Comparison | * \Doctrine\ORM\Query\Expr\Func | * \Doctrine\ORM\Query\Expr\Orx * $x Optional clause. Defaults = null, but requires at least one defined when converting to string. * @return Expr\Andx */ public function andX($x = null) { return new Expr\Andx(func_get_args()); } /** * Creates a disjunction of the given boolean expressions. * * Example: * * [php] * // (u.type = ?1) OR (u.role = ?2) * $q->where($q->expr()->orX('u.type = ?1', 'u.role = ?2')); * * @param mixed $x Optional clause. Defaults = null, but requires * at least one defined when converting to string. * @return Expr\Orx */ public function orX($x = null) { return new Expr\Orx(func_get_args()); } /** * Creates an ASCending order expression. * * @param $sort * @return Expr\OrderBy */ public function asc($expr) { return new Expr\OrderBy($expr, 'ASC'); } /** * Creates a DESCending order expression. * * @param $sort * @return Expr\OrderBy */ public function desc($expr) { return new Expr\OrderBy($expr, 'DESC'); } /** * Creates an equality comparison expression with the given arguments. * * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a = . Example: * * [php] * // u.id = ?1 * $expr->eq('u.id', '?1'); * * @param mixed $x Left expression * @param mixed $y Right expression * @return Expr\Comparison */ public function eq($x, $y) { return new Expr\Comparison($x, Expr\Comparison::EQ, $y); } /** * Creates an instance of Expr\Comparison, with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <> . Example: * * [php] * // u.id <> ?1 * $q->where($q->expr()->neq('u.id', '?1')); * * @param mixed $x Left expression * @param mixed $y Right expression * @return Expr\Comparison */ public function neq($x, $y) { return new Expr\Comparison($x, Expr\Comparison::NEQ, $y); } /** * Creates an instance of Expr\Comparison, with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a < . Example: * * [php] * // u.id < ?1 * $q->where($q->expr()->lt('u.id', '?1')); * * @param mixed $x Left expression * @param mixed $y Right expression * @return Expr\Comparison */ public function lt($x, $y) { return new Expr\Comparison($x, Expr\Comparison::LT, $y); } /** * Creates an instance of Expr\Comparison, with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <= . Example: * * [php] * // u.id <= ?1 * $q->where($q->expr()->lte('u.id', '?1')); * * @param mixed $x Left expression * @param mixed $y Right expression * @return Expr\Comparison */ public function lte($x, $y) { return new Expr\Comparison($x, Expr\Comparison::LTE, $y); } /** * Creates an instance of Expr\Comparison, with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a > . Example: * * [php] * // u.id > ?1 * $q->where($q->expr()->gt('u.id', '?1')); * * @param mixed $x Left expression * @param mixed $y Right expression * @return Expr\Comparison */ public function gt($x, $y) { return new Expr\Comparison($x, Expr\Comparison::GT, $y); } /** * Creates an instance of Expr\Comparison, with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a >= . Example: * * [php] * // u.id >= ?1 * $q->where($q->expr()->gte('u.id', '?1')); * * @param mixed $x Left expression * @param mixed $y Right expression * @return Expr\Comparison */ public function gte($x, $y) { return new Expr\Comparison($x, Expr\Comparison::GTE, $y); } /** * Creates an instance of AVG() function, with the given argument. * * @param mixed $x Argument to be used in AVG() function. * @return Expr\Func */ public function avg($x) { return new Expr\Func('AVG', array($x)); } /** * Creates an instance of MAX() function, with the given argument. * * @param mixed $x Argument to be used in MAX() function. * @return Expr\Func */ public function max($x) { return new Expr\Func('MAX', array($x)); } /** * Creates an instance of MIN() function, with the given argument. * * @param mixed $x Argument to be used in MIN() function. * @return Expr\Func */ public function min($x) { return new Expr\Func('MIN', array($x)); } /** * Creates an instance of COUNT() function, with the given argument. * * @param mixed $x Argument to be used in COUNT() function. * @return Expr\Func */ public function count($x) { return new Expr\Func('COUNT', array($x)); } /** * Creates an instance of COUNT(DISTINCT) function, with the given argument. * * @param mixed $x Argument to be used in COUNT(DISTINCT) function. * @return string */ public function countDistinct($x) { return 'COUNT(DISTINCT ' . implode(', ', func_get_args()) . ')'; } /** * Creates an instance of EXISTS() function, with the given DQL Subquery. * * @param mixed $subquery DQL Subquery to be used in EXISTS() function. * @return Expr\Func */ public function exists($subquery) { return new Expr\Func('EXISTS', array($subquery)); } /** * Creates an instance of ALL() function, with the given DQL Subquery. * * @param mixed $subquery DQL Subquery to be used in ALL() function. * @return Expr\Func */ public function all($subquery) { return new Expr\Func('ALL', array($subquery)); } /** * Creates a SOME() function expression with the given DQL subquery. * * @param mixed $subquery DQL Subquery to be used in SOME() function. * @return Expr\Func */ public function some($subquery) { return new Expr\Func('SOME', array($subquery)); } /** * Creates an ANY() function expression with the given DQL subquery. * * @param mixed $subquery DQL Subquery to be used in ANY() function. * @return Expr\Func */ public function any($subquery) { return new Expr\Func('ANY', array($subquery)); } /** * Creates a negation expression of the given restriction. * * @param mixed $restriction Restriction to be used in NOT() function. * @return Expr\Func */ public function not($restriction) { return new Expr\Func('NOT', array($restriction)); } /** * Creates an ABS() function expression with the given argument. * * @param mixed $x Argument to be used in ABS() function. * @return Expr\Func */ public function abs($x) { return new Expr\Func('ABS', array($x)); } /** * Creates a product mathematical expression with the given arguments. * * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a * . Example: * * [php] * // u.salary * u.percentAnualSalaryIncrease * $q->expr()->prod('u.salary', 'u.percentAnualSalaryIncrease') * * @param mixed $x Left expression * @param mixed $y Right expression * @return Expr\Math */ public function prod($x, $y) { return new Expr\Math($x, '*', $y); } /** * Creates a difference mathematical expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a - . Example: * * [php] * // u.monthlySubscriptionCount - 1 * $q->expr()->diff('u.monthlySubscriptionCount', '1') * * @param mixed $x Left expression * @param mixed $y Right expression * @return Expr\Math */ public function diff($x, $y) { return new Expr\Math($x, '-', $y); } /** * Creates a sum mathematical expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a + . Example: * * [php] * // u.numChildren + 1 * $q->expr()->diff('u.numChildren', '1') * * @param mixed $x Left expression * @param mixed $y Right expression * @return Expr\Math */ public function sum($x, $y) { return new Expr\Math($x, '+', $y); } /** * Creates a quotient mathematical expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a / . Example: * * [php] * // u.total / u.period * $expr->quot('u.total', 'u.period') * * @param mixed $x Left expression * @param mixed $y Right expression * @return Expr\Math */ public function quot($x, $y) { return new Expr\Math($x, '/', $y); } /** * Creates a SQRT() function expression with the given argument. * * @param mixed $x Argument to be used in SQRT() function. * @return Expr\Func */ public function sqrt($x) { return new Expr\Func('SQRT', array($x)); } /** * Creates an IN() expression with the given arguments. * * @param string $x Field in string format to be restricted by IN() function * @param mixed $y Argument to be used in IN() function. * @return Expr\Func */ public function in($x, $y) { if (is_array($y)) { foreach ($y as &$literal) { if ( ! ($literal instanceof Expr\Literal)) { $literal = $this->_quoteLiteral($literal); } } } return new Expr\Func($x . ' IN', (array) $y); } /** * Creates a NOT IN() expression with the given arguments. * * @param string $x Field in string format to be restricted by NOT IN() function * @param mixed $y Argument to be used in NOT IN() function. * @return Expr\Func */ public function notIn($x, $y) { if (is_array($y)) { foreach ($y as &$literal) { if ( ! ($literal instanceof Expr\Literal)) { $literal = $this->_quoteLiteral($literal); } } } return new Expr\Func($x . ' NOT IN', (array) $y); } /** * Creates an IS NULL expression with the given arguments. * * @param string $x Field in string format to be restricted by IS NULL * @return string */ public function isNull($x) { return $x . ' IS NULL'; } /** * Creates an IS NOT NULL expression with the given arguments. * * @param string $x Field in string format to be restricted by IS NOT NULL * @return string */ public function isNotNull($x) { return $x . ' IS NOT NULL'; } /** * Creates a LIKE() comparison expression with the given arguments. * * @param string $x Field in string format to be inspected by LIKE() comparison. * @param mixed $y Argument to be used in LIKE() comparison. * @return Expr\Comparison */ public function like($x, $y) { return new Expr\Comparison($x, 'LIKE', $y); } /** * Creates a CONCAT() function expression with the given arguments. * * @param mixed $x First argument to be used in CONCAT() function. * @param mixed $x Second argument to be used in CONCAT() function. * @return Expr\Func */ public function concat($x, $y) { return new Expr\Func('CONCAT', array($x, $y)); } /** * Creates a SUBSTRING() function expression with the given arguments. * * @param mixed $x Argument to be used as string to be cropped by SUBSTRING() function. * @param integer $from Initial offset to start cropping string. May accept negative values. * @param integer $len Length of crop. May accept negative values. * @return Expr\Func */ public function substring($x, $from, $len = null) { $args = array($x, $from); if (null !== $len) { $args[] = $len; } return new Expr\Func('SUBSTRING', $args); } /** * Creates a LOWER() function expression with the given argument. * * @param mixed $x Argument to be used in LOWER() function. * @return Expr\Func A LOWER function expression. */ public function lower($x) { return new Expr\Func('LOWER', array($x)); } /** * Creates an UPPER() function expression with the given argument. * * @param mixed $x Argument to be used in UPPER() function. * @return Expr\Func An UPPER function expression. */ public function upper($x) { return new Expr\Func('UPPER', array($x)); } /** * Creates a LENGTH() function expression with the given argument. * * @param mixed $x Argument to be used as argument of LENGTH() function. * @return Expr\Func A LENGTH function expression. */ public function length($x) { return new Expr\Func('LENGTH', array($x)); } /** * Creates a literal expression of the given argument. * * @param mixed $literal Argument to be converted to literal. * @return Expr\Literal */ public function literal($literal) { return new Expr\Literal($this->_quoteLiteral($literal)); } /** * Quotes a literal value, if necessary, according to the DQL syntax. * * @param mixed $literal The literal value. * @return string */ private function _quoteLiteral($literal) { if (is_numeric($literal) && !is_string($literal)) { return (string) $literal; } else if (is_bool($literal)) { return $literal ? "true" : "false"; } else { return "'" . str_replace("'", "''", $literal) . "'"; } } /** * Creates an instance of BETWEEN() function, with the given argument. * * @param mixed $val Valued to be inspected by range values. * @param integer $x Starting range value to be used in BETWEEN() function. * @param integer $y End point value to be used in BETWEEN() function. * @return Expr\Func A BETWEEN expression. */ public function between($val, $x, $y) { return $val . ' BETWEEN ' . $x . ' AND ' . $y; } /** * Creates an instance of TRIM() function, with the given argument. * * @param mixed $x Argument to be used as argument of TRIM() function. * @return Expr\Func a TRIM expression. */ public function trim($x) { return new Expr\Func('TRIM', $x); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/FilterCollection.php0000644000175100017510000001227212143374607023250 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query; use Doctrine\ORM\Configuration, Doctrine\ORM\EntityManager; /** * Collection class for all the query filters. * * @author Alexander */ class FilterCollection { /* Filter STATES */ /** * A filter object is in CLEAN state when it has no changed parameters. */ const FILTERS_STATE_CLEAN = 1; /** * A filter object is in DIRTY state when it has changed parameters. */ const FILTERS_STATE_DIRTY = 2; /** * The used Configuration. * * @var Doctrine\ORM\Configuration */ private $config; /** * The EntityManager that "owns" this FilterCollection instance. * * @var Doctrine\ORM\EntityManager */ private $em; /** * Instances of enabled filters. * * @var array */ private $enabledFilters = array(); /** * @var string The filter hash from the last time the query was parsed. */ private $filterHash; /** * @var integer $state The current state of this filter */ private $filtersState = self::FILTERS_STATE_CLEAN; /** * Constructor. * * @param EntityManager $em */ public function __construct(EntityManager $em) { $this->em = $em; $this->config = $em->getConfiguration(); } /** * Get all the enabled filters. * * @return array The enabled filters. */ public function getEnabledFilters() { return $this->enabledFilters; } /** * Enables a filter from the collection. * * @param string $name Name of the filter. * * @throws \InvalidArgumentException If the filter does not exist. * * @return SQLFilter The enabled filter. */ public function enable($name) { if (null === $filterClass = $this->config->getFilterClassName($name)) { throw new \InvalidArgumentException("Filter '" . $name . "' does not exist."); } if (!isset($this->enabledFilters[$name])) { $this->enabledFilters[$name] = new $filterClass($this->em); // Keep the enabled filters sorted for the hash ksort($this->enabledFilters); // Now the filter collection is dirty $this->filtersState = self::FILTERS_STATE_DIRTY; } return $this->enabledFilters[$name]; } /** * Disables a filter. * * @param string $name Name of the filter. * * @return SQLFilter The disabled filter. * * @throws \InvalidArgumentException If the filter does not exist. */ public function disable($name) { // Get the filter to return it $filter = $this->getFilter($name); unset($this->enabledFilters[$name]); // Now the filter collection is dirty $this->filtersState = self::FILTERS_STATE_DIRTY; return $filter; } /** * Get an enabled filter from the collection. * * @param string $name Name of the filter. * * @return SQLFilter The filter. * * @throws \InvalidArgumentException If the filter is not enabled. */ public function getFilter($name) { if (!isset($this->enabledFilters[$name])) { throw new \InvalidArgumentException("Filter '" . $name . "' is not enabled."); } return $this->enabledFilters[$name]; } /** * @return boolean True, if the filter collection is clean. */ public function isClean() { return self::FILTERS_STATE_CLEAN === $this->filtersState; } /** * Generates a string of currently enabled filters to use for the cache id. * * @return string */ public function getHash() { // If there are only clean filters, the previous hash can be returned if (self::FILTERS_STATE_CLEAN === $this->filtersState) { return $this->filterHash; } $filterHash = ''; foreach ($this->enabledFilters as $name => $filter) { $filterHash .= $name . $filter; } return $filterHash; } /** * Set the filter state to dirty. */ public function setFiltersStateDirty() { $this->filtersState = self::FILTERS_STATE_DIRTY; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Lexer.php0000644000175100017510000001600712143374607021066 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query; /** * Scans a DQL query for tokens. * * @author Guilherme Blanco * @author Janne Vanhala * @author Roman Borschel * @since 2.0 */ class Lexer extends \Doctrine\Common\Lexer { // All tokens that are not valid identifiers must be < 100 const T_NONE = 1; const T_INTEGER = 2; const T_STRING = 3; const T_INPUT_PARAMETER = 4; const T_FLOAT = 5; const T_CLOSE_PARENTHESIS = 6; const T_OPEN_PARENTHESIS = 7; const T_COMMA = 8; const T_DIVIDE = 9; const T_DOT = 10; const T_EQUALS = 11; const T_GREATER_THAN = 12; const T_LOWER_THAN = 13; const T_MINUS = 14; const T_MULTIPLY = 15; const T_NEGATE = 16; const T_PLUS = 17; const T_OPEN_CURLY_BRACE = 18; const T_CLOSE_CURLY_BRACE = 19; // All tokens that are also identifiers should be >= 100 const T_IDENTIFIER = 100; const T_ALL = 101; const T_AND = 102; const T_ANY = 103; const T_AS = 104; const T_ASC = 105; const T_AVG = 106; const T_BETWEEN = 107; const T_BOTH = 108; const T_BY = 109; const T_CASE = 110; const T_COALESCE = 111; const T_COUNT = 112; const T_DELETE = 113; const T_DESC = 114; const T_DISTINCT = 115; const T_ELSE = 116; const T_EMPTY = 117; const T_END = 118; const T_ESCAPE = 119; const T_EXISTS = 120; const T_FALSE = 121; const T_FROM = 122; const T_GROUP = 123; const T_HAVING = 124; const T_HIDDEN = 125; const T_IN = 126; const T_INDEX = 127; const T_INNER = 128; const T_INSTANCE = 129; const T_IS = 130; const T_JOIN = 131; const T_LEADING = 132; const T_LEFT = 133; const T_LIKE = 134; const T_MAX = 135; const T_MEMBER = 136; const T_MIN = 137; const T_NOT = 138; const T_NULL = 139; const T_NULLIF = 140; const T_OF = 141; const T_OR = 142; const T_ORDER = 143; const T_OUTER = 144; const T_SELECT = 145; const T_SET = 146; const T_SOME = 147; const T_SUM = 148; const T_THEN = 149; const T_TRAILING = 150; const T_TRUE = 151; const T_UPDATE = 152; const T_WHEN = 153; const T_WHERE = 154; const T_WITH = 155; const T_PARTIAL = 156; /** * Creates a new query scanner object. * * @param string $input a query string */ public function __construct($input) { $this->setInput($input); } /** * @inheritdoc */ protected function getCatchablePatterns() { return array( '[a-z_\\\][a-z0-9_\:\\\]*[a-z0-9_]{1}', '(?:[0-9]+(?:[\.][0-9]+)*)(?:e[+-]?[0-9]+)?', "'(?:[^']|'')*'", '\?[0-9]*|:[a-z]{1}[a-z0-9_]{0,}' ); } /** * @inheritdoc */ protected function getNonCatchablePatterns() { return array('\s+', '(.)'); } /** * @inheritdoc */ protected function getType(&$value) { $type = self::T_NONE; switch (true) { // Recognize numeric values case (is_numeric($value)): if (strpos($value, '.') !== false || stripos($value, 'e') !== false) { return self::T_FLOAT; } return self::T_INTEGER; // Recognize quoted strings case ($value[0] === "'"): $value = str_replace("''", "'", substr($value, 1, strlen($value) - 2)); return self::T_STRING; // Recognize identifiers case (ctype_alpha($value[0]) || $value[0] === '_'): $name = 'Doctrine\ORM\Query\Lexer::T_' . strtoupper($value); if (defined($name)) { $type = constant($name); if ($type > 100) { return $type; } } return self::T_IDENTIFIER; // Recognize input parameters case ($value[0] === '?' || $value[0] === ':'): return self::T_INPUT_PARAMETER; // Recognize symbols case ($value === '.'): return self::T_DOT; case ($value === ','): return self::T_COMMA; case ($value === '('): return self::T_OPEN_PARENTHESIS; case ($value === ')'): return self::T_CLOSE_PARENTHESIS; case ($value === '='): return self::T_EQUALS; case ($value === '>'): return self::T_GREATER_THAN; case ($value === '<'): return self::T_LOWER_THAN; case ($value === '+'): return self::T_PLUS; case ($value === '-'): return self::T_MINUS; case ($value === '*'): return self::T_MULTIPLY; case ($value === '/'): return self::T_DIVIDE; case ($value === '!'): return self::T_NEGATE; case ($value === '{'): return self::T_OPEN_CURLY_BRACE; case ($value === '}'): return self::T_CLOSE_CURLY_BRACE; // Default default: // Do nothing } return $type; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Parameter.php0000644000175100017510000000472712143374607021735 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query; /** * Define a Query Parameter * * @link www.doctrine-project.org * @since 2.3 * @author Guilherme Blanco */ class Parameter { /** * @var string Parameter name */ private $name; /** * @var mixed Parameter value */ private $value; /** * @var mixed Parameter type */ private $type; /** * Constructor. * * @param string $name Parameter name * @param mixed $value Parameter value * @param mixed $type Parameter type */ public function __construct($name, $value, $type = null) { $this->name = trim($name, ':'); $this->setValue($value, $type); } /** * Retrieve the Parameter name. * * @return string */ public function getName() { return $this->name; } /** * Retrieve the Parameter value. * * @return mixed */ public function getValue() { return $this->value; } /** * Retrieve the Parameter type. * * @return mixed */ public function getType() { return $this->type; } /** * Define the Parameter value. * * @param mixed $value Parameter value * @param mixed $type Parameter type */ public function setValue($value, $type = null) { $this->value = $value; $this->type = $type ?: ParameterTypeInferer::inferType($value); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/ParameterTypeInferer.php0000644000175100017510000000417412143374607024106 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Types\Type; /** * Provides an enclosed support for parameter infering. * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ParameterTypeInferer { /** * Infer type of a given value, returning a compatible constant: * - Type (\Doctrine\DBAL\Types\Type::*) * - Connection (\Doctrine\DBAL\Connection::PARAM_*) * * @param mixed $value Parameter value * * @return mixed Parameter type constant */ public static function inferType($value) { if (is_integer($value)) { return Type::INTEGER; } if ($value instanceof \DateTime) { return Type::DATETIME; } if (is_array($value)) { return is_integer(current($value)) ? Connection::PARAM_INT_ARRAY : Connection::PARAM_STR_ARRAY; } return \PDO::PARAM_STR; } }DoctrineORM-2.3.3/Doctrine/ORM/Query/Parser.php0000644000175100017510000031163412143374607021247 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query; use Doctrine\ORM\Query; use Doctrine\ORM\Mapping\ClassMetadata; /** * An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language. * Parses a DQL query, reports any errors in it, and generates an AST. * * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Janne Vanhala */ class Parser { /** READ-ONLY: Maps BUILT-IN string function names to AST class names. */ private static $_STRING_FUNCTIONS = array( 'concat' => 'Doctrine\ORM\Query\AST\Functions\ConcatFunction', 'substring' => 'Doctrine\ORM\Query\AST\Functions\SubstringFunction', 'trim' => 'Doctrine\ORM\Query\AST\Functions\TrimFunction', 'lower' => 'Doctrine\ORM\Query\AST\Functions\LowerFunction', 'upper' => 'Doctrine\ORM\Query\AST\Functions\UpperFunction', 'identity' => 'Doctrine\ORM\Query\AST\Functions\IdentityFunction', ); /** READ-ONLY: Maps BUILT-IN numeric function names to AST class names. */ private static $_NUMERIC_FUNCTIONS = array( 'length' => 'Doctrine\ORM\Query\AST\Functions\LengthFunction', 'locate' => 'Doctrine\ORM\Query\AST\Functions\LocateFunction', 'abs' => 'Doctrine\ORM\Query\AST\Functions\AbsFunction', 'sqrt' => 'Doctrine\ORM\Query\AST\Functions\SqrtFunction', 'mod' => 'Doctrine\ORM\Query\AST\Functions\ModFunction', 'size' => 'Doctrine\ORM\Query\AST\Functions\SizeFunction', 'date_diff' => 'Doctrine\ORM\Query\AST\Functions\DateDiffFunction', 'bit_and' => 'Doctrine\ORM\Query\AST\Functions\BitAndFunction', 'bit_or' => 'Doctrine\ORM\Query\AST\Functions\BitOrFunction', ); /** READ-ONLY: Maps BUILT-IN datetime function names to AST class names. */ private static $_DATETIME_FUNCTIONS = array( 'current_date' => 'Doctrine\ORM\Query\AST\Functions\CurrentDateFunction', 'current_time' => 'Doctrine\ORM\Query\AST\Functions\CurrentTimeFunction', 'current_timestamp' => 'Doctrine\ORM\Query\AST\Functions\CurrentTimestampFunction', 'date_add' => 'Doctrine\ORM\Query\AST\Functions\DateAddFunction', 'date_sub' => 'Doctrine\ORM\Query\AST\Functions\DateSubFunction', ); /** * Expressions that were encountered during parsing of identifiers and expressions * and still need to be validated. */ private $_deferredIdentificationVariables = array(); private $_deferredPartialObjectExpressions = array(); private $_deferredPathExpressions = array(); private $_deferredResultVariables = array(); /** * The lexer. * * @var \Doctrine\ORM\Query\Lexer */ private $_lexer; /** * The parser result. * * @var \Doctrine\ORM\Query\ParserResult */ private $_parserResult; /** * The EntityManager. * * @var EnityManager */ private $_em; /** * The Query to parse. * * @var Query */ private $_query; /** * Map of declared query components in the parsed query. * * @var array */ private $_queryComponents = array(); /** * Keeps the nesting level of defined ResultVariables * * @var integer */ private $_nestingLevel = 0; /** * 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 $_customOutputWalker; /** * @var array */ private $_identVariableExpressions = array(); /** * Check if a function is internally defined. Used to prevent overwriting * of built-in functions through user-defined functions. * * @param string $functionName * @return bool */ static public function isInternalFunction($functionName) { $functionName = strtolower($functionName); return isset(self::$_STRING_FUNCTIONS[$functionName]) || isset(self::$_DATETIME_FUNCTIONS[$functionName]) || isset(self::$_NUMERIC_FUNCTIONS[$functionName]); } /** * Creates a new query parser object. * * @param Query $query The Query to parse. */ public function __construct(Query $query) { $this->_query = $query; $this->_em = $query->getEntityManager(); $this->_lexer = new Lexer($query->getDql()); $this->_parserResult = new ParserResult(); } /** * 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 $className */ public function addCustomTreeWalker($className) { $this->_customTreeWalkers[] = $className; } /** * Gets the lexer used by the parser. * * @return \Doctrine\ORM\Query\Lexer */ public function getLexer() { return $this->_lexer; } /** * Gets the ParserResult that is being filled with information during parsing. * * @return \Doctrine\ORM\Query\ParserResult */ public function getParserResult() { return $this->_parserResult; } /** * Gets the EntityManager used by the parser. * * @return EntityManager */ public function getEntityManager() { return $this->_em; } /** * Parse and build AST for the given Query. * * @return \Doctrine\ORM\Query\AST\SelectStatement | * \Doctrine\ORM\Query\AST\UpdateStatement | * \Doctrine\ORM\Query\AST\DeleteStatement */ public function getAST() { // Parse & build AST $AST = $this->QueryLanguage(); // Process any deferred validations of some nodes in the AST. // This also allows post-processing of the AST for modification purposes. $this->_processDeferredIdentificationVariables(); if ($this->_deferredPartialObjectExpressions) { $this->_processDeferredPartialObjectExpressions(); } if ($this->_deferredPathExpressions) { $this->_processDeferredPathExpressions($AST); } if ($this->_deferredResultVariables) { $this->_processDeferredResultVariables(); } $this->_processRootEntityAliasSelected(); // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot! $this->fixIdentificationVariableOrder($AST); return $AST; } /** * Attempts to match the given token with the current lookahead token. * * If they match, updates the lookahead token; otherwise raises a syntax * error. * * @param int token type * @return void * @throws QueryException If the tokens dont match. */ public function match($token) { $lookaheadType = $this->_lexer->lookahead['type']; // short-circuit on first condition, usually types match if ($lookaheadType !== $token && $token !== Lexer::T_IDENTIFIER && $lookaheadType <= Lexer::T_IDENTIFIER) { $this->syntaxError($this->_lexer->getLiteral($token)); } $this->_lexer->moveNext(); } /** * Free this parser enabling it to be reused * * @param boolean $deep Whether to clean peek and reset errors * @param integer $position Position to reset */ public function free($deep = false, $position = 0) { // WARNING! Use this method with care. It resets the scanner! $this->_lexer->resetPosition($position); // Deep = true cleans peek and also any previously defined errors if ($deep) { $this->_lexer->resetPeek(); } $this->_lexer->token = null; $this->_lexer->lookahead = null; } /** * Parses a query string. * * @return ParserResult */ public function parse() { $AST = $this->getAST(); if (($customWalkers = $this->_query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) { $this->_customTreeWalkers = $customWalkers; } if (($customOutputWalker = $this->_query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER)) !== false) { $this->_customOutputWalker = $customOutputWalker; } // 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); } switch (true) { case ($AST instanceof AST\UpdateStatement): $treeWalkerChain->walkUpdateStatement($AST); break; case ($AST instanceof AST\DeleteStatement): $treeWalkerChain->walkDeleteStatement($AST); break; case ($AST instanceof AST\SelectStatement): default: $treeWalkerChain->walkSelectStatement($AST); } } $outputWalkerClass = $this->_customOutputWalker ?: __NAMESPACE__ . '\SqlWalker'; $outputWalker = new $outputWalkerClass($this->_query, $this->_parserResult, $this->_queryComponents); // Assign an SQL executor to the parser result $this->_parserResult->setSqlExecutor($outputWalker->getExecutor($AST)); return $this->_parserResult; } /** * Fix order of identification variables. * * They have to appear in the select clause in the same order as the * declarations (from ... x join ... y join ... z ...) appear in the query * as the hydration process relies on that order for proper operation. * * @param AST\SelectStatement|AST\DeleteStatement|AST\UpdateStatement $AST * @return void */ private function fixIdentificationVariableOrder($AST) { if (count($this->_identVariableExpressions) <= 1) { return; } foreach ($this->_queryComponents as $dqlAlias => $qComp) { if ( ! isset($this->_identVariableExpressions[$dqlAlias])) { continue; } $expr = $this->_identVariableExpressions[$dqlAlias]; $key = array_search($expr, $AST->selectClause->selectExpressions); unset($AST->selectClause->selectExpressions[$key]); $AST->selectClause->selectExpressions[] = $expr; } } /** * Generates a new syntax error. * * @param string $expected Expected string. * @param array $token Got token. * * @throws \Doctrine\ORM\Query\QueryException */ public function syntaxError($expected = '', $token = null) { if ($token === null) { $token = $this->_lexer->lookahead; } $tokenPos = (isset($token['position'])) ? $token['position'] : '-1'; $message = "line 0, col {$tokenPos}: Error: "; $message .= ($expected !== '') ? "Expected {$expected}, got " : 'Unexpected '; $message .= ($this->_lexer->lookahead === null) ? 'end of string.' : "'{$token['value']}'"; throw QueryException::syntaxError($message, QueryException::dqlError($this->_query->getDQL())); } /** * Generates a new semantical error. * * @param string $message Optional message. * @param array $token Optional token. * * @throws \Doctrine\ORM\Query\QueryException */ public function semanticalError($message = '', $token = null) { if ($token === null) { $token = $this->_lexer->lookahead; } // Minimum exposed chars ahead of token $distance = 12; // Find a position of a final word to display in error string $dql = $this->_query->getDql(); $length = strlen($dql); $pos = $token['position'] + $distance; $pos = strpos($dql, ' ', ($length > $pos) ? $pos : $length); $length = ($pos !== false) ? $pos - $token['position'] : $distance; $tokenPos = (isset($token['position']) && $token['position'] > 0) ? $token['position'] : '-1'; $tokenStr = substr($dql, $token['position'], $length); // Building informative message $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message; throw QueryException::semanticalError($message, QueryException::dqlError($this->_query->getDQL())); } /** * Peek beyond the matched closing parenthesis and return the first token after that one. * * @param boolean $resetPeek Reset peek after finding the closing parenthesis * @return array */ private function _peekBeyondClosingParenthesis($resetPeek = true) { $token = $this->_lexer->peek(); $numUnmatched = 1; while ($numUnmatched > 0 && $token !== null) { switch ($token['type']) { case Lexer::T_OPEN_PARENTHESIS: ++$numUnmatched; break; case Lexer::T_CLOSE_PARENTHESIS: --$numUnmatched; break; default: // Do nothing } $token = $this->_lexer->peek(); } if ($resetPeek) { $this->_lexer->resetPeek(); } return $token; } /** * Checks if the given token indicates a mathematical operator. * * @return boolean TRUE if the token is a mathematical operator, FALSE otherwise. */ private function _isMathOperator($token) { return in_array($token['type'], array(Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY)); } /** * Checks if the next-next (after lookahead) token starts a function. * * @return boolean TRUE if the next-next tokens start a function, FALSE otherwise. */ private function _isFunction() { $peek = $this->_lexer->peek(); $nextpeek = $this->_lexer->peek(); $this->_lexer->resetPeek(); // We deny the COUNT(SELECT * FROM User u) here. COUNT won't be considered a function return ($peek['type'] === Lexer::T_OPEN_PARENTHESIS && $nextpeek['type'] !== Lexer::T_SELECT); } /** * Checks whether the given token type indicates an aggregate function. * * @return boolean TRUE if the token type is an aggregate function, FALSE otherwise. */ private function _isAggregateFunction($tokenType) { return in_array($tokenType, array(Lexer::T_AVG, Lexer::T_MIN, Lexer::T_MAX, Lexer::T_SUM, Lexer::T_COUNT)); } /** * Checks whether the current lookahead token of the lexer has the type T_ALL, T_ANY or T_SOME. * * @return boolean */ private function _isNextAllAnySome() { return in_array($this->_lexer->lookahead['type'], array(Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME)); } /** * Validates that the given IdentificationVariable is semantically correct. * It must exist in query components list. * * @return void */ private function _processDeferredIdentificationVariables() { foreach ($this->_deferredIdentificationVariables as $deferredItem) { $identVariable = $deferredItem['expression']; // Check if IdentificationVariable exists in queryComponents if ( ! isset($this->_queryComponents[$identVariable])) { $this->semanticalError( "'$identVariable' is not defined.", $deferredItem['token'] ); } $qComp = $this->_queryComponents[$identVariable]; // Check if queryComponent points to an AbstractSchemaName or a ResultVariable if ( ! isset($qComp['metadata'])) { $this->semanticalError( "'$identVariable' does not point to a Class.", $deferredItem['token'] ); } // Validate if identification variable nesting level is lower or equal than the current one if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) { $this->semanticalError( "'$identVariable' is used outside the scope of its declaration.", $deferredItem['token'] ); } } } /** * Validates that the given PartialObjectExpression is semantically correct. * It must exist in query components list. * * @return void */ private function _processDeferredPartialObjectExpressions() { foreach ($this->_deferredPartialObjectExpressions as $deferredItem) { $expr = $deferredItem['expression']; $class = $this->_queryComponents[$expr->identificationVariable]['metadata']; foreach ($expr->partialFieldSet as $field) { if (isset($class->fieldMappings[$field])) { continue; } $this->semanticalError( "There is no mapped field named '$field' on class " . $class->name . ".", $deferredItem['token'] ); } if (array_intersect($class->identifier, $expr->partialFieldSet) != $class->identifier) { $this->semanticalError( "The partial field selection of class " . $class->name . " must contain the identifier.", $deferredItem['token'] ); } } } /** * Validates that the given ResultVariable is semantically correct. * It must exist in query components list. * * @return void */ private function _processDeferredResultVariables() { foreach ($this->_deferredResultVariables as $deferredItem) { $resultVariable = $deferredItem['expression']; // Check if ResultVariable exists in queryComponents if ( ! isset($this->_queryComponents[$resultVariable])) { $this->semanticalError( "'$resultVariable' is not defined.", $deferredItem['token'] ); } $qComp = $this->_queryComponents[$resultVariable]; // Check if queryComponent points to an AbstractSchemaName or a ResultVariable if ( ! isset($qComp['resultVariable'])) { $this->semanticalError( "'$resultVariable' does not point to a ResultVariable.", $deferredItem['token'] ); } // Validate if identification variable nesting level is lower or equal than the current one if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) { $this->semanticalError( "'$resultVariable' is used outside the scope of its declaration.", $deferredItem['token'] ); } } } /** * Validates that the given PathExpression is semantically correct for grammar rules: * * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression * StateFieldPathExpression ::= IdentificationVariable "." StateField * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField * * @param array $deferredItem * @param mixed $AST */ private function _processDeferredPathExpressions($AST) { foreach ($this->_deferredPathExpressions as $deferredItem) { $pathExpression = $deferredItem['expression']; $qComp = $this->_queryComponents[$pathExpression->identificationVariable]; $class = $qComp['metadata']; if (($field = $pathExpression->field) === null) { $field = $pathExpression->field = $class->identifier[0]; } // Check if field or association exists if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) { $this->semanticalError( 'Class ' . $class->name . ' has no field or association named ' . $field, $deferredItem['token'] ); } $fieldType = AST\PathExpression::TYPE_STATE_FIELD; if (isset($class->associationMappings[$field])) { $assoc = $class->associationMappings[$field]; $fieldType = ($assoc['type'] & ClassMetadata::TO_ONE) ? AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION : AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION; } // Validate if PathExpression is one of the expected types $expectedType = $pathExpression->expectedType; if ( ! ($expectedType & $fieldType)) { // We need to recognize which was expected type(s) $expectedStringTypes = array(); // Validate state field type if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) { $expectedStringTypes[] = 'StateFieldPathExpression'; } // Validate single valued association (*-to-one) if ($expectedType & AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) { $expectedStringTypes[] = 'SingleValuedAssociationField'; } // Validate single valued association (*-to-many) if ($expectedType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) { $expectedStringTypes[] = 'CollectionValuedAssociationField'; } // Build the error message $semanticalError = 'Invalid PathExpression. '; $semanticalError .= (count($expectedStringTypes) == 1) ? 'Must be a ' . $expectedStringTypes[0] . '.' : implode(' or ', $expectedStringTypes) . ' expected.'; $this->semanticalError($semanticalError, $deferredItem['token']); } // We need to force the type in PathExpression $pathExpression->type = $fieldType; } } private function _processRootEntityAliasSelected() { if ( ! count($this->_identVariableExpressions)) { return; } $foundRootEntity = false; foreach ($this->_identVariableExpressions as $dqlAlias => $expr) { if (isset($this->_queryComponents[$dqlAlias]) && $this->_queryComponents[$dqlAlias]['parent'] === null) { $foundRootEntity = true; } } if ( ! $foundRootEntity) { $this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.'); } } /** * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement * * @return \Doctrine\ORM\Query\AST\SelectStatement | * \Doctrine\ORM\Query\AST\UpdateStatement | * \Doctrine\ORM\Query\AST\DeleteStatement */ public function QueryLanguage() { $this->_lexer->moveNext(); switch ($this->_lexer->lookahead['type']) { case Lexer::T_SELECT: $statement = $this->SelectStatement(); break; case Lexer::T_UPDATE: $statement = $this->UpdateStatement(); break; case Lexer::T_DELETE: $statement = $this->DeleteStatement(); break; default: $this->syntaxError('SELECT, UPDATE or DELETE'); break; } // Check for end of string if ($this->_lexer->lookahead !== null) { $this->syntaxError('end of string'); } return $statement; } /** * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] * * @return \Doctrine\ORM\Query\AST\SelectStatement */ public function SelectStatement() { $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause()); $selectStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; $selectStatement->groupByClause = $this->_lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null; $selectStatement->havingClause = $this->_lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null; $selectStatement->orderByClause = $this->_lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null; return $selectStatement; } /** * UpdateStatement ::= UpdateClause [WhereClause] * * @return \Doctrine\ORM\Query\AST\UpdateStatement */ public function UpdateStatement() { $updateStatement = new AST\UpdateStatement($this->UpdateClause()); $updateStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; return $updateStatement; } /** * DeleteStatement ::= DeleteClause [WhereClause] * * @return \Doctrine\ORM\Query\AST\DeleteStatement */ public function DeleteStatement() { $deleteStatement = new AST\DeleteStatement($this->DeleteClause()); $deleteStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; return $deleteStatement; } /** * IdentificationVariable ::= identifier * * @return string */ public function IdentificationVariable() { $this->match(Lexer::T_IDENTIFIER); $identVariable = $this->_lexer->token['value']; $this->_deferredIdentificationVariables[] = array( 'expression' => $identVariable, 'nestingLevel' => $this->_nestingLevel, 'token' => $this->_lexer->token, ); return $identVariable; } /** * AliasIdentificationVariable = identifier * * @return string */ public function AliasIdentificationVariable() { $this->match(Lexer::T_IDENTIFIER); $aliasIdentVariable = $this->_lexer->token['value']; $exists = isset($this->_queryComponents[$aliasIdentVariable]); if ($exists) { $this->semanticalError("'$aliasIdentVariable' is already defined.", $this->_lexer->token); } return $aliasIdentVariable; } /** * AbstractSchemaName ::= identifier * * @return string */ public function AbstractSchemaName() { $this->match(Lexer::T_IDENTIFIER); $schemaName = ltrim($this->_lexer->token['value'], '\\'); if (strrpos($schemaName, ':') !== false) { list($namespaceAlias, $simpleClassName) = explode(':', $schemaName); $schemaName = $this->_em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName; } $exists = class_exists($schemaName, true); if ( ! $exists) { $this->semanticalError("Class '$schemaName' is not defined.", $this->_lexer->token); } return $schemaName; } /** * AliasResultVariable ::= identifier * * @return string */ public function AliasResultVariable() { $this->match(Lexer::T_IDENTIFIER); $resultVariable = $this->_lexer->token['value']; $exists = isset($this->_queryComponents[$resultVariable]); if ($exists) { $this->semanticalError("'$resultVariable' is already defined.", $this->_lexer->token); } return $resultVariable; } /** * ResultVariable ::= identifier * * @return string */ public function ResultVariable() { $this->match(Lexer::T_IDENTIFIER); $resultVariable = $this->_lexer->token['value']; // Defer ResultVariable validation $this->_deferredResultVariables[] = array( 'expression' => $resultVariable, 'nestingLevel' => $this->_nestingLevel, 'token' => $this->_lexer->token, ); return $resultVariable; } /** * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) * * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression */ public function JoinAssociationPathExpression() { $identVariable = $this->IdentificationVariable(); if ( ! isset($this->_queryComponents[$identVariable])) { $this->semanticalError( 'Identification Variable ' . $identVariable .' used in join path expression but was not defined before.' ); } $this->match(Lexer::T_DOT); $this->match(Lexer::T_IDENTIFIER); $field = $this->_lexer->token['value']; // Validate association field $qComp = $this->_queryComponents[$identVariable]; $class = $qComp['metadata']; if ( ! $class->hasAssociation($field)) { $this->semanticalError('Class ' . $class->name . ' has no association named ' . $field); } return new AST\JoinAssociationPathExpression($identVariable, $field); } /** * Parses an arbitrary path expression and defers semantical validation * based on expected types. * * PathExpression ::= IdentificationVariable "." identifier * * @param integer $expectedTypes * @return \Doctrine\ORM\Query\AST\PathExpression */ public function PathExpression($expectedTypes) { $identVariable = $this->IdentificationVariable(); $field = null; if ($this->_lexer->isNextToken(Lexer::T_DOT)) { $this->match(Lexer::T_DOT); $this->match(Lexer::T_IDENTIFIER); $field = $this->_lexer->token['value']; } // Creating AST node $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field); // Defer PathExpression validation if requested to be defered $this->_deferredPathExpressions[] = array( 'expression' => $pathExpr, 'nestingLevel' => $this->_nestingLevel, 'token' => $this->_lexer->token, ); return $pathExpr; } /** * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression * * @return \Doctrine\ORM\Query\AST\PathExpression */ public function AssociationPathExpression() { return $this->PathExpression( AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION | AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION ); } /** * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression * * @return \Doctrine\ORM\Query\AST\PathExpression */ public function SingleValuedPathExpression() { return $this->PathExpression( AST\PathExpression::TYPE_STATE_FIELD | AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION ); } /** * StateFieldPathExpression ::= IdentificationVariable "." StateField * * @return \Doctrine\ORM\Query\AST\PathExpression */ public function StateFieldPathExpression() { return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD); } /** * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField * * @return \Doctrine\ORM\Query\AST\PathExpression */ public function SingleValuedAssociationPathExpression() { return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION); } /** * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField * * @return \Doctrine\ORM\Query\AST\PathExpression */ public function CollectionValuedPathExpression() { return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION); } /** * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression} * * @return \Doctrine\ORM\Query\AST\SelectClause */ public function SelectClause() { $isDistinct = false; $this->match(Lexer::T_SELECT); // Check for DISTINCT if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) { $this->match(Lexer::T_DISTINCT); $isDistinct = true; } // Process SelectExpressions (1..N) $selectExpressions = array(); $selectExpressions[] = $this->SelectExpression(); while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $selectExpressions[] = $this->SelectExpression(); } return new AST\SelectClause($selectExpressions, $isDistinct); } /** * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression * * @return \Doctrine\ORM\Query\AST\SimpleSelectClause */ public function SimpleSelectClause() { $isDistinct = false; $this->match(Lexer::T_SELECT); if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) { $this->match(Lexer::T_DISTINCT); $isDistinct = true; } return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct); } /** * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}* * * @return \Doctrine\ORM\Query\AST\UpdateClause */ public function UpdateClause() { $this->match(Lexer::T_UPDATE); $token = $this->_lexer->lookahead; $abstractSchemaName = $this->AbstractSchemaName(); if ($this->_lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } $aliasIdentificationVariable = $this->AliasIdentificationVariable(); $class = $this->_em->getClassMetadata($abstractSchemaName); // Building queryComponent $queryComponent = array( 'metadata' => $class, 'parent' => null, 'relation' => null, 'map' => null, 'nestingLevel' => $this->_nestingLevel, 'token' => $token, ); $this->_queryComponents[$aliasIdentificationVariable] = $queryComponent; $this->match(Lexer::T_SET); $updateItems = array(); $updateItems[] = $this->UpdateItem(); while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $updateItems[] = $this->UpdateItem(); } $updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems); $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable; return $updateClause; } /** * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable * * @return \Doctrine\ORM\Query\AST\DeleteClause */ public function DeleteClause() { $this->match(Lexer::T_DELETE); if ($this->_lexer->isNextToken(Lexer::T_FROM)) { $this->match(Lexer::T_FROM); } $token = $this->_lexer->lookahead; $deleteClause = new AST\DeleteClause($this->AbstractSchemaName()); if ($this->_lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } $aliasIdentificationVariable = $this->AliasIdentificationVariable(); $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable; $class = $this->_em->getClassMetadata($deleteClause->abstractSchemaName); // Building queryComponent $queryComponent = array( 'metadata' => $class, 'parent' => null, 'relation' => null, 'map' => null, 'nestingLevel' => $this->_nestingLevel, 'token' => $token, ); $this->_queryComponents[$aliasIdentificationVariable] = $queryComponent; return $deleteClause; } /** * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}* * * @return \Doctrine\ORM\Query\AST\FromClause */ public function FromClause() { $this->match(Lexer::T_FROM); $identificationVariableDeclarations = array(); $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration(); while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration(); } return new AST\FromClause($identificationVariableDeclarations); } /** * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}* * * @return \Doctrine\ORM\Query\AST\SubselectFromClause */ public function SubselectFromClause() { $this->match(Lexer::T_FROM); $identificationVariables = array(); $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration(); while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration(); } return new AST\SubselectFromClause($identificationVariables); } /** * WhereClause ::= "WHERE" ConditionalExpression * * @return \Doctrine\ORM\Query\AST\WhereClause */ public function WhereClause() { $this->match(Lexer::T_WHERE); return new AST\WhereClause($this->ConditionalExpression()); } /** * HavingClause ::= "HAVING" ConditionalExpression * * @return \Doctrine\ORM\Query\AST\HavingClause */ public function HavingClause() { $this->match(Lexer::T_HAVING); return new AST\HavingClause($this->ConditionalExpression()); } /** * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}* * * @return \Doctrine\ORM\Query\AST\GroupByClause */ public function GroupByClause() { $this->match(Lexer::T_GROUP); $this->match(Lexer::T_BY); $groupByItems = array($this->GroupByItem()); while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $groupByItems[] = $this->GroupByItem(); } return new AST\GroupByClause($groupByItems); } /** * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* * * @return \Doctrine\ORM\Query\AST\OrderByClause */ public function OrderByClause() { $this->match(Lexer::T_ORDER); $this->match(Lexer::T_BY); $orderByItems = array(); $orderByItems[] = $this->OrderByItem(); while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $orderByItems[] = $this->OrderByItem(); } return new AST\OrderByClause($orderByItems); } /** * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] * * @return \Doctrine\ORM\Query\AST\Subselect */ public function Subselect() { // Increase query nesting level $this->_nestingLevel++; $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause()); $subselect->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; $subselect->groupByClause = $this->_lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null; $subselect->havingClause = $this->_lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null; $subselect->orderByClause = $this->_lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null; // Decrease query nesting level $this->_nestingLevel--; return $subselect; } /** * UpdateItem ::= SingleValuedPathExpression "=" NewValue * * @return \Doctrine\ORM\Query\AST\UpdateItem */ public function UpdateItem() { $pathExpr = $this->SingleValuedPathExpression(); $this->match(Lexer::T_EQUALS); $updateItem = new AST\UpdateItem($pathExpr, $this->NewValue()); return $updateItem; } /** * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression * * @return string | \Doctrine\ORM\Query\AST\PathExpression */ public function GroupByItem() { // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression $glimpse = $this->_lexer->glimpse(); if ($glimpse['type'] === Lexer::T_DOT) { return $this->SingleValuedPathExpression(); } // Still need to decide between IdentificationVariable or ResultVariable $lookaheadValue = $this->_lexer->lookahead['value']; if ( ! isset($this->_queryComponents[$lookaheadValue])) { $this->semanticalError('Cannot group by undefined identification or result variable.'); } return (isset($this->_queryComponents[$lookaheadValue]['metadata'])) ? $this->IdentificationVariable() : $this->ResultVariable(); } /** * OrderByItem ::= ( * SimpleArithmeticExpression | SingleValuedPathExpression | * ScalarExpression | ResultVariable * ) ["ASC" | "DESC"] * * @return \Doctrine\ORM\Query\AST\OrderByItem */ public function OrderByItem() { $this->_lexer->peek(); // lookahead => '.' $this->_lexer->peek(); // lookahead => token after '.' $peek = $this->_lexer->peek(); // lookahead => token after the token after the '.' $this->_lexer->resetPeek(); $glimpse = $this->_lexer->glimpse(); switch (true) { case ($this->_isMathOperator($peek)): $expr = $this->SimpleArithmeticExpression(); break; case ($glimpse['type'] === Lexer::T_DOT): $expr = $this->SingleValuedPathExpression(); break; case ($this->_lexer->peek() && $this->_isMathOperator($this->_peekBeyondClosingParenthesis())): $expr = $this->ScalarExpression(); break; default: $expr = $this->ResultVariable(); break; } $type = 'ASC'; $item = new AST\OrderByItem($expr); switch (true) { case ($this->_lexer->isNextToken(Lexer::T_DESC)): $this->match(Lexer::T_DESC); $type = 'DESC'; break; case ($this->_lexer->isNextToken(Lexer::T_ASC)): $this->match(Lexer::T_ASC); break; default: // Do nothing } $item->type = $type; return $item; } /** * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary | * EnumPrimary | SimpleEntityExpression | "NULL" * * NOTE: Since it is not possible to correctly recognize individual types, here is the full * grammar that needs to be supported: * * NewValue ::= SimpleArithmeticExpression | "NULL" * * SimpleArithmeticExpression covers all *Primary grammar rules and also SimpleEntityExpression */ public function NewValue() { if ($this->_lexer->isNextToken(Lexer::T_NULL)) { $this->match(Lexer::T_NULL); return null; } if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { $this->match(Lexer::T_INPUT_PARAMETER); return new AST\InputParameter($this->_lexer->token['value']); } return $this->SimpleArithmeticExpression(); } /** * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}* * * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration */ public function IdentificationVariableDeclaration() { $rangeVariableDeclaration = $this->RangeVariableDeclaration(); $indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null; $joins = array(); while ( $this->_lexer->isNextToken(Lexer::T_LEFT) || $this->_lexer->isNextToken(Lexer::T_INNER) || $this->_lexer->isNextToken(Lexer::T_JOIN) ) { $joins[] = $this->Join(); } return new AST\IdentificationVariableDeclaration( $rangeVariableDeclaration, $indexBy, $joins ); } /** * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable) * * @return \Doctrine\ORM\Query\AST\SubselectIdentificationVariableDeclaration | * \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration */ public function SubselectIdentificationVariableDeclaration() { $this->_lexer->glimpse(); /* NOT YET IMPLEMENTED! if ($glimpse['type'] == Lexer::T_DOT) { $subselectIdVarDecl = new AST\SubselectIdentificationVariableDeclaration(); $subselectIdVarDecl->associationPathExpression = $this->AssociationPathExpression(); $this->match(Lexer::T_AS); $subselectIdVarDecl->aliasIdentificationVariable = $this->AliasIdentificationVariable(); return $subselectIdVarDecl; } */ return $this->IdentificationVariableDeclaration(); } /** * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" * (JoinAssociationDeclaration | RangeVariableDeclaration) * ["WITH" ConditionalExpression] * * @return \Doctrine\ORM\Query\AST\Join */ public function Join() { // Check Join type $joinType = AST\Join::JOIN_TYPE_INNER; switch (true) { case ($this->_lexer->isNextToken(Lexer::T_LEFT)): $this->match(Lexer::T_LEFT); $joinType = AST\Join::JOIN_TYPE_LEFT; // Possible LEFT OUTER join if ($this->_lexer->isNextToken(Lexer::T_OUTER)) { $this->match(Lexer::T_OUTER); $joinType = AST\Join::JOIN_TYPE_LEFTOUTER; } break; case ($this->_lexer->isNextToken(Lexer::T_INNER)): $this->match(Lexer::T_INNER); break; default: // Do nothing } $this->match(Lexer::T_JOIN); $next = $this->_lexer->glimpse(); $joinDeclaration = ($next['type'] === Lexer::T_DOT) ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration(); // Create AST node $join = new AST\Join($joinType, $joinDeclaration); // Check for ad-hoc Join conditions if ($this->_lexer->isNextToken(Lexer::T_WITH) || $joinDeclaration instanceof AST\RangeVariableDeclaration) { $this->match(Lexer::T_WITH); $join->conditionalExpression = $this->ConditionalExpression(); } return $join; } /** * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable * * @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration */ public function RangeVariableDeclaration() { $abstractSchemaName = $this->AbstractSchemaName(); if ($this->_lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } $token = $this->_lexer->lookahead; $aliasIdentificationVariable = $this->AliasIdentificationVariable(); $classMetadata = $this->_em->getClassMetadata($abstractSchemaName); // Building queryComponent $queryComponent = array( 'metadata' => $classMetadata, 'parent' => null, 'relation' => null, 'map' => null, 'nestingLevel' => $this->_nestingLevel, 'token' => $token ); $this->_queryComponents[$aliasIdentificationVariable] = $queryComponent; return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable); } /** * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy] * * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression */ public function JoinAssociationDeclaration() { $joinAssociationPathExpression = $this->JoinAssociationPathExpression(); if ($this->_lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } $aliasIdentificationVariable = $this->AliasIdentificationVariable(); $indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null; $identificationVariable = $joinAssociationPathExpression->identificationVariable; $field = $joinAssociationPathExpression->associationField; $class = $this->_queryComponents[$identificationVariable]['metadata']; $targetClass = $this->_em->getClassMetadata($class->associationMappings[$field]['targetEntity']); // Building queryComponent $joinQueryComponent = array( 'metadata' => $targetClass, 'parent' => $joinAssociationPathExpression->identificationVariable, 'relation' => $class->getAssociationMapping($field), 'map' => null, 'nestingLevel' => $this->_nestingLevel, 'token' => $this->_lexer->lookahead ); $this->_queryComponents[$aliasIdentificationVariable] = $joinQueryComponent; return new AST\JoinAssociationDeclaration($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy); } /** * PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet * PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}" * * @return array */ public function PartialObjectExpression() { $this->match(Lexer::T_PARTIAL); $partialFieldSet = array(); $identificationVariable = $this->IdentificationVariable(); $this->match(Lexer::T_DOT); $this->match(Lexer::T_OPEN_CURLY_BRACE); $this->match(Lexer::T_IDENTIFIER); $partialFieldSet[] = $this->_lexer->token['value']; while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $this->match(Lexer::T_IDENTIFIER); $partialFieldSet[] = $this->_lexer->token['value']; } $this->match(Lexer::T_CLOSE_CURLY_BRACE); $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet); // Defer PartialObjectExpression validation $this->_deferredPartialObjectExpressions[] = array( 'expression' => $partialObjectExpression, 'nestingLevel' => $this->_nestingLevel, 'token' => $this->_lexer->token, ); return $partialObjectExpression; } /** * IndexBy ::= "INDEX" "BY" StateFieldPathExpression * * @return \Doctrine\ORM\Query\AST\IndexBy */ public function IndexBy() { $this->match(Lexer::T_INDEX); $this->match(Lexer::T_BY); $pathExpr = $this->StateFieldPathExpression(); // Add the INDEX BY info to the query component $this->_queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field; return new AST\IndexBy($pathExpr); } /** * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | * StateFieldPathExpression | BooleanPrimary | CaseExpression | * InstanceOfExpression * * @return mixed One of the possible expressions or subexpressions. */ public function ScalarExpression() { $lookahead = $this->_lexer->lookahead['type']; switch ($lookahead) { case Lexer::T_IDENTIFIER: $this->_lexer->peek(); // lookahead => '.' $this->_lexer->peek(); // lookahead => token after '.' $peek = $this->_lexer->peek(); // lookahead => token after the token after the '.' $this->_lexer->resetPeek(); if ($this->_isMathOperator($peek)) { return $this->SimpleArithmeticExpression(); } return $this->StateFieldPathExpression(); case Lexer::T_INTEGER: case Lexer::T_FLOAT: return $this->SimpleArithmeticExpression(); case Lexer::T_STRING: return $this->StringPrimary(); case Lexer::T_TRUE: case Lexer::T_FALSE: $this->match($lookahead); return new AST\Literal(AST\Literal::BOOLEAN, $this->_lexer->token['value']); case Lexer::T_INPUT_PARAMETER: return $this->InputParameter(); case Lexer::T_CASE: case Lexer::T_COALESCE: case Lexer::T_NULLIF: // Since NULLIF and COALESCE can be identified as a function, // we need to check if before check for FunctionDeclaration return $this->CaseExpression(); default: if ( ! ($this->_isFunction() || $this->_isAggregateFunction($lookahead))) { $this->syntaxError(); } // We may be in an ArithmeticExpression (find the matching ")" and inspect for Math operator) $this->_lexer->peek(); // "(" $peek = $this->_peekBeyondClosingParenthesis(); if ($this->_isMathOperator($peek)) { return $this->SimpleArithmeticExpression(); } if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { return $this->AggregateExpression(); } return $this->FunctionDeclaration(); } } /** * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" * NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" * * @return mixed One of the possible expressions or subexpressions. */ public function CaseExpression() { $lookahead = $this->_lexer->lookahead['type']; switch ($lookahead) { case Lexer::T_NULLIF: return $this->NullIfExpression(); case Lexer::T_COALESCE: return $this->CoalesceExpression(); case Lexer::T_CASE: $this->_lexer->resetPeek(); $peek = $this->_lexer->peek(); if ($peek['type'] === Lexer::T_WHEN) { return $this->GeneralCaseExpression(); } return $this->SimpleCaseExpression(); default: // Do nothing break; } $this->syntaxError(); } /** * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" * * @return \Doctrine\ORM\Query\AST\CoalesceExpression */ public function CoalesceExpression() { $this->match(Lexer::T_COALESCE); $this->match(Lexer::T_OPEN_PARENTHESIS); // Process ScalarExpressions (1..N) $scalarExpressions = array(); $scalarExpressions[] = $this->ScalarExpression(); while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $scalarExpressions[] = $this->ScalarExpression(); } $this->match(Lexer::T_CLOSE_PARENTHESIS); return new AST\CoalesceExpression($scalarExpressions); } /** * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" * * @return \Doctrine\ORM\Query\AST\NullIfExpression */ public function NullIfExpression() { $this->match(Lexer::T_NULLIF); $this->match(Lexer::T_OPEN_PARENTHESIS); $firstExpression = $this->ScalarExpression(); $this->match(Lexer::T_COMMA); $secondExpression = $this->ScalarExpression(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return new AST\NullIfExpression($firstExpression, $secondExpression); } /** * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" * * @return \Doctrine\ORM\Query\AST\GeneralExpression */ public function GeneralCaseExpression() { $this->match(Lexer::T_CASE); // Process WhenClause (1..N) $whenClauses = array(); do { $whenClauses[] = $this->WhenClause(); } while ($this->_lexer->isNextToken(Lexer::T_WHEN)); $this->match(Lexer::T_ELSE); $scalarExpression = $this->ScalarExpression(); $this->match(Lexer::T_END); return new AST\GeneralCaseExpression($whenClauses, $scalarExpression); } /** * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator */ public function SimpleCaseExpression() { $this->match(Lexer::T_CASE); $caseOperand = $this->StateFieldPathExpression(); // Process SimpleWhenClause (1..N) $simpleWhenClauses = array(); do { $simpleWhenClauses[] = $this->SimpleWhenClause(); } while ($this->_lexer->isNextToken(Lexer::T_WHEN)); $this->match(Lexer::T_ELSE); $scalarExpression = $this->ScalarExpression(); $this->match(Lexer::T_END); return new AST\SimpleCaseExpression($caseOperand, $simpleWhenClauses, $scalarExpression); } /** * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression * * @return \Doctrine\ORM\Query\AST\WhenExpression */ public function WhenClause() { $this->match(Lexer::T_WHEN); $conditionalExpression = $this->ConditionalExpression(); $this->match(Lexer::T_THEN); return new AST\WhenClause($conditionalExpression, $this->ScalarExpression()); } /** * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression * * @return \Doctrine\ORM\Query\AST\SimpleWhenExpression */ public function SimpleWhenClause() { $this->match(Lexer::T_WHEN); $conditionalExpression = $this->ScalarExpression(); $this->match(Lexer::T_THEN); return new AST\SimpleWhenClause($conditionalExpression, $this->ScalarExpression()); } /** * SelectExpression ::= ( * IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | * PartialObjectExpression | "(" Subselect ")" | CaseExpression * ) [["AS"] ["HIDDEN"] AliasResultVariable] * * @return \Doctrine\ORM\Query\AST\SelectExpression */ public function SelectExpression() { $expression = null; $identVariable = null; $peek = $this->_lexer->glimpse(); $lookaheadType = $this->_lexer->lookahead['type']; switch (true) { // ScalarExpression (u.name) case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT): $expression = $this->ScalarExpression(); break; // IdentificationVariable (u) case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS): $expression = $identVariable = $this->IdentificationVariable(); break; // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...)) case ($lookaheadType === Lexer::T_CASE): case ($lookaheadType === Lexer::T_COALESCE): case ($lookaheadType === Lexer::T_NULLIF): $expression = $this->CaseExpression(); break; // DQL Function (SUM(u.value) or SUM(u.value) + 1) case ($this->_isFunction()): $this->_lexer->peek(); // "(" switch (true) { case ($this->_isMathOperator($this->_peekBeyondClosingParenthesis())): // SUM(u.id) + COUNT(u.id) $expression = $this->ScalarExpression(); break; case ($this->_isAggregateFunction($lookaheadType)): // COUNT(u.id) $expression = $this->AggregateExpression(); break; default: // IDENTITY(u) $expression = $this->FunctionDeclaration(); break; } break; // PartialObjectExpression (PARTIAL u.{id, name}) case ($lookaheadType === Lexer::T_PARTIAL): $expression = $this->PartialObjectExpression(); $identVariable = $expression->identificationVariable; break; // Subselect case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT): $this->match(Lexer::T_OPEN_PARENTHESIS); $expression = $this->Subselect(); $this->match(Lexer::T_CLOSE_PARENTHESIS); break; // Shortcut: ScalarExpression => SimpleArithmeticExpression case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS): case ($lookaheadType === Lexer::T_INTEGER): case ($lookaheadType === Lexer::T_STRING): case ($lookaheadType === Lexer::T_FLOAT): // SimpleArithmeticExpression : (- u.value ) or ( + u.value ) case ($lookaheadType === Lexer::T_MINUS): case ($lookaheadType === Lexer::T_PLUS): $expression = $this->SimpleArithmeticExpression(); break; default: $this->syntaxError( 'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression', $this->_lexer->lookahead ); } // [["AS"] ["HIDDEN"] AliasResultVariable] if ($this->_lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } $hiddenAliasResultVariable = false; if ($this->_lexer->isNextToken(Lexer::T_HIDDEN)) { $this->match(Lexer::T_HIDDEN); $hiddenAliasResultVariable = true; } $aliasResultVariable = null; if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { $token = $this->_lexer->lookahead; $aliasResultVariable = $this->AliasResultVariable(); // Include AliasResultVariable in query components. $this->_queryComponents[$aliasResultVariable] = array( 'resultVariable' => $expression, 'nestingLevel' => $this->_nestingLevel, 'token' => $token, ); } // AST $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable); if ($identVariable) { $this->_identVariableExpressions[$identVariable] = $expr; } return $expr; } /** * SimpleSelectExpression ::= ( * StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | * AggregateExpression | "(" Subselect ")" | ScalarExpression * ) [["AS"] AliasResultVariable] * * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression */ public function SimpleSelectExpression() { $peek = $this->_lexer->glimpse(); switch ($this->_lexer->lookahead['type']) { case Lexer::T_IDENTIFIER: switch (true) { case ($peek['type'] === Lexer::T_DOT): $expression = $this->StateFieldPathExpression(); return new AST\SimpleSelectExpression($expression); case ($peek['type'] !== Lexer::T_OPEN_PARENTHESIS): $expression = $this->IdentificationVariable(); return new AST\SimpleSelectExpression($expression); case ($this->_isFunction()): // SUM(u.id) + COUNT(u.id) if ($this->_isMathOperator($this->_peekBeyondClosingParenthesis())) { return new AST\SimpleSelectExpression($this->ScalarExpression()); } // COUNT(u.id) if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { return new AST\SimpleSelectExpression($this->AggregateExpression()); } // IDENTITY(u) return new AST\SimpleSelectExpression($this->FunctionDeclaration()); default: // Do nothing } break; case Lexer::T_OPEN_PARENTHESIS: if ($peek['type'] !== Lexer::T_SELECT) { // Shortcut: ScalarExpression => SimpleArithmeticExpression $expression = $this->SimpleArithmeticExpression(); return new AST\SimpleSelectExpression($expression); } // Subselect $this->match(Lexer::T_OPEN_PARENTHESIS); $expression = $this->Subselect(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return new AST\SimpleSelectExpression($expression); default: // Do nothing } $this->_lexer->peek(); $expression = $this->ScalarExpression(); $expr = new AST\SimpleSelectExpression($expression); if ($this->_lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { $token = $this->_lexer->lookahead; $resultVariable = $this->AliasResultVariable(); $expr->fieldIdentificationVariable = $resultVariable; // Include AliasResultVariable in query components. $this->_queryComponents[$resultVariable] = array( 'resultvariable' => $expr, 'nestingLevel' => $this->_nestingLevel, 'token' => $token, ); } return $expr; } /** * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* * * @return \Doctrine\ORM\Query\AST\ConditionalExpression */ public function ConditionalExpression() { $conditionalTerms = array(); $conditionalTerms[] = $this->ConditionalTerm(); while ($this->_lexer->isNextToken(Lexer::T_OR)) { $this->match(Lexer::T_OR); $conditionalTerms[] = $this->ConditionalTerm(); } // Phase 1 AST optimization: Prevent AST\ConditionalExpression // if only one AST\ConditionalTerm is defined if (count($conditionalTerms) == 1) { return $conditionalTerms[0]; } return new AST\ConditionalExpression($conditionalTerms); } /** * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* * * @return \Doctrine\ORM\Query\AST\ConditionalTerm */ public function ConditionalTerm() { $conditionalFactors = array(); $conditionalFactors[] = $this->ConditionalFactor(); while ($this->_lexer->isNextToken(Lexer::T_AND)) { $this->match(Lexer::T_AND); $conditionalFactors[] = $this->ConditionalFactor(); } // Phase 1 AST optimization: Prevent AST\ConditionalTerm // if only one AST\ConditionalFactor is defined if (count($conditionalFactors) == 1) { return $conditionalFactors[0]; } return new AST\ConditionalTerm($conditionalFactors); } /** * ConditionalFactor ::= ["NOT"] ConditionalPrimary * * @return \Doctrine\ORM\Query\AST\ConditionalFactor */ public function ConditionalFactor() { $not = false; if ($this->_lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } $conditionalPrimary = $this->ConditionalPrimary(); // Phase 1 AST optimization: Prevent AST\ConditionalFactor // if only one AST\ConditionalPrimary is defined if ( ! $not) { return $conditionalPrimary; } $conditionalFactor = new AST\ConditionalFactor($conditionalPrimary); $conditionalFactor->not = $not; return $conditionalFactor; } /** * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" * * @return \Doctrine\ORM\Query\AST\ConditionalPrimary */ public function ConditionalPrimary() { $condPrimary = new AST\ConditionalPrimary; if ( ! $this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression(); return $condPrimary; } // Peek beyond the matching closing paranthesis ')' $peek = $this->_peekBeyondClosingParenthesis(); if (in_array($peek['value'], array("=", "<", "<=", "<>", ">", ">=", "!=")) || in_array($peek['type'], array(Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS)) || $this->_isMathOperator($peek)) { $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression(); return $condPrimary; } $this->match(Lexer::T_OPEN_PARENTHESIS); $condPrimary->conditionalExpression = $this->ConditionalExpression(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return $condPrimary; } /** * SimpleConditionalExpression ::= * ComparisonExpression | BetweenExpression | LikeExpression | * InExpression | NullComparisonExpression | ExistsExpression | * EmptyCollectionComparisonExpression | CollectionMemberExpression | * InstanceOfExpression */ public function SimpleConditionalExpression() { $token = $this->_lexer->lookahead; if ($this->_lexer->isNextToken(Lexer::T_NOT)) { $token = $this->_lexer->glimpse(); } if ($token['type'] === Lexer::T_EXISTS) { return $this->ExistsExpression(); } $peek = $this->_lexer->glimpse(); if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER) { if ($peek['value'] == '(') { // Peek beyond the matching closing paranthesis ')' $this->_lexer->peek(); $token = $this->_peekBeyondClosingParenthesis(false); if ($token['type'] === Lexer::T_NOT) { $token = $this->_lexer->peek(); } $this->_lexer->resetPeek(); } else { // Peek beyond the PathExpression (or InputParameter) $peek = $this->_lexer->peek(); while ($peek['value'] === '.') { $this->_lexer->peek(); $peek = $this->_lexer->peek(); } // Also peek beyond a NOT if there is one if ($peek['type'] === Lexer::T_NOT) { $peek = $this->_lexer->peek(); } $token = $peek; // We need to go even further in case of IS (differenciate between NULL and EMPTY) $lookahead = $this->_lexer->peek(); // Also peek beyond a NOT if there is one if ($lookahead['type'] === Lexer::T_NOT) { $lookahead = $this->_lexer->peek(); } $this->_lexer->resetPeek(); } } switch ($token['type']) { case Lexer::T_BETWEEN: return $this->BetweenExpression(); case Lexer::T_LIKE: return $this->LikeExpression(); case Lexer::T_IN: return $this->InExpression(); case Lexer::T_INSTANCE: return $this->InstanceOfExpression(); case Lexer::T_IS: if ($lookahead['type'] == Lexer::T_NULL) { return $this->NullComparisonExpression(); } return $this->EmptyCollectionComparisonExpression(); case Lexer::T_MEMBER: return $this->CollectionMemberExpression(); default: return $this->ComparisonExpression(); } } /** * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" * * @return \Doctrine\ORM\Query\AST\EmptyCollectionComparisonExpression */ public function EmptyCollectionComparisonExpression() { $emptyColletionCompExpr = new AST\EmptyCollectionComparisonExpression( $this->CollectionValuedPathExpression() ); $this->match(Lexer::T_IS); if ($this->_lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $emptyColletionCompExpr->not = true; } $this->match(Lexer::T_EMPTY); return $emptyColletionCompExpr; } /** * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression * * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression * SimpleEntityExpression ::= IdentificationVariable | InputParameter * * @return \Doctrine\ORM\Query\AST\CollectionMemberExpression */ public function CollectionMemberExpression() { $not = false; $entityExpr = $this->EntityExpression(); if ($this->_lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } $this->match(Lexer::T_MEMBER); if ($this->_lexer->isNextToken(Lexer::T_OF)) { $this->match(Lexer::T_OF); } $collMemberExpr = new AST\CollectionMemberExpression( $entityExpr, $this->CollectionValuedPathExpression() ); $collMemberExpr->not = $not; return $collMemberExpr; } /** * Literal ::= string | char | integer | float | boolean * * @return string */ public function Literal() { switch ($this->_lexer->lookahead['type']) { case Lexer::T_STRING: $this->match(Lexer::T_STRING); return new AST\Literal(AST\Literal::STRING, $this->_lexer->token['value']); case Lexer::T_INTEGER: case Lexer::T_FLOAT: $this->match( $this->_lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT ); return new AST\Literal(AST\Literal::NUMERIC, $this->_lexer->token['value']); case Lexer::T_TRUE: case Lexer::T_FALSE: $this->match( $this->_lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE ); return new AST\Literal(AST\Literal::BOOLEAN, $this->_lexer->token['value']); default: $this->syntaxError('Literal'); } } /** * InParameter ::= Literal | InputParameter * * @return string | \Doctrine\ORM\Query\AST\InputParameter */ public function InParameter() { if ($this->_lexer->lookahead['type'] == Lexer::T_INPUT_PARAMETER) { return $this->InputParameter(); } return $this->Literal(); } /** * InputParameter ::= PositionalParameter | NamedParameter * * @return \Doctrine\ORM\Query\AST\InputParameter */ public function InputParameter() { $this->match(Lexer::T_INPUT_PARAMETER); return new AST\InputParameter($this->_lexer->token['value']); } /** * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" * * @return \Doctrine\ORM\Query\AST\ArithmeticExpression */ public function ArithmeticExpression() { $expr = new AST\ArithmeticExpression; if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { $peek = $this->_lexer->glimpse(); if ($peek['type'] === Lexer::T_SELECT) { $this->match(Lexer::T_OPEN_PARENTHESIS); $expr->subselect = $this->Subselect(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return $expr; } } $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression(); return $expr; } /** * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* * * @return \Doctrine\ORM\Query\AST\SimpleArithmeticExpression */ public function SimpleArithmeticExpression() { $terms = array(); $terms[] = $this->ArithmeticTerm(); while (($isPlus = $this->_lexer->isNextToken(Lexer::T_PLUS)) || $this->_lexer->isNextToken(Lexer::T_MINUS)) { $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS); $terms[] = $this->_lexer->token['value']; $terms[] = $this->ArithmeticTerm(); } // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression // if only one AST\ArithmeticTerm is defined if (count($terms) == 1) { return $terms[0]; } return new AST\SimpleArithmeticExpression($terms); } /** * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}* * * @return \Doctrine\ORM\Query\AST\ArithmeticTerm */ public function ArithmeticTerm() { $factors = array(); $factors[] = $this->ArithmeticFactor(); while (($isMult = $this->_lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->_lexer->isNextToken(Lexer::T_DIVIDE)) { $this->match(($isMult) ? Lexer::T_MULTIPLY : Lexer::T_DIVIDE); $factors[] = $this->_lexer->token['value']; $factors[] = $this->ArithmeticFactor(); } // Phase 1 AST optimization: Prevent AST\ArithmeticTerm // if only one AST\ArithmeticFactor is defined if (count($factors) == 1) { return $factors[0]; } return new AST\ArithmeticTerm($factors); } /** * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary * * @return \Doctrine\ORM\Query\AST\ArithmeticFactor */ public function ArithmeticFactor() { $sign = null; if (($isPlus = $this->_lexer->isNextToken(Lexer::T_PLUS)) || $this->_lexer->isNextToken(Lexer::T_MINUS)) { $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS); $sign = $isPlus; } $primary = $this->ArithmeticPrimary(); // Phase 1 AST optimization: Prevent AST\ArithmeticFactor // if only one AST\ArithmeticPrimary is defined if ($sign === null) { return $primary; } return new AST\ArithmeticFactor($primary, $sign); } /** * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")" * | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings * | FunctionsReturningDatetime | IdentificationVariable | ResultVariable * | InputParameter | CaseExpression */ public function ArithmeticPrimary() { if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { $this->match(Lexer::T_OPEN_PARENTHESIS); $expr = $this->SimpleArithmeticExpression(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return $expr; } switch ($this->_lexer->lookahead['type']) { case Lexer::T_COALESCE: case Lexer::T_NULLIF: case Lexer::T_CASE: return $this->CaseExpression(); case Lexer::T_IDENTIFIER: $peek = $this->_lexer->glimpse(); if ($peek['value'] == '(') { return $this->FunctionDeclaration(); } if ($peek['value'] == '.') { return $this->SingleValuedPathExpression(); } if (isset($this->_queryComponents[$this->_lexer->lookahead['value']]['resultVariable'])) { return $this->ResultVariable(); } return $this->StateFieldPathExpression(); case Lexer::T_INPUT_PARAMETER: return $this->InputParameter(); default: $peek = $this->_lexer->glimpse(); if ($peek['value'] == '(') { if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { return $this->AggregateExpression(); } return $this->FunctionDeclaration(); } return $this->Literal(); } } /** * StringExpression ::= StringPrimary | "(" Subselect ")" * * @return \Doctrine\ORM\Query\AST\StringPrimary | * \Doctrine]ORM\Query\AST\Subselect */ public function StringExpression() { if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { $peek = $this->_lexer->glimpse(); if ($peek['type'] === Lexer::T_SELECT) { $this->match(Lexer::T_OPEN_PARENTHESIS); $expr = $this->Subselect(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return $expr; } } return $this->StringPrimary(); } /** * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression */ public function StringPrimary() { $lookaheadType = $this->_lexer->lookahead['type']; switch ($lookaheadType) { case Lexer::T_IDENTIFIER: $peek = $this->_lexer->glimpse(); if ($peek['value'] == '.') { return $this->StateFieldPathExpression(); } if ($peek['value'] == '(') { // do NOT directly go to FunctionsReturningString() because it doesnt check for custom functions. return $this->FunctionDeclaration(); } $this->syntaxError("'.' or '('"); break; case Lexer::T_STRING: $this->match(Lexer::T_STRING); return new AST\Literal(AST\Literal::STRING, $this->_lexer->token['value']); case Lexer::T_INPUT_PARAMETER: return $this->InputParameter(); case Lexer::T_CASE: case Lexer::T_COALESCE: case Lexer::T_NULLIF: return $this->CaseExpression(); default: if ($this->_isAggregateFunction($lookaheadType)) { return $this->AggregateExpression(); } } $this->syntaxError( 'StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression' ); } /** * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression * * @return \Doctrine\ORM\Query\AST\SingleValuedAssociationPathExpression | * \Doctrine\ORM\Query\AST\SimpleEntityExpression */ public function EntityExpression() { $glimpse = $this->_lexer->glimpse(); if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') { return $this->SingleValuedAssociationPathExpression(); } return $this->SimpleEntityExpression(); } /** * SimpleEntityExpression ::= IdentificationVariable | InputParameter * * @return string | \Doctrine\ORM\Query\AST\InputParameter */ public function SimpleEntityExpression() { if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { return $this->InputParameter(); } return $this->StateFieldPathExpression(); } /** * AggregateExpression ::= * ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" | * "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")" * * @return \Doctrine\ORM\Query\AST\AggregateExpression */ public function AggregateExpression() { $lookaheadType = $this->_lexer->lookahead['type']; $isDistinct = false; if ( ! in_array($lookaheadType, array(Lexer::T_COUNT, Lexer::T_AVG, Lexer::T_MAX, Lexer::T_MIN, Lexer::T_SUM))) { $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT'); } $this->match($lookaheadType); $functionName = $this->_lexer->token['value']; $this->match(Lexer::T_OPEN_PARENTHESIS); if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) { $this->match(Lexer::T_DISTINCT); $isDistinct = true; } $pathExp = ($lookaheadType === Lexer::T_COUNT) ? $this->SingleValuedPathExpression() : $this->SimpleArithmeticExpression(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return new AST\AggregateExpression($functionName, $pathExp, $isDistinct); } /** * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" * * @return \Doctrine\ORM\Query\AST\QuantifiedExpression */ public function QuantifiedExpression() { $lookaheadType = $this->_lexer->lookahead['type']; $value = $this->_lexer->lookahead['value']; if ( ! in_array($lookaheadType, array(Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME))) { $this->syntaxError('ALL, ANY or SOME'); } $this->match($lookaheadType); $this->match(Lexer::T_OPEN_PARENTHESIS); $qExpr = new AST\QuantifiedExpression($this->Subselect()); $qExpr->type = $value; $this->match(Lexer::T_CLOSE_PARENTHESIS); return $qExpr; } /** * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression * * @return \Doctrine\ORM\Query\AST\BetweenExpression */ public function BetweenExpression() { $not = false; $arithExpr1 = $this->ArithmeticExpression(); if ($this->_lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } $this->match(Lexer::T_BETWEEN); $arithExpr2 = $this->ArithmeticExpression(); $this->match(Lexer::T_AND); $arithExpr3 = $this->ArithmeticExpression(); $betweenExpr = new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3); $betweenExpr->not = $not; return $betweenExpr; } /** * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) * * @return \Doctrine\ORM\Query\AST\ComparisonExpression */ public function ComparisonExpression() { $this->_lexer->glimpse(); $leftExpr = $this->ArithmeticExpression(); $operator = $this->ComparisonOperator(); $rightExpr = ($this->_isNextAllAnySome()) ? $this->QuantifiedExpression() : $this->ArithmeticExpression(); return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr); } /** * InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")" * * @return \Doctrine\ORM\Query\AST\InExpression */ public function InExpression() { $inExpression = new AST\InExpression($this->ArithmeticExpression()); if ($this->_lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $inExpression->not = true; } $this->match(Lexer::T_IN); $this->match(Lexer::T_OPEN_PARENTHESIS); if ($this->_lexer->isNextToken(Lexer::T_SELECT)) { $inExpression->subselect = $this->Subselect(); } else { $literals = array(); $literals[] = $this->InParameter(); while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $literals[] = $this->InParameter(); } $inExpression->literals = $literals; } $this->match(Lexer::T_CLOSE_PARENTHESIS); return $inExpression; } /** * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")") * * @return \Doctrine\ORM\Query\AST\InstanceOfExpression */ public function InstanceOfExpression() { $instanceOfExpression = new AST\InstanceOfExpression($this->IdentificationVariable()); if ($this->_lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $instanceOfExpression->not = true; } $this->match(Lexer::T_INSTANCE); $this->match(Lexer::T_OF); $exprValues = array(); if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { $this->match(Lexer::T_OPEN_PARENTHESIS); $exprValues[] = $this->InstanceOfParameter(); while ($this->_lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $exprValues[] = $this->InstanceOfParameter(); } $this->match(Lexer::T_CLOSE_PARENTHESIS); $instanceOfExpression->value = $exprValues; return $instanceOfExpression; } $exprValues[] = $this->InstanceOfParameter(); $instanceOfExpression->value = $exprValues; return $instanceOfExpression; } /** * InstanceOfParameter ::= AbstractSchemaName | InputParameter * * @return mixed */ public function InstanceOfParameter() { if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { $this->match(Lexer::T_INPUT_PARAMETER); return new AST\InputParameter($this->_lexer->token['value']); } return $this->AliasIdentificationVariable(); } /** * LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char] * * @return \Doctrine\ORM\Query\AST\LikeExpression */ public function LikeExpression() { $stringExpr = $this->StringExpression(); $not = false; if ($this->_lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } $this->match(Lexer::T_LIKE); if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { $this->match(Lexer::T_INPUT_PARAMETER); $stringPattern = new AST\InputParameter($this->_lexer->token['value']); } else { $stringPattern = $this->StringPrimary(); } $escapeChar = null; if ($this->_lexer->lookahead['type'] === Lexer::T_ESCAPE) { $this->match(Lexer::T_ESCAPE); $this->match(Lexer::T_STRING); $escapeChar = new AST\Literal(AST\Literal::STRING, $this->_lexer->token['value']); } $likeExpr = new AST\LikeExpression($stringExpr, $stringPattern, $escapeChar); $likeExpr->not = $not; return $likeExpr; } /** * NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL" * * @return \Doctrine\ORM\Query\AST\NullComparisonExpression */ public function NullComparisonExpression() { if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { $this->match(Lexer::T_INPUT_PARAMETER); $expr = new AST\InputParameter($this->_lexer->token['value']); } else { $expr = $this->SingleValuedPathExpression(); } $nullCompExpr = new AST\NullComparisonExpression($expr); $this->match(Lexer::T_IS); if ($this->_lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $nullCompExpr->not = true; } $this->match(Lexer::T_NULL); return $nullCompExpr; } /** * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" * * @return \Doctrine\ORM\Query\AST\ExistsExpression */ public function ExistsExpression() { $not = false; if ($this->_lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } $this->match(Lexer::T_EXISTS); $this->match(Lexer::T_OPEN_PARENTHESIS); $existsExpression = new AST\ExistsExpression($this->Subselect()); $existsExpression->not = $not; $this->match(Lexer::T_CLOSE_PARENTHESIS); return $existsExpression; } /** * ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!=" * * @return string */ public function ComparisonOperator() { switch ($this->_lexer->lookahead['value']) { case '=': $this->match(Lexer::T_EQUALS); return '='; case '<': $this->match(Lexer::T_LOWER_THAN); $operator = '<'; if ($this->_lexer->isNextToken(Lexer::T_EQUALS)) { $this->match(Lexer::T_EQUALS); $operator .= '='; } else if ($this->_lexer->isNextToken(Lexer::T_GREATER_THAN)) { $this->match(Lexer::T_GREATER_THAN); $operator .= '>'; } return $operator; case '>': $this->match(Lexer::T_GREATER_THAN); $operator = '>'; if ($this->_lexer->isNextToken(Lexer::T_EQUALS)) { $this->match(Lexer::T_EQUALS); $operator .= '='; } return $operator; case '!': $this->match(Lexer::T_NEGATE); $this->match(Lexer::T_EQUALS); return '<>'; default: $this->syntaxError('=, <, <=, <>, >, >=, !='); } } /** * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime */ public function FunctionDeclaration() { $token = $this->_lexer->lookahead; $funcName = strtolower($token['value']); // Check for built-in functions first! switch (true) { case (isset(self::$_STRING_FUNCTIONS[$funcName])): return $this->FunctionsReturningStrings(); case (isset(self::$_NUMERIC_FUNCTIONS[$funcName])): return $this->FunctionsReturningNumerics(); case (isset(self::$_DATETIME_FUNCTIONS[$funcName])): return $this->FunctionsReturningDatetime(); default: return $this->CustomFunctionDeclaration(); } } /** * Helper function for FunctionDeclaration grammar rule */ private function CustomFunctionDeclaration() { $token = $this->_lexer->lookahead; $funcName = strtolower($token['value']); // Check for custom functions afterwards $config = $this->_em->getConfiguration(); switch (true) { case ($config->getCustomStringFunction($funcName) !== null): return $this->CustomFunctionsReturningStrings(); case ($config->getCustomNumericFunction($funcName) !== null): return $this->CustomFunctionsReturningNumerics(); case ($config->getCustomDatetimeFunction($funcName) !== null): return $this->CustomFunctionsReturningDatetime(); default: $this->syntaxError('known function', $token); } } /** * FunctionsReturningNumerics ::= * "LENGTH" "(" StringPrimary ")" | * "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" | * "ABS" "(" SimpleArithmeticExpression ")" | * "SQRT" "(" SimpleArithmeticExpression ")" | * "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | * "SIZE" "(" CollectionValuedPathExpression ")" */ public function FunctionsReturningNumerics() { $funcNameLower = strtolower($this->_lexer->lookahead['value']); $funcClass = self::$_NUMERIC_FUNCTIONS[$funcNameLower]; $function = new $funcClass($funcNameLower); $function->parse($this); return $function; } public function CustomFunctionsReturningNumerics() { // getCustomNumericFunction is case-insensitive $funcName = strtolower($this->_lexer->lookahead['value']); $funcClass = $this->_em->getConfiguration()->getCustomNumericFunction($funcName); $function = new $funcClass($funcName); $function->parse($this); return $function; } /** * FunctionsReturningDateTime ::= "CURRENT_DATE" | "CURRENT_TIME" | "CURRENT_TIMESTAMP" */ public function FunctionsReturningDatetime() { $funcNameLower = strtolower($this->_lexer->lookahead['value']); $funcClass = self::$_DATETIME_FUNCTIONS[$funcNameLower]; $function = new $funcClass($funcNameLower); $function->parse($this); return $function; } public function CustomFunctionsReturningDatetime() { // getCustomDatetimeFunction is case-insensitive $funcName = $this->_lexer->lookahead['value']; $funcClass = $this->_em->getConfiguration()->getCustomDatetimeFunction($funcName); $function = new $funcClass($funcName); $function->parse($this); return $function; } /** * FunctionsReturningStrings ::= * "CONCAT" "(" StringPrimary "," StringPrimary ")" | * "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | * "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" | * "LOWER" "(" StringPrimary ")" | * "UPPER" "(" StringPrimary ")" */ public function FunctionsReturningStrings() { $funcNameLower = strtolower($this->_lexer->lookahead['value']); $funcClass = self::$_STRING_FUNCTIONS[$funcNameLower]; $function = new $funcClass($funcNameLower); $function->parse($this); return $function; } public function CustomFunctionsReturningStrings() { // getCustomStringFunction is case-insensitive $funcName = $this->_lexer->lookahead['value']; $funcClass = $this->_em->getConfiguration()->getCustomStringFunction($funcName); $function = new $funcClass($funcName); $function->parse($this); return $function; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/ParserResult.php0000644000175100017510000001023212143374607022434 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query; /** * Encapsulates the resulting components from a DQL query parsing process that * can be serialized. * * @author Guilherme Blanco * @author Janne Vanhala * @author Roman Borschel * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link http://www.doctrine-project.org * @since 2.0 */ class ParserResult { /** * The SQL executor used for executing the SQL. * * @var \Doctrine\ORM\Query\Exec\AbstractSqlExecutor */ private $_sqlExecutor; /** * The ResultSetMapping that describes how to map the SQL result set. * * @var \Doctrine\ORM\Query\ResultSetMapping */ private $_resultSetMapping; /** * The mappings of DQL parameter names/positions to SQL parameter positions. * * @var array */ private $_parameterMappings = array(); /** * Initializes a new instance of the ParserResult class. * The new instance is initialized with an empty ResultSetMapping. */ public function __construct() { $this->_resultSetMapping = new ResultSetMapping; } /** * Gets the ResultSetMapping for the parsed query. * * @return ResultSetMapping The result set mapping of the parsed query or NULL * if the query is not a SELECT query. */ public function getResultSetMapping() { return $this->_resultSetMapping; } /** * Sets the ResultSetMapping of the parsed query. * * @param ResultSetMapping $rsm */ public function setResultSetMapping(ResultSetMapping $rsm) { $this->_resultSetMapping = $rsm; } /** * Sets the SQL executor that should be used for this ParserResult. * * @param \Doctrine\ORM\Query\Exec\AbstractSqlExecutor $executor */ public function setSqlExecutor($executor) { $this->_sqlExecutor = $executor; } /** * Gets the SQL executor used by this ParserResult. * * @return \Doctrine\ORM\Query\Exec\AbstractSqlExecutor */ public function getSqlExecutor() { return $this->_sqlExecutor; } /** * Adds a DQL to SQL parameter mapping. One DQL parameter name/position can map to * several SQL parameter positions. * * @param string|integer $dqlPosition * @param integer $sqlPosition */ public function addParameterMapping($dqlPosition, $sqlPosition) { $this->_parameterMappings[$dqlPosition][] = $sqlPosition; } /** * Gets all DQL to SQL parameter mappings. * * @return array The parameter mappings. */ public function getParameterMappings() { return $this->_parameterMappings; } /** * Gets the SQL parameter positions for a DQL parameter name/position. * * @param string|integer $dqlPosition The name or position of the DQL parameter. * @return array The positions of the corresponding SQL parameters. */ public function getSqlParameterPositions($dqlPosition) { return $this->_parameterMappings[$dqlPosition]; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Printer.php0000644000175100017510000000517312143374607021434 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query; /** * A parse tree printer for Doctrine Query Language parser. * * @author Janne Vanhala * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link http://www.phpdoctrine.org * @since 2.0 */ class Printer { /** * Current indentation level * * @var int */ protected $_indent = 0; /** * Defines whether parse tree is printed (default, false) or not (true). * * @var bool */ protected $_silent; /** * Constructs a new parse tree printer. * * @param bool $silent Parse tree will not be printed if true. */ public function __construct($silent = false) { $this->_silent = $silent; } /** * Prints an opening parenthesis followed by production name and increases * indentation level by one. * * This method is called before executing a production. * * @param string $name production name */ public function startProduction($name) { $this->println('(' . $name); $this->_indent++; } /** * Decreases indentation level by one and prints a closing parenthesis. * * This method is called after executing a production. */ public function endProduction() { $this->_indent--; $this->println(')'); } /** * Prints text indented with spaces depending on current indentation level. * * @param string $str text */ public function println($str) { if ( ! $this->_silent) { echo str_repeat(' ', $this->_indent), $str, "\n"; } } } DoctrineORM-2.3.3/Doctrine/ORM/Query/QueryException.php0000644000175100017510000001261112143374607022770 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query; use Doctrine\ORM\Query\AST\PathExpression; /** * Description of QueryException * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class QueryException extends \Doctrine\ORM\ORMException { public static function dqlError($dql) { return new self($dql); } public static function syntaxError($message, $previous = null) { return new self('[Syntax Error] ' . $message, 0, $previous); } public static function semanticalError($message, $previous = null) { return new self('[Semantical Error] ' . $message, 0, $previous); } public static function invalidLockMode() { return new self('Invalid lock mode hint provided.'); } public static function invalidParameterType($expected, $received) { return new self('Invalid parameter type, ' . $received . ' given, but ' . $expected . ' expected.'); } public static function invalidParameterPosition($pos) { return new self('Invalid parameter position: ' . $pos); } public static function invalidParameterNumber() { return new self("Invalid parameter number: number of bound variables does not match number of tokens"); } public static function invalidParameterFormat($value) { return new self('Invalid parameter format, '.$value.' given, but : or ? expected.'); } public static function unknownParameter($key) { return new self("Invalid parameter: token ".$key." is not defined in the query."); } public static function parameterTypeMissmatch() { return new self("DQL Query parameter and type numbers missmatch, but have to be exactly equal."); } public static function invalidPathExpression($pathExpr) { return new self( "Invalid PathExpression '" . $pathExpr->identificationVariable . "." . $pathExpr->field . "'." ); } public static function invalidLiteral($literal) { return new self("Invalid literal '$literal'"); } /** * @param array $assoc */ public static function iterateWithFetchJoinCollectionNotAllowed($assoc) { return new self( "Invalid query operation: Not allowed to iterate over fetch join collections ". "in class ".$assoc['sourceEntity']." assocation ".$assoc['fieldName'] ); } public static function partialObjectsAreDangerous() { return new self( "Loading partial objects is dangerous. Fetch full objects or consider " . "using a different fetch mode. If you really want partial objects, " . "set the doctrine.forcePartialLoad query hint to TRUE." ); } public static function overwritingJoinConditionsNotYetSupported($assoc) { return new self( "Unsupported query operation: It is not yet possible to overwrite the join ". "conditions in class ".$assoc['sourceEntityName']." assocation ".$assoc['fieldName'].". ". "Use WITH to append additional join conditions to the association." ); } public static function associationPathInverseSideNotSupported() { return new self( "A single-valued association path expression to an inverse side is not supported". " in DQL queries. Use an explicit join instead." ); } public static function iterateWithFetchJoinNotAllowed($assoc) { return new self( "Iterate with fetch join in class " . $assoc['sourceEntity'] . " using association " . $assoc['fieldName'] . " not allowed." ); } public static function associationPathCompositeKeyNotSupported() { return new self( "A single-valued association path expression to an entity with a composite primary ". "key is not supported. Explicitly name the components of the composite primary key ". "in the query." ); } public static function instanceOfUnrelatedClass($className, $rootClass) { return new self("Cannot check if a child of '" . $rootClass . "' is instanceof '" . $className . "', " . "inheritance hierachy exists between these two classes."); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/ResultSetMapping.php0000644000175100017510000003522212143374607023255 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query; /** * A ResultSetMapping describes how a result set of an SQL query maps to a Doctrine result. * * IMPORTANT NOTE: * The properties of this class are only public for fast internal READ access and to (drastically) * reduce the size of serialized instances for more effective caching due to better (un-)serialization * performance. * * Users should use the public methods. * * @author Roman Borschel * @since 2.0 * @todo Think about whether the number of lookup maps can be reduced. */ class ResultSetMapping { /** * @ignore * @var boolean Whether the result is mixed (contains scalar values together with field values). */ public $isMixed = false; /** * @ignore * @var array Maps alias names to class names. */ public $aliasMap = array(); /** * @ignore * @var array Maps alias names to related association field names. */ public $relationMap = array(); /** * @ignore * @var array Maps alias names to parent alias names. */ public $parentAliasMap = array(); /** * @ignore * @var array Maps column names in the result set to field names for each class. */ public $fieldMappings = array(); /** * @ignore * @var array Maps column names in the result set to the alias/field name to use in the mapped result. */ public $scalarMappings = array(); /** * @ignore * @var array Maps column names in the result set to the alias/field type to use in the mapped result. */ public $typeMappings = array(); /** * @ignore * @var array Maps entities in the result set to the alias name to use in the mapped result. */ public $entityMappings = array(); /** * @ignore * @var array Maps column names of meta columns (foreign keys, discriminator columns, ...) to field names. */ public $metaMappings = array(); /** * @ignore * @var array Maps column names in the result set to the alias they belong to. */ public $columnOwnerMap = array(); /** * @ignore * @var array List of columns in the result set that are used as discriminator columns. */ public $discriminatorColumns = array(); /** * @ignore * @var array Maps alias names to field names that should be used for indexing. */ public $indexByMap = array(); /** * @ignore * @var array Map from column names to class names that declare the field the column is mapped to. */ public $declaringClasses = array(); /** * @var array This is necessary to hydrate derivate foreign keys correctly. */ public $isIdentifierColumn = array(); /** * Adds an entity result to this ResultSetMapping. * * @param string $class The class name of the entity. * @param string $alias The alias for the class. The alias must be unique among all entity * results or joined entity results within this ResultSetMapping. * @param string $resultAlias The result alias with which the entity result should be * placed in the result structure. * @return ResultSetMapping This ResultSetMapping instance. * @todo Rename: addRootEntity */ public function addEntityResult($class, $alias, $resultAlias = null) { $this->aliasMap[$alias] = $class; $this->entityMappings[$alias] = $resultAlias; if ($resultAlias !== null) { $this->isMixed = true; } return $this; } /** * Sets a discriminator column for an entity result or joined entity result. * The discriminator column will be used to determine the concrete class name to * instantiate. * * @param string $alias The alias of the entity result or joined entity result the discriminator * column should be used for. * @param string $discrColumn The name of the discriminator column in the SQL result set. * @return ResultSetMapping This ResultSetMapping instance. * @todo Rename: addDiscriminatorColumn */ public function setDiscriminatorColumn($alias, $discrColumn) { $this->discriminatorColumns[$alias] = $discrColumn; $this->columnOwnerMap[$discrColumn] = $alias; return $this; } /** * Sets a field to use for indexing an entity result or joined entity result. * * @param string $alias The alias of an entity result or joined entity result. * @param string $fieldName The name of the field to use for indexing. * @return ResultSetMapping This ResultSetMapping instance. */ public function addIndexBy($alias, $fieldName) { $found = false; foreach ($this->fieldMappings as $columnName => $columnFieldName) { if ( ! ($columnFieldName === $fieldName && $this->columnOwnerMap[$columnName] === $alias)) continue; $this->addIndexByColumn($alias, $columnName); $found = true; break; } /* TODO: check if this exception can be put back, for now it's gone because of assumptions made by some ORM internals if ( ! $found) { $message = sprintf( 'Cannot add index by for DQL alias %s and field %s without calling addFieldResult() for them before.', $alias, $fieldName ); throw new \LogicException($message); } */ return $this; } /** * Set to index by a scalar result column name * * @param $resultColumnName * @return ResultSetMapping This ResultSetMapping instance. */ public function addIndexByScalar($resultColumnName) { $this->indexByMap['scalars'] = $resultColumnName; return $this; } /** * Sets a column to use for indexing an entity or joined entity result by the given alias name. * * @param $alias * @param $resultColumnName * @return ResultSetMapping This ResultSetMapping instance. */ public function addIndexByColumn($alias, $resultColumnName) { $this->indexByMap[$alias] = $resultColumnName; return $this; } /** * Checks whether an entity result or joined entity result with a given alias has * a field set for indexing. * * @param string $alias * @return boolean * @todo Rename: isIndexed($alias) */ public function hasIndexBy($alias) { return isset($this->indexByMap[$alias]); } /** * Checks whether the column with the given name is mapped as a field result * as part of an entity result or joined entity result. * * @param string $columnName The name of the column in the SQL result set. * @return boolean * @todo Rename: isField */ public function isFieldResult($columnName) { return isset($this->fieldMappings[$columnName]); } /** * Adds a field to the result that belongs to an entity or joined entity. * * @param string $alias The alias of the root entity or joined entity to which the field belongs. * @param string $columnName The name of the column in the SQL result set. * @param string $fieldName The name of the field on the declaring class. * @param string $declaringClass The name of the class that declares/owns the specified field. * When $alias refers to a superclass in a mapped hierarchy but * the field $fieldName is defined on a subclass, specify that here. * If not specified, the field is assumed to belong to the class * designated by $alias. * @return ResultSetMapping This ResultSetMapping instance. * @todo Rename: addField */ public function addFieldResult($alias, $columnName, $fieldName, $declaringClass = null) { // column name (in result set) => field name $this->fieldMappings[$columnName] = $fieldName; // column name => alias of owner $this->columnOwnerMap[$columnName] = $alias; // field name => class name of declaring class $this->declaringClasses[$columnName] = $declaringClass ?: $this->aliasMap[$alias]; if ( ! $this->isMixed && $this->scalarMappings) { $this->isMixed = true; } return $this; } /** * Adds a joined entity result. * * @param string $class The class name of the joined entity. * @param string $alias The unique alias to use for the joined entity. * @param string $parentAlias The alias of the entity result that is the parent of this joined result. * @param object $relation The association field that connects the parent entity result with the joined entity result. * @return ResultSetMapping This ResultSetMapping instance. * @todo Rename: addJoinedEntity */ public function addJoinedEntityResult($class, $alias, $parentAlias, $relation) { $this->aliasMap[$alias] = $class; $this->parentAliasMap[$alias] = $parentAlias; $this->relationMap[$alias] = $relation; return $this; } /** * Adds a scalar result mapping. * * @param string $columnName The name of the column in the SQL result set. * @param string $alias The result alias with which the scalar result should be placed in the result structure. * @param string $type The column type * * @return ResultSetMapping This ResultSetMapping instance. * * @todo Rename: addScalar */ public function addScalarResult($columnName, $alias, $type = 'string') { $this->scalarMappings[$columnName] = $alias; $this->typeMappings[$columnName] = $type; if ( ! $this->isMixed && $this->fieldMappings) { $this->isMixed = true; } return $this; } /** * Checks whether a column with a given name is mapped as a scalar result. * * @param string $columName The name of the column in the SQL result set. * @return boolean * @todo Rename: isScalar */ public function isScalarResult($columnName) { return isset($this->scalarMappings[$columnName]); } /** * Gets the name of the class of an entity result or joined entity result, * identified by the given unique alias. * * @param string $alias * @return string */ public function getClassName($alias) { return $this->aliasMap[$alias]; } /** * Gets the field alias for a column that is mapped as a scalar value. * * @param string $columnName The name of the column in the SQL result set. * @return string */ public function getScalarAlias($columnName) { return $this->scalarMappings[$columnName]; } /** * Gets the name of the class that owns a field mapping for the specified column. * * @param string $columnName * @return string */ public function getDeclaringClass($columnName) { return $this->declaringClasses[$columnName]; } /** * * @param string $alias * @return AssociationMapping */ public function getRelation($alias) { return $this->relationMap[$alias]; } /** * * @param string $alias * @return boolean */ public function isRelation($alias) { return isset($this->relationMap[$alias]); } /** * Gets the alias of the class that owns a field mapping for the specified column. * * @param string $columnName * @return string */ public function getEntityAlias($columnName) { return $this->columnOwnerMap[$columnName]; } /** * Gets the parent alias of the given alias. * * @param string $alias * @return string */ public function getParentAlias($alias) { return $this->parentAliasMap[$alias]; } /** * Checks whether the given alias has a parent alias. * * @param string $alias * @return boolean */ public function hasParentAlias($alias) { return isset($this->parentAliasMap[$alias]); } /** * Gets the field name for a column name. * * @param string $columnName * @return string */ public function getFieldName($columnName) { return $this->fieldMappings[$columnName]; } /** * * @return array */ public function getAliasMap() { return $this->aliasMap; } /** * Gets the number of different entities that appear in the mapped result. * * @return integer */ public function getEntityResultCount() { return count($this->aliasMap); } /** * Checks whether this ResultSetMapping defines a mixed result. * Mixed results can only occur in object and array (graph) hydration. In such a * case a mixed result means that scalar values are mixed with objects/array in * the result. * * @return boolean */ public function isMixedResult() { return $this->isMixed; } /** * Adds a meta column (foreign key or discriminator column) to the result set. * * @param string $alias * @param string $columnName * @param string $fieldName * @param bool * @return ResultSetMapping This ResultSetMapping instance. */ public function addMetaResult($alias, $columnName, $fieldName, $isIdentifierColumn = false) { $this->metaMappings[$columnName] = $fieldName; $this->columnOwnerMap[$columnName] = $alias; if ($isIdentifierColumn) { $this->isIdentifierColumn[$alias][$columnName] = true; } return $this; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/ResultSetMappingBuilder.php0000644000175100017510000002674412143374607024575 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\ClassMetadataInfo; /** * A ResultSetMappingBuilder uses the EntityManager to automatically populate entity fields * * @author Michael Ridgway * @since 2.1 */ class ResultSetMappingBuilder extends ResultSetMapping { /** * @var EntityManager */ private $em; /** * @param EntityManager */ public function __construct(EntityManager $em) { $this->em = $em; } /** * Adds a root entity and all of its fields to the result set. * * @param string $class The class name of the root entity. * @param string $alias The unique alias to use for the root entity. * @param array $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName) */ public function addRootEntityFromClassMetadata($class, $alias, $renamedColumns = array()) { $this->addEntityResult($class, $alias); $this->addAllClassFields($class, $alias, $renamedColumns); } /** * Adds a joined entity and all of its fields to the result set. * * @param string $class The class name of the joined entity. * @param string $alias The unique alias to use for the joined entity. * @param string $parentAlias The alias of the entity result that is the parent of this joined result. * @param object $relation The association field that connects the parent entity result with the joined entity result. * @param array $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName) */ public function addJoinedEntityFromClassMetadata($class, $alias, $parentAlias, $relation, $renamedColumns = array()) { $this->addJoinedEntityResult($class, $alias, $parentAlias, $relation); $this->addAllClassFields($class, $alias, $renamedColumns); } /** * Adds all fields of the given class to the result set mapping (columns and meta fields) */ protected function addAllClassFields($class, $alias, $renamedColumns = array()) { $classMetadata = $this->em->getClassMetadata($class); if ($classMetadata->isInheritanceTypeSingleTable() || $classMetadata->isInheritanceTypeJoined()) { throw new \InvalidArgumentException('ResultSetMapping builder does not currently support inheritance.'); } $platform = $this->em->getConnection()->getDatabasePlatform(); foreach ($classMetadata->getColumnNames() as $columnName) { $propertyName = $classMetadata->getFieldName($columnName); if (isset($renamedColumns[$columnName])) { $columnName = $renamedColumns[$columnName]; } $columnName = $platform->getSQLResultCasing($columnName); if (isset($this->fieldMappings[$columnName])) { throw new \InvalidArgumentException("The column '$columnName' conflicts with another column in the mapper."); } $this->addFieldResult($alias, $columnName, $propertyName); } foreach ($classMetadata->associationMappings as $associationMapping) { if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) { foreach ($associationMapping['joinColumns'] as $joinColumn) { $columnName = $joinColumn['name']; $renamedColumnName = isset($renamedColumns[$columnName]) ? $renamedColumns[$columnName] : $columnName; $renamedColumnName = $platform->getSQLResultCasing($renamedColumnName); if (isset($this->metaMappings[$renamedColumnName])) { throw new \InvalidArgumentException("The column '$renamedColumnName' conflicts with another column in the mapper."); } $this->addMetaResult($alias, $renamedColumnName, $columnName); } } } } /** * Adds the mappings of the results of native SQL queries to the result set. * * @param ClassMetadataInfo $class * @param array $queryMapping * @return ResultSetMappingBuilder */ public function addNamedNativeQueryMapping(ClassMetadataInfo $class, array $queryMapping) { if (isset($queryMapping['resultClass'])) { return $this->addNamedNativeQueryResultClassMapping($class, $queryMapping['resultClass']); } return $this->addNamedNativeQueryResultSetMapping($class, $queryMapping['resultSetMapping']); } /** * Adds the class mapping of the results of native SQL queries to the result set. * * @param ClassMetadataInfo $class * @param string $resultClassName * @return ResultSetMappingBuilder */ public function addNamedNativeQueryResultClassMapping(ClassMetadataInfo $class, $resultClassName) { $classMetadata = $this->em->getClassMetadata($resultClassName); $shortName = $classMetadata->reflClass->getShortName(); $alias = strtolower($shortName[0]).'0'; $this->addEntityResult($class->name, $alias); if ($classMetadata->discriminatorColumn) { $discriminatorColumn = $classMetadata->discriminatorColumn; $this->setDiscriminatorColumn($alias, $discriminatorColumn['name']); $this->addMetaResult($alias, $discriminatorColumn['name'], $discriminatorColumn['fieldName']); } foreach ($classMetadata->getColumnNames() as $key => $columnName) { $propertyName = $classMetadata->getFieldName($columnName); $this->addFieldResult($alias, $columnName, $propertyName); } foreach ($classMetadata->associationMappings as $associationMapping) { if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) { foreach ($associationMapping['joinColumns'] as $joinColumn) { $columnName = $joinColumn['name']; $this->addMetaResult($alias, $columnName, $columnName, $classMetadata->isIdentifier($columnName)); } } } return $this; } /** * Adds the result set mapping of the results of native SQL queries to the result set. * * @param ClassMetadataInfo $class * @param string $resultSetMappingName * @return ResultSetMappingBuilder */ public function addNamedNativeQueryResultSetMapping(ClassMetadataInfo $class, $resultSetMappingName) { $counter = 0; $resultMapping = $class->getSqlResultSetMapping($resultSetMappingName); $rooShortName = $class->reflClass->getShortName(); $rootAlias = strtolower($rooShortName[0]) . $counter; if (isset($resultMapping['entities'])) { foreach ($resultMapping['entities'] as $key => $entityMapping) { $classMetadata = $this->em->getClassMetadata($entityMapping['entityClass']); if ($class->reflClass->name == $classMetadata->reflClass->name) { $this->addEntityResult($classMetadata->name, $rootAlias); $this->addNamedNativeQueryEntityResultMapping($classMetadata, $entityMapping, $rootAlias); } else { $shortName = $classMetadata->reflClass->getShortName(); $joinAlias = strtolower($shortName[0]) . ++ $counter; $associations = $class->getAssociationsByTargetClass($classMetadata->name); foreach ($associations as $relation => $mapping) { $this->addJoinedEntityResult($mapping['targetEntity'], $joinAlias, $rootAlias, $relation); $this->addNamedNativeQueryEntityResultMapping($classMetadata, $entityMapping, $joinAlias); } } } } if (isset($resultMapping['columns'])) { foreach ($resultMapping['columns'] as $entityMapping) { $this->addScalarResult($entityMapping['name'], $entityMapping['name']); } } return $this; } /** * Adds the entity result mapping of the results of native SQL queries to the result set. * * @param ClassMetadataInfo $classMetadata * @param array $entityMapping * @param string $alias * @return ResultSetMappingBuilder */ public function addNamedNativeQueryEntityResultMapping(ClassMetadataInfo $classMetadata, array $entityMapping, $alias) { if (isset($entityMapping['discriminatorColumn']) && $entityMapping['discriminatorColumn']) { $discriminatorColumn = $entityMapping['discriminatorColumn']; $this->setDiscriminatorColumn($alias, $discriminatorColumn); $this->addMetaResult($alias, $discriminatorColumn, $discriminatorColumn); } if (isset($entityMapping['fields']) && !empty($entityMapping['fields'])) { foreach ($entityMapping['fields'] as $field) { $fieldName = $field['name']; $relation = null; if(strpos($fieldName, '.')){ list($relation, $fieldName) = explode('.', $fieldName); } if (isset($classMetadata->associationMappings[$relation])) { if($relation) { $associationMapping = $classMetadata->associationMappings[$relation]; $joinAlias = $alias.$relation; $parentAlias = $alias; $this->addJoinedEntityResult($associationMapping['targetEntity'], $joinAlias, $parentAlias, $relation); $this->addFieldResult($joinAlias, $field['column'], $fieldName); }else { $this->addFieldResult($alias, $field['column'], $fieldName, $classMetadata->name); } } else { if(!isset($classMetadata->fieldMappings[$fieldName])) { throw new \InvalidArgumentException("Entity '".$classMetadata->name."' has no field '".$fieldName."'. "); } $this->addFieldResult($alias, $field['column'], $fieldName, $classMetadata->name); } } } else { foreach ($classMetadata->getColumnNames() as $columnName) { $propertyName = $classMetadata->getFieldName($columnName); $this->addFieldResult($alias, $columnName, $propertyName); } } return $this; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/SqlWalker.php0000644000175100017510000024135012143374607021715 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query; use Doctrine\DBAL\LockMode, Doctrine\DBAL\Types\Type, Doctrine\ORM\Mapping\ClassMetadata, Doctrine\ORM\Query, Doctrine\ORM\Query\QueryException, Doctrine\ORM\Mapping\ClassMetadataInfo; /** * The SqlWalker is a TreeWalker that walks over a DQL AST and constructs * the corresponding SQL. * * @author Guilherme Blanco * @author Roman Borschel * @author Benjamin Eberlei * @author Alexander * @since 2.0 * @todo Rename: SQLWalker */ class SqlWalker implements TreeWalker { /** * @var string */ const HINT_DISTINCT = 'doctrine.distinct'; /** * @var ResultSetMapping */ private $rsm; /** * Counters for generating unique column aliases. * * @var integer */ private $aliasCounter = 0; /** * Counters for generating unique table aliases. * * @var integer */ private $tableAliasCounter = 0; /** * Counters for generating unique scalar result. * * @var integer */ private $scalarResultCounter = 1; /** * Counters for generating unique parameter indexes. * * @var integer */ private $sqlParamIndex = 0; /** * @var ParserResult */ private $parserResult; /** * @var EntityManager */ private $em; /** * @var \Doctrine\DBAL\Connection */ private $conn; /** * @var AbstractQuery */ private $query; /** * @var array */ private $tableAliasMap = array(); /** * Map from result variable names to their SQL column alias names. * * @var array */ private $scalarResultAliasMap = array(); /** * Map from DQL-Alias + Field-Name to SQL Column Alias * * @var array */ private $scalarFields = array(); /** * Map of all components/classes that appear in the DQL query. * * @var array */ private $queryComponents; /** * A list of classes that appear in non-scalar SelectExpressions. * * @var array */ private $selectedClasses = array(); /** * The DQL alias of the root class of the currently traversed query. * * @var array */ private $rootAliases = array(); /** * Flag that indicates whether to generate SQL table aliases in the SQL. * These should only be generated for SELECT queries, not for UPDATE/DELETE. * * @var boolean */ private $useSqlTableAliases = true; /** * The database platform abstraction. * * @var AbstractPlatform */ private $platform; /** * The quote strategy. * * @var \Doctrine\ORM\Mapping\QuoteStrategy */ private $quoteStrategy; /** * {@inheritDoc} */ public function __construct($query, $parserResult, array $queryComponents) { $this->query = $query; $this->parserResult = $parserResult; $this->queryComponents = $queryComponents; $this->rsm = $parserResult->getResultSetMapping(); $this->em = $query->getEntityManager(); $this->conn = $this->em->getConnection(); $this->platform = $this->conn->getDatabasePlatform(); $this->quoteStrategy = $this->em->getConfiguration()->getQuoteStrategy(); } /** * Gets the Query instance used by the walker. * * @return Query. */ public function getQuery() { return $this->query; } /** * Gets the Connection used by the walker. * * @return Connection */ public function getConnection() { return $this->conn; } /** * Gets the EntityManager used by the walker. * * @return EntityManager */ public function getEntityManager() { return $this->em; } /** * Gets the information about a single query component. * * @param string $dqlAlias The DQL alias. * @return array */ public function getQueryComponent($dqlAlias) { return $this->queryComponents[$dqlAlias]; } /** * Gets an executor that can be used to execute the result of this walker. * * @return AbstractExecutor */ public function getExecutor($AST) { switch (true) { case ($AST instanceof AST\DeleteStatement): $primaryClass = $this->em->getClassMetadata($AST->deleteClause->abstractSchemaName); return ($primaryClass->isInheritanceTypeJoined()) ? new Exec\MultiTableDeleteExecutor($AST, $this) : new Exec\SingleTableDeleteUpdateExecutor($AST, $this); case ($AST instanceof AST\UpdateStatement): $primaryClass = $this->em->getClassMetadata($AST->updateClause->abstractSchemaName); return ($primaryClass->isInheritanceTypeJoined()) ? new Exec\MultiTableUpdateExecutor($AST, $this) : new Exec\SingleTableDeleteUpdateExecutor($AST, $this); default: return new Exec\SingleSelectExecutor($AST, $this); } } /** * Generates a unique, short SQL table alias. * * @param string $tableName Table name * @param string $dqlAlias The DQL alias. * @return string Generated table alias. */ public function getSQLTableAlias($tableName, $dqlAlias = '') { $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : ''; if ( ! isset($this->tableAliasMap[$tableName])) { $this->tableAliasMap[$tableName] = strtolower(substr($tableName, 0, 1)) . $this->tableAliasCounter++ . '_'; } return $this->tableAliasMap[$tableName]; } /** * Forces the SqlWalker to use a specific alias for a table name, rather than * generating an alias on its own. * * @param string $tableName * @param string $alias * @param string $dqlAlias * @return string */ public function setSQLTableAlias($tableName, $alias, $dqlAlias = '') { $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : ''; $this->tableAliasMap[$tableName] = $alias; return $alias; } /** * Gets an SQL column alias for a column name. * * @param string $columnName * @return string */ public function getSQLColumnAlias($columnName) { return $this->quoteStrategy->getColumnAlias($columnName, $this->aliasCounter++, $this->platform); } /** * Generates the SQL JOINs that are necessary for Class Table Inheritance * for the given class. * * @param ClassMetadata $class The class for which to generate the joins. * @param string $dqlAlias The DQL alias of the class. * @return string The SQL. */ private function _generateClassTableInheritanceJoins($class, $dqlAlias) { $sql = ''; $baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); // INNER JOIN parent class tables foreach ($class->parentClasses as $parentClassName) { $parentClass = $this->em->getClassMetadata($parentClassName); $tableAlias = $this->getSQLTableAlias($parentClass->getTableName(), $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 ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON '; $sqlParts = array(); foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) { $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName; } // Add filters on the root class if ($filterSql = $this->generateFilterConditionSQL($parentClass, $tableAlias)) { $sqlParts[] = $filterSql; } $sql .= implode(' AND ', $sqlParts); } // Ignore subclassing inclusion if partial objects is disallowed if ($this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { return $sql; } // LEFT JOIN child class tables foreach ($class->subClasses as $subClassName) { $subClass = $this->em->getClassMetadata($subClassName); $tableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); $sql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON '; $sqlParts = array(); foreach ($this->quoteStrategy->getIdentifierColumnNames($subClass, $this->platform) as $columnName) { $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName; } $sql .= implode(' AND ', $sqlParts); } return $sql; } private function _generateOrderedCollectionOrderByItems() { $sqlParts = array(); foreach ($this->selectedClasses as $selectedClass) { $dqlAlias = $selectedClass['dqlAlias']; $qComp = $this->queryComponents[$dqlAlias]; if ( ! isset($qComp['relation']['orderBy'])) continue; foreach ($qComp['relation']['orderBy'] as $fieldName => $orientation) { $columnName = $this->quoteStrategy->getColumnName($fieldName, $qComp['metadata'], $this->platform); $tableName = ($qComp['metadata']->isInheritanceTypeJoined()) ? $this->em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name)->getOwningTable($fieldName) : $qComp['metadata']->getTableName(); $sqlParts[] = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName . ' ' . $orientation; } } return implode(', ', $sqlParts); } /** * Generates a discriminator column SQL condition for the class with the given DQL alias. * * @param array $dqlAliases List of root DQL aliases to inspect for discriminator restrictions. * @return string */ private function _generateDiscriminatorColumnConditionSQL(array $dqlAliases) { $sqlParts = array(); foreach ($dqlAliases as $dqlAlias) { $class = $this->queryComponents[$dqlAlias]['metadata']; if ( ! $class->isInheritanceTypeSingleTable()) continue; $conn = $this->em->getConnection(); $values = array(); if ($class->discriminatorValue !== null) { // discrimnators can be 0 $values[] = $conn->quote($class->discriminatorValue); } foreach ($class->subClasses as $subclassName) { $values[] = $conn->quote($this->em->getClassMetadata($subclassName)->discriminatorValue); } $sqlParts[] = (($this->useSqlTableAliases) ? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.' : '') . $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')'; } $sql = implode(' AND ', $sqlParts); return (count($sqlParts) > 1) ? '(' . $sql . ')' : $sql; } /** * Generates the filter SQL for a given entity and table alias. * * @param ClassMetadata $targetEntity Metadata of the target entity. * @param string $targetTableAlias The table alias of the joined/selected table. * * @return string The SQL query part to add to a query. */ private function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias) { if (!$this->em->hasFilters()) { return ''; } switch($targetEntity->inheritanceType) { case ClassMetadata::INHERITANCE_TYPE_NONE: break; case ClassMetadata::INHERITANCE_TYPE_JOINED: // The classes in the inheritance will be added to the query one by one, // but only the root node is getting filtered if ($targetEntity->name !== $targetEntity->rootEntityName) { return ''; } break; case ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE: // With STI the table will only be queried once, make sure that the filters // are added to the root entity $targetEntity = $this->em->getClassMetadata($targetEntity->rootEntityName); break; default: //@todo: throw exception? return ''; break; } $filterClauses = array(); foreach ($this->em->getFilters()->getEnabledFilters() as $filter) { if ('' !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) { $filterClauses[] = '(' . $filterExpr . ')'; } } return implode(' AND ', $filterClauses); } /** * Walks down a SelectStatement AST node, thereby generating the appropriate SQL. * * @return string The SQL. */ public function walkSelectStatement(AST\SelectStatement $AST) { $sql = $this->walkSelectClause($AST->selectClause); $sql .= $this->walkFromClause($AST->fromClause); $sql .= $this->walkWhereClause($AST->whereClause); $sql .= $AST->groupByClause ? $this->walkGroupByClause($AST->groupByClause) : ''; $sql .= $AST->havingClause ? $this->walkHavingClause($AST->havingClause) : ''; if (($orderByClause = $AST->orderByClause) !== null) { $sql .= $AST->orderByClause ? $this->walkOrderByClause($AST->orderByClause) : ''; } else if (($orderBySql = $this->_generateOrderedCollectionOrderByItems()) !== '') { $sql .= ' ORDER BY ' . $orderBySql; } $sql = $this->platform->modifyLimitQuery( $sql, $this->query->getMaxResults(), $this->query->getFirstResult() ); if (($lockMode = $this->query->getHint(Query::HINT_LOCK_MODE)) !== false) { switch ($lockMode) { case LockMode::PESSIMISTIC_READ: $sql .= ' ' . $this->platform->getReadLockSQL(); break; case LockMode::PESSIMISTIC_WRITE: $sql .= ' ' . $this->platform->getWriteLockSQL(); break; case LockMode::OPTIMISTIC: foreach ($this->selectedClasses as $selectedClass) { if ( ! $selectedClass['class']->isVersioned) { throw \Doctrine\ORM\OptimisticLockException::lockFailed($selectedClass['class']->name); } } break; case LockMode::NONE: break; default: throw \Doctrine\ORM\Query\QueryException::invalidLockMode(); } } return $sql; } /** * Walks down an UpdateStatement AST node, thereby generating the appropriate SQL. * * @param UpdateStatement * @return string The SQL. */ public function walkUpdateStatement(AST\UpdateStatement $AST) { $this->useSqlTableAliases = false; return $this->walkUpdateClause($AST->updateClause) . $this->walkWhereClause($AST->whereClause); } /** * Walks down a DeleteStatement AST node, thereby generating the appropriate SQL. * * @param DeleteStatement * @return string The SQL. */ public function walkDeleteStatement(AST\DeleteStatement $AST) { $this->useSqlTableAliases = false; return $this->walkDeleteClause($AST->deleteClause) . $this->walkWhereClause($AST->whereClause); } /** * Walks down an IdentificationVariable AST node, thereby generating the appropriate SQL. * This one differs of ->walkIdentificationVariable() because it generates the entity identifiers. * * @param string $identVariable * @return string */ public function walkEntityIdentificationVariable($identVariable) { $class = $this->queryComponents[$identVariable]['metadata']; $tableAlias = $this->getSQLTableAlias($class->getTableName(), $identVariable); $sqlParts = array(); foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) { $sqlParts[] = $tableAlias . '.' . $columnName; } return implode(', ', $sqlParts); } /** * Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL. * * @param string $identificationVariable * @param string $fieldName * @return string The SQL. */ public function walkIdentificationVariable($identificationVariable, $fieldName = null) { $class = $this->queryComponents[$identificationVariable]['metadata']; if ( $fieldName !== null && $class->isInheritanceTypeJoined() && isset($class->fieldMappings[$fieldName]['inherited']) ) { $class = $this->em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']); } return $this->getSQLTableAlias($class->getTableName(), $identificationVariable); } /** * Walks down a PathExpression AST node, thereby generating the appropriate SQL. * * @param mixed * @return string The SQL. */ public function walkPathExpression($pathExpr) { $sql = ''; switch ($pathExpr->type) { case AST\PathExpression::TYPE_STATE_FIELD: $fieldName = $pathExpr->field; $dqlAlias = $pathExpr->identificationVariable; $class = $this->queryComponents[$dqlAlias]['metadata']; if ($this->useSqlTableAliases) { $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.'; } $sql .= $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform); break; case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION: // 1- the owning side: // Just use the foreign key, i.e. u.group_id $fieldName = $pathExpr->field; $dqlAlias = $pathExpr->identificationVariable; $class = $this->queryComponents[$dqlAlias]['metadata']; if (isset($class->associationMappings[$fieldName]['inherited'])) { $class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']); } $assoc = $class->associationMappings[$fieldName]; if ( ! $assoc['isOwningSide']) { throw QueryException::associationPathInverseSideNotSupported(); } // COMPOSITE KEYS NOT (YET?) SUPPORTED if (count($assoc['sourceToTargetKeyColumns']) > 1) { throw QueryException::associationPathCompositeKeyNotSupported(); } if ($this->useSqlTableAliases) { $sql .= $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.'; } $sql .= reset($assoc['targetToSourceKeyColumns']); break; default: throw QueryException::invalidPathExpression($pathExpr); } return $sql; } /** * Walks down a SelectClause AST node, thereby generating the appropriate SQL. * * @param $selectClause * @return string The SQL. */ public function walkSelectClause($selectClause) { $sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : ''); $sqlSelectExpressions = array_filter(array_map(array($this, 'walkSelectExpression'), $selectClause->selectExpressions)); if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && $selectClause->isDistinct) { $this->query->setHint(self::HINT_DISTINCT, true); } $addMetaColumns = ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) && $this->query->getHydrationMode() == Query::HYDRATE_OBJECT || $this->query->getHydrationMode() != Query::HYDRATE_OBJECT && $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS); foreach ($this->selectedClasses as $selectedClass) { $class = $selectedClass['class']; $dqlAlias = $selectedClass['dqlAlias']; $resultAlias = $selectedClass['resultAlias']; // Register as entity or joined entity result if ($this->queryComponents[$dqlAlias]['relation'] === null) { $this->rsm->addEntityResult($class->name, $dqlAlias, $resultAlias); } else { $this->rsm->addJoinedEntityResult( $class->name, $dqlAlias, $this->queryComponents[$dqlAlias]['parent'], $this->queryComponents[$dqlAlias]['relation']['fieldName'] ); } if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) { // Add discriminator columns to SQL $rootClass = $this->em->getClassMetadata($class->rootEntityName); $tblAlias = $this->getSQLTableAlias($rootClass->getTableName(), $dqlAlias); $discrColumn = $rootClass->discriminatorColumn; $columnAlias = $this->getSQLColumnAlias($discrColumn['name']); $sqlSelectExpressions[] = $tblAlias . '.' . $discrColumn['name'] . ' AS ' . $columnAlias; $this->rsm->setDiscriminatorColumn($dqlAlias, $columnAlias); $this->rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName']); } // Add foreign key columns to SQL, if necessary if ( ! $addMetaColumns && ! $class->containsForeignIdentifier) { continue; } // Add foreign key columns of class and also parent classes foreach ($class->associationMappings as $assoc) { if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) { continue; } else if ( !$addMetaColumns && !isset($assoc['id'])) { continue; } $owningClass = (isset($assoc['inherited'])) ? $this->em->getClassMetadata($assoc['inherited']) : $class; $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias); foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { $columnAlias = $this->getSQLColumnAlias($srcColumn); $sqlSelectExpressions[] = $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; $this->rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn, (isset($assoc['id']) && $assoc['id'] === true)); } } // Add foreign key columns to SQL, if necessary if ( ! $addMetaColumns) { continue; } // Add foreign key columns of subclasses foreach ($class->subClasses as $subClassName) { $subClass = $this->em->getClassMetadata($subClassName); $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); foreach ($subClass->associationMappings as $assoc) { // Skip if association is inherited if (isset($assoc['inherited'])) continue; if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) continue; foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { $columnAlias = $this->getSQLColumnAlias($srcColumn); $sqlSelectExpressions[] = $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; $this->rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn); } } } } $sql .= implode(', ', $sqlSelectExpressions); return $sql; } /** * Walks down a FromClause AST node, thereby generating the appropriate SQL. * * @return string The SQL. */ public function walkFromClause($fromClause) { $identificationVarDecls = $fromClause->identificationVariableDeclarations; $sqlParts = array(); foreach ($identificationVarDecls as $identificationVariableDecl) { $sql = $this->platform->appendLockHint( $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration), $this->query->getHint(Query::HINT_LOCK_MODE) ); foreach ($identificationVariableDecl->joins as $join) { $sql .= $this->walkJoin($join); } if ($identificationVariableDecl->indexBy) { $alias = $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->identificationVariable; $field = $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->field; if (isset($this->scalarFields[$alias][$field])) { $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]); } else { $this->rsm->addIndexBy( $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->identificationVariable, $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->field ); } } $sqlParts[] = $sql; } return ' FROM ' . implode(', ', $sqlParts); } /** * Walks down a RangeVariableDeclaration AST node, thereby generating the appropriate SQL. * * @return string */ public function walkRangeVariableDeclaration($rangeVariableDeclaration) { $class = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName); $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable; $this->rootAliases[] = $dqlAlias; $sql = $class->getQuotedTableName($this->platform) . ' ' . $this->getSQLTableAlias($class->getTableName(), $dqlAlias); if ($class->isInheritanceTypeJoined()) { $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias); } return $sql; } /** * Walks down a JoinAssociationDeclaration AST node, thereby generating the appropriate SQL. * * @return string */ public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER) { $sql = ''; $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression; $joinedDqlAlias = $joinAssociationDeclaration->aliasIdentificationVariable; $indexBy = $joinAssociationDeclaration->indexBy; $relation = $this->queryComponents[$joinedDqlAlias]['relation']; $targetClass = $this->em->getClassMetadata($relation['targetEntity']); $sourceClass = $this->em->getClassMetadata($relation['sourceEntity']); $targetTableName = $targetClass->getQuotedTableName($this->platform); $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias); $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable); // Ensure we got the owning side, since it has all mapping info $assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation; if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && (!$this->query->getHint(self::HINT_DISTINCT) || isset($this->selectedClasses[$joinedDqlAlias]))) { if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) { throw QueryException::iterateWithFetchJoinNotAllowed($assoc); } } // This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot // be the owning side and previously we ensured that $assoc is always the owning side of the associations. // The owning side is necessary at this point because only it contains the JoinColumn information. switch (true) { case ($assoc['type'] & ClassMetadata::TO_ONE): $conditions = array(); foreach ($assoc['joinColumns'] as $joinColumn) { $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform); if ($relation['isOwningSide']) { $conditions[] = $sourceTableAlias . '.' . $quotedSourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn; continue; } $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $quotedSourceColumn; } // Apply remaining inheritance restrictions $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias)); if ($discrSql) { $conditions[] = $discrSql; } // Apply the filters $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias); if ($filterExpr) { $conditions[] = $filterExpr; } $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON ' . implode(' AND ', $conditions); break; case ($assoc['type'] == ClassMetadata::MANY_TO_MANY): // Join relation table $joinTable = $assoc['joinTable']; $joinTableAlias = $this->getSQLTableAlias($joinTable['name'], $joinedDqlAlias); $joinTableName = $sourceClass->getQuotedJoinTableName($assoc, $this->platform); $conditions = array(); $relationColumns = ($relation['isOwningSide']) ? $assoc['joinTable']['joinColumns'] : $assoc['joinTable']['inverseJoinColumns']; foreach ($relationColumns as $joinColumn) { $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform); $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn; } $sql .= $joinTableName . ' ' . $joinTableAlias . ' ON ' . implode(' AND ', $conditions); // Join target table $sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN '; $conditions = array(); $relationColumns = ($relation['isOwningSide']) ? $assoc['joinTable']['inverseJoinColumns'] : $assoc['joinTable']['joinColumns']; foreach ($relationColumns as $joinColumn) { $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform); $conditions[] = $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn; } // Apply remaining inheritance restrictions $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias)); if ($discrSql) { $conditions[] = $discrSql; } // Apply the filters $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias); if ($filterExpr) { $conditions[] = $filterExpr; } $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON ' . implode(' AND ', $conditions); break; } // FIXME: these should either be nested or all forced to be left joins (DDC-XXX) if ($targetClass->isInheritanceTypeJoined()) { $sql .= $this->_generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias); } // Apply the indexes if ($indexBy) { // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently. $this->rsm->addIndexBy( $indexBy->simpleStateFieldPathExpression->identificationVariable, $indexBy->simpleStateFieldPathExpression->field ); } else if (isset($relation['indexBy'])) { $this->rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']); } return $sql; } /** * Walks down a FunctionNode AST node, thereby generating the appropriate SQL. * * @return string The SQL. */ public function walkFunction($function) { return $function->getSql($this); } /** * Walks down an OrderByClause AST node, thereby generating the appropriate SQL. * * @param OrderByClause * @return string The SQL. */ public function walkOrderByClause($orderByClause) { $orderByItems = array_map(array($this, 'walkOrderByItem'), $orderByClause->orderByItems); if (($collectionOrderByItems = $this->_generateOrderedCollectionOrderByItems()) !== '') { $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems); } return ' ORDER BY ' . implode(', ', $orderByItems); } /** * Walks down an OrderByItem AST node, thereby generating the appropriate SQL. * * @param OrderByItem * @return string The SQL. */ public function walkOrderByItem($orderByItem) { $expr = $orderByItem->expression; $sql = ($expr instanceof AST\Node) ? $expr->dispatch($this) : $this->walkResultVariable($this->queryComponents[$expr]['token']['value']); return $sql . ' ' . strtoupper($orderByItem->type); } /** * Walks down a HavingClause AST node, thereby generating the appropriate SQL. * * @param HavingClause * @return string The SQL. */ public function walkHavingClause($havingClause) { return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression); } /** * Walks down a Join AST node and creates the corresponding SQL. * * @return string The SQL. */ public function walkJoin($join) { $joinType = $join->joinType; $joinDeclaration = $join->joinAssociationDeclaration; $sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN '; switch (true) { case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\RangeVariableDeclaration): $class = $this->em->getClassMetadata($joinDeclaration->abstractSchemaName); $condExprConjunction = $class->isInheritanceTypeJoined() && $joinType != AST\Join::JOIN_TYPE_LEFT && $joinType != AST\Join::JOIN_TYPE_LEFTOUTER ? ' AND ' : ' ON '; $sql .= $this->walkRangeVariableDeclaration($joinDeclaration) . $condExprConjunction . '(' . $this->walkConditionalExpression($join->conditionalExpression) . ')'; break; case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\JoinAssociationDeclaration): $sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType); // Handle WITH clause if (($condExpr = $join->conditionalExpression) !== null) { // Phase 2 AST optimization: Skip processment of ConditionalExpression // if only one ConditionalTerm is defined $sql .= ' AND (' . $this->walkConditionalExpression($condExpr) . ')'; } break; } return $sql; } /** * Walks down a CaseExpression AST node and generates the corresponding SQL. * * @param CoalesceExpression|NullIfExpression|GeneralCaseExpression|SimpleCaseExpression $expression * @return string The SQL. */ public function walkCaseExpression($expression) { switch (true) { case ($expression instanceof AST\CoalesceExpression): return $this->walkCoalesceExpression($expression); case ($expression instanceof AST\NullIfExpression): return $this->walkNullIfExpression($expression); case ($expression instanceof AST\GeneralCaseExpression): return $this->walkGeneralCaseExpression($expression); case ($expression instanceof AST\SimpleCaseExpression): return $this->walkSimpleCaseExpression($expression); default: return ''; } } /** * Walks down a CoalesceExpression AST node and generates the corresponding SQL. * * @param CoalesceExpression $coalesceExpression * @return string The SQL. */ public function walkCoalesceExpression($coalesceExpression) { $sql = 'COALESCE('; $scalarExpressions = array(); foreach ($coalesceExpression->scalarExpressions as $scalarExpression) { $scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression); } $sql .= implode(', ', $scalarExpressions) . ')'; return $sql; } /** * Walks down a NullIfExpression AST node and generates the corresponding SQL. * * @param NullIfExpression $nullIfExpression * @return string The SQL. */ public function walkNullIfExpression($nullIfExpression) { $firstExpression = is_string($nullIfExpression->firstExpression) ? $this->conn->quote($nullIfExpression->firstExpression) : $this->walkSimpleArithmeticExpression($nullIfExpression->firstExpression); $secondExpression = is_string($nullIfExpression->secondExpression) ? $this->conn->quote($nullIfExpression->secondExpression) : $this->walkSimpleArithmeticExpression($nullIfExpression->secondExpression); return 'NULLIF(' . $firstExpression . ', ' . $secondExpression . ')'; } /** * Walks down a GeneralCaseExpression AST node and generates the corresponding SQL. * * @param GeneralCaseExpression $generalCaseExpression * @return string The SQL. */ public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCaseExpression) { $sql = 'CASE'; foreach ($generalCaseExpression->whenClauses as $whenClause) { $sql .= ' WHEN ' . $this->walkConditionalExpression($whenClause->caseConditionExpression); $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($whenClause->thenScalarExpression); } $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($generalCaseExpression->elseScalarExpression) . ' END'; return $sql; } /** * Walks down a SimpleCaseExpression AST node and generates the corresponding SQL. * * @param SimpleCaseExpression $simpleCaseExpression * @return string The SQL. */ public function walkSimpleCaseExpression($simpleCaseExpression) { $sql = 'CASE ' . $this->walkStateFieldPathExpression($simpleCaseExpression->caseOperand); foreach ($simpleCaseExpression->simpleWhenClauses as $simpleWhenClause) { $sql .= ' WHEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->caseScalarExpression); $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->thenScalarExpression); } $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($simpleCaseExpression->elseScalarExpression) . ' END'; return $sql; } /** * Walks down a SelectExpression AST node and generates the corresponding SQL. * * @param SelectExpression $selectExpression * @return string The SQL. */ public function walkSelectExpression($selectExpression) { $sql = ''; $expr = $selectExpression->expression; $hidden = $selectExpression->hiddenAliasResultVariable; switch (true) { case ($expr instanceof AST\PathExpression): if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) { throw QueryException::invalidPathExpression($expr); } $fieldName = $expr->field; $dqlAlias = $expr->identificationVariable; $qComp = $this->queryComponents[$dqlAlias]; $class = $qComp['metadata']; $resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName; $tableName = ($class->isInheritanceTypeJoined()) ? $this->em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName) : $class->getTableName(); $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); $columnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform); $columnAlias = $this->getSQLColumnAlias($class->fieldMappings[$fieldName]['columnName']); $col = $sqlTableAlias . '.' . $columnName; $fieldType = $class->getTypeOfField($fieldName); if (isset($class->fieldMappings[$fieldName]['requireSQLConversion'])) { $type = Type::getType($fieldType); $col = $type->convertToPHPValueSQL($col, $this->conn->getDatabasePlatform()); } $sql .= $col . ' AS ' . $columnAlias; $this->scalarResultAliasMap[$resultAlias] = $columnAlias; if ( ! $hidden) { $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType); $this->scalarFields[$dqlAlias][$fieldName] = $columnAlias; } break; case ($expr instanceof AST\AggregateExpression): case ($expr instanceof AST\Functions\FunctionNode): case ($expr instanceof AST\SimpleArithmeticExpression): case ($expr instanceof AST\ArithmeticTerm): case ($expr instanceof AST\ArithmeticFactor): case ($expr instanceof AST\Literal): case ($expr instanceof AST\NullIfExpression): case ($expr instanceof AST\CoalesceExpression): case ($expr instanceof AST\GeneralCaseExpression): case ($expr instanceof AST\SimpleCaseExpression): $columnAlias = $this->getSQLColumnAlias('sclr'); $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++; $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias; $this->scalarResultAliasMap[$resultAlias] = $columnAlias; if ( ! $hidden) { // We cannot resolve field type here; assume 'string'. $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string'); } break; case ($expr instanceof AST\Subselect): $columnAlias = $this->getSQLColumnAlias('sclr'); $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++; $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias; $this->scalarResultAliasMap[$resultAlias] = $columnAlias; if ( ! $hidden) { // We cannot resolve field type here; assume 'string'. $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string'); } break; default: // IdentificationVariable or PartialObjectExpression if ($expr instanceof AST\PartialObjectExpression) { $dqlAlias = $expr->identificationVariable; $partialFieldSet = $expr->partialFieldSet; } else { $dqlAlias = $expr; $partialFieldSet = array(); } $queryComp = $this->queryComponents[$dqlAlias]; $class = $queryComp['metadata']; $resultAlias = $selectExpression->fieldIdentificationVariable ?: null; if ( ! isset($this->selectedClasses[$dqlAlias])) { $this->selectedClasses[$dqlAlias] = array( 'class' => $class, 'dqlAlias' => $dqlAlias, 'resultAlias' => $resultAlias ); } $sqlParts = array(); // Select all fields from the queried class foreach ($class->fieldMappings as $fieldName => $mapping) { if ($partialFieldSet && ! in_array($fieldName, $partialFieldSet)) { continue; } $tableName = (isset($mapping['inherited'])) ? $this->em->getClassMetadata($mapping['inherited'])->getTableName() : $class->getTableName(); $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); $columnAlias = $this->getSQLColumnAlias($mapping['columnName']); $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform); $col = $sqlTableAlias . '.' . $quotedColumnName; if (isset($class->fieldMappings[$fieldName]['requireSQLConversion'])) { $type = Type::getType($class->getTypeOfField($fieldName)); $col = $type->convertToPHPValueSQL($col, $this->platform); } $sqlParts[] = $col . ' AS '. $columnAlias; $this->scalarResultAliasMap[$resultAlias][] = $columnAlias; $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); } // Add any additional fields of subclasses (excluding inherited fields) // 1) on Single Table Inheritance: always, since its marginal overhead // 2) on Class Table Inheritance only if partial objects are disallowed, // since it requires outer joining subtables. if ($class->isInheritanceTypeSingleTable() || ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { foreach ($class->subClasses as $subClassName) { $subClass = $this->em->getClassMetadata($subClassName); $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); foreach ($subClass->fieldMappings as $fieldName => $mapping) { if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) { continue; } $columnAlias = $this->getSQLColumnAlias($mapping['columnName']); $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $subClass, $this->platform); $col = $sqlTableAlias . '.' . $quotedColumnName; if (isset($subClass->fieldMappings[$fieldName]['requireSQLConversion'])) { $type = Type::getType($subClass->getTypeOfField($fieldName)); $col = $type->convertToPHPValueSQL($col, $this->platform); } $sqlParts[] = $col . ' AS ' . $columnAlias; $this->scalarResultAliasMap[$resultAlias][] = $columnAlias; $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName); } } } $sql .= implode(', ', $sqlParts); } return $sql; } /** * Walks down a QuantifiedExpression AST node, thereby generating the appropriate SQL. * * @param QuantifiedExpression * @return string The SQL. */ public function walkQuantifiedExpression($qExpr) { return ' ' . strtoupper($qExpr->type) . '(' . $this->walkSubselect($qExpr->subselect) . ')'; } /** * Walks down a Subselect AST node, thereby generating the appropriate SQL. * * @param Subselect * @return string The SQL. */ public function walkSubselect($subselect) { $useAliasesBefore = $this->useSqlTableAliases; $rootAliasesBefore = $this->rootAliases; $this->rootAliases = array(); // reset the rootAliases for the subselect $this->useSqlTableAliases = true; $sql = $this->walkSimpleSelectClause($subselect->simpleSelectClause); $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause); $sql .= $this->walkWhereClause($subselect->whereClause); $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : ''; $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : ''; $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : ''; $this->rootAliases = $rootAliasesBefore; // put the main aliases back $this->useSqlTableAliases = $useAliasesBefore; return $sql; } /** * Walks down a SubselectFromClause AST node, thereby generating the appropriate SQL. * * @param SubselectFromClause * @return string The SQL. */ public function walkSubselectFromClause($subselectFromClause) { $identificationVarDecls = $subselectFromClause->identificationVariableDeclarations; $sqlParts = array (); foreach ($identificationVarDecls as $subselectIdVarDecl) { $sql = $this->platform->appendLockHint( $this->walkRangeVariableDeclaration($subselectIdVarDecl->rangeVariableDeclaration), $this->query->getHint(Query::HINT_LOCK_MODE) ); foreach ($subselectIdVarDecl->joins as $join) { $sql .= $this->walkJoin($join); } $sqlParts[] = $sql; } return ' FROM ' . implode(', ', $sqlParts); } /** * Walks down a SimpleSelectClause AST node, thereby generating the appropriate SQL. * * @param SimpleSelectClause * @return string The SQL. */ public function walkSimpleSelectClause($simpleSelectClause) { return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '') . $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression); } /** * Walks down a SimpleSelectExpression AST node, thereby generating the appropriate SQL. * * @param SimpleSelectExpression * @return string The SQL. */ public function walkSimpleSelectExpression($simpleSelectExpression) { $expr = $simpleSelectExpression->expression; $sql = ' '; switch (true) { case ($expr instanceof AST\PathExpression): $sql .= $this->walkPathExpression($expr); break; case ($expr instanceof AST\AggregateExpression): $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++; $sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias; break; case ($expr instanceof AST\Subselect): $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++; $columnAlias = 'sclr' . $this->aliasCounter++; $this->scalarResultAliasMap[$alias] = $columnAlias; $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias; break; case ($expr instanceof AST\Functions\FunctionNode): case ($expr instanceof AST\SimpleArithmeticExpression): case ($expr instanceof AST\ArithmeticTerm): case ($expr instanceof AST\ArithmeticFactor): case ($expr instanceof AST\Literal): case ($expr instanceof AST\NullIfExpression): case ($expr instanceof AST\CoalesceExpression): case ($expr instanceof AST\GeneralCaseExpression): case ($expr instanceof AST\SimpleCaseExpression): $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++; $columnAlias = $this->getSQLColumnAlias('sclr'); $this->scalarResultAliasMap[$alias] = $columnAlias; $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias; break; default: // IdentificationVariable $sql .= $this->walkEntityIdentificationVariable($expr); break; } return $sql; } /** * Walks down an AggregateExpression AST node, thereby generating the appropriate SQL. * * @param AggregateExpression * @return string The SQL. */ public function walkAggregateExpression($aggExpression) { return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '') . $this->walkSimpleArithmeticExpression($aggExpression->pathExpression) . ')'; } /** * Walks down a GroupByClause AST node, thereby generating the appropriate SQL. * * @param GroupByClause * @return string The SQL. */ public function walkGroupByClause($groupByClause) { $sqlParts = array(); foreach ($groupByClause->groupByItems as $groupByItem) { $sqlParts[] = $this->walkGroupByItem($groupByItem); } return ' GROUP BY ' . implode(', ', $sqlParts); } /** * Walks down a GroupByItem AST node, thereby generating the appropriate SQL. * * @param GroupByItem * @return string The SQL. */ public function walkGroupByItem($groupByItem) { // StateFieldPathExpression if ( ! is_string($groupByItem)) { return $this->walkPathExpression($groupByItem); } // ResultVariable if (isset($this->queryComponents[$groupByItem]['resultVariable'])) { return $this->walkResultVariable($groupByItem); } // IdentificationVariable $sqlParts = array(); foreach ($this->queryComponents[$groupByItem]['metadata']->fieldNames as $field) { $item = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $field); $item->type = AST\PathExpression::TYPE_STATE_FIELD; $sqlParts[] = $this->walkPathExpression($item); } foreach ($this->queryComponents[$groupByItem]['metadata']->associationMappings as $mapping) { if ($mapping['isOwningSide'] && $mapping['type'] & ClassMetadataInfo::TO_ONE) { $item = new AST\PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $groupByItem, $mapping['fieldName']); $item->type = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION; $sqlParts[] = $this->walkPathExpression($item); } } return implode(', ', $sqlParts); } /** * Walks down a DeleteClause AST node, thereby generating the appropriate SQL. * * @param DeleteClause * @return string The SQL. */ public function walkDeleteClause(AST\DeleteClause $deleteClause) { $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName); $tableName = $class->getTableName(); $sql = 'DELETE FROM ' . $this->quoteStrategy->getTableName($class, $this->platform); $this->setSQLTableAlias($tableName, $tableName, $deleteClause->aliasIdentificationVariable); $this->rootAliases[] = $deleteClause->aliasIdentificationVariable; return $sql; } /** * Walks down an UpdateClause AST node, thereby generating the appropriate SQL. * * @param UpdateClause * @return string The SQL. */ public function walkUpdateClause($updateClause) { $class = $this->em->getClassMetadata($updateClause->abstractSchemaName); $tableName = $class->getTableName(); $sql = 'UPDATE ' . $this->quoteStrategy->getTableName($class, $this->platform); $this->setSQLTableAlias($tableName, $tableName, $updateClause->aliasIdentificationVariable); $this->rootAliases[] = $updateClause->aliasIdentificationVariable; $sql .= ' SET ' . implode(', ', array_map(array($this, 'walkUpdateItem'), $updateClause->updateItems)); return $sql; } /** * Walks down an UpdateItem AST node, thereby generating the appropriate SQL. * * @param UpdateItem * @return string The SQL. */ public function walkUpdateItem($updateItem) { $useTableAliasesBefore = $this->useSqlTableAliases; $this->useSqlTableAliases = false; $sql = $this->walkPathExpression($updateItem->pathExpression) . ' = '; $newValue = $updateItem->newValue; switch (true) { case ($newValue instanceof AST\Node): $sql .= $newValue->dispatch($this); break; case ($newValue === null): $sql .= 'NULL'; break; default: $sql .= $this->conn->quote($newValue); break; } $this->useSqlTableAliases = $useTableAliasesBefore; return $sql; } /** * Walks down a WhereClause AST node, thereby generating the appropriate SQL. * WhereClause or not, the appropriate discriminator sql is added. * * @param WhereClause * @return string The SQL. */ public function walkWhereClause($whereClause) { $condSql = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : ''; $discrSql = $this->_generateDiscriminatorColumnConditionSql($this->rootAliases); if ($this->em->hasFilters()) { $filterClauses = array(); foreach ($this->rootAliases as $dqlAlias) { $class = $this->queryComponents[$dqlAlias]['metadata']; $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); if ($filterExpr = $this->generateFilterConditionSQL($class, $tableAlias)) { $filterClauses[] = $filterExpr; } } if (count($filterClauses)) { if ($condSql) { $condSql = '(' . $condSql . ') AND '; } $condSql .= implode(' AND ', $filterClauses); } } if ($condSql) { return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql); } if ($discrSql) { return ' WHERE ' . $discrSql; } return ''; } /** * Walk down a ConditionalExpression AST node, thereby generating the appropriate SQL. * * @param ConditionalExpression * @return string The SQL. */ public function walkConditionalExpression($condExpr) { // Phase 2 AST optimization: Skip processment of ConditionalExpression // if only one ConditionalTerm is defined if ( ! ($condExpr instanceof AST\ConditionalExpression)) { return $this->walkConditionalTerm($condExpr); } return implode(' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms)); } /** * Walks down a ConditionalTerm AST node, thereby generating the appropriate SQL. * * @param ConditionalTerm * @return string The SQL. */ public function walkConditionalTerm($condTerm) { // Phase 2 AST optimization: Skip processment of ConditionalTerm // if only one ConditionalFactor is defined if ( ! ($condTerm instanceof AST\ConditionalTerm)) { return $this->walkConditionalFactor($condTerm); } return implode(' AND ', array_map(array($this, 'walkConditionalFactor'), $condTerm->conditionalFactors)); } /** * Walks down a ConditionalFactor AST node, thereby generating the appropriate SQL. * * @param ConditionalFactor * @return string The SQL. */ public function walkConditionalFactor($factor) { // Phase 2 AST optimization: Skip processment of ConditionalFactor // if only one ConditionalPrimary is defined return ( ! ($factor instanceof AST\ConditionalFactor)) ? $this->walkConditionalPrimary($factor) : ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary); } /** * Walks down a ConditionalPrimary AST node, thereby generating the appropriate SQL. * * @param ConditionalPrimary * @return string The SQL. */ public function walkConditionalPrimary($primary) { if ($primary->isSimpleConditionalExpression()) { return $primary->simpleConditionalExpression->dispatch($this); } if ($primary->isConditionalExpression()) { $condExpr = $primary->conditionalExpression; return '(' . $this->walkConditionalExpression($condExpr) . ')'; } } /** * Walks down an ExistsExpression AST node, thereby generating the appropriate SQL. * * @param ExistsExpression * @return string The SQL. */ public function walkExistsExpression($existsExpr) { $sql = ($existsExpr->not) ? 'NOT ' : ''; $sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->subselect) . ')'; return $sql; } /** * Walks down a CollectionMemberExpression AST node, thereby generating the appropriate SQL. * * @param CollectionMemberExpression * @return string The SQL. */ public function walkCollectionMemberExpression($collMemberExpr) { $sql = $collMemberExpr->not ? 'NOT ' : ''; $sql .= 'EXISTS (SELECT 1 FROM '; $entityExpr = $collMemberExpr->entityExpression; $collPathExpr = $collMemberExpr->collectionValuedPathExpression; $fieldName = $collPathExpr->field; $dqlAlias = $collPathExpr->identificationVariable; $class = $this->queryComponents[$dqlAlias]['metadata']; switch (true) { // InputParameter case ($entityExpr instanceof AST\InputParameter): $dqlParamKey = $entityExpr->name; $entitySql = '?'; break; // SingleValuedAssociationPathExpression | IdentificationVariable case ($entityExpr instanceof AST\PathExpression): $entitySql = $this->walkPathExpression($entityExpr); break; default: throw new \BadMethodCallException("Not implemented"); } $assoc = $class->associationMappings[$fieldName]; if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) { $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName()); $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); $sql .= $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' WHERE '; $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']]; $sqlParts = array(); foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) { $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $this->platform); $sqlParts[] = $sourceTableAlias . '.' . $targetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn; } foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) { if (isset($dqlParamKey)) { $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++); } $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' = ' . $entitySql; } $sql .= implode(' AND ', $sqlParts); } else { // many-to-many $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); $owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']]; $joinTable = $owningAssoc['joinTable']; // SQL table aliases $joinTableAlias = $this->getSQLTableAlias($joinTable['name']); $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName()); $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); // join to target table $sql .= $this->quoteStrategy->getJoinTableName($owningAssoc, $targetClass, $this->platform) . ' ' . $joinTableAlias . ' INNER JOIN ' . $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' ON '; // join conditions $joinColumns = $assoc['isOwningSide'] ? $joinTable['inverseJoinColumns'] : $joinTable['joinColumns']; $joinSqlParts = array(); foreach ($joinColumns as $joinColumn) { $targetColumn = $this->quoteStrategy->getColumnName($targetClass->fieldNames[$joinColumn['referencedColumnName']], $targetClass, $this->platform); $joinSqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $targetTableAlias . '.' . $targetColumn; } $sql .= implode(' AND ', $joinSqlParts); $sql .= ' WHERE '; $joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns']; $sqlParts = array(); foreach ($joinColumns as $joinColumn) { $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$joinColumn['referencedColumnName']], $class, $this->platform); $sqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $sourceTableAlias . '.' . $targetColumn; } foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) { if (isset($dqlParamKey)) { $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++); } $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' = ' . $entitySql; } $sql .= implode(' AND ', $sqlParts); } return $sql . ')'; } /** * Walks down an EmptyCollectionComparisonExpression AST node, thereby generating the appropriate SQL. * * @param EmptyCollectionComparisonExpression * @return string The SQL. */ public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr) { $sizeFunc = new AST\Functions\SizeFunction('size'); $sizeFunc->collectionPathExpression = $emptyCollCompExpr->expression; return $sizeFunc->getSql($this) . ($emptyCollCompExpr->not ? ' > 0' : ' = 0'); } /** * Walks down a NullComparisonExpression AST node, thereby generating the appropriate SQL. * * @param NullComparisonExpression * @return string The SQL. */ public function walkNullComparisonExpression($nullCompExpr) { $sql = ''; $innerExpr = $nullCompExpr->expression; if ($innerExpr instanceof AST\InputParameter) { $dqlParamKey = $innerExpr->name; $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++); $sql .= ' ?'; } else { $sql .= $this->walkPathExpression($innerExpr); } $sql .= ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL'; return $sql; } /** * Walks down an InExpression AST node, thereby generating the appropriate SQL. * * @param InExpression * @return string The SQL. */ public function walkInExpression($inExpr) { $sql = $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN ('; $sql .= ($inExpr->subselect) ? $this->walkSubselect($inExpr->subselect) : implode(', ', array_map(array($this, 'walkInParameter'), $inExpr->literals)); $sql .= ')'; return $sql; } /** * Walks down an InstanceOfExpression AST node, thereby generating the appropriate SQL. * * @param InstanceOfExpression * @return string The SQL. */ public function walkInstanceOfExpression($instanceOfExpr) { $sql = ''; $dqlAlias = $instanceOfExpr->identificationVariable; $discrClass = $class = $this->queryComponents[$dqlAlias]['metadata']; if ($class->discriminatorColumn) { $discrClass = $this->em->getClassMetadata($class->rootEntityName); } if ($this->useSqlTableAliases) { $sql .= $this->getSQLTableAlias($discrClass->getTableName(), $dqlAlias) . '.'; } $sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' NOT IN ' : ' IN '); $sqlParameterList = array(); foreach ($instanceOfExpr->value as $parameter) { if ($parameter instanceof AST\InputParameter) { // We need to modify the parameter value to be its correspondent mapped value $dqlParamKey = $parameter->name; $dqlParam = $this->query->getParameter($dqlParamKey); $paramValue = $this->query->processParameterValue($dqlParam->getValue()); if ( ! ($paramValue instanceof \Doctrine\ORM\Mapping\ClassMetadata)) { throw QueryException::invalidParameterType('ClassMetadata', get_class($paramValue)); } $entityClassName = $paramValue->name; } else { // Get name from ClassMetadata to resolve aliases. $entityClassName = $this->em->getClassMetadata($parameter)->name; } if ($entityClassName == $class->name) { $sqlParameterList[] = $this->conn->quote($class->discriminatorValue); } else { $discrMap = array_flip($class->discriminatorMap); if (!isset($discrMap[$entityClassName])) { throw QueryException::instanceOfUnrelatedClass($entityClassName, $class->rootEntityName); } $sqlParameterList[] = $this->conn->quote($discrMap[$entityClassName]); } } $sql .= '(' . implode(', ', $sqlParameterList) . ')'; return $sql; } /** * Walks down an InParameter AST node, thereby generating the appropriate SQL. * * @param InParameter * @return string The SQL. */ public function walkInParameter($inParam) { return $inParam instanceof AST\InputParameter ? $this->walkInputParameter($inParam) : $this->walkLiteral($inParam); } /** * Walks down a literal that represents an AST node, thereby generating the appropriate SQL. * * @param mixed * @return string The SQL. */ public function walkLiteral($literal) { switch ($literal->type) { case AST\Literal::STRING: return $this->conn->quote($literal->value); case AST\Literal::BOOLEAN: $bool = strtolower($literal->value) == 'true' ? true : false; $boolVal = $this->conn->getDatabasePlatform()->convertBooleans($bool); return $boolVal; case AST\Literal::NUMERIC: return $literal->value; default: throw QueryException::invalidLiteral($literal); } } /** * Walks down a BetweenExpression AST node, thereby generating the appropriate SQL. * * @param BetweenExpression * @return string The SQL. */ public function walkBetweenExpression($betweenExpr) { $sql = $this->walkArithmeticExpression($betweenExpr->expression); if ($betweenExpr->not) $sql .= ' NOT'; $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression) . ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression); return $sql; } /** * Walks down a LikeExpression AST node, thereby generating the appropriate SQL. * * @param LikeExpression * @return string The SQL. */ public function walkLikeExpression($likeExpr) { $stringExpr = $likeExpr->stringExpression; $sql = $stringExpr->dispatch($this) . ($likeExpr->not ? ' NOT' : '') . ' LIKE '; if ($likeExpr->stringPattern instanceof AST\InputParameter) { $inputParam = $likeExpr->stringPattern; $dqlParamKey = $inputParam->name; $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++); $sql .= '?'; } elseif ($likeExpr->stringPattern instanceof AST\Functions\FunctionNode ) { $sql .= $this->walkFunction($likeExpr->stringPattern); } elseif ($likeExpr->stringPattern instanceof AST\PathExpression) { $sql .= $this->walkPathExpression($likeExpr->stringPattern); } else { $sql .= $this->walkLiteral($likeExpr->stringPattern); } if ($likeExpr->escapeChar) { $sql .= ' ESCAPE ' . $this->walkLiteral($likeExpr->escapeChar); } return $sql; } /** * Walks down a StateFieldPathExpression AST node, thereby generating the appropriate SQL. * * @param StateFieldPathExpression * @return string The SQL. */ public function walkStateFieldPathExpression($stateFieldPathExpression) { return $this->walkPathExpression($stateFieldPathExpression); } /** * Walks down a ComparisonExpression AST node, thereby generating the appropriate SQL. * * @param ComparisonExpression * @return string The SQL. */ public function walkComparisonExpression($compExpr) { $leftExpr = $compExpr->leftExpression; $rightExpr = $compExpr->rightExpression; $sql = ''; $sql .= ($leftExpr instanceof AST\Node) ? $leftExpr->dispatch($this) : (is_numeric($leftExpr) ? $leftExpr : $this->conn->quote($leftExpr)); $sql .= ' ' . $compExpr->operator . ' '; $sql .= ($rightExpr instanceof AST\Node) ? $rightExpr->dispatch($this) : (is_numeric($rightExpr) ? $rightExpr : $this->conn->quote($rightExpr)); return $sql; } /** * Walks down an InputParameter AST node, thereby generating the appropriate SQL. * * @param InputParameter * @return string The SQL. */ public function walkInputParameter($inputParam) { $this->parserResult->addParameterMapping($inputParam->name, $this->sqlParamIndex++); return '?'; } /** * Walks down an ArithmeticExpression AST node, thereby generating the appropriate SQL. * * @param ArithmeticExpression * @return string The SQL. */ public function walkArithmeticExpression($arithmeticExpr) { return ($arithmeticExpr->isSimpleArithmeticExpression()) ? $this->walkSimpleArithmeticExpression($arithmeticExpr->simpleArithmeticExpression) : '(' . $this->walkSubselect($arithmeticExpr->subselect) . ')'; } /** * Walks down an SimpleArithmeticExpression AST node, thereby generating the appropriate SQL. * * @param SimpleArithmeticExpression * @return string The SQL. */ public function walkSimpleArithmeticExpression($simpleArithmeticExpr) { if ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) { return $this->walkArithmeticTerm($simpleArithmeticExpr); } return implode(' ', array_map(array($this, 'walkArithmeticTerm'), $simpleArithmeticExpr->arithmeticTerms)); } /** * Walks down an ArithmeticTerm AST node, thereby generating the appropriate SQL. * * @param mixed * @return string The SQL. */ public function walkArithmeticTerm($term) { if (is_string($term)) { return (isset($this->queryComponents[$term])) ? $this->walkResultVariable($this->queryComponents[$term]['token']['value']) : $term; } // Phase 2 AST optimization: Skip processment of ArithmeticTerm // if only one ArithmeticFactor is defined if ( ! ($term instanceof AST\ArithmeticTerm)) { return $this->walkArithmeticFactor($term); } return implode(' ', array_map(array($this, 'walkArithmeticFactor'), $term->arithmeticFactors)); } /** * Walks down an ArithmeticFactor that represents an AST node, thereby generating the appropriate SQL. * * @param mixed * @return string The SQL. */ public function walkArithmeticFactor($factor) { if (is_string($factor)) { return $factor; } // Phase 2 AST optimization: Skip processment of ArithmeticFactor // if only one ArithmeticPrimary is defined if ( ! ($factor instanceof AST\ArithmeticFactor)) { return $this->walkArithmeticPrimary($factor); } $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : ''); return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary); } /** * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL. * * @param mixed * @return string The SQL. */ public function walkArithmeticPrimary($primary) { if ($primary instanceof AST\SimpleArithmeticExpression) { return '(' . $this->walkSimpleArithmeticExpression($primary) . ')'; } if ($primary instanceof AST\Node) { return $primary->dispatch($this); } return $this->walkEntityIdentificationVariable($primary); } /** * Walks down a StringPrimary that represents an AST node, thereby generating the appropriate SQL. * * @param mixed * @return string The SQL. */ public function walkStringPrimary($stringPrimary) { return (is_string($stringPrimary)) ? $this->conn->quote($stringPrimary) : $stringPrimary->dispatch($this); } /** * Walks down a ResultVriable that represents an AST node, thereby generating the appropriate SQL. * * @param string $resultVariable * @return string The SQL. */ public function walkResultVariable($resultVariable) { $resultAlias = $this->scalarResultAliasMap[$resultVariable]; if (is_array($resultAlias)) { return implode(', ', $resultAlias); } return $resultAlias; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/TreeWalker.php0000644000175100017510000002724112143374607022056 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query; /** * Interface for walkers of DQL ASTs (abstract syntax trees). * * @author Roman Borschel * @since 2.0 */ interface TreeWalker { /** * Initializes TreeWalker with important information about the ASTs to be walked * * @param Query $query The parsed Query. * @param ParserResult $parserResult The result of the parsing process. * @param array $queryComponents Query components (symbol table) */ public function __construct($query, $parserResult, array $queryComponents); /** * Walks down a SelectStatement AST node, thereby generating the appropriate SQL. * * @return string The SQL. */ function walkSelectStatement(AST\SelectStatement $AST); /** * Walks down a SelectClause AST node, thereby generating the appropriate SQL. * * @return string The SQL. */ function walkSelectClause($selectClause); /** * Walks down a FromClause AST node, thereby generating the appropriate SQL. * * @return string The SQL. */ function walkFromClause($fromClause); /** * Walks down a FunctionNode AST node, thereby generating the appropriate SQL. * * @return string The SQL. */ function walkFunction($function); /** * Walks down an OrderByClause AST node, thereby generating the appropriate SQL. * * @param OrderByClause * @return string The SQL. */ function walkOrderByClause($orderByClause); /** * Walks down an OrderByItem AST node, thereby generating the appropriate SQL. * * @param OrderByItem * @return string The SQL. */ function walkOrderByItem($orderByItem); /** * Walks down a HavingClause AST node, thereby generating the appropriate SQL. * * @param HavingClause * @return string The SQL. */ function walkHavingClause($havingClause); /** * Walks down a Join AST node and creates the corresponding SQL. * * @param Join $joinVarDecl * @return string The SQL. */ function walkJoin($join); /** * Walks down a SelectExpression AST node and generates the corresponding SQL. * * @param SelectExpression $selectExpression * @return string The SQL. */ function walkSelectExpression($selectExpression); /** * Walks down a QuantifiedExpression AST node, thereby generating the appropriate SQL. * * @param QuantifiedExpression * @return string The SQL. */ function walkQuantifiedExpression($qExpr); /** * Walks down a Subselect AST node, thereby generating the appropriate SQL. * * @param Subselect * @return string The SQL. */ function walkSubselect($subselect); /** * Walks down a SubselectFromClause AST node, thereby generating the appropriate SQL. * * @param SubselectFromClause * @return string The SQL. */ function walkSubselectFromClause($subselectFromClause); /** * Walks down a SimpleSelectClause AST node, thereby generating the appropriate SQL. * * @param SimpleSelectClause * @return string The SQL. */ function walkSimpleSelectClause($simpleSelectClause); /** * Walks down a SimpleSelectExpression AST node, thereby generating the appropriate SQL. * * @param SimpleSelectExpression * @return string The SQL. */ function walkSimpleSelectExpression($simpleSelectExpression); /** * Walks down an AggregateExpression AST node, thereby generating the appropriate SQL. * * @param AggregateExpression * @return string The SQL. */ function walkAggregateExpression($aggExpression); /** * Walks down a GroupByClause AST node, thereby generating the appropriate SQL. * * @param GroupByClause * @return string The SQL. */ function walkGroupByClause($groupByClause); /** * Walks down a GroupByItem AST node, thereby generating the appropriate SQL. * * @param GroupByItem * @return string The SQL. */ function walkGroupByItem($groupByItem); /** * Walks down an UpdateStatement AST node, thereby generating the appropriate SQL. * * @param UpdateStatement * @return string The SQL. */ function walkUpdateStatement(AST\UpdateStatement $AST); /** * Walks down a DeleteStatement AST node, thereby generating the appropriate SQL. * * @param DeleteStatement * @return string The SQL. */ function walkDeleteStatement(AST\DeleteStatement $AST); /** * Walks down a DeleteClause AST node, thereby generating the appropriate SQL. * * @param DeleteClause * @return string The SQL. */ function walkDeleteClause(AST\DeleteClause $deleteClause); /** * Walks down an UpdateClause AST node, thereby generating the appropriate SQL. * * @param UpdateClause * @return string The SQL. */ function walkUpdateClause($updateClause); /** * Walks down an UpdateItem AST node, thereby generating the appropriate SQL. * * @param UpdateItem * @return string The SQL. */ function walkUpdateItem($updateItem); /** * Walks down a WhereClause AST node, thereby generating the appropriate SQL. * * @param WhereClause * @return string The SQL. */ function walkWhereClause($whereClause); /** * Walks down a ConditionalExpression AST node, thereby generating the appropriate SQL. * * @param ConditionalExpression * @return string The SQL. */ function walkConditionalExpression($condExpr); /** * Walks down a ConditionalTerm AST node, thereby generating the appropriate SQL. * * @param ConditionalTerm * @return string The SQL. */ function walkConditionalTerm($condTerm); /** * Walks down a ConditionalFactor AST node, thereby generating the appropriate SQL. * * @param ConditionalFactor * @return string The SQL. */ function walkConditionalFactor($factor); /** * Walks down a ConditionalPrimary AST node, thereby generating the appropriate SQL. * * @param ConditionalPrimary * @return string The SQL. */ function walkConditionalPrimary($primary); /** * Walks down an ExistsExpression AST node, thereby generating the appropriate SQL. * * @param ExistsExpression * @return string The SQL. */ function walkExistsExpression($existsExpr); /** * Walks down a CollectionMemberExpression AST node, thereby generating the appropriate SQL. * * @param CollectionMemberExpression * @return string The SQL. */ function walkCollectionMemberExpression($collMemberExpr); /** * Walks down an EmptyCollectionComparisonExpression AST node, thereby generating the appropriate SQL. * * @param EmptyCollectionComparisonExpression * @return string The SQL. */ function walkEmptyCollectionComparisonExpression($emptyCollCompExpr); /** * Walks down a NullComparisonExpression AST node, thereby generating the appropriate SQL. * * @param NullComparisonExpression * @return string The SQL. */ function walkNullComparisonExpression($nullCompExpr); /** * Walks down an InExpression AST node, thereby generating the appropriate SQL. * * @param InExpression * @return string The SQL. */ function walkInExpression($inExpr); /** * Walks down an InstanceOfExpression AST node, thereby generating the appropriate SQL. * * @param InstanceOfExpression * @return string The SQL. */ function walkInstanceOfExpression($instanceOfExpr); /** * Walks down a literal that represents an AST node, thereby generating the appropriate SQL. * * @param mixed * @return string The SQL. */ function walkLiteral($literal); /** * Walks down a BetweenExpression AST node, thereby generating the appropriate SQL. * * @param BetweenExpression * @return string The SQL. */ function walkBetweenExpression($betweenExpr); /** * Walks down a LikeExpression AST node, thereby generating the appropriate SQL. * * @param LikeExpression * @return string The SQL. */ function walkLikeExpression($likeExpr); /** * Walks down a StateFieldPathExpression AST node, thereby generating the appropriate SQL. * * @param StateFieldPathExpression * @return string The SQL. */ function walkStateFieldPathExpression($stateFieldPathExpression); /** * Walks down a ComparisonExpression AST node, thereby generating the appropriate SQL. * * @param ComparisonExpression * @return string The SQL. */ function walkComparisonExpression($compExpr); /** * Walks down an InputParameter AST node, thereby generating the appropriate SQL. * * @param InputParameter * @return string The SQL. */ function walkInputParameter($inputParam); /** * Walks down an ArithmeticExpression AST node, thereby generating the appropriate SQL. * * @param ArithmeticExpression * @return string The SQL. */ function walkArithmeticExpression($arithmeticExpr); /** * Walks down an ArithmeticTerm AST node, thereby generating the appropriate SQL. * * @param mixed * @return string The SQL. */ function walkArithmeticTerm($term); /** * Walks down a StringPrimary that represents an AST node, thereby generating the appropriate SQL. * * @param mixed * @return string The SQL. */ function walkStringPrimary($stringPrimary); /** * Walks down an ArithmeticFactor that represents an AST node, thereby generating the appropriate SQL. * * @param mixed * @return string The SQL. */ function walkArithmeticFactor($factor); /** * Walks down an SimpleArithmeticExpression AST node, thereby generating the appropriate SQL. * * @param SimpleArithmeticExpression * @return string The SQL. */ function walkSimpleArithmeticExpression($simpleArithmeticExpr); /** * Walks down an PathExpression AST node, thereby generating the appropriate SQL. * * @param mixed * @return string The SQL. */ function walkPathExpression($pathExpr); /** * Walks down an ResultVariable AST node, thereby generating the appropriate SQL. * * @param string $resultVariable * @return string The SQL. */ function walkResultVariable($resultVariable); /** * Gets an executor that can be used to execute the result of this walker. * * @return AbstractExecutor */ function getExecutor($AST); } DoctrineORM-2.3.3/Doctrine/ORM/Query/TreeWalkerAdapter.php0000644000175100017510000003126412143374607023357 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query; /** * An adapter implementation of the TreeWalker interface. The methods in this class * are empty. This class exists as convenience for creating tree walkers. * * @author Roman Borschel * @since 2.0 */ abstract class TreeWalkerAdapter implements TreeWalker { private $_query; private $_parserResult; private $_queryComponents; /** * {@inheritdoc} */ public function __construct($query, $parserResult, array $queryComponents) { $this->_query = $query; $this->_parserResult = $parserResult; $this->_queryComponents = $queryComponents; } /** * @return array */ protected function _getQueryComponents() { return $this->_queryComponents; } /** * Retrieve Query Instance reponsible for the current walkers execution. * * @return \Doctrine\ORM\Query */ protected function _getQuery() { return $this->_query; } /** * Retrieve ParserResult * * @return \Doctrine\ORM\Query\ParserResult */ protected function _getParserResult() { return $this->_parserResult; } /** * Walks down a SelectStatement AST node, thereby generating the appropriate SQL. * * @return string The SQL. */ public function walkSelectStatement(AST\SelectStatement $AST) {} /** * Walks down a SelectClause AST node, thereby generating the appropriate SQL. * * @return string The SQL. */ public function walkSelectClause($selectClause) {} /** * Walks down a FromClause AST node, thereby generating the appropriate SQL. * * @return string The SQL. */ public function walkFromClause($fromClause) {} /** * Walks down a FunctionNode AST node, thereby generating the appropriate SQL. * * @return string The SQL. */ public function walkFunction($function) {} /** * Walks down an OrderByClause AST node, thereby generating the appropriate SQL. * * @param OrderByClause * @return string The SQL. */ public function walkOrderByClause($orderByClause) {} /** * Walks down an OrderByItem AST node, thereby generating the appropriate SQL. * * @param OrderByItem * @return string The SQL. */ public function walkOrderByItem($orderByItem) {} /** * Walks down a HavingClause AST node, thereby generating the appropriate SQL. * * @param HavingClause * @return string The SQL. */ public function walkHavingClause($havingClause) {} /** * Walks down a Join AST node and creates the corresponding SQL. * * @param Join $join * @return string The SQL. */ public function walkJoin($join) {} /** * Walks down a SelectExpression AST node and generates the corresponding SQL. * * @param SelectExpression $selectExpression * @return string The SQL. */ public function walkSelectExpression($selectExpression) {} /** * Walks down a QuantifiedExpression AST node, thereby generating the appropriate SQL. * * @param QuantifiedExpression * @return string The SQL. */ public function walkQuantifiedExpression($qExpr) {} /** * Walks down a Subselect AST node, thereby generating the appropriate SQL. * * @param Subselect * @return string The SQL. */ public function walkSubselect($subselect) {} /** * Walks down a SubselectFromClause AST node, thereby generating the appropriate SQL. * * @param SubselectFromClause * @return string The SQL. */ public function walkSubselectFromClause($subselectFromClause) {} /** * Walks down a SimpleSelectClause AST node, thereby generating the appropriate SQL. * * @param SimpleSelectClause * @return string The SQL. */ public function walkSimpleSelectClause($simpleSelectClause) {} /** * Walks down a SimpleSelectExpression AST node, thereby generating the appropriate SQL. * * @param SimpleSelectExpression * @return string The SQL. */ public function walkSimpleSelectExpression($simpleSelectExpression) {} /** * Walks down an AggregateExpression AST node, thereby generating the appropriate SQL. * * @param AggregateExpression * @return string The SQL. */ public function walkAggregateExpression($aggExpression) {} /** * Walks down a GroupByClause AST node, thereby generating the appropriate SQL. * * @param GroupByClause * @return string The SQL. */ public function walkGroupByClause($groupByClause) {} /** * Walks down a GroupByItem AST node, thereby generating the appropriate SQL. * * @param GroupByItem * @return string The SQL. */ public function walkGroupByItem($groupByItem) {} /** * Walks down an UpdateStatement AST node, thereby generating the appropriate SQL. * * @param UpdateStatement * @return string The SQL. */ public function walkUpdateStatement(AST\UpdateStatement $AST) {} /** * Walks down a DeleteStatement AST node, thereby generating the appropriate SQL. * * @param DeleteStatement * @return string The SQL. */ public function walkDeleteStatement(AST\DeleteStatement $AST) {} /** * Walks down a DeleteClause AST node, thereby generating the appropriate SQL. * * @param DeleteClause * @return string The SQL. */ public function walkDeleteClause(AST\DeleteClause $deleteClause) {} /** * Walks down an UpdateClause AST node, thereby generating the appropriate SQL. * * @param UpdateClause * @return string The SQL. */ public function walkUpdateClause($updateClause) {} /** * Walks down an UpdateItem AST node, thereby generating the appropriate SQL. * * @param UpdateItem * @return string The SQL. */ public function walkUpdateItem($updateItem) {} /** * Walks down a WhereClause AST node, thereby generating the appropriate SQL. * * @param WhereClause * @return string The SQL. */ public function walkWhereClause($whereClause) {} /** * Walks down a ConditionalExpression AST node, thereby generating the appropriate SQL. * * @param ConditionalExpression * @return string The SQL. */ public function walkConditionalExpression($condExpr) {} /** * Walks down a ConditionalTerm AST node, thereby generating the appropriate SQL. * * @param ConditionalTerm * @return string The SQL. */ public function walkConditionalTerm($condTerm) {} /** * Walks down a ConditionalFactor AST node, thereby generating the appropriate SQL. * * @param ConditionalFactor * @return string The SQL. */ public function walkConditionalFactor($factor) {} /** * Walks down a ConditionalPrimary AST node, thereby generating the appropriate SQL. * * @param ConditionalPrimary * @return string The SQL. */ public function walkConditionalPrimary($primary) {} /** * Walks down an ExistsExpression AST node, thereby generating the appropriate SQL. * * @param ExistsExpression * @return string The SQL. */ public function walkExistsExpression($existsExpr) {} /** * Walks down a CollectionMemberExpression AST node, thereby generating the appropriate SQL. * * @param CollectionMemberExpression * @return string The SQL. */ public function walkCollectionMemberExpression($collMemberExpr) {} /** * Walks down an EmptyCollectionComparisonExpression AST node, thereby generating the appropriate SQL. * * @param EmptyCollectionComparisonExpression * @return string The SQL. */ public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr) {} /** * Walks down a NullComparisonExpression AST node, thereby generating the appropriate SQL. * * @param NullComparisonExpression * @return string The SQL. */ public function walkNullComparisonExpression($nullCompExpr) {} /** * Walks down an InExpression AST node, thereby generating the appropriate SQL. * * @param InExpression * @return string The SQL. */ public function walkInExpression($inExpr) {} /** * Walks down an InstanceOfExpression AST node, thereby generating the appropriate SQL. * * @param InstanceOfExpression * @return string The SQL. */ function walkInstanceOfExpression($instanceOfExpr) {} /** * Walks down a literal that represents an AST node, thereby generating the appropriate SQL. * * @param mixed * @return string The SQL. */ public function walkLiteral($literal) {} /** * Walks down a BetweenExpression AST node, thereby generating the appropriate SQL. * * @param BetweenExpression * @return string The SQL. */ public function walkBetweenExpression($betweenExpr) {} /** * Walks down a LikeExpression AST node, thereby generating the appropriate SQL. * * @param LikeExpression * @return string The SQL. */ public function walkLikeExpression($likeExpr) {} /** * Walks down a StateFieldPathExpression AST node, thereby generating the appropriate SQL. * * @param StateFieldPathExpression * @return string The SQL. */ public function walkStateFieldPathExpression($stateFieldPathExpression) {} /** * Walks down a ComparisonExpression AST node, thereby generating the appropriate SQL. * * @param ComparisonExpression * @return string The SQL. */ public function walkComparisonExpression($compExpr) {} /** * Walks down an InputParameter AST node, thereby generating the appropriate SQL. * * @param InputParameter * @return string The SQL. */ public function walkInputParameter($inputParam) {} /** * Walks down an ArithmeticExpression AST node, thereby generating the appropriate SQL. * * @param ArithmeticExpression * @return string The SQL. */ public function walkArithmeticExpression($arithmeticExpr) {} /** * Walks down an ArithmeticTerm AST node, thereby generating the appropriate SQL. * * @param mixed * @return string The SQL. */ public function 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) {} /** * Walks down an ArithmeticFactor that represents an AST node, thereby generating the appropriate SQL. * * @param mixed * @return string The SQL. */ public function walkArithmeticFactor($factor) {} /** * Walks down an SimpleArithmeticExpression AST node, thereby generating the appropriate SQL. * * @param SimpleArithmeticExpression * @return string The SQL. */ public function walkSimpleArithmeticExpression($simpleArithmeticExpr) {} /** * Walks down an PathExpression AST node, thereby generating the appropriate SQL. * * @param mixed * @return string The SQL. */ public function walkPathExpression($pathExpr) {} /** * Walks down an ResultVariable AST node, thereby generating the appropriate SQL. * * @param string $resultVariable * @return string The SQL. */ public function walkResultVariable($resultVariable) {} /** * Gets an executor that can be used to execute the result of this walker. * * @return AbstractExecutor */ public function getExecutor($AST) {} } DoctrineORM-2.3.3/Doctrine/ORM/Query/TreeWalkerChain.php0000644000175100017510000004447112143374607023025 0ustar beberleibeberlei. */ 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 * @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 Join AST node and creates the corresponding SQL. * * @param Join $join * @return string The SQL. */ public function walkJoin($join) { foreach ($this->_walkers as $walker) { $walker->walkJoin($join); } } /** * 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($groupByItem) { foreach ($this->_walkers as $walker) { $walker->walkGroupByItem($groupByItem); } } /** * 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 ConditionalExpression AST node, thereby generating the appropriate SQL. * * @param ConditionalExpression * @return string The SQL. */ public function walkConditionalExpression($condExpr) { foreach ($this->_walkers as $walker) { $walker->walkConditionalExpression($condExpr); } } /** * 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 a ConditionalPrimary AST node, thereby generating the appropriate SQL. * * @param ConditionalPrimary * @return string The SQL. */ public function walkConditionalPrimary($condPrimary) { foreach ($this->_walkers as $walker) { $walker->walkConditionalPrimary($condPrimary); } } /** * 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 an InstanceOfExpression AST node, thereby generating the appropriate SQL. * * @param InstanceOfExpression * @return string The SQL. */ function walkInstanceOfExpression($instanceOfExpr) { foreach ($this->_walkers as $walker) { $walker->walkInstanceOfExpression($instanceOfExpr); } } /** * 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); } } /** * Walks down an ResultVariable AST node, thereby generating the appropriate SQL. * * @param string $resultVariable * @return string The SQL. */ public function walkResultVariable($resultVariable) { foreach ($this->_walkers as $walker) { $walker->walkResultVariable($resultVariable); } } /** * Gets an executor that can be used to execute the result of this walker. * * @return AbstractExecutor */ public function getExecutor($AST) {} } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/AggregateExpression.php0000644000175100017510000000341512143374607024403 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * Description of AggregateExpression * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class AggregateExpression extends Node { public $functionName; public $pathExpression; public $isDistinct = false; // Some aggregate expressions support distinct, eg COUNT public function __construct($functionName, $pathExpression, $isDistinct) { $this->functionName = $functionName; $this->pathExpression = $pathExpression; $this->isDistinct = $isDistinct; } public function dispatch($walker) { return $walker->walkAggregateExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/ArithmeticExpression.php0000644000175100017510000000332312143374607024604 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ArithmeticExpression extends Node { public $simpleArithmeticExpression; public $subselect; public function isSimpleArithmeticExpression() { return (bool) $this->simpleArithmeticExpression; } public function isSubselect() { return (bool) $this->subselect; } public function dispatch($walker) { return $walker->walkArithmeticExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/ArithmeticFactor.php0000644000175100017510000000373112143374607023666 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ArithmeticFactor extends Node { /** * @var ArithmeticPrimary */ public $arithmeticPrimary; /** * @var null|boolean NULL represents no sign, TRUE means positive and FALSE means negative sign */ public $sign; public function __construct($arithmeticPrimary, $sign = null) { $this->arithmeticPrimary = $arithmeticPrimary; $this->sign = $sign; } public function isPositiveSigned() { return $this->sign === true; } public function isNegativeSigned() { return $this->sign === false; } public function dispatch($sqlWalker) { return $sqlWalker->walkArithmeticFactor($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/ArithmeticTerm.php0000644000175100017510000000312712143374607023356 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}* * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ArithmeticTerm extends Node { public $arithmeticFactors; public function __construct(array $arithmeticFactors) { $this->arithmeticFactors = $arithmeticFactors; } public function dispatch($sqlWalker) { return $sqlWalker->walkArithmeticTerm($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/ASTException.php0000644000175100017510000000245112143374607022742 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; use Doctrine\ORM\Query\QueryException; /** * Base exception class for AST exceptions. */ class ASTException extends QueryException { public static function noDispatchForNode($node) { return new self("Double-dispatch for node " . get_class($node) . " is not supported."); } }DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/BetweenExpression.php0000644000175100017510000000334112143374607024104 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * Description of BetweenExpression * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class BetweenExpression extends Node { public $expression; public $leftBetweenExpression; public $rightBetweenExpression; public $not; public function __construct($expr, $leftExpr, $rightExpr) { $this->expression = $expr; $this->leftBetweenExpression = $leftExpr; $this->rightBetweenExpression = $rightExpr; } public function dispatch($sqlWalker) { return $sqlWalker->walkBetweenExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/CoalesceExpression.php0000644000175100017510000000325512143374607024235 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" * * @since 2.1 * * @link www.doctrine-project.org * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class CoalesceExpression extends Node { public $scalarExpressions = array(); public function __construct(array $scalarExpressions) { $this->scalarExpressions = $scalarExpressions; } public function dispatch($sqlWalker) { return $sqlWalker->walkCoalesceExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/CollectionMemberExpression.php0000644000175100017510000000341612143374607025741 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class CollectionMemberExpression extends Node { public $entityExpression; public $collectionValuedPathExpression; public $not; public function __construct($entityExpr, $collValuedPathExpr) { $this->entityExpression = $entityExpr; $this->collectionValuedPathExpression = $collValuedPathExpr; } public function dispatch($walker) { return $walker->walkCollectionMemberExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/ComparisonExpression.php0000644000175100017510000000445012143374607024627 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) | * StringExpression ComparisonOperator (StringExpression | QuantifiedExpression) | * BooleanExpression ("=" | "<>" | "!=") (BooleanExpression | QuantifiedExpression) | * EnumExpression ("=" | "<>" | "!=") (EnumExpression | QuantifiedExpression) | * DatetimeExpression ComparisonOperator (DatetimeExpression | QuantifiedExpression) | * EntityExpression ("=" | "<>") (EntityExpression | QuantifiedExpression) * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ComparisonExpression extends Node { public $leftExpression; public $rightExpression; public $operator; public function __construct($leftExpr, $operator, $rightExpr) { $this->leftExpression = $leftExpr; $this->rightExpression = $rightExpr; $this->operator = $operator; } public function dispatch($sqlWalker) { return $sqlWalker->walkComparisonExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/ConditionalExpression.php0000644000175100017510000000315112143374607024755 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ConditionalExpression extends Node { public $conditionalTerms = array(); public function __construct(array $conditionalTerms) { $this->conditionalTerms = $conditionalTerms; } public function dispatch($sqlWalker) { return $sqlWalker->walkConditionalExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/ConditionalFactor.php0000644000175100017510000000314112143374607024033 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * ConditionalFactor ::= ["NOT"] ConditionalPrimary * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ConditionalFactor extends Node { public $not = false; public $conditionalPrimary; public function __construct($conditionalPrimary) { $this->conditionalPrimary = $conditionalPrimary; } public function dispatch($sqlWalker) { return $sqlWalker->walkConditionalFactor($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/ConditionalPrimary.php0000644000175100017510000000340712143374607024245 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ConditionalPrimary extends Node { public $simpleConditionalExpression; public $conditionalExpression; public function isSimpleConditionalExpression() { return (bool) $this->simpleConditionalExpression; } public function isConditionalExpression() { return (bool) $this->conditionalExpression; } public function dispatch($sqlWalker) { return $sqlWalker->walkConditionalPrimary($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/ConditionalTerm.php0000644000175100017510000000314312143374607023526 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ConditionalTerm extends Node { public $conditionalFactors = array(); public function __construct(array $conditionalFactors) { $this->conditionalFactors = $conditionalFactors; } public function dispatch($sqlWalker) { return $sqlWalker->walkConditionalTerm($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/DeleteClause.php0000644000175100017510000000322212143374607022770 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName [["AS"] AliasIdentificationVariable] * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class DeleteClause extends Node { public $abstractSchemaName; public $aliasIdentificationVariable; public function __construct($abstractSchemaName) { $this->abstractSchemaName = $abstractSchemaName; } public function dispatch($sqlWalker) { return $sqlWalker->walkDeleteClause($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/DeleteStatement.php0000644000175100017510000000310112143374607023514 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * DeleteStatement = DeleteClause [WhereClause] * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class DeleteStatement extends Node { public $deleteClause; public $whereClause; public function __construct($deleteClause) { $this->deleteClause = $deleteClause; } public function dispatch($sqlWalker) { return $sqlWalker->walkDeleteStatement($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/EmptyCollectionComparisonExpression.php0000644000175100017510000000321112143374607027654 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class EmptyCollectionComparisonExpression extends Node { public $expression; public $not; public function __construct($expression) { $this->expression = $expression; } public function dispatch($sqlWalker) { return $sqlWalker->walkEmptyCollectionComparisonExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/ExistsExpression.php0000644000175100017510000000307312143374607023774 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ExistsExpression extends Node { public $not; public $subselect; public function __construct($subselect) { $this->subselect = $subselect; } public function dispatch($sqlWalker) { return $sqlWalker->walkExistsExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/FromClause.php0000644000175100017510000000327112143374607022475 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration} * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class FromClause extends Node { public $identificationVariableDeclarations = array(); public function __construct(array $identificationVariableDeclarations) { $this->identificationVariableDeclarations = $identificationVariableDeclarations; } public function dispatch($sqlWalker) { return $sqlWalker->walkFromClause($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/GeneralCaseExpression.php0000644000175100017510000000343312143374607024666 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" * * @since 2.2 * * @link www.doctrine-project.org * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class GeneralCaseExpression extends Node { public $whenClauses = array(); public $elseScalarExpression = null; public function __construct(array $whenClauses, $elseScalarExpression) { $this->whenClauses = $whenClauses; $this->elseScalarExpression = $elseScalarExpression; } public function dispatch($sqlWalker) { return $sqlWalker->walkGeneralCaseExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/GroupByClause.php0000644000175100017510000000304412143374607023157 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * Description of GroupByClause * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class GroupByClause extends Node { public $groupByItems = array(); public function __construct(array $groupByItems) { $this->groupByItems = $groupByItems; } public function dispatch($sqlWalker) { return $sqlWalker->walkGroupByClause($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/HavingClause.php0000644000175100017510000000306512143374607023007 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * Description of HavingClause * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class HavingClause extends Node { public $conditionalExpression; public function __construct($conditionalExpression) { $this->conditionalExpression = $conditionalExpression; } public function dispatch($sqlWalker) { return $sqlWalker->walkHavingClause($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/IdentificationVariableDeclaration.php0000644000175100017510000000350112143374607027176 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class IdentificationVariableDeclaration extends Node { public $rangeVariableDeclaration = null; public $indexBy = null; public $joins = array(); public function __construct($rangeVariableDecl, $indexBy, array $joins) { $this->rangeVariableDeclaration = $rangeVariableDecl; $this->indexBy = $indexBy; $this->joins = $joins; } public function dispatch($sqlWalker) { return $sqlWalker->walkIdentificationVariableDeclaration($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/IndexBy.php0000644000175100017510000000316212143374607021776 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class IndexBy extends Node { public $simpleStateFieldPathExpression = null; public function __construct($simpleStateFieldPathExpression) { $this->simpleStateFieldPathExpression = $simpleStateFieldPathExpression; } public function dispatch($sqlWalker) { return $sqlWalker->walkIndexBy($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/InExpression.php0000644000175100017510000000323112143374607023057 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * InExpression ::= StateFieldPathExpression ["NOT"] "IN" "(" (Literal {"," Literal}* | Subselect) ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class InExpression extends Node { public $not; public $expression; public $literals = array(); public $subselect; public function __construct($expression) { $this->expression = $expression; } public function dispatch($sqlWalker) { return $sqlWalker->walkInExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/InputParameter.php0000644000175100017510000000341312143374607023373 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * Description of InputParameter * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class InputParameter extends Node { public $isNamed; public $name; /** * @param string $value */ public function __construct($value) { if (strlen($value) == 1) { throw \Doctrine\ORM\Query\QueryException::invalidParameterFormat($value); } $param = substr($value, 1); $this->isNamed = ! is_numeric($param); $this->name = $param; } public function dispatch($walker) { return $walker->walkInputParameter($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/InstanceOfExpression.php0000644000175100017510000000343112143374607024544 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")") * InstanceOfParameter ::= AbstractSchemaName | InputParameter * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class InstanceOfExpression extends Node { public $not; public $identificationVariable; public $value; public function __construct($identVariable) { $this->identificationVariable = $identVariable; } public function dispatch($sqlWalker) { return $sqlWalker->walkInstanceOfExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Join.php0000644000175100017510000000366412143374607021342 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression * ["AS"] AliasIdentificationVariable [("ON" | "WITH") ConditionalExpression] * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Join extends Node { const JOIN_TYPE_LEFT = 1; const JOIN_TYPE_LEFTOUTER = 2; const JOIN_TYPE_INNER = 3; public $joinType = self::JOIN_TYPE_INNER; public $joinAssociationDeclaration = null; public $conditionalExpression = null; public function __construct($joinType, $joinAssociationDeclaration) { $this->joinType = $joinType; $this->joinAssociationDeclaration = $joinAssociationDeclaration; } public function dispatch($sqlWalker) { return $sqlWalker->walkJoin($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/JoinAssociationDeclaration.php0000644000175100017510000000347312143374607025703 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable * * @link www.doctrine-project.org * @since 2.3 * @author Guilherme Blanco */ class JoinAssociationDeclaration extends Node { public $joinAssociationPathExpression; public $aliasIdentificationVariable; public $indexBy; public function __construct($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy) { $this->joinAssociationPathExpression = $joinAssociationPathExpression; $this->aliasIdentificationVariable = $aliasIdentificationVariable; $this->indexBy = $indexBy; } public function dispatch($sqlWalker) { return $sqlWalker->walkJoinAssociationDeclaration($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/JoinAssociationPathExpression.php0000644000175100017510000000342712143374607026431 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * JoinAssociationPathExpression ::= IdentificationVariable "." (SingleValuedAssociationField | CollectionValuedAssociationField) * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class JoinAssociationPathExpression extends Node { public $identificationVariable; public $associationField; public function __construct($identificationVariable, $associationField) { $this->identificationVariable = $identificationVariable; $this->associationField = $associationField; } public function dispatch($sqlWalker) { return $sqlWalker->walkPathExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/JoinClassPathExpression.php0000644000175100017510000000330212143374607025212 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * JoinClassPathExpression ::= AbstractSchemaName ["AS"] AliasIdentificationVariable * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.3 * @author Alexander */ class JoinClassPathExpression extends Node { public $abstractSchemaName; public $aliasIdentificationVariable; public function __construct($abstractSchemaName, $aliasIdentificationVar) { $this->abstractSchemaName = $abstractSchemaName; $this->aliasIdentificationVariable = $aliasIdentificationVar; } public function dispatch($walker) { return $walker->walkJoinPathExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/LikeExpression.php0000644000175100017510000000342212143374607023377 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * LikeExpression ::= StringExpression ["NOT"] "LIKE" string ["ESCAPE" char] * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class LikeExpression extends Node { public $not; public $stringExpression; public $stringPattern; public $escapeChar; public function __construct($stringExpression, $stringPattern, $escapeChar = null) { $this->stringExpression = $stringExpression; $this->stringPattern = $stringPattern; $this->escapeChar = $escapeChar; } public function dispatch($sqlWalker) { return $sqlWalker->walkLikeExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Literal.php0000644000175100017510000000253712143374607022035 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; class Literal extends Node { const STRING = 1; const BOOLEAN = 2; const NUMERIC = 3; public $type; public $value; public function __construct($type, $value) { $this->type = $type; $this->value = $value; } public function dispatch($walker) { return $walker->walkLiteral($this); } }DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Node.php0000644000175100017510000000560612143374607021326 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * Abstract class of an AST node * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ abstract class Node { /** * Double-dispatch method, supposed to dispatch back to the walker. * * Implementation is not mandatory for all nodes. * * @param $walker */ public function dispatch($walker) { throw ASTException::noDispatchForNode($this); } /** * Dumps the AST Node into a string representation for information purpose only * * @return string */ public function __toString() { return $this->dump($this); } public function dump($obj) { static $ident = 0; $str = ''; if ($obj instanceof Node) { $str .= get_class($obj) . '(' . PHP_EOL; $props = get_object_vars($obj); foreach ($props as $name => $prop) { $ident += 4; $str .= str_repeat(' ', $ident) . '"' . $name . '": ' . $this->dump($prop) . ',' . PHP_EOL; $ident -= 4; } $str .= str_repeat(' ', $ident) . ')'; } else if (is_array($obj)) { $ident += 4; $str .= 'array('; $some = false; foreach ($obj as $k => $v) { $str .= PHP_EOL . str_repeat(' ', $ident) . '"' . $k . '" => ' . $this->dump($v) . ','; $some = true; } $ident -= 4; $str .= ($some ? PHP_EOL . str_repeat(' ', $ident) : '') . ')'; } else if (is_object($obj)) { $str .= 'instanceof(' . get_class($obj) . ')'; } else { $str .= var_export($obj, true); } return $str; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/NullComparisonExpression.php0000644000175100017510000000316612143374607025465 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class NullComparisonExpression extends Node { public $not; public $expression; public function __construct($expression) { $this->expression = $expression; } public function dispatch($sqlWalker) { return $sqlWalker->walkNullComparisonExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/NullIfExpression.php0000644000175100017510000000336012143374607023705 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" * * @since 2.1 * * @link www.doctrine-project.org * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class NullIfExpression extends Node { public $firstExpression; public $secondExpression; public function __construct($firstExpression, $secondExpression) { $this->firstExpression = $firstExpression; $this->secondExpression = $secondExpression; } public function dispatch($sqlWalker) { return $sqlWalker->walkNullIfExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/OrderByClause.php0000644000175100017510000000310512143374607023134 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class OrderByClause extends Node { public $orderByItems = array(); public function __construct(array $orderByItems) { $this->orderByItems = $orderByItems; } public function dispatch($sqlWalker) { return $sqlWalker->walkOrderByClause($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/OrderByItem.php0000644000175100017510000000340012143374607022614 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"] * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class OrderByItem extends Node { public $expression; public $type; public function __construct($expression) { $this->expression = $expression; } public function isAsc() { return strtoupper($this->type) == 'ASC'; } public function isDesc() { return strtoupper($this->type) == 'DESC'; } public function dispatch($sqlWalker) { return $sqlWalker->walkOrderByItem($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/PartialObjectExpression.php0000644000175100017510000000250112143374607025233 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; class PartialObjectExpression extends Node { public $identificationVariable; public $partialFieldSet; public function __construct($identificationVariable, array $partialFieldSet) { $this->identificationVariable = $identificationVariable; $this->partialFieldSet = $partialFieldSet; } }DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/PathExpression.php0000644000175100017510000000464412143374607023416 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression * StateFieldPathExpression ::= SimpleStateFieldPathExpression | SimpleStateFieldAssociationPathExpression * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField * StateField ::= {EmbeddedClassStateField "."}* SimpleStateField * SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField * * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class PathExpression extends Node { const TYPE_COLLECTION_VALUED_ASSOCIATION = 2; const TYPE_SINGLE_VALUED_ASSOCIATION = 4; const TYPE_STATE_FIELD = 8; public $type; public $expectedType; public $identificationVariable; public $field; public function __construct($expectedType, $identificationVariable, $field = null) { $this->expectedType = $expectedType; $this->identificationVariable = $identificationVariable; $this->field = $field; } public function dispatch($walker) { return $walker->walkPathExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/QuantifiedExpression.php0000644000175100017510000000360012143374607024602 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class QuantifiedExpression extends Node { public $type; public $subselect; public function __construct($subselect) { $this->subselect = $subselect; } public function isAll() { return strtoupper($this->type) == 'ALL'; } public function isAny() { return strtoupper($this->type) == 'ANY'; } public function isSome() { return strtoupper($this->type) == 'SOME'; } /** * @override */ public function dispatch($sqlWalker) { return $sqlWalker->walkQuantifiedExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/RangeVariableDeclaration.php0000644000175100017510000000337212143374607025307 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class RangeVariableDeclaration extends Node { public $abstractSchemaName; public $aliasIdentificationVariable; public function __construct($abstractSchemaName, $aliasIdentificationVar) { $this->abstractSchemaName = $abstractSchemaName; $this->aliasIdentificationVariable = $aliasIdentificationVar; } public function dispatch($walker) { return $walker->walkRangeVariableDeclaration($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/SelectClause.php0000644000175100017510000000326412143374607023013 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * SelectClause = "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression} * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class SelectClause extends Node { public $isDistinct; public $selectExpressions = array(); public function __construct(array $selectExpressions, $isDistinct) { $this->isDistinct = $isDistinct; $this->selectExpressions = $selectExpressions; } public function dispatch($sqlWalker) { return $sqlWalker->walkSelectClause($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/SelectExpression.php0000644000175100017510000000374212143374607023737 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * SelectExpression ::= IdentificationVariable ["." "*"] | StateFieldPathExpression | * (AggregateExpression | "(" Subselect ")") [["AS"] ["HIDDEN"] FieldAliasIdentificationVariable] * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class SelectExpression extends Node { public $expression; public $fieldIdentificationVariable; public $hiddenAliasResultVariable; public function __construct($expression, $fieldIdentificationVariable, $hiddenAliasResultVariable = false) { $this->expression = $expression; $this->fieldIdentificationVariable = $fieldIdentificationVariable; $this->hiddenAliasResultVariable = $hiddenAliasResultVariable; } public function dispatch($sqlWalker) { return $sqlWalker->walkSelectExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/SelectStatement.php0000644000175100017510000000342512143374607023542 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * SelectStatement = SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class SelectStatement extends Node { public $selectClause; public $fromClause; public $whereClause; public $groupByClause; public $havingClause; public $orderByClause; public function __construct($selectClause, $fromClause) { $this->selectClause = $selectClause; $this->fromClause = $fromClause; } public function dispatch($sqlWalker) { return $sqlWalker->walkSelectStatement($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/SimpleArithmeticExpression.php0000644000175100017510000000317112143374607025757 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class SimpleArithmeticExpression extends Node { public $arithmeticTerms = array(); public function __construct(array $arithmeticTerms) { $this->arithmeticTerms = $arithmeticTerms; } public function dispatch($sqlWalker) { return $sqlWalker->walkSimpleArithmeticExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/SimpleCaseExpression.php0000644000175100017510000000364112143374607024543 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" * * @since 2.2 * * @link www.doctrine-project.org * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class SimpleCaseExpression extends Node { public $caseOperand = null; public $simpleWhenClauses = array(); public $elseScalarExpression = null; public function __construct($caseOperand, array $simpleWhenClauses, $elseScalarExpression) { $this->caseOperand = $caseOperand; $this->simpleWhenClauses = $simpleWhenClauses; $this->elseScalarExpression = $elseScalarExpression; } public function dispatch($sqlWalker) { return $sqlWalker->walkSimpleCaseExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/SimpleSelectClause.php0000644000175100017510000000330412143374607024160 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class SimpleSelectClause extends Node { public $isDistinct = false; public $simpleSelectExpression; public function __construct($simpleSelectExpression, $isDistinct) { $this->simpleSelectExpression = $simpleSelectExpression; $this->isDistinct = $isDistinct; } public function dispatch($sqlWalker) { return $sqlWalker->walkSimpleSelectClause($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/SimpleSelectExpression.php0000644000175100017510000000332512143374607025106 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * SimpleSelectExpression ::= StateFieldPathExpression | IdentificationVariable * | (AggregateExpression [["AS"] FieldAliasIdentificationVariable]) * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class SimpleSelectExpression extends Node { public $expression; public $fieldIdentificationVariable; public function __construct($expression) { $this->expression = $expression; } public function dispatch($sqlWalker) { return $sqlWalker->walkSimpleSelectExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/SimpleWhenClause.php0000644000175100017510000000343512143374607023647 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression * * @since 2.2 * * @link www.doctrine-project.org * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class SimpleWhenClause extends Node { public $caseScalarExpression = null; public $thenScalarExpression = null; public function __construct($caseScalarExpression, $thenScalarExpression) { $this->caseScalarExpression = $caseScalarExpression; $this->thenScalarExpression = $thenScalarExpression; } public function dispatch($sqlWalker) { return $sqlWalker->walkWhenClauseExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Subselect.php0000644000175100017510000000352412143374607022367 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Subselect extends Node { public $simpleSelectClause; public $subselectFromClause; public $whereClause; public $groupByClause; public $havingClause; public $orderByClause; public function __construct($simpleSelectClause, $subselectFromClause) { $this->simpleSelectClause = $simpleSelectClause; $this->subselectFromClause = $subselectFromClause; } public function dispatch($sqlWalker) { return $sqlWalker->walkSubselect($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/SubselectFromClause.php0000644000175100017510000000334712143374607024353 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}* * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class SubselectFromClause extends Node { public $identificationVariableDeclarations = array(); public function __construct(array $identificationVariableDeclarations) { $this->identificationVariableDeclarations = $identificationVariableDeclarations; } public function dispatch($sqlWalker) { return $sqlWalker->walkSubselectFromClause($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/UpdateClause.php0000644000175100017510000000341612143374607023015 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * UpdateClause ::= "UPDATE" AbstractSchemaName [["AS"] AliasIdentificationVariable] "SET" UpdateItem {"," UpdateItem}* * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class UpdateClause extends Node { public $abstractSchemaName; public $aliasIdentificationVariable; public $updateItems = array(); public function __construct($abstractSchemaName, array $updateItems) { $this->abstractSchemaName = $abstractSchemaName; $this->updateItems = $updateItems; } public function dispatch($sqlWalker) { return $sqlWalker->walkUpdateClause($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/UpdateItem.php0000644000175100017510000000350312143374607022474 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * UpdateItem ::= [IdentificationVariable "."] {StateField | SingleValuedAssociationField} "=" NewValue * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary | * EnumPrimary | SimpleEntityExpression | "NULL" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class UpdateItem extends Node { public $pathExpression; public $newValue; public function __construct($pathExpression, $newValue) { $this->pathExpression = $pathExpression; $this->newValue = $newValue; } public function dispatch($sqlWalker) { return $sqlWalker->walkUpdateItem($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/UpdateStatement.php0000644000175100017510000000310012143374607023533 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * UpdateStatement = UpdateClause [WhereClause] * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class UpdateStatement extends Node { public $updateClause; public $whereClause; public function __construct($updateClause) { $this->updateClause = $updateClause; } public function dispatch($sqlWalker) { return $sqlWalker->walkUpdateStatement($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/WhenClause.php0000644000175100017510000000344212143374607022473 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression * * @since 2.2 * * @link www.doctrine-project.org * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class WhenClause extends Node { public $caseConditionExpression = null; public $thenScalarExpression = null; public function __construct($caseConditionExpression, $thenScalarExpression) { $this->caseConditionExpression = $caseConditionExpression; $this->thenScalarExpression = $thenScalarExpression; } public function dispatch($sqlWalker) { return $sqlWalker->walkWhenClauseExpression($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/WhereClause.php0000644000175100017510000000310512143374607022640 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST; /** * WhereClause ::= "WHERE" ConditionalExpression * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class WhereClause extends Node { public $conditionalExpression; public function __construct($conditionalExpression) { $this->conditionalExpression = $conditionalExpression; } public function dispatch($sqlWalker) { return $sqlWalker->walkWhereClause($this); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/AbsFunction.php0000644000175100017510000000401012143374607024610 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "ABS" "(" SimpleArithmeticExpression ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class AbsFunction extends FunctionNode { public $simpleArithmeticExpression; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return 'ABS(' . $sqlWalker->walkSimpleArithmeticExpression( $this->simpleArithmeticExpression ) . ')'; } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/BitAndFunction.php0000644000175100017510000000414612143374607025256 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" * * * @link www.doctrine-project.org * @since 2.2 * @author Fabio B. Silva */ class BitAndFunction extends FunctionNode { public $firstArithmetic; public $secondArithmetic; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { $platform = $sqlWalker->getConnection()->getDatabasePlatform(); return $platform->getBitAndComparisonExpression( $this->firstArithmetic->dispatch($sqlWalker), $this->secondArithmetic->dispatch($sqlWalker) ); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->firstArithmetic = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_COMMA); $this->secondArithmetic = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/BitOrFunction.php0000644000175100017510000000414312143374607025131 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")" * * * @link www.doctrine-project.org * @since 2.2 * @author Fabio B. Silva */ class BitOrFunction extends FunctionNode { public $firstArithmetic; public $secondArithmetic; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { $platform = $sqlWalker->getConnection()->getDatabasePlatform(); return $platform->getBitOrComparisonExpression( $this->firstArithmetic->dispatch($sqlWalker), $this->secondArithmetic->dispatch($sqlWalker) ); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->firstArithmetic = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_COMMA); $this->secondArithmetic = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/ConcatFunction.php0000644000175100017510000000441112143374607025317 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "CONCAT" "(" StringPrimary "," StringPrimary ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class ConcatFunction extends FunctionNode { public $firstStringPrimary; public $secondStringPriamry; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { $platform = $sqlWalker->getConnection()->getDatabasePlatform(); return $platform->getConcatExpression( $sqlWalker->walkStringPrimary($this->firstStringPrimary), $sqlWalker->walkStringPrimary($this->secondStringPrimary) ); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->firstStringPrimary = $parser->StringPrimary(); $parser->match(Lexer::T_COMMA); $this->secondStringPrimary = $parser->StringPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/CurrentDateFunction.php0000644000175100017510000000351412143374607026333 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "CURRENT_DATE" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class CurrentDateFunction extends FunctionNode { /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getCurrentDateSQL(); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/CurrentTimeFunction.php0000644000175100017510000000351412143374607026354 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "CURRENT_TIME" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class CurrentTimeFunction extends FunctionNode { /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getCurrentTimeSQL(); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/CurrentTimestampFunction.php0000644000175100017510000000353312143374607027422 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "CURRENT_TIMESTAMP" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class CurrentTimestampFunction extends FunctionNode { /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getCurrentTimestampSQL(); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/DateAddFunction.php0000644000175100017510000000553212143374607025403 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\QueryException; /** * "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Benjamin Eberlei */ class DateAddFunction extends FunctionNode { public $firstDateExpression = null; public $intervalExpression = null; public $unit = null; public function getSql(SqlWalker $sqlWalker) { switch (strtolower($this->unit->value)) { case 'day': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddDaysExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); case 'month': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddMonthExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); default: throw QueryException::semanticalError( 'DATE_ADD() only supports units of type day and month.' ); } } public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->firstDateExpression = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_COMMA); $this->intervalExpression = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_COMMA); $this->unit = $parser->StringPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/DateDiffFunction.php0000644000175100017510000000372712143374607025567 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\Parser; /** * "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei */ class DateDiffFunction extends FunctionNode { public $date1; public $date2; public function getSql(SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getDateDiffExpression( $this->date1->dispatch($sqlWalker), $this->date2->dispatch($sqlWalker) ); } public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->date1 = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_COMMA); $this->date2 = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/DateSubFunction.php0000644000175100017510000000427212143374607025444 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\QueryException; /** * "DATE_ADD(date1, interval, unit)" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Benjamin Eberlei */ class DateSubFunction extends DateAddFunction { public function getSql(SqlWalker $sqlWalker) { switch (strtolower($this->unit->value)) { case 'day': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubDaysExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); case 'month': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubMonthExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); default: throw QueryException::semanticalError( 'DATE_SUB() only supports units of type day and month.' ); } } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/FunctionNode.php0000644000175100017510000000335112143374607024777 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\AST\Node; /** * Abtract Function Node. * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ abstract class FunctionNode extends Node { public $name; public function __construct($name) { $this->name = $name; } abstract public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker); public function dispatch($sqlWalker) { return $sqlWalker->walkFunction($this); } abstract public function parse(\Doctrine\ORM\Query\Parser $parser); } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/IdentityFunction.php0000644000175100017510000000435012143374607025703 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "IDENTITY" "(" SingleValuedAssociationPathExpression ")" * * * @link www.doctrine-project.org * @since 2.2 * @author Guilherme Blanco * @author Benjamin Eberlei */ class IdentityFunction extends FunctionNode { public $pathExpression; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { $dqlAlias = $this->pathExpression->identificationVariable; $assocField = $this->pathExpression->field; $qComp = $sqlWalker->getQueryComponent($dqlAlias); $class = $qComp['metadata']; $assoc = $class->associationMappings[$assocField]; $tableAlias = $sqlWalker->getSQLTableAlias($class->getTableName(), $dqlAlias); return $tableAlias . '.' . reset($assoc['targetToSourceKeyColumns']);; } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->pathExpression = $parser->SingleValuedAssociationPathExpression(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/LengthFunction.php0000644000175100017510000000401212143374607025326 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "LENGTH" "(" StringPrimary ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class LengthFunction extends FunctionNode { public $stringPrimary; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getLengthExpression( $sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary) ); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->stringPrimary = $parser->StringPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/LocateFunction.php0000644000175100017510000000540512143374607025323 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class LocateFunction extends FunctionNode { public $firstStringPrimary; public $secondStringPrimary; public $simpleArithmeticExpression = false; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getLocateExpression( $sqlWalker->walkStringPrimary($this->secondStringPrimary), // its the other way around in platform $sqlWalker->walkStringPrimary($this->firstStringPrimary), (($this->simpleArithmeticExpression) ? $sqlWalker->walkSimpleArithmeticExpression($this->simpleArithmeticExpression) : false ) ); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->firstStringPrimary = $parser->StringPrimary(); $parser->match(Lexer::T_COMMA); $this->secondStringPrimary = $parser->StringPrimary(); $lexer = $parser->getLexer(); if ($lexer->isNextToken(Lexer::T_COMMA)) { $parser->match(Lexer::T_COMMA); $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); } $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/LowerFunction.php0000644000175100017510000000400712143374607025201 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "LOWER" "(" StringPrimary ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class LowerFunction extends FunctionNode { public $stringPrimary; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getLowerExpression( $sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary) ); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->stringPrimary = $parser->StringPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/ModFunction.php0000644000175100017510000000460512143374607024634 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class ModFunction extends FunctionNode { public $firstSimpleArithmeticExpression; public $secondSimpleArithmeticExpression; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getModExpression( $sqlWalker->walkSimpleArithmeticExpression($this->firstSimpleArithmeticExpression), $sqlWalker->walkSimpleArithmeticExpression($this->secondSimpleArithmeticExpression) ); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->firstSimpleArithmeticExpression = $parser->SimpleArithmeticExpression(); $parser->match(Lexer::T_COMMA); $this->secondSimpleArithmeticExpression = $parser->SimpleArithmeticExpression(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/SizeFunction.php0000644000175100017510000001157012143374607025026 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "SIZE" "(" CollectionValuedPathExpression ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class SizeFunction extends FunctionNode { public $collectionPathExpression; /** * @override * @todo If the collection being counted is already joined, the SQL can be simpler (more efficient). */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { $platform = $sqlWalker->getEntityManager()->getConnection()->getDatabasePlatform(); $quoteStrategy = $sqlWalker->getEntityManager()->getConfiguration()->getQuoteStrategy(); $dqlAlias = $this->collectionPathExpression->identificationVariable; $assocField = $this->collectionPathExpression->field; $qComp = $sqlWalker->getQueryComponent($dqlAlias); $class = $qComp['metadata']; $assoc = $class->associationMappings[$assocField]; $sql = 'SELECT COUNT(*) FROM '; if ($assoc['type'] == \Doctrine\ORM\Mapping\ClassMetadata::ONE_TO_MANY) { $targetClass = $sqlWalker->getEntityManager()->getClassMetadata($assoc['targetEntity']); $targetTableAlias = $sqlWalker->getSQLTableAlias($targetClass->getTableName()); $sourceTableAlias = $sqlWalker->getSQLTableAlias($class->getTableName(), $dqlAlias); $sql .= $quoteStrategy->getTableName($targetClass, $platform) . ' ' . $targetTableAlias . ' WHERE '; $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']]; $first = true; foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) { if ($first) $first = false; else $sql .= ' AND '; $sql .= $targetTableAlias . '.' . $sourceColumn . ' = ' . $sourceTableAlias . '.' . $quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $platform); } } else { // many-to-many $targetClass = $sqlWalker->getEntityManager()->getClassMetadata($assoc['targetEntity']); $owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']]; $joinTable = $owningAssoc['joinTable']; // SQL table aliases $joinTableAlias = $sqlWalker->getSQLTableAlias($joinTable['name']); $sourceTableAlias = $sqlWalker->getSQLTableAlias($class->getTableName(), $dqlAlias); // join to target table $sql .= $quoteStrategy->getJoinTableName($owningAssoc, $targetClass, $platform) . ' ' . $joinTableAlias . ' WHERE '; $joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns']; $first = true; foreach ($joinColumns as $joinColumn) { if ($first) $first = false; else $sql .= ' AND '; $sourceColumnName = $quoteStrategy->getColumnName( $class->fieldNames[$joinColumn['referencedColumnName']], $class, $platform ); $sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $sourceTableAlias . '.' . $sourceColumnName; } } return '(' . $sql . ')'; } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->collectionPathExpression = $parser->CollectionValuedPathExpression(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/SqrtFunction.php0000644000175100017510000000410212143374607025036 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "SQRT" "(" SimpleArithmeticExpression ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class SqrtFunction extends FunctionNode { public $simpleArithmeticExpression; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getSqrtExpression( $sqlWalker->walkSimpleArithmeticExpression($this->simpleArithmeticExpression) ); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/SubstringFunction.php0000644000175100017510000000567412143374607026104 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class SubstringFunction extends FunctionNode { public $stringPrimary; public $firstSimpleArithmeticExpression; public $secondSimpleArithmeticExpression = null; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { $optionalSecondSimpleArithmeticExpression = null; if ($this->secondSimpleArithmeticExpression !== null) { $optionalSecondSimpleArithmeticExpression = $sqlWalker->walkSimpleArithmeticExpression($this->secondSimpleArithmeticExpression); } return $sqlWalker->getConnection()->getDatabasePlatform()->getSubstringExpression( $sqlWalker->walkStringPrimary($this->stringPrimary), $sqlWalker->walkSimpleArithmeticExpression($this->firstSimpleArithmeticExpression), $optionalSecondSimpleArithmeticExpression ); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->stringPrimary = $parser->StringPrimary(); $parser->match(Lexer::T_COMMA); $this->firstSimpleArithmeticExpression = $parser->SimpleArithmeticExpression(); $lexer = $parser->getLexer(); if ($lexer->isNextToken(Lexer::T_COMMA)) { $parser->match(Lexer::T_COMMA); $this->secondSimpleArithmeticExpression = $parser->SimpleArithmeticExpression(); } $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/TrimFunction.php0000644000175100017510000000661712143374607025035 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class TrimFunction extends FunctionNode { public $leading; public $trailing; public $both; public $trimChar = false; public $stringPrimary; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { $pos = AbstractPlatform::TRIM_UNSPECIFIED; if ($this->leading) { $pos = AbstractPlatform::TRIM_LEADING; } else if ($this->trailing) { $pos = AbstractPlatform::TRIM_TRAILING; } else if ($this->both) { $pos = AbstractPlatform::TRIM_BOTH; } return $sqlWalker->getConnection()->getDatabasePlatform()->getTrimExpression( $sqlWalker->walkStringPrimary($this->stringPrimary), $pos, ($this->trimChar != false) ? $sqlWalker->getConnection()->quote($this->trimChar) : false ); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $lexer = $parser->getLexer(); $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); if (strcasecmp('leading', $lexer->lookahead['value']) === 0) { $parser->match(Lexer::T_LEADING); $this->leading = true; } else if (strcasecmp('trailing', $lexer->lookahead['value']) === 0) { $parser->match(Lexer::T_TRAILING); $this->trailing = true; } else if (strcasecmp('both', $lexer->lookahead['value']) === 0) { $parser->match(Lexer::T_BOTH); $this->both = true; } if ($lexer->isNextToken(Lexer::T_STRING)) { $parser->match(Lexer::T_STRING); $this->trimChar = $lexer->token['value']; } if ($this->leading || $this->trailing || $this->both || $this->trimChar) { $parser->match(Lexer::T_FROM); } $this->stringPrimary = $parser->StringPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/AST/Functions/UpperFunction.php0000644000175100017510000000400712143374607025204 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; /** * "UPPER" "(" StringPrimary ")" * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class UpperFunction extends FunctionNode { public $stringPrimary; /** * @override */ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getUpperExpression( $sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary) ); } /** * @override */ public function parse(\Doctrine\ORM\Query\Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->stringPrimary = $parser->StringPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php0000644000175100017510000000441312143374607024633 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\Exec; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Cache\QueryCacheProfile; /** * Base class for SQL statement executors. * * @author Roman Borschel * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link http://www.doctrine-project.org * @since 2.0 * @todo Rename: AbstractSQLExecutor */ abstract class AbstractSqlExecutor { /** * @var array */ protected $_sqlStatements; /** * @var QueryCacheProfile */ protected $queryCacheProfile; /** * Gets the SQL statements that are executed by the executor. * * @return array All the SQL update statements. */ public function getSqlStatements() { return $this->_sqlStatements; } public function setQueryCacheProfile(QueryCacheProfile $qcp) { $this->queryCacheProfile = $qcp; } /** * Executes all sql statements. * * @param \Doctrine\DBAL\Connection $conn The database connection that is used to execute the queries. * @param array $params The parameters. * @param array $types The parameter types. * @return \Doctrine\DBAL\Driver\Statement */ abstract public function execute(Connection $conn, array $params, array $types); } DoctrineORM-2.3.3/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php0000644000175100017510000001316312143374607025577 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\Exec; use Doctrine\DBAL\Connection, Doctrine\ORM\Query\AST; /** * Executes the SQL statements for bulk DQL DELETE statements on classes in * Class Table Inheritance (JOINED). * * @author Roman Borschel * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link http://www.doctrine-project.org * @since 2.0 */ class MultiTableDeleteExecutor extends AbstractSqlExecutor { private $_createTempTableSql; private $_dropTempTableSql; private $_insertSql; /** * Initializes a new MultiTableDeleteExecutor. * * @param Node $AST The root AST node of the DQL query. * @param SqlWalker $sqlWalker The walker used for SQL generation from the AST. * @internal Any SQL construction and preparation takes place in the constructor for * best performance. With a query cache the executor will be cached. */ public function __construct(AST\Node $AST, $sqlWalker) { $em = $sqlWalker->getEntityManager(); $conn = $em->getConnection(); $platform = $conn->getDatabasePlatform(); $quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); $primaryClass = $em->getClassMetadata($AST->deleteClause->abstractSchemaName); $primaryDqlAlias = $AST->deleteClause->aliasIdentificationVariable; $rootClass = $em->getClassMetadata($primaryClass->rootEntityName); $tempTable = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName()); $idColumnNames = $rootClass->getIdentifierColumnNames(); $idColumnList = implode(', ', $idColumnNames); // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause() $sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 't0', $primaryDqlAlias); $this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')' . ' SELECT t0.' . implode(', t0.', $idColumnNames); $rangeDecl = new AST\RangeVariableDeclaration($primaryClass->name, $primaryDqlAlias); $fromClause = new AST\FromClause(array(new AST\IdentificationVariableDeclaration($rangeDecl, null, array()))); $this->_insertSql .= $sqlWalker->walkFromClause($fromClause); // Append WHERE clause, if there is one. if ($AST->whereClause) { $this->_insertSql .= $sqlWalker->walkWhereClause($AST->whereClause); } // 2. Create ID subselect statement used in DELETE ... WHERE ... IN (subselect) $idSubselect = 'SELECT ' . $idColumnList . ' FROM ' . $tempTable; // 3. Create and store DELETE statements $classNames = array_merge($primaryClass->parentClasses, array($primaryClass->name), $primaryClass->subClasses); foreach (array_reverse($classNames) as $className) { $tableName = $quoteStrategy->getTableName($em->getClassMetadata($className), $platform); $this->_sqlStatements[] = 'DELETE FROM ' . $tableName . ' WHERE (' . $idColumnList . ') IN (' . $idSubselect . ')'; } // 4. Store DDL for temporary identifier table. $columnDefinitions = array(); foreach ($idColumnNames as $idColumnName) { $columnDefinitions[$idColumnName] = array( 'notnull' => true, 'type' => \Doctrine\DBAL\Types\Type::getType($rootClass->getTypeOfColumn($idColumnName)) ); } $this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; $this->_dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable); } /** * {@inheritDoc} */ public function execute(Connection $conn, array $params, array $types) { $numDeleted = 0; // Create temporary id table $conn->executeUpdate($this->_createTempTableSql); try { // Insert identifiers $numDeleted = $conn->executeUpdate($this->_insertSql, $params, $types); // Execute DELETE statements foreach ($this->_sqlStatements as $sql) { $conn->executeUpdate($sql); } } catch (\Exception $exception) { // FAILURE! Drop temporary table to avoid possible collisions $conn->executeUpdate($this->_dropTempTableSql); // Re-throw exception throw $exception; } // Drop temporary table $conn->executeUpdate($this->_dropTempTableSql); return $numDeleted; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php0000644000175100017510000001677512143374607025633 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\Exec; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Query\ParameterTypeInferer; use Doctrine\ORM\Query\AST; /** * Executes the SQL statements for bulk DQL UPDATE statements on classes in * Class Table Inheritance (JOINED). * * @author Roman Borschel * @since 2.0 */ class MultiTableUpdateExecutor extends AbstractSqlExecutor { private $_createTempTableSql; private $_dropTempTableSql; private $_insertSql; private $_sqlParameters = array(); private $_numParametersInUpdateClause = 0; /** * Initializes a new MultiTableUpdateExecutor. * * @param Node $AST The root AST node of the DQL query. * @param SqlWalker $sqlWalker The walker used for SQL generation from the AST. * @internal Any SQL construction and preparation takes place in the constructor for * best performance. With a query cache the executor will be cached. */ public function __construct(AST\Node $AST, $sqlWalker) { $em = $sqlWalker->getEntityManager(); $conn = $em->getConnection(); $platform = $conn->getDatabasePlatform(); $quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); $updateClause = $AST->updateClause; $primaryClass = $sqlWalker->getEntityManager()->getClassMetadata($updateClause->abstractSchemaName); $rootClass = $em->getClassMetadata($primaryClass->rootEntityName); $updateItems = $updateClause->updateItems; $tempTable = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName()); $idColumnNames = $rootClass->getIdentifierColumnNames(); $idColumnList = implode(', ', $idColumnNames); // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause() $sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 't0', $updateClause->aliasIdentificationVariable); $this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')' . ' SELECT t0.' . implode(', t0.', $idColumnNames); $rangeDecl = new AST\RangeVariableDeclaration($primaryClass->name, $updateClause->aliasIdentificationVariable); $fromClause = new AST\FromClause(array(new AST\IdentificationVariableDeclaration($rangeDecl, null, array()))); $this->_insertSql .= $sqlWalker->walkFromClause($fromClause); // 2. Create ID subselect statement used in UPDATE ... WHERE ... IN (subselect) $idSubselect = 'SELECT ' . $idColumnList . ' FROM ' . $tempTable; // 3. Create and store UPDATE statements $classNames = array_merge($primaryClass->parentClasses, array($primaryClass->name), $primaryClass->subClasses); $i = -1; foreach (array_reverse($classNames) as $className) { $affected = false; $class = $em->getClassMetadata($className); $updateSql = 'UPDATE ' . $quoteStrategy->getTableName($class, $platform) . ' SET '; foreach ($updateItems as $updateItem) { $field = $updateItem->pathExpression->field; if (isset($class->fieldMappings[$field]) && ! isset($class->fieldMappings[$field]['inherited']) || isset($class->associationMappings[$field]) && ! isset($class->associationMappings[$field]['inherited'])) { $newValue = $updateItem->newValue; if ( ! $affected) { $affected = true; ++$i; } else { $updateSql .= ', '; } $updateSql .= $sqlWalker->walkUpdateItem($updateItem); if ($newValue instanceof AST\InputParameter) { $this->_sqlParameters[$i][] = $newValue->name; ++$this->_numParametersInUpdateClause; } } } if ($affected) { $this->_sqlStatements[$i] = $updateSql . ' WHERE (' . $idColumnList . ') IN (' . $idSubselect . ')'; } } // Append WHERE clause to insertSql, if there is one. if ($AST->whereClause) { $this->_insertSql .= $sqlWalker->walkWhereClause($AST->whereClause); } // 4. Store DDL for temporary identifier table. $columnDefinitions = array(); foreach ($idColumnNames as $idColumnName) { $columnDefinitions[$idColumnName] = array( 'notnull' => true, 'type' => Type::getType($rootClass->getTypeOfColumn($idColumnName)) ); } $this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; $this->_dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable); } /** * {@inheritDoc} */ public function execute(Connection $conn, array $params, array $types) { $numUpdated = 0; // Create temporary id table $conn->executeUpdate($this->_createTempTableSql); try { // Insert identifiers. Parameters from the update clause are cut off. $numUpdated = $conn->executeUpdate( $this->_insertSql, array_slice($params, $this->_numParametersInUpdateClause), array_slice($types, $this->_numParametersInUpdateClause) ); // Execute UPDATE statements foreach ($this->_sqlStatements as $key => $statement) { $paramValues = array(); $paramTypes = array(); if (isset($this->_sqlParameters[$key])) { foreach ($this->_sqlParameters[$key] as $parameterKey => $parameterName) { $paramValues[] = $params[$parameterKey]; $paramTypes[] = isset($types[$parameterKey]) ? $types[$parameterKey] : ParameterTypeInferer::inferType($params[$parameterKey]); } } $conn->executeUpdate($statement, $paramValues, $paramTypes); } } catch (\Exception $exception) { // FAILURE! Drop temporary table to avoid possible collisions $conn->executeUpdate($this->_dropTempTableSql); // Re-throw exception throw $exception; } // Drop temporary table $conn->executeUpdate($this->_dropTempTableSql); return $numUpdated; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Exec/SingleSelectExecutor.php0000644000175100017510000000350212143374607024767 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\Exec; use Doctrine\DBAL\Connection, Doctrine\ORM\Query\AST\SelectStatement, Doctrine\ORM\Query\SqlWalker; /** * Executor that executes the SQL statement for simple DQL SELECT statements. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @author Roman Borschel * @link www.doctrine-project.org * @since 2.0 */ class SingleSelectExecutor extends AbstractSqlExecutor { public function __construct(SelectStatement $AST, SqlWalker $sqlWalker) { $this->_sqlStatements = $sqlWalker->walkSelectStatement($AST); } /** * {@inheritDoc} */ public function execute(Connection $conn, array $params, array $types) { return $conn->executeQuery($this->_sqlStatements, $params, $types, $this->queryCacheProfile); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php0000644000175100017510000000410512143374607027065 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\Exec; use Doctrine\DBAL\Connection, Doctrine\ORM\Query\AST; /** * Executor that executes the SQL statements for DQL DELETE/UPDATE statements on classes * that are mapped to a single table. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @author Roman Borschel * @link www.doctrine-project.org * @since 2.0 * @todo This is exactly the same as SingleSelectExecutor. Unify in SingleStatementExecutor. */ class SingleTableDeleteUpdateExecutor extends AbstractSqlExecutor { public function __construct(AST\Node $AST, $sqlWalker) { if ($AST instanceof AST\UpdateStatement) { $this->_sqlStatements = $sqlWalker->walkUpdateStatement($AST); } else if ($AST instanceof AST\DeleteStatement) { $this->_sqlStatements = $sqlWalker->walkDeleteStatement($AST); } } /** * {@inheritDoc} */ public function execute(Connection $conn, array $params, array $types) { return $conn->executeUpdate($this->_sqlStatements, $params, $types); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Expr/Andx.php0000644000175100017510000000333512143374607021617 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for building DQL and parts * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Andx extends Composite { /** * @var string */ protected $separator = ' AND '; /** * @var array */ protected $allowedClasses = array( 'Doctrine\ORM\Query\Expr\Comparison', 'Doctrine\ORM\Query\Expr\Func', 'Doctrine\ORM\Query\Expr\Orx', 'Doctrine\ORM\Query\Expr\Andx', ); /** * @return array */ public function getParts() { return $this->parts; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Expr/Base.php0000644000175100017510000000600012143374607021567 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\Expr; /** * Abstract base Expr class for building DQL parts * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ abstract class Base { /** * @var string */ protected $preSeparator = '('; /** * @var string */ protected $separator = ', '; /** * @var string */ protected $postSeparator = ')'; /** * @var array */ protected $allowedClasses = array(); /** * @var array */ protected $parts = array(); /** * @param array $args */ public function __construct($args = array()) { $this->addMultiple($args); } /** * @param array $args * @return Base */ public function addMultiple($args = array()) { foreach ((array) $args as $arg) { $this->add($arg); } return $this; } /** * @param mixed $arg * @return Base */ public function add($arg) { if ( $arg !== null && (!$arg instanceof self || $arg->count() > 0) ) { // If we decide to keep Expr\Base instances, we can use this check if ( ! is_string($arg)) { $class = get_class($arg); if ( ! in_array($class, $this->allowedClasses)) { throw new \InvalidArgumentException("Expression of type '$class' not allowed in this context."); } } $this->parts[] = $arg; } return $this; } /** * @return integer */ public function count() { return count($this->parts); } /** * @return string */ public function __toString() { if ($this->count() == 1) { return (string) $this->parts[0]; } return $this->preSeparator . implode($this->separator, $this->parts) . $this->postSeparator; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Expr/Comparison.php0000644000175100017510000000474712143374607023047 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for DQL comparison expressions * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Comparison { const EQ = '='; const NEQ = '<>'; const LT = '<'; const LTE = '<='; const GT = '>'; const GTE = '>='; /** * @var mixed */ protected $leftExpr; /** * @var string */ protected $operator; /** * @var mixed */ protected $rightExpr; /** * Creates a comparison expression with the given arguments. * * @param mixed $leftExpr * @param string $operator * @param mixed $rightExpr */ public function __construct($leftExpr, $operator, $rightExpr) { $this->leftExpr = $leftExpr; $this->operator = $operator; $this->rightExpr = $rightExpr; } /** * @return mixed */ public function getLeftExpr() { return $this->leftExpr; } /** * @return string */ public function getOperator() { return $this->operator; } /** * @return mixed */ public function getRightExpr() { return $this->rightExpr; } /** * @return string */ public function __toString() { return $this->leftExpr . ' ' . $this->operator . ' ' . $this->rightExpr; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Expr/Composite.php0000644000175100017510000000446312143374607022672 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for building DQL and parts * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Composite extends Base { /** * @return string */ public function __toString() { if ($this->count() === 1) { return (string) $this->parts[0]; } $components = array(); foreach ($this->parts as $part) { $components[] = $this->processQueryPart($part); } return implode($this->separator, $components); } /** * @param string $part * @return string */ private function processQueryPart($part) { $queryPart = (string) $part; if (is_object($part) && $part instanceof self && $part->count() > 1) { return $this->preSeparator . $queryPart . $this->postSeparator; } // Fixes DDC-1237: User may have added a where item containing nested expression (with "OR" or "AND") if (stripos($queryPart, ' OR ') !== false || stripos($queryPart, ' AND ') !== false) { return $this->preSeparator . $queryPart . $this->postSeparator; } return $queryPart; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Expr/From.php0000644000175100017510000000447512143374607021636 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for DQL from * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class From { /** * @var string */ protected $from; /** * @var string */ protected $alias; /** * @var string */ protected $indexBy; /** * @param string $from The class name. * @param string $alias The alias of the class. * @param string $indexBy The index for the from. */ public function __construct($from, $alias, $indexBy = null) { $this->from = $from; $this->alias = $alias; $this->indexBy = $indexBy; } /** * @return string */ public function getFrom() { return $this->from; } /** * @return string */ public function getAlias() { return $this->alias; } /** * @return string */ public function getIndexBy() { return $this->indexBy; } /** * @return string */ public function __toString() { return $this->from . ' ' . $this->alias . ($this->indexBy ? ' INDEX BY ' . $this->indexBy : ''); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Expr/Func.php0000644000175100017510000000407412143374607021621 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for generating DQL functions * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Func { /** * @var string */ protected $name; /** * @var array */ protected $arguments; /** * Creates a function, with the given argument. * * @param string $name * @param array $arguments */ public function __construct($name, $arguments) { $this->name = $name; $this->arguments = (array) $arguments; } /** * @return string */ public function getName() { return $this->name; } /** * @return array */ public function getArguments() { return $this->arguments; } /** * @return string */ public function __toString() { return $this->name . '(' . implode(', ', $this->arguments) . ')'; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Expr/GroupBy.php0000644000175100017510000000306012143374607022307 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for building DQL Group By parts * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class GroupBy extends Base { /** * @var string */ protected $preSeparator = ''; /** * @var string */ protected $postSeparator = ''; /** * @return array */ public function getParts() { return $this->parts; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Expr/Join.php0000644000175100017510000000720012143374607021617 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for DQL from * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Join { const INNER_JOIN = 'INNER'; const LEFT_JOIN = 'LEFT'; const ON = 'ON'; const WITH = 'WITH'; /** * @var string */ protected $joinType; /** * @var string */ protected $join; /** * @var string */ protected $alias; /** * @var string */ protected $conditionType; /** * @var string */ protected $condition; /** * @var string */ protected $indexBy; /** * @param string $joinType The condition type constant. Either INNER_JOIN or LEFT_JOIN. * @param string $join The relationship to join * @param string $alias The alias of the join * @param string $conditionType The condition type constant. Either ON or WITH. * @param string $condition The condition for the join * @param string $indexBy The index for the join */ public function __construct($joinType, $join, $alias = null, $conditionType = null, $condition = null, $indexBy = null) { $this->joinType = $joinType; $this->join = $join; $this->alias = $alias; $this->conditionType = $conditionType; $this->condition = $condition; $this->indexBy = $indexBy; } /** * @return string */ public function getJoinType() { return $this->joinType; } /** * @return string */ public function getJoin() { return $this->join; } /** * @return string */ public function getAlias() { return $this->alias; } /** * @return string */ public function getConditionType() { return $this->conditionType; } /** * @return string */ public function getCondition() { return $this->condition; } /** * @return string */ public function getIndexBy() { return $this->indexBy; } /** * @return string */ public function __toString() { return strtoupper($this->joinType) . ' JOIN ' . $this->join . ($this->alias ? ' ' . $this->alias : '') . ($this->condition ? ' ' . strtoupper($this->conditionType) . ' ' . $this->condition : '') . ($this->indexBy ? ' INDEX BY ' . $this->indexBy : ''); } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Expr/Literal.php0000644000175100017510000000305612143374607022321 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for generating DQL functions * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Literal extends Base { /** * @var string */ protected $preSeparator = ''; /** * @var string */ protected $postSeparator = ''; /** * @return array */ public function getParts() { return $this->parts; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Expr/Math.php0000644000175100017510000000531512143374607021616 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for DQL math statements * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Math { /** * @var mixed */ protected $leftExpr; /** * @var string */ protected $operator; /** * @var mixed */ protected $rightExpr; /** * Creates a mathematical expression with the given arguments. * * @param mixed $leftExpr * @param string $operator * @param mixed $rightExpr */ public function __construct($leftExpr, $operator, $rightExpr) { $this->leftExpr = $leftExpr; $this->operator = $operator; $this->rightExpr = $rightExpr; } /** * @return mixed */ public function getLeftExpr() { return $this->leftExpr; } /** * @return string */ public function getOperator() { return $this->operator; } /** * @return mixed */ public function getRightExpr() { return $this->rightExpr; } /** * @return string */ public function __toString() { // Adjusting Left Expression $leftExpr = (string) $this->leftExpr; if ($this->leftExpr instanceof Math) { $leftExpr = '(' . $leftExpr . ')'; } // Adjusting Right Expression $rightExpr = (string) $this->rightExpr; if ($this->rightExpr instanceof Math) { $rightExpr = '(' . $rightExpr . ')'; } return $leftExpr . ' ' . $this->operator . ' ' . $rightExpr; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Expr/OrderBy.php0000644000175100017510000000472612143374607022300 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for building DQL Order By parts * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class OrderBy { /** * @var string */ protected $preSeparator = ''; /** * @var string */ protected $separator = ', '; /** * @var string */ protected $postSeparator = ''; /** * @var array */ protected $allowedClasses = array(); /** * @var array */ protected $parts = array(); /** * @param string $sort * @param string $order */ public function __construct($sort = null, $order = null) { if ($sort) { $this->add($sort, $order); } } /** * @param string $sort * @param string $order */ public function add($sort, $order = null) { $order = ! $order ? 'ASC' : $order; $this->parts[] = $sort . ' '. $order; } /** * @return integer */ public function count() { return count($this->parts); } /** * @return array */ public function getParts() { return $this->parts; } /** * @return string */ public function __tostring() { return $this->preSeparator . implode($this->separator, $this->parts) . $this->postSeparator; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Expr/Orx.php0000644000175100017510000000333412143374607021474 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for building DQL OR clauses * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Orx extends Composite { /** * @var string */ protected $separator = ' OR '; /** * @var array */ protected $allowedClasses = array( 'Doctrine\ORM\Query\Expr\Comparison', 'Doctrine\ORM\Query\Expr\Func', 'Doctrine\ORM\Query\Expr\Andx', 'Doctrine\ORM\Query\Expr\Orx', ); /** * @return array */ public function getParts() { return $this->parts; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Expr/Select.php0000644000175100017510000000325112143374607022141 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\Expr; /** * Expression class for building DQL select statements * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Select extends Base { /** * @var string */ protected $preSeparator = ''; /** * @var string */ protected $postSeparator = ''; /** * @var array */ protected $allowedClasses = array( 'Doctrine\ORM\Query\Expr\Func' ); /** * @return array */ public function getParts() { return $this->parts; } } DoctrineORM-2.3.3/Doctrine/ORM/Query/Filter/SQLFilter.php0000644000175100017510000000747012143374607023045 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Query\Filter; use Doctrine\ORM\EntityManager, Doctrine\ORM\Mapping\ClassMetaData, Doctrine\ORM\Query\ParameterTypeInferer; /** * The base class that user defined filters should extend. * * Handles the setting and escaping of parameters. * * @author Alexander * @author Benjamin Eberlei * @abstract */ abstract class SQLFilter { /** * The entity manager. * @var EntityManager */ private $em; /** * Parameters for the filter. * @var array */ private $parameters; /** * Constructs the SQLFilter object. * * @param EntityManager $em The EM */ final public function __construct(EntityManager $em) { $this->em = $em; } /** * Sets a parameter that can be used by the filter. * * @param string $name Name of the parameter. * @param string $value Value of the parameter. * @param string $type The parameter type. If specified, the given value will be run through * the type conversion of this type. This is usually not needed for * strings and numeric types. * * @return SQLFilter The current SQL filter. */ final public function setParameter($name, $value, $type = null) { if (null === $type) { $type = ParameterTypeInferer::inferType($value); } $this->parameters[$name] = array('value' => $value, 'type' => $type); // Keep the parameters sorted for the hash ksort($this->parameters); // The filter collection of the EM is now dirty $this->em->getFilters()->setFiltersStateDirty(); return $this; } /** * Gets a parameter to use in a query. * * The function is responsible for the right output escaping to use the * value in a query. * * @param string $name Name of the parameter. * * @return string The SQL escaped parameter to use in a query. */ final public function getParameter($name) { if (!isset($this->parameters[$name])) { throw new \InvalidArgumentException("Parameter '" . $name . "' does not exist."); } return $this->em->getConnection()->quote($this->parameters[$name]['value'], $this->parameters[$name]['type']); } /** * Returns as string representation of the SQLFilter parameters (the state). * * @return string String representation of the SQLFilter. */ final public function __toString() { return serialize($this->parameters); } /** * Gets the SQL query part to add to a query. * * @return string The constraint SQL if there is available, empty string otherwise */ abstract public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias); } DoctrineORM-2.3.3/Doctrine/ORM/Tools/ConvertDoctrine1Schema.php0000644000175100017510000002414612143374607024317 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools; use Doctrine\ORM\Mapping\ClassMetadataInfo, Doctrine\ORM\Tools\Export\Driver\AbstractExporter, Doctrine\Common\Util\Inflector; /** * Class to help with converting Doctrine 1 schema files to Doctrine 2 mapping files * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ConvertDoctrine1Schema { private $_legacyTypeMap = array( // TODO: This list may need to be updated 'clob' => 'text', 'timestamp' => 'datetime', 'enum' => 'string' ); /** * Constructor passes the directory or array of directories * to convert the Doctrine 1 schema files from * * @param array $from * @author Jonathan Wage */ public function __construct($from) { $this->_from = (array) $from; } /** * Get an array of ClassMetadataInfo instances from the passed * Doctrine 1 schema * * @return array $metadatas An array of ClassMetadataInfo instances */ public function getMetadata() { $schema = array(); foreach ($this->_from as $path) { if (is_dir($path)) { $files = glob($path . '/*.yml'); foreach ($files as $file) { $schema = array_merge($schema, (array) \Symfony\Component\Yaml\Yaml::parse($file)); } } else { $schema = array_merge($schema, (array) \Symfony\Component\Yaml\Yaml::parse($path)); } } $metadatas = array(); foreach ($schema as $className => $mappingInformation) { $metadatas[] = $this->_convertToClassMetadataInfo($className, $mappingInformation); } return $metadatas; } private function _convertToClassMetadataInfo($className, $mappingInformation) { $metadata = new ClassMetadataInfo($className); $this->_convertTableName($className, $mappingInformation, $metadata); $this->_convertColumns($className, $mappingInformation, $metadata); $this->_convertIndexes($className, $mappingInformation, $metadata); $this->_convertRelations($className, $mappingInformation, $metadata); return $metadata; } private function _convertTableName($className, array $model, ClassMetadataInfo $metadata) { if (isset($model['tableName']) && $model['tableName']) { $e = explode('.', $model['tableName']); if (count($e) > 1) { $metadata->table['schema'] = $e[0]; $metadata->table['name'] = $e[1]; } else { $metadata->table['name'] = $e[0]; } } } private function _convertColumns($className, array $model, ClassMetadataInfo $metadata) { $id = false; if (isset($model['columns']) && $model['columns']) { foreach ($model['columns'] as $name => $column) { $fieldMapping = $this->_convertColumn($className, $name, $column, $metadata); if (isset($fieldMapping['id']) && $fieldMapping['id']) { $id = true; } } } if ( ! $id) { $fieldMapping = array( 'fieldName' => 'id', 'columnName' => 'id', 'type' => 'integer', 'id' => true ); $metadata->mapField($fieldMapping); $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO); } } private function _convertColumn($className, $name, $column, ClassMetadataInfo $metadata) { if (is_string($column)) { $string = $column; $column = array(); $column['type'] = $string; } if ( ! isset($column['name'])) { $column['name'] = $name; } // check if a column alias was used (column_name as field_name) if (preg_match("/(\w+)\sas\s(\w+)/i", $column['name'], $matches)) { $name = $matches[1]; $column['name'] = $name; $column['alias'] = $matches[2]; } if (preg_match("/([a-zA-Z]+)\(([0-9]+)\)/", $column['type'], $matches)) { $column['type'] = $matches[1]; $column['length'] = $matches[2]; } $column['type'] = strtolower($column['type']); // check if legacy column type (1.x) needs to be mapped to a 2.0 one if (isset($this->_legacyTypeMap[$column['type']])) { $column['type'] = $this->_legacyTypeMap[$column['type']]; } if ( ! \Doctrine\DBAL\Types\Type::hasType($column['type'])) { throw ToolsException::couldNotMapDoctrine1Type($column['type']); } $fieldMapping = array(); if (isset($column['primary'])) { $fieldMapping['id'] = true; } $fieldMapping['fieldName'] = isset($column['alias']) ? $column['alias'] : $name; $fieldMapping['columnName'] = $column['name']; $fieldMapping['type'] = $column['type']; if (isset($column['length'])) { $fieldMapping['length'] = $column['length']; } $allowed = array('precision', 'scale', 'unique', 'options', 'notnull', 'version'); foreach ($column as $key => $value) { if (in_array($key, $allowed)) { $fieldMapping[$key] = $value; } } $metadata->mapField($fieldMapping); if (isset($column['autoincrement'])) { $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO); } else if (isset($column['sequence'])) { $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE); $definition = array( 'sequenceName' => is_array($column['sequence']) ? $column['sequence']['name']:$column['sequence'] ); if (isset($column['sequence']['size'])) { $definition['allocationSize'] = $column['sequence']['size']; } if (isset($column['sequence']['value'])) { $definition['initialValue'] = $column['sequence']['value']; } $metadata->setSequenceGeneratorDefinition($definition); } return $fieldMapping; } private function _convertIndexes($className, array $model, ClassMetadataInfo $metadata) { if (isset($model['indexes']) && $model['indexes']) { foreach ($model['indexes'] as $name => $index) { $type = (isset($index['type']) && $index['type'] == 'unique') ? 'uniqueConstraints' : 'indexes'; $metadata->table[$type][$name] = array( 'columns' => $index['fields'] ); } } } private function _convertRelations($className, array $model, ClassMetadataInfo $metadata) { if (isset($model['relations']) && $model['relations']) { foreach ($model['relations'] as $name => $relation) { if ( ! isset($relation['alias'])) { $relation['alias'] = $name; } if ( ! isset($relation['class'])) { $relation['class'] = $name; } if ( ! isset($relation['local'])) { $relation['local'] = Inflector::tableize($relation['class']); } if ( ! isset($relation['foreign'])) { $relation['foreign'] = 'id'; } if ( ! isset($relation['foreignAlias'])) { $relation['foreignAlias'] = $className; } if (isset($relation['refClass'])) { $type = 'many'; $foreignType = 'many'; $joinColumns = array(); } else { $type = isset($relation['type']) ? $relation['type'] : 'one'; $foreignType = isset($relation['foreignType']) ? $relation['foreignType'] : 'many'; $joinColumns = array( array( 'name' => $relation['local'], 'referencedColumnName' => $relation['foreign'], 'onDelete' => isset($relation['onDelete']) ? $relation['onDelete'] : null, ) ); } if ($type == 'one' && $foreignType == 'one') { $method = 'mapOneToOne'; } else if ($type == 'many' && $foreignType == 'many') { $method = 'mapManyToMany'; } else { $method = 'mapOneToMany'; } $associationMapping = array(); $associationMapping['fieldName'] = $relation['alias']; $associationMapping['targetEntity'] = $relation['class']; $associationMapping['mappedBy'] = $relation['foreignAlias']; $associationMapping['joinColumns'] = $joinColumns; $metadata->$method($associationMapping); } } } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/DebugUnitOfWorkListener.php0000644000175100017510000001306712143374607024531 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools; use Doctrine\ORM\Event\OnFlushEventArgs; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\UnitOfWork; /** * Use this logger to dump the identity map during the onFlush event. This is useful for debugging * weird UnitOfWork behavior with complex operations. */ class DebugUnitOfWorkListener { private $file; private $context; /** * Pass a stream and contet information for the debugging session. * * The stream can be php://output to print to the screen. * * @param string $file * @param string $context */ public function __construct($file = 'php://output', $context = '') { $this->file = $file; $this->context = $context; } public function onFlush(OnFlushEventArgs $args) { $this->dumpIdentityMap($args->getEntityManager()); } /** * Dump the contents of the identity map into a stream. * * @param EntityManager $em * @return void */ public function dumpIdentityMap(EntityManager $em) { $uow = $em->getUnitOfWork(); $identityMap = $uow->getIdentityMap(); $fh = fopen($this->file, "x+"); if (count($identityMap) == 0) { fwrite($fh, "Flush Operation [".$this->context."] - Empty identity map.\n"); return; } fwrite($fh, "Flush Operation [".$this->context."] - Dumping identity map:\n"); foreach ($identityMap as $className => $map) { fwrite($fh, "Class: ". $className . "\n"); foreach ($map as $entity) { fwrite($fh, " Entity: " . $this->getIdString($entity, $uow) . " " . spl_object_hash($entity)."\n"); fwrite($fh, " Associations:\n"); $cm = $em->getClassMetadata($className); foreach ($cm->associationMappings as $field => $assoc) { fwrite($fh, " " . $field . " "); $value = $cm->reflFields[$field]->getValue($entity); if ($assoc['type'] & ClassMetadata::TO_ONE) { if ($value === null) { fwrite($fh, " NULL\n"); } else { if ($value instanceof Proxy && !$value->__isInitialized__) { fwrite($fh, "[PROXY] "); } fwrite($fh, $this->getIdString($value, $uow) . " " . spl_object_hash($value) . "\n"); } } else { $initialized = !($value instanceof PersistentCollection) || $value->isInitialized(); if ($value === null) { fwrite($fh, " NULL\n"); } else if ($initialized) { fwrite($fh, "[INITIALIZED] " . $this->getType($value). " " . count($value) . " elements\n"); foreach ($value as $obj) { fwrite($fh, " " . $this->getIdString($obj, $uow) . " " . spl_object_hash($obj)."\n"); } } else { fwrite($fh, "[PROXY] " . $this->getType($value) . " unknown element size\n"); foreach ($value->unwrap() as $obj) { fwrite($fh, " " . $this->getIdString($obj, $uow) . " " . spl_object_hash($obj)."\n"); } } } } } } fclose($fh); } private function getType($var) { if (is_object($var)) { $refl = new \ReflectionObject($var); return $refl->getShortname(); } else { return gettype($var); } } private function getIdString($entity, $uow) { if ($uow->isInIdentityMap($entity)) { $ids = $uow->getEntityIdentifier($entity); $idstring = ""; foreach ($ids as $k => $v) { $idstring .= $k."=".$v; } } else { $idstring = "NEWOBJECT "; } $state = $uow->getEntityState($entity); if ($state == UnitOfWork::STATE_NEW) { $idstring .= " [NEW]"; } else if ($state == UnitOfWork::STATE_REMOVED) { $idstring .= " [REMOVED]"; } else if ($state == UnitOfWork::STATE_MANAGED) { $idstring .= " [MANAGED]"; } else if ($state == UnitOfwork::STATE_DETACHED) { $idstring .= " [DETACHED]"; } return $idstring; } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/DisconnectedClassMetadataFactory.php0000644000175100017510000000350712143374607026364 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools; use Doctrine\ORM\Mapping\ClassMetadataFactory; use Doctrine\ORM\Mapping\ClassMetadataInfo; /** * The DisconnectedClassMetadataFactory is used to create ClassMetadataInfo objects * that do not require the entity class actually exist. This allows us to * load some mapping information and use it to do things like generate code * from the mapping information. * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class DisconnectedClassMetadataFactory extends ClassMetadataFactory { public function getReflectionService() { return new \Doctrine\Common\Persistence\Mapping\StaticReflectionService; } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/EntityGenerator.php0000644000175100017510000012602712143374607023131 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools; use Doctrine\ORM\Mapping\ClassMetadataInfo, Doctrine\Common\Util\Inflector, Doctrine\DBAL\Types\Type; /** * Generic class used to generate PHP5 entity classes from ClassMetadataInfo instances * * [php] * $classes = $em->getClassMetadataFactory()->getAllMetadata(); * * $generator = new \Doctrine\ORM\Tools\EntityGenerator(); * $generator->setGenerateAnnotations(true); * $generator->setGenerateStubMethods(true); * $generator->setRegenerateEntityIfExists(false); * $generator->setUpdateEntityIfExists(true); * $generator->generate($classes, '/path/to/generate/entities'); * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class EntityGenerator { /** * Specifies class fields should be protected */ const FIELD_VISIBLE_PROTECTED = 'protected'; /** * Specifies class fields should be private */ const FIELD_VISIBLE_PRIVATE = 'private'; /** * @var bool */ private $backupExisting = true; /** * The extension to use for written php files * * @var string */ private $extension = '.php'; /** * Whether or not the current ClassMetadataInfo instance is new or old * * @var boolean */ private $isNew = true; /** * @var array */ private $staticReflection = array(); /** * Number of spaces to use for indention in generated code */ private $numSpaces = 4; /** * The actual spaces to use for indention * * @var string */ private $spaces = ' '; /** * The class all generated entities should extend * * @var string */ private $classToExtend; /** * Whether or not to generation annotations * * @var boolean */ private $generateAnnotations = false; /** * @var string */ private $annotationsPrefix = ''; /** * Whether or not to generated sub methods * * @var boolean */ private $generateEntityStubMethods = false; /** * Whether or not to update the entity class if it exists already * * @var boolean */ private $updateEntityIfExists = false; /** * Whether or not to re-generate entity class if it exists already * * @var boolean */ private $regenerateEntityIfExists = false; /** * @var boolean */ private $fieldVisibility = 'private'; /** * Hash-map for handle types * * @var array */ private $typeAlias = array( Type::DATETIMETZ => '\DateTime', Type::DATETIME => '\DateTime', Type::DATE => '\DateTime', Type::TIME => '\DateTime', Type::OBJECT => '\stdClass', Type::BIGINT => 'integer', Type::SMALLINT => 'integer', Type::TEXT => 'string', Type::BLOB => 'string', Type::DECIMAL => 'float', Type::JSON_ARRAY => 'array', Type::SIMPLE_ARRAY => 'array', ); /** * @var array Hash-map to handle generator types string. */ protected static $generatorStrategyMap = array( ClassMetadataInfo::GENERATOR_TYPE_AUTO => 'AUTO', ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE => 'SEQUENCE', ClassMetadataInfo::GENERATOR_TYPE_TABLE => 'TABLE', ClassMetadataInfo::GENERATOR_TYPE_IDENTITY => 'IDENTITY', ClassMetadataInfo::GENERATOR_TYPE_NONE => 'NONE', ClassMetadataInfo::GENERATOR_TYPE_UUID => 'UUID', ClassMetadataInfo::GENERATOR_TYPE_CUSTOM => 'CUSTOM' ); /** * @var array Hash-map to handle the change tracking policy string. */ protected static $changeTrackingPolicyMap = array( ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT => 'DEFERRED_IMPLICIT', ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT => 'DEFERRED_EXPLICIT', ClassMetadataInfo::CHANGETRACKING_NOTIFY => 'NOTIFY', ); /** * @var array Hash-map to handle the inheritance type string. */ protected static $inheritanceTypeMap = array( ClassMetadataInfo::INHERITANCE_TYPE_NONE => 'NONE', ClassMetadataInfo::INHERITANCE_TYPE_JOINED => 'JOINED', ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE => 'SINGLE_TABLE', ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS => 'TABLE_PER_CLASS', ); /** * @var string */ private static $classTemplate = ' use Doctrine\ORM\Mapping as ORM; { } '; /** * @var string */ private static $getMethodTemplate = '/** * * * @return */ public function () { return $this->; }'; /** * @var string */ private static $setMethodTemplate = '/** * * * @param $ * @return */ public function ($) { $this-> = $; return $this; }'; /** * @var string */ private static $addMethodTemplate = '/** * * * @param $ * @return */ public function ($) { $this->[] = $; return $this; }'; /** * @var string */ private static $removeMethodTemplate = '/** * * * @param $ */ public function ($) { $this->->removeElement($); }'; /** * @var string */ private static $lifecycleCallbackMethodTemplate = '/** * @ */ public function () { // Add your code here }'; /** * @var string */ private static $constructorMethodTemplate = '/** * Constructor */ public function __construct() { } '; public function __construct() { if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) { $this->annotationsPrefix = 'ORM\\'; } } /** * Generate and write entity classes for the given array of ClassMetadataInfo instances * * @param array $metadatas * @param string $outputDirectory * @return void */ public function generate(array $metadatas, $outputDirectory) { foreach ($metadatas as $metadata) { $this->writeEntityClass($metadata, $outputDirectory); } } /** * Generated and write entity class to disk for the given ClassMetadataInfo instance * * @param ClassMetadataInfo $metadata * @param string $outputDirectory * @return void */ public function writeEntityClass(ClassMetadataInfo $metadata, $outputDirectory) { $path = $outputDirectory . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $metadata->name) . $this->extension; $dir = dirname($path); if ( ! is_dir($dir)) { mkdir($dir, 0777, true); } $this->isNew = !file_exists($path) || (file_exists($path) && $this->regenerateEntityIfExists); if ( ! $this->isNew) { $this->parseTokensInEntityFile(file_get_contents($path)); } else { $this->staticReflection[$metadata->name] = array('properties' => array(), 'methods' => array()); } if ($this->backupExisting && file_exists($path)) { $backupPath = dirname($path) . DIRECTORY_SEPARATOR . basename($path) . "~"; if (!copy($path, $backupPath)) { throw new \RuntimeException("Attempt to backup overwritten entity file but copy operation failed."); } } // If entity doesn't exist or we're re-generating the entities entirely if ($this->isNew) { file_put_contents($path, $this->generateEntityClass($metadata)); // If entity exists and we're allowed to update the entity class } else if ( ! $this->isNew && $this->updateEntityIfExists) { file_put_contents($path, $this->generateUpdatedEntityClass($metadata, $path)); } } /** * Generate a PHP5 Doctrine 2 entity class from the given ClassMetadataInfo instance * * @param ClassMetadataInfo $metadata * @return string $code */ public function generateEntityClass(ClassMetadataInfo $metadata) { $placeHolders = array( '', '', '', '' ); $replacements = array( $this->generateEntityNamespace($metadata), $this->generateEntityDocBlock($metadata), $this->generateEntityClassName($metadata), $this->generateEntityBody($metadata) ); $code = str_replace($placeHolders, $replacements, self::$classTemplate); return str_replace('', $this->spaces, $code); } /** * Generate the updated code for the given ClassMetadataInfo and entity at path * * @param ClassMetadataInfo $metadata * @param string $path * @return string $code; */ public function generateUpdatedEntityClass(ClassMetadataInfo $metadata, $path) { $currentCode = file_get_contents($path); $body = $this->generateEntityBody($metadata); $body = str_replace('', $this->spaces, $body); $last = strrpos($currentCode, '}'); return substr($currentCode, 0, $last) . $body . (strlen($body) > 0 ? "\n" : ''). "}"; } /** * Set the number of spaces the exported class should have * * @param integer $numSpaces * @return void */ public function setNumSpaces($numSpaces) { $this->spaces = str_repeat(' ', $numSpaces); $this->numSpaces = $numSpaces; } /** * Set the extension to use when writing php files to disk * * @param string $extension * @return void */ public function setExtension($extension) { $this->extension = $extension; } /** * Set the name of the class the generated classes should extend from * * @return void */ public function setClassToExtend($classToExtend) { $this->classToExtend = $classToExtend; } /** * Set whether or not to generate annotations for the entity * * @param bool $bool * @return void */ public function setGenerateAnnotations($bool) { $this->generateAnnotations = $bool; } /** * Set the class fields visibility for the entity (can either be private or protected) * * @param bool $bool * @return void */ public function setFieldVisibility($visibility) { if ($visibility !== self::FIELD_VISIBLE_PRIVATE && $visibility !== self::FIELD_VISIBLE_PROTECTED) { throw new \InvalidArgumentException('Invalid provided visibilty (only private and protected are allowed): ' . $visibility); } $this->fieldVisibility = $visibility; } /** * Set an annotation prefix. * * @param string $prefix */ public function setAnnotationPrefix($prefix) { $this->annotationsPrefix = $prefix; } /** * Set whether or not to try and update the entity if it already exists * * @param bool $bool * @return void */ public function setUpdateEntityIfExists($bool) { $this->updateEntityIfExists = $bool; } /** * Set whether or not to regenerate the entity if it exists * * @param bool $bool * @return void */ public function setRegenerateEntityIfExists($bool) { $this->regenerateEntityIfExists = $bool; } /** * Set whether or not to generate stub methods for the entity * * @param bool $bool * @return void */ public function setGenerateStubMethods($bool) { $this->generateEntityStubMethods = $bool; } /** * Should an existing entity be backed up if it already exists? */ public function setBackupExisting($bool) { $this->backupExisting = $bool; } /** * @param string $type * @return string */ private function getType($type) { if (isset($this->typeAlias[$type])) { return $this->typeAlias[$type]; } return $type; } private function generateEntityNamespace(ClassMetadataInfo $metadata) { if ($this->hasNamespace($metadata)) { return 'namespace ' . $this->getNamespace($metadata) .';'; } } private function generateEntityClassName(ClassMetadataInfo $metadata) { return 'class ' . $this->getClassName($metadata) . ($this->extendsClass() ? ' extends ' . $this->getClassToExtendName() : null); } private function generateEntityBody(ClassMetadataInfo $metadata) { $fieldMappingProperties = $this->generateEntityFieldMappingProperties($metadata); $associationMappingProperties = $this->generateEntityAssociationMappingProperties($metadata); $stubMethods = $this->generateEntityStubMethods ? $this->generateEntityStubMethods($metadata) : null; $lifecycleCallbackMethods = $this->generateEntityLifecycleCallbackMethods($metadata); $code = array(); if ($fieldMappingProperties) { $code[] = $fieldMappingProperties; } if ($associationMappingProperties) { $code[] = $associationMappingProperties; } $code[] = $this->generateEntityConstructor($metadata); if ($stubMethods) { $code[] = $stubMethods; } if ($lifecycleCallbackMethods) { $code[] = $lifecycleCallbackMethods; } return implode("\n", $code); } private function generateEntityConstructor(ClassMetadataInfo $metadata) { if ($this->hasMethod('__construct', $metadata)) { return ''; } $collections = array(); foreach ($metadata->associationMappings as $mapping) { if ($mapping['type'] & ClassMetadataInfo::TO_MANY) { $collections[] = '$this->'.$mapping['fieldName'].' = new \Doctrine\Common\Collections\ArrayCollection();'; } } if ($collections) { return $this->prefixCodeWithSpaces(str_replace("", implode("\n".$this->spaces, $collections), self::$constructorMethodTemplate)); } return ''; } /** * @todo this won't work if there is a namespace in brackets and a class outside of it. * @param string $src */ private function parseTokensInEntityFile($src) { $tokens = token_get_all($src); $lastSeenNamespace = ""; $lastSeenClass = false; $inNamespace = false; $inClass = false; for ($i = 0; $i < count($tokens); $i++) { $token = $tokens[$i]; if (in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT))) { continue; } if ($inNamespace) { if ($token[0] == T_NS_SEPARATOR || $token[0] == T_STRING) { $lastSeenNamespace .= $token[1]; } else if (is_string($token) && in_array($token, array(';', '{'))) { $inNamespace = false; } } if ($inClass) { $inClass = false; $lastSeenClass = $lastSeenNamespace . ($lastSeenNamespace ? '\\' : '') . $token[1]; $this->staticReflection[$lastSeenClass]['properties'] = array(); $this->staticReflection[$lastSeenClass]['methods'] = array(); } if ($token[0] == T_NAMESPACE) { $lastSeenNamespace = ""; $inNamespace = true; } else if ($token[0] == T_CLASS) { $inClass = true; } else if ($token[0] == T_FUNCTION) { if ($tokens[$i+2][0] == T_STRING) { $this->staticReflection[$lastSeenClass]['methods'][] = $tokens[$i+2][1]; } else if ($tokens[$i+2] == "&" && $tokens[$i+3][0] == T_STRING) { $this->staticReflection[$lastSeenClass]['methods'][] = $tokens[$i+3][1]; } } else if (in_array($token[0], array(T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED)) && $tokens[$i+2][0] != T_FUNCTION) { $this->staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i+2][1], 1); } } } private function hasProperty($property, ClassMetadataInfo $metadata) { if ($this->extendsClass()) { // don't generate property if its already on the base class. $reflClass = new \ReflectionClass($this->getClassToExtend()); if ($reflClass->hasProperty($property)) { return true; } } return ( isset($this->staticReflection[$metadata->name]) && in_array($property, $this->staticReflection[$metadata->name]['properties']) ); } private function hasMethod($method, ClassMetadataInfo $metadata) { if ($this->extendsClass()) { // don't generate method if its already on the base class. $reflClass = new \ReflectionClass($this->getClassToExtend()); if ($reflClass->hasMethod($method)) { return true; } } return ( isset($this->staticReflection[$metadata->name]) && in_array($method, $this->staticReflection[$metadata->name]['methods']) ); } private function hasNamespace(ClassMetadataInfo $metadata) { return strpos($metadata->name, '\\') ? true : false; } private function extendsClass() { return $this->classToExtend ? true : false; } private function getClassToExtend() { return $this->classToExtend; } private function getClassToExtendName() { $refl = new \ReflectionClass($this->getClassToExtend()); return '\\' . $refl->getName(); } private function getClassName(ClassMetadataInfo $metadata) { return ($pos = strrpos($metadata->name, '\\')) ? substr($metadata->name, $pos + 1, strlen($metadata->name)) : $metadata->name; } private function getNamespace(ClassMetadataInfo $metadata) { return substr($metadata->name, 0, strrpos($metadata->name, '\\')); } private function generateEntityDocBlock(ClassMetadataInfo $metadata) { $lines = array(); $lines[] = '/**'; $lines[] = ' * ' . $this->getClassName($metadata); if ($this->generateAnnotations) { $lines[] = ' *'; $methods = array( 'generateTableAnnotation', 'generateInheritanceAnnotation', 'generateDiscriminatorColumnAnnotation', 'generateDiscriminatorMapAnnotation' ); foreach ($methods as $method) { if ($code = $this->$method($metadata)) { $lines[] = ' * ' . $code; } } if ($metadata->isMappedSuperclass) { $lines[] = ' * @' . $this->annotationsPrefix . 'MappedSuperClass'; } else { $lines[] = ' * @' . $this->annotationsPrefix . 'Entity'; } if ($metadata->customRepositoryClassName) { $lines[count($lines) - 1] .= '(repositoryClass="' . $metadata->customRepositoryClassName . '")'; } if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) { $lines[] = ' * @' . $this->annotationsPrefix . 'HasLifecycleCallbacks'; } } $lines[] = ' */'; return implode("\n", $lines); } private function generateTableAnnotation($metadata) { $table = array(); if (isset($metadata->table['schema'])) { $table[] = 'schema="' . $metadata->table['schema'] . '"'; } if (isset($metadata->table['name'])) { $table[] = 'name="' . $metadata->table['name'] . '"'; } if (isset($metadata->table['uniqueConstraints']) && $metadata->table['uniqueConstraints']) { $constraints = $this->generateTableConstraints('UniqueConstraint', $metadata->table['uniqueConstraints']); $table[] = 'uniqueConstraints={' . $constraints . '}'; } if (isset($metadata->table['indexes']) && $metadata->table['indexes']) { $constraints = $this->generateTableConstraints('Index', $metadata->table['indexes']); $table[] = 'indexes={' . $constraints . '}'; } return '@' . $this->annotationsPrefix . 'Table(' . implode(', ', $table) . ')'; } private function generateTableConstraints($constraintName, $constraints) { $annotations = array(); foreach ($constraints as $name => $constraint) { $columns = array(); foreach ($constraint['columns'] as $column) { $columns[] = '"' . $column . '"'; } $annotations[] = '@' . $this->annotationsPrefix . $constraintName . '(name="' . $name . '", columns={' . implode(', ', $columns) . '})'; } return implode(', ', $annotations); } private function generateInheritanceAnnotation($metadata) { if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) { return '@' . $this->annotationsPrefix . 'InheritanceType("'.$this->getInheritanceTypeString($metadata->inheritanceType).'")'; } } private function generateDiscriminatorColumnAnnotation($metadata) { if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) { $discrColumn = $metadata->discriminatorValue; $columnDefinition = 'name="' . $discrColumn['name'] . '", type="' . $discrColumn['type'] . '", length=' . $discrColumn['length']; return '@' . $this->annotationsPrefix . 'DiscriminatorColumn(' . $columnDefinition . ')'; } } private function generateDiscriminatorMapAnnotation($metadata) { if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) { $inheritanceClassMap = array(); foreach ($metadata->discriminatorMap as $type => $class) { $inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"'; } return '@' . $this->annotationsPrefix . 'DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})'; } } private function generateEntityStubMethods(ClassMetadataInfo $metadata) { $methods = array(); foreach ($metadata->fieldMappings as $fieldMapping) { if ( ! isset($fieldMapping['id']) || ! $fieldMapping['id'] || $metadata->generatorType == ClassMetadataInfo::GENERATOR_TYPE_NONE) { if ($code = $this->generateEntityStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'])) { $methods[] = $code; } } if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'])) { $methods[] = $code; } } foreach ($metadata->associationMappings as $associationMapping) { if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { $nullable = $this->isAssociationIsNullable($associationMapping) ? 'null' : null; if ($code = $this->generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) { $methods[] = $code; } if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) { $methods[] = $code; } } else if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) { if ($code = $this->generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) { $methods[] = $code; } if ($code = $this->generateEntityStubMethod($metadata, 'remove', $associationMapping['fieldName'], $associationMapping['targetEntity'])) { $methods[] = $code; } if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], 'Doctrine\Common\Collections\Collection')) { $methods[] = $code; } } } return implode("\n\n", $methods); } private function isAssociationIsNullable($associationMapping) { if (isset($associationMapping['id']) && $associationMapping['id']) { return false; } if (isset($associationMapping['joinColumns'])) { $joinColumns = $associationMapping['joinColumns']; } else { //@todo thereis no way to retreive targetEntity metadata $joinColumns = array(); } foreach ($joinColumns as $joinColumn) { if(isset($joinColumn['nullable']) && !$joinColumn['nullable']) { return false; } } return true; } private function generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata) { if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) { $methods = array(); foreach ($metadata->lifecycleCallbacks as $name => $callbacks) { foreach ($callbacks as $callback) { if ($code = $this->generateLifecycleCallbackMethod($name, $callback, $metadata)) { $methods[] = $code; } } } return implode("\n\n", $methods); } return ""; } private function generateEntityAssociationMappingProperties(ClassMetadataInfo $metadata) { $lines = array(); foreach ($metadata->associationMappings as $associationMapping) { if ($this->hasProperty($associationMapping['fieldName'], $metadata)) { continue; } $lines[] = $this->generateAssociationMappingPropertyDocBlock($associationMapping, $metadata); $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $associationMapping['fieldName'] . ($associationMapping['type'] == 'manyToMany' ? ' = array()' : null) . ";\n"; } return implode("\n", $lines); } private function generateEntityFieldMappingProperties(ClassMetadataInfo $metadata) { $lines = array(); foreach ($metadata->fieldMappings as $fieldMapping) { if ($this->hasProperty($fieldMapping['fieldName'], $metadata) || $metadata->isInheritedField($fieldMapping['fieldName'])) { continue; } $lines[] = $this->generateFieldMappingPropertyDocBlock($fieldMapping, $metadata); $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldMapping['fieldName'] . (isset($fieldMapping['default']) ? ' = ' . var_export($fieldMapping['default'], true) : null) . ";\n"; } return implode("\n", $lines); } private function generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null, $defaultValue = null) { $methodName = $type . Inflector::classify($fieldName); if (in_array($type, array("add", "remove")) && substr($methodName, -1) == "s") { $methodName = substr($methodName, 0, -1); } if ($this->hasMethod($methodName, $metadata)) { return; } $this->staticReflection[$metadata->name]['methods'][] = $methodName; $var = sprintf('%sMethodTemplate', $type); $template = self::$$var; $methodTypeHint = null; $types = Type::getTypesMap(); $variableType = $typeHint ? $this->getType($typeHint) . ' ' : null; if ($typeHint && ! isset($types[$typeHint])) { $variableType = '\\' . ltrim($variableType, '\\'); $methodTypeHint = '\\' . $typeHint . ' '; } $replacements = array( '' => ucfirst($type) . ' ' . $fieldName, '' => $methodTypeHint, '' => $variableType, '' => Inflector::camelize($fieldName), '' => $methodName, '' => $fieldName, '' => ($defaultValue !== null ) ? (' = '.$defaultValue) : '', '' => $this->getClassName($metadata) ); $method = str_replace( array_keys($replacements), array_values($replacements), $template ); return $this->prefixCodeWithSpaces($method); } private function generateLifecycleCallbackMethod($name, $methodName, $metadata) { if ($this->hasMethod($methodName, $metadata)) { return; } $this->staticReflection[$metadata->name]['methods'][] = $methodName; $replacements = array( '' => $this->annotationsPrefix . ucfirst($name), '' => $methodName, ); $method = str_replace( array_keys($replacements), array_values($replacements), self::$lifecycleCallbackMethodTemplate ); return $this->prefixCodeWithSpaces($method); } private function generateJoinColumnAnnotation(array $joinColumn) { $joinColumnAnnot = array(); if (isset($joinColumn['name'])) { $joinColumnAnnot[] = 'name="' . $joinColumn['name'] . '"'; } if (isset($joinColumn['referencedColumnName'])) { $joinColumnAnnot[] = 'referencedColumnName="' . $joinColumn['referencedColumnName'] . '"'; } if (isset($joinColumn['unique']) && $joinColumn['unique']) { $joinColumnAnnot[] = 'unique=' . ($joinColumn['unique'] ? 'true' : 'false'); } if (isset($joinColumn['nullable'])) { $joinColumnAnnot[] = 'nullable=' . ($joinColumn['nullable'] ? 'true' : 'false'); } if (isset($joinColumn['onDelete'])) { $joinColumnAnnot[] = 'onDelete="' . ($joinColumn['onDelete'] . '"'); } if (isset($joinColumn['columnDefinition'])) { $joinColumnAnnot[] = 'columnDefinition="' . $joinColumn['columnDefinition'] . '"'; } return '@' . $this->annotationsPrefix . 'JoinColumn(' . implode(', ', $joinColumnAnnot) . ')'; } private function generateAssociationMappingPropertyDocBlock(array $associationMapping, ClassMetadataInfo $metadata) { $lines = array(); $lines[] = $this->spaces . '/**'; if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) { $lines[] = $this->spaces . ' * @var \Doctrine\Common\Collections\Collection'; } else { $lines[] = $this->spaces . ' * @var \\' . ltrim($associationMapping['targetEntity'], '\\'); } if ($this->generateAnnotations) { $lines[] = $this->spaces . ' *'; if (isset($associationMapping['id']) && $associationMapping['id']) { $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id'; if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) { $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")'; } } $type = null; switch ($associationMapping['type']) { case ClassMetadataInfo::ONE_TO_ONE: $type = 'OneToOne'; break; case ClassMetadataInfo::MANY_TO_ONE: $type = 'ManyToOne'; break; case ClassMetadataInfo::ONE_TO_MANY: $type = 'OneToMany'; break; case ClassMetadataInfo::MANY_TO_MANY: $type = 'ManyToMany'; break; } $typeOptions = array(); if (isset($associationMapping['targetEntity'])) { $typeOptions[] = 'targetEntity="' . $associationMapping['targetEntity'] . '"'; } if (isset($associationMapping['inversedBy'])) { $typeOptions[] = 'inversedBy="' . $associationMapping['inversedBy'] . '"'; } if (isset($associationMapping['mappedBy'])) { $typeOptions[] = 'mappedBy="' . $associationMapping['mappedBy'] . '"'; } if ($associationMapping['cascade']) { $cascades = array(); if ($associationMapping['isCascadePersist']) $cascades[] = '"persist"'; if ($associationMapping['isCascadeRemove']) $cascades[] = '"remove"'; if ($associationMapping['isCascadeDetach']) $cascades[] = '"detach"'; if ($associationMapping['isCascadeMerge']) $cascades[] = '"merge"'; if ($associationMapping['isCascadeRefresh']) $cascades[] = '"refresh"'; $typeOptions[] = 'cascade={' . implode(',', $cascades) . '}'; } if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']) { $typeOptions[] = 'orphanRemoval=' . ($associationMapping['orphanRemoval'] ? 'true' : 'false'); } $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . '' . $type . '(' . implode(', ', $typeOptions) . ')'; if (isset($associationMapping['joinColumns']) && $associationMapping['joinColumns']) { $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinColumns({'; $joinColumnsLines = array(); foreach ($associationMapping['joinColumns'] as $joinColumn) { if ($joinColumnAnnot = $this->generateJoinColumnAnnotation($joinColumn)) { $joinColumnsLines[] = $this->spaces . ' * ' . $joinColumnAnnot; } } $lines[] = implode(",\n", $joinColumnsLines); $lines[] = $this->spaces . ' * })'; } if (isset($associationMapping['joinTable']) && $associationMapping['joinTable']) { $joinTable = array(); $joinTable[] = 'name="' . $associationMapping['joinTable']['name'] . '"'; if (isset($associationMapping['joinTable']['schema'])) { $joinTable[] = 'schema="' . $associationMapping['joinTable']['schema'] . '"'; } $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinTable(' . implode(', ', $joinTable) . ','; $lines[] = $this->spaces . ' * joinColumns={'; $joinColumnsLines = array(); foreach ($associationMapping['joinTable']['joinColumns'] as $joinColumn) { $joinColumnsLines[] = $this->spaces . ' * ' . $this->generateJoinColumnAnnotation($joinColumn); } $lines[] = implode(",". PHP_EOL, $joinColumnsLines); $lines[] = $this->spaces . ' * },'; $lines[] = $this->spaces . ' * inverseJoinColumns={'; $inverseJoinColumnsLines = array(); foreach ($associationMapping['joinTable']['inverseJoinColumns'] as $joinColumn) { $inverseJoinColumnsLines[] = $this->spaces . ' * ' . $this->generateJoinColumnAnnotation($joinColumn); } $lines[] = implode(",". PHP_EOL, $inverseJoinColumnsLines); $lines[] = $this->spaces . ' * }'; $lines[] = $this->spaces . ' * )'; } if (isset($associationMapping['orderBy'])) { $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'OrderBy({'; foreach ($associationMapping['orderBy'] as $name => $direction) { $lines[] = $this->spaces . ' * "' . $name . '"="' . $direction . '",'; } $lines[count($lines) - 1] = substr($lines[count($lines) - 1], 0, strlen($lines[count($lines) - 1]) - 1); $lines[] = $this->spaces . ' * })'; } } $lines[] = $this->spaces . ' */'; return implode("\n", $lines); } private function generateFieldMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata) { $lines = array(); $lines[] = $this->spaces . '/**'; $lines[] = $this->spaces . ' * @var ' . $this->getType($fieldMapping['type']); if ($this->generateAnnotations) { $lines[] = $this->spaces . ' *'; $column = array(); if (isset($fieldMapping['columnName'])) { $column[] = 'name="' . $fieldMapping['columnName'] . '"'; } if (isset($fieldMapping['type'])) { $column[] = 'type="' . $fieldMapping['type'] . '"'; } if (isset($fieldMapping['length'])) { $column[] = 'length=' . $fieldMapping['length']; } if (isset($fieldMapping['precision'])) { $column[] = 'precision=' . $fieldMapping['precision']; } if (isset($fieldMapping['scale'])) { $column[] = 'scale=' . $fieldMapping['scale']; } if (isset($fieldMapping['nullable'])) { $column[] = 'nullable=' . var_export($fieldMapping['nullable'], true); } if (isset($fieldMapping['columnDefinition'])) { $column[] = 'columnDefinition="' . $fieldMapping['columnDefinition'] . '"'; } if (isset($fieldMapping['unique'])) { $column[] = 'unique=' . var_export($fieldMapping['unique'], true); } $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Column(' . implode(', ', $column) . ')'; if (isset($fieldMapping['id']) && $fieldMapping['id']) { $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id'; if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) { $lines[] = $this->spaces.' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")'; } if ($metadata->sequenceGeneratorDefinition) { $sequenceGenerator = array(); if (isset($metadata->sequenceGeneratorDefinition['sequenceName'])) { $sequenceGenerator[] = 'sequenceName="' . $metadata->sequenceGeneratorDefinition['sequenceName'] . '"'; } if (isset($metadata->sequenceGeneratorDefinition['allocationSize'])) { $sequenceGenerator[] = 'allocationSize=' . $metadata->sequenceGeneratorDefinition['allocationSize']; } if (isset($metadata->sequenceGeneratorDefinition['initialValue'])) { $sequenceGenerator[] = 'initialValue=' . $metadata->sequenceGeneratorDefinition['initialValue']; } $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')'; } } if (isset($fieldMapping['version']) && $fieldMapping['version']) { $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Version'; } } $lines[] = $this->spaces . ' */'; return implode("\n", $lines); } private function prefixCodeWithSpaces($code, $num = 1) { $lines = explode("\n", $code); foreach ($lines as $key => $value) { $lines[$key] = str_repeat($this->spaces, $num) . $lines[$key]; } return implode("\n", $lines); } /** * @param integer $type The inheritance type used by the class and it's subclasses. * @return string The literal string for the inheritance type. * @throws \InvalidArgumentException When the inheritance type does not exists. */ protected function getInheritanceTypeString($type) { if ( ! isset(self::$inheritanceTypeMap[$type])) { throw new \InvalidArgumentException(sprintf('Invalid provided InheritanceType: %s', $type)); } return self::$inheritanceTypeMap[$type]; } /** * @param integer $type The policy used for change-tracking for the mapped class. * @return string The literal string for the change-tracking type. * @throws \InvalidArgumentException When the change-tracking type does not exists. */ protected function getChangeTrackingPolicyString($type) { if ( ! isset(self::$changeTrackingPolicyMap[$type])) { throw new \InvalidArgumentException(sprintf('Invalid provided ChangeTrackingPolicy: %s', $type)); } return self::$changeTrackingPolicyMap[$type]; } /** * @param integer $type The generator to use for the mapped class. * @return string The literal string for the generetor type. * @throws \InvalidArgumentException When the generator type does not exists. */ protected function getIdGeneratorTypeString($type) { if ( ! isset(self::$generatorStrategyMap[$type])) { throw new \InvalidArgumentException(sprintf('Invalid provided IdGeneratorType: %s', $type)); } return self::$generatorStrategyMap[$type]; } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/EntityRepositoryGenerator.php0000644000175100017510000000511712143374607025225 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools; /** * Class to generate entity repository classes * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class EntityRepositoryGenerator { protected static $_template = '; use Doctrine\ORM\EntityRepository; /** * * * This class was generated by the Doctrine ORM. Add your own custom * repository methods below. */ class extends EntityRepository { } '; public function generateEntityRepositoryClass($fullClassName) { $namespace = substr($fullClassName, 0, strrpos($fullClassName, '\\')); $className = substr($fullClassName, strrpos($fullClassName, '\\') + 1, strlen($fullClassName)); $variables = array( '' => $namespace, '' => $className ); return str_replace(array_keys($variables), array_values($variables), self::$_template); } public function writeEntityRepositoryClass($fullClassName, $outputDirectory) { $code = $this->generateEntityRepositoryClass($fullClassName); $path = $outputDirectory . DIRECTORY_SEPARATOR . str_replace('\\', \DIRECTORY_SEPARATOR, $fullClassName) . '.php'; $dir = dirname($path); if ( ! is_dir($dir)) { mkdir($dir, 0777, true); } if ( ! file_exists($path)) { file_put_contents($path, $code); } } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/ResolveTargetEntityListener.php0000644000175100017510000000632212143374607025472 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools; use Doctrine\ORM\Event\LoadClassMetadataEventArgs; use Doctrine\ORM\Mapping\ClassMetadata; /** * ResolveTargetEntityListener * * Mechanism to overwrite interfaces or classes specified as association * targets. * * @author Benjamin Eberlei * @since 2.2 */ class ResolveTargetEntityListener { /** * @var array */ private $resolveTargetEntities = array(); /** * Add a target-entity class name to resolve to a new class name. * * @param string $originalEntity * @param string $newEntity * @param array $mapping * @return void */ public function addResolveTargetEntity($originalEntity, $newEntity, array $mapping) { $mapping['targetEntity'] = ltrim($newEntity, "\\"); $this->resolveTargetEntities[ltrim($originalEntity, "\\")] = $mapping; } /** * Process event and resolve new target entity names. * * @param LoadClassMetadataEventArgs $args * @return void */ public function loadClassMetadata(LoadClassMetadataEventArgs $args) { $cm = $args->getClassMetadata(); foreach ($cm->associationMappings as $mapping) { if (isset($this->resolveTargetEntities[$mapping['targetEntity']])) { $this->remapAssociation($cm, $mapping); } } } private function remapAssociation($classMetadata, $mapping) { $newMapping = $this->resolveTargetEntities[$mapping['targetEntity']]; $newMapping = array_replace_recursive($mapping, $newMapping); $newMapping['fieldName'] = $mapping['fieldName']; unset($classMetadata->associationMappings[$mapping['fieldName']]); switch ($mapping['type']) { case ClassMetadata::MANY_TO_MANY: $classMetadata->mapManyToMany($newMapping); break; case ClassMetadata::MANY_TO_ONE: $classMetadata->mapManyToOne($newMapping); break; case ClassMetadata::ONE_TO_MANY: $classMetadata->mapOneToMany($newMapping); break; case ClassMetadata::ONE_TO_ONE: $classMetadata->mapOneToOne($newMapping); break; } } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/SchemaTool.php0000644000175100017510000007177512143374607022055 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools; use Doctrine\ORM\ORMException, Doctrine\DBAL\Types\Type, Doctrine\DBAL\Schema\Schema, Doctrine\DBAL\Schema\Visitor\RemoveNamespacedAssets, Doctrine\ORM\EntityManager, Doctrine\ORM\Mapping\ClassMetadata, Doctrine\ORM\Internal\CommitOrderCalculator, Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs, Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; /** * The SchemaTool is a tool to create/drop/update database schemas based on * ClassMetadata class descriptors. * * * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Benjamin Eberlei */ class SchemaTool { /** * @var \Doctrine\ORM\EntityManager */ private $em; /** * @var \Doctrine\DBAL\Platforms\AbstractPlatform */ private $platform; /** * The quote strategy. * * @var \Doctrine\ORM\Mapping\QuoteStrategy */ private $quoteStrategy; /** * Initializes a new SchemaTool instance that uses the connection of the * provided EntityManager. * * @param \Doctrine\ORM\EntityManager $em */ public function __construct(EntityManager $em) { $this->em = $em; $this->platform = $em->getConnection()->getDatabasePlatform(); $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); } /** * Creates the database schema for the given array of ClassMetadata instances. * * @throws ToolsException * @param array $classes * @return void */ public function createSchema(array $classes) { $createSchemaSql = $this->getCreateSchemaSql($classes); $conn = $this->em->getConnection(); foreach ($createSchemaSql as $sql) { try { $conn->executeQuery($sql); } catch(\Exception $e) { throw ToolsException::schemaToolFailure($sql, $e); } } } /** * Gets the list of DDL statements that are required to create the database schema for * the given list of ClassMetadata instances. * * @param array $classes * @return array $sql The SQL statements needed to create the schema for the classes. */ public function getCreateSchemaSql(array $classes) { $schema = $this->getSchemaFromMetadata($classes); return $schema->toSql($this->platform); } /** * Some instances of ClassMetadata don't need to be processed in the SchemaTool context. This method detects them. * * @param ClassMetadata $class * @param array $processedClasses * @return bool */ private function processingNotRequired($class, array $processedClasses) { return ( isset($processedClasses[$class->name]) || $class->isMappedSuperclass || ($class->isInheritanceTypeSingleTable() && $class->name != $class->rootEntityName) ); } /** * From a given set of metadata classes this method creates a Schema instance. * * @param array $classes * @return Schema */ public function getSchemaFromMetadata(array $classes) { // Reminder for processed classes, used for hierarchies $processedClasses = array(); $eventManager = $this->em->getEventManager(); $schemaManager = $this->em->getConnection()->getSchemaManager(); $metadataSchemaConfig = $schemaManager->createSchemaConfig(); $metadataSchemaConfig->setExplicitForeignKeyIndexes(false); $schema = new Schema(array(), array(), $metadataSchemaConfig); foreach ($classes as $class) { if ($this->processingNotRequired($class, $processedClasses)) { continue; } $table = $schema->createTable($this->quoteStrategy->getTableName($class, $this->platform)); $columns = array(); // table columns if ($class->isInheritanceTypeSingleTable()) { $columns = $this->_gatherColumns($class, $table); $this->_gatherRelationsSql($class, $table, $schema); // Add the discriminator column $this->addDiscriminatorColumnDefinition($class, $table); // Aggregate all the information from all classes in the hierarchy foreach ($class->parentClasses as $parentClassName) { // Parent class information is already contained in this class $processedClasses[$parentClassName] = true; } foreach ($class->subClasses as $subClassName) { $subClass = $this->em->getClassMetadata($subClassName); $this->_gatherColumns($subClass, $table); $this->_gatherRelationsSql($subClass, $table, $schema); $processedClasses[$subClassName] = true; } } else if ($class->isInheritanceTypeJoined()) { // Add all non-inherited fields as columns $pkColumns = array(); foreach ($class->fieldMappings as $fieldName => $mapping) { if ( ! isset($mapping['inherited'])) { $columnName = $this->quoteStrategy->getColumnName($mapping['fieldName'], $class, $this->platform); $this->_gatherColumn($class, $mapping, $table); if ($class->isIdentifier($fieldName)) { $pkColumns[] = $columnName; } } } $this->_gatherRelationsSql($class, $table, $schema); // Add the discriminator column only to the root table if ($class->name == $class->rootEntityName) { $this->addDiscriminatorColumnDefinition($class, $table); } else { // Add an ID FK column to child tables /* @var \Doctrine\ORM\Mapping\ClassMetadata $class */ $idMapping = $class->fieldMappings[$class->identifier[0]]; $this->_gatherColumn($class, $idMapping, $table); $columnName = $this->quoteStrategy->getColumnName($class->identifier[0], $class, $this->platform); // TODO: This seems rather hackish, can we optimize it? $table->getColumn($columnName)->setAutoincrement(false); $pkColumns[] = $columnName; // Add a FK constraint on the ID column $table->addUnnamedForeignKeyConstraint( $this->quoteStrategy->getTableName($this->em->getClassMetadata($class->rootEntityName), $this->platform), array($columnName), array($columnName), array('onDelete' => 'CASCADE') ); } $table->setPrimaryKey($pkColumns); } else if ($class->isInheritanceTypeTablePerClass()) { throw ORMException::notSupported(); } else { $this->_gatherColumns($class, $table); $this->_gatherRelationsSql($class, $table, $schema); } $pkColumns = array(); foreach ($class->identifier as $identifierField) { if (isset($class->fieldMappings[$identifierField])) { $pkColumns[] = $this->quoteStrategy->getColumnName($identifierField, $class, $this->platform); } else if (isset($class->associationMappings[$identifierField])) { /* @var $assoc \Doctrine\ORM\Mapping\OneToOne */ $assoc = $class->associationMappings[$identifierField]; foreach ($assoc['joinColumns'] as $joinColumn) { $pkColumns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } } } if ( ! $table->hasIndex('primary')) { $table->setPrimaryKey($pkColumns); } if (isset($class->table['indexes'])) { foreach ($class->table['indexes'] as $indexName => $indexData) { $table->addIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName); } } if (isset($class->table['uniqueConstraints'])) { foreach ($class->table['uniqueConstraints'] as $indexName => $indexData) { $table->addUniqueIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName); } } if (isset($class->table['options'])) { foreach ($class->table['options'] as $key => $val) { $table->addOption($key, $val); } } $processedClasses[$class->name] = true; if ($class->isIdGeneratorSequence() && $class->name == $class->rootEntityName) { $seqDef = $class->sequenceGeneratorDefinition; $quotedName = $this->quoteStrategy->getSequenceName($seqDef, $class, $this->platform); if ( ! $schema->hasSequence($quotedName)) { $schema->createSequence( $quotedName, $seqDef['allocationSize'], $seqDef['initialValue'] ); } } if ($eventManager->hasListeners(ToolEvents::postGenerateSchemaTable)) { $eventManager->dispatchEvent(ToolEvents::postGenerateSchemaTable, new GenerateSchemaTableEventArgs($class, $schema, $table)); } } if ( ! $this->platform->supportsSchemas() && ! $this->platform->canEmulateSchemas() ) { $schema->visit(new RemoveNamespacedAssets()); } if ($eventManager->hasListeners(ToolEvents::postGenerateSchema)) { $eventManager->dispatchEvent(ToolEvents::postGenerateSchema, new GenerateSchemaEventArgs($this->em, $schema)); } return $schema; } /** * Gets a portable column definition as required by the DBAL for the discriminator * column of a class. * * @param ClassMetadata $class * @return array The portable column definition of the discriminator column as required by * the DBAL. */ private function addDiscriminatorColumnDefinition($class, $table) { $discrColumn = $class->discriminatorColumn; if ( ! isset($discrColumn['type']) || (strtolower($discrColumn['type']) == 'string' && $discrColumn['length'] === null)) { $discrColumn['type'] = 'string'; $discrColumn['length'] = 255; } $options = array( 'length' => isset($discrColumn['length']) ? $discrColumn['length'] : null, 'notnull' => true ); if (isset($discrColumn['columnDefinition'])) { $options['columnDefinition'] = $discrColumn['columnDefinition']; } $table->addColumn($discrColumn['name'], $discrColumn['type'], $options); } /** * Gathers the column definitions as required by the DBAL of all field mappings * found in the given class. * * @param ClassMetadata $class * @param Table $table * @return array The list of portable column definitions as required by the DBAL. */ private function _gatherColumns($class, $table) { $columns = array(); $pkColumns = array(); foreach ($class->fieldMappings as $fieldName => $mapping) { if ($class->isInheritanceTypeSingleTable() && isset($mapping['inherited'])) { continue; } $column = $this->_gatherColumn($class, $mapping, $table); if ($class->isIdentifier($mapping['fieldName'])) { $pkColumns[] = $this->quoteStrategy->getColumnName($mapping['fieldName'], $class, $this->platform); } } // For now, this is a hack required for single table inheritence, since this method is called // twice by single table inheritence relations if(!$table->hasIndex('primary')) { //$table->setPrimaryKey($pkColumns); } return $columns; } /** * Creates a column definition as required by the DBAL from an ORM field mapping definition. * * @param ClassMetadata $class The class that owns the field mapping. * @param array $mapping The field mapping. * @param Table $table * @return array The portable column definition as required by the DBAL. */ private function _gatherColumn($class, array $mapping, $table) { $columnName = $this->quoteStrategy->getColumnName($mapping['fieldName'], $class, $this->platform); $columnType = $mapping['type']; $options = array(); $options['length'] = isset($mapping['length']) ? $mapping['length'] : null; $options['notnull'] = isset($mapping['nullable']) ? ! $mapping['nullable'] : true; if ($class->isInheritanceTypeSingleTable() && count($class->parentClasses) > 0) { $options['notnull'] = false; } $options['platformOptions'] = array(); $options['platformOptions']['version'] = $class->isVersioned && $class->versionField == $mapping['fieldName'] ? true : false; if(strtolower($columnType) == 'string' && $options['length'] === null) { $options['length'] = 255; } if (isset($mapping['precision'])) { $options['precision'] = $mapping['precision']; } if (isset($mapping['scale'])) { $options['scale'] = $mapping['scale']; } if (isset($mapping['default'])) { $options['default'] = $mapping['default']; } if (isset($mapping['columnDefinition'])) { $options['columnDefinition'] = $mapping['columnDefinition']; } if (isset($mapping['options'])) { if (isset($mapping['options']['comment'])) { $options['comment'] = $mapping['options']['comment']; unset($mapping['options']['comment']); } if (isset($mapping['options']['unsigned'])) { $options['unsigned'] = $mapping['options']['unsigned']; unset($mapping['options']['unsigned']); } if (isset($mapping['options']['fixed'])) { $options['fixed'] = $mapping['options']['fixed']; unset($mapping['options']['fixed']); } $options['customSchemaOptions'] = $mapping['options']; } if ($class->isIdGeneratorIdentity() && $class->getIdentifierFieldNames() == array($mapping['fieldName'])) { $options['autoincrement'] = true; } if ($class->isInheritanceTypeJoined() && $class->name != $class->rootEntityName) { $options['autoincrement'] = false; } if ($table->hasColumn($columnName)) { // required in some inheritance scenarios $table->changeColumn($columnName, $options); } else { $table->addColumn($columnName, $columnType, $options); } $isUnique = isset($mapping['unique']) ? $mapping['unique'] : false; if ($isUnique) { $table->addUniqueIndex(array($columnName)); } } /** * Gathers the SQL for properly setting up the relations of the given class. * This includes the SQL for foreign key constraints and join tables. * * @param ClassMetadata $class * @param \Doctrine\DBAL\Schema\Table $table * @param \Doctrine\DBAL\Schema\Schema $schema * @return void */ private function _gatherRelationsSql($class, $table, $schema) { foreach ($class->associationMappings as $fieldName => $mapping) { if (isset($mapping['inherited'])) { continue; } $foreignClass = $this->em->getClassMetadata($mapping['targetEntity']); if ($mapping['type'] & ClassMetadata::TO_ONE && $mapping['isOwningSide']) { $primaryKeyColumns = $uniqueConstraints = array(); // PK is unnecessary for this relation-type $this->_gatherRelationJoinColumns($mapping['joinColumns'], $table, $foreignClass, $mapping, $primaryKeyColumns, $uniqueConstraints); foreach($uniqueConstraints as $indexName => $unique) { $table->addUniqueIndex($unique['columns'], is_numeric($indexName) ? null : $indexName); } } else if ($mapping['type'] == ClassMetadata::ONE_TO_MANY && $mapping['isOwningSide']) { //... create join table, one-many through join table supported later throw ORMException::notSupported(); } else if ($mapping['type'] == ClassMetadata::MANY_TO_MANY && $mapping['isOwningSide']) { // create join table $joinTable = $mapping['joinTable']; $theJoinTable = $schema->createTable($this->quoteStrategy->getJoinTableName($mapping, $foreignClass, $this->platform)); $primaryKeyColumns = $uniqueConstraints = array(); // Build first FK constraint (relation table => source table) $this->_gatherRelationJoinColumns($joinTable['joinColumns'], $theJoinTable, $class, $mapping, $primaryKeyColumns, $uniqueConstraints); // Build second FK constraint (relation table => target table) $this->_gatherRelationJoinColumns($joinTable['inverseJoinColumns'], $theJoinTable, $foreignClass, $mapping, $primaryKeyColumns, $uniqueConstraints); $theJoinTable->setPrimaryKey($primaryKeyColumns); foreach($uniqueConstraints as $indexName => $unique) { $theJoinTable->addUniqueIndex($unique['columns'], is_numeric($indexName) ? null : $indexName); } } } } /** * Get the class metadata that is responsible for the definition of the referenced column name. * * Previously this was a simple task, but with DDC-117 this problem is actually recursive. If its * not a simple field, go through all identifier field names that are associations recursivly and * find that referenced column name. * * TODO: Is there any way to make this code more pleasing? * * @param ClassMetadata $class * @param string $referencedColumnName * @return array(ClassMetadata, referencedFieldName) */ private function getDefiningClass($class, $referencedColumnName) { $referencedFieldName = $class->getFieldName($referencedColumnName); if ($class->hasField($referencedFieldName)) { return array($class, $referencedFieldName); } else if (in_array($referencedColumnName, $class->getIdentifierColumnNames())) { // it seems to be an entity as foreign key foreach ($class->getIdentifierFieldNames() as $fieldName) { if ($class->hasAssociation($fieldName) && $class->getSingleAssociationJoinColumnName($fieldName) == $referencedColumnName) { return $this->getDefiningClass( $this->em->getClassMetadata($class->associationMappings[$fieldName]['targetEntity']), $class->getSingleAssociationReferencedJoinColumnName($fieldName) ); } } } return null; } /** * Gather columns and fk constraints that are required for one part of relationship. * * @param array $joinColumns * @param \Doctrine\DBAL\Schema\Table $theJoinTable * @param ClassMetadata $class * @param array $mapping * @param array $primaryKeyColumns * @param array $uniqueConstraints */ private function _gatherRelationJoinColumns($joinColumns, $theJoinTable, $class, $mapping, &$primaryKeyColumns, &$uniqueConstraints) { $localColumns = array(); $foreignColumns = array(); $fkOptions = array(); $foreignTableName = $this->quoteStrategy->getTableName($class, $this->platform); foreach ($joinColumns as $joinColumn) { list($definingClass, $referencedFieldName) = $this->getDefiningClass($class, $joinColumn['referencedColumnName']); if ( ! $definingClass) { throw new \Doctrine\ORM\ORMException( "Column name `".$joinColumn['referencedColumnName']."` referenced for relation from ". $mapping['sourceEntity'] . " towards ". $mapping['targetEntity'] . " does not exist." ); } $quotedColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); $quotedRefColumnName = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $class, $this->platform); $primaryKeyColumns[] = $quotedColumnName; $localColumns[] = $quotedColumnName; $foreignColumns[] = $quotedRefColumnName; if ( ! $theJoinTable->hasColumn($quotedColumnName)) { // Only add the column to the table if it does not exist already. // It might exist already if the foreign key is mapped into a regular // property as well. $fieldMapping = $definingClass->getFieldMapping($referencedFieldName); $columnDef = null; if (isset($joinColumn['columnDefinition'])) { $columnDef = $joinColumn['columnDefinition']; } else if (isset($fieldMapping['columnDefinition'])) { $columnDef = $fieldMapping['columnDefinition']; } $columnOptions = array('notnull' => false, 'columnDefinition' => $columnDef); if (isset($joinColumn['nullable'])) { $columnOptions['notnull'] = !$joinColumn['nullable']; } if (isset($fieldMapping['options'])) { $columnOptions['options'] = $fieldMapping['options']; } if ($fieldMapping['type'] == "string" && isset($fieldMapping['length'])) { $columnOptions['length'] = $fieldMapping['length']; } else if ($fieldMapping['type'] == "decimal") { $columnOptions['scale'] = $fieldMapping['scale']; $columnOptions['precision'] = $fieldMapping['precision']; } $theJoinTable->addColumn($quotedColumnName, $fieldMapping['type'], $columnOptions); } if (isset($joinColumn['unique']) && $joinColumn['unique'] == true) { $uniqueConstraints[] = array('columns' => array($quotedColumnName)); } if (isset($joinColumn['onDelete'])) { $fkOptions['onDelete'] = $joinColumn['onDelete']; } } $theJoinTable->addUnnamedForeignKeyConstraint( $foreignTableName, $localColumns, $foreignColumns, $fkOptions ); } /** * Drops the database schema for the given classes. * * In any way when an exception is thrown it is supressed since drop was * issued for all classes of the schema and some probably just don't exist. * * @param array $classes * @return void */ public function dropSchema(array $classes) { $dropSchemaSql = $this->getDropSchemaSQL($classes); $conn = $this->em->getConnection(); foreach ($dropSchemaSql as $sql) { try { $conn->executeQuery($sql); } catch(\Exception $e) { } } } /** * Drops all elements in the database of the current connection. * * @return void */ public function dropDatabase() { $dropSchemaSql = $this->getDropDatabaseSQL(); $conn = $this->em->getConnection(); foreach ($dropSchemaSql as $sql) { $conn->executeQuery($sql); } } /** * Gets the SQL needed to drop the database schema for the connections database. * * @return array */ public function getDropDatabaseSQL() { $sm = $this->em->getConnection()->getSchemaManager(); $schema = $sm->createSchema(); $visitor = new \Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector($this->platform); /* @var $schema \Doctrine\DBAL\Schema\Schema */ $schema->visit($visitor); return $visitor->getQueries(); } /** * Get SQL to drop the tables defined by the passed classes. * * @param array $classes * @return array */ public function getDropSchemaSQL(array $classes) { $visitor = new \Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector($this->platform); $schema = $this->getSchemaFromMetadata($classes); $sm = $this->em->getConnection()->getSchemaManager(); $fullSchema = $sm->createSchema(); foreach ($fullSchema->getTables() as $table) { if ( ! $schema->hasTable($table->getName())) { foreach ($table->getForeignKeys() as $foreignKey) { /* @var $foreignKey \Doctrine\DBAL\Schema\ForeignKeyConstraint */ if ($schema->hasTable($foreignKey->getForeignTableName())) { $visitor->acceptForeignKey($table, $foreignKey); } } } else { $visitor->acceptTable($table); foreach ($table->getForeignKeys() as $foreignKey) { $visitor->acceptForeignKey($table, $foreignKey); } } } if ($this->platform->supportsSequences()) { foreach ($schema->getSequences() as $sequence) { $visitor->acceptSequence($sequence); } foreach ($schema->getTables() as $table) { /* @var $sequence Table */ if ($table->hasPrimaryKey()) { $columns = $table->getPrimaryKey()->getColumns(); if (count($columns) == 1) { $checkSequence = $table->getName() . "_" . $columns[0] . "_seq"; if ($fullSchema->hasSequence($checkSequence)) { $visitor->acceptSequence($fullSchema->getSequence($checkSequence)); } } } } } return $visitor->getQueries(); } /** * Updates the database schema of the given classes by comparing the ClassMetadata * instances to the current database schema that is inspected. If $saveMode is set * to true the command is executed in the Database, else SQL is returned. * * @param array $classes * @param boolean $saveMode * @return void */ public function updateSchema(array $classes, $saveMode=false) { $updateSchemaSql = $this->getUpdateSchemaSql($classes, $saveMode); $conn = $this->em->getConnection(); foreach ($updateSchemaSql as $sql) { $conn->executeQuery($sql); } } /** * Gets the sequence of SQL statements that need to be performed in order * to bring the given class mappings in-synch with the relational schema. * If $saveMode is set to true the command is executed in the Database, * else SQL is returned. * * @param array $classes The classes to consider. * @param boolean $saveMode True for writing to DB, false for SQL string * @return array The sequence of SQL statements. */ public function getUpdateSchemaSql(array $classes, $saveMode=false) { $sm = $this->em->getConnection()->getSchemaManager(); $fromSchema = $sm->createSchema(); $toSchema = $this->getSchemaFromMetadata($classes); $comparator = new \Doctrine\DBAL\Schema\Comparator(); $schemaDiff = $comparator->compare($fromSchema, $toSchema); if ($saveMode) { return $schemaDiff->toSaveSql($this->platform); } else { return $schemaDiff->toSql($this->platform); } } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/SchemaValidator.php0000644000175100017510000003544612143374607023060 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\DBAL\Types\Type; /** * Performs strict validation of the mapping schema * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class SchemaValidator { /** * @var EntityManager */ private $em; /** * @param EntityManager $em */ public function __construct(EntityManager $em) { $this->em = $em; } /** * Checks the internal consistency of all mapping files. * * There are several checks that can't be done at runtime or are too expensive, which can be verified * with this command. For example: * * 1. Check if a relation with "mappedBy" is actually connected to that specified field. * 2. Check if "mappedBy" and "inversedBy" are consistent to each other. * 3. Check if "referencedColumnName" attributes are really pointing to primary key columns. * 4. Check if there are public properties that might cause problems with lazy loading. * * @return array */ public function validateMapping() { $errors = array(); $cmf = $this->em->getMetadataFactory(); $classes = $cmf->getAllMetadata(); foreach ($classes as $class) { if ($ce = $this->validateClass($class)) { $errors[$class->name] = $ce; } } return $errors; } /** * Validate a single class of the current * * @param ClassMetadataInfo $class * @return array */ public function validateClass(ClassMetadataInfo $class) { $ce = array(); $cmf = $this->em->getMetadataFactory(); foreach ($class->fieldMappings as $fieldName => $mapping) { if (!Type::hasType($mapping['type'])) { $ce[] = "The field '" . $class->name . "#" . $fieldName."' uses a non-existant type '" . $mapping['type'] . "'."; } } foreach ($class->associationMappings as $fieldName => $assoc) { if (!class_exists($assoc['targetEntity']) || $cmf->isTransient($assoc['targetEntity'])) { $ce[] = "The target entity '" . $assoc['targetEntity'] . "' specified on " . $class->name . '#' . $fieldName . ' is unknown or not an entity.'; return $ce; } if ($assoc['mappedBy'] && $assoc['inversedBy']) { $ce[] = "The association " . $class . "#" . $fieldName . " cannot be defined as both inverse and owning."; } $targetMetadata = $cmf->getMetadataFor($assoc['targetEntity']); if (isset($assoc['id']) && $targetMetadata->containsForeignIdentifier) { $ce[] = "Cannot map association '" . $class->name. "#". $fieldName ." as identifier, because " . "the target entity '". $targetMetadata->name . "' also maps an association as identifier."; } /* @var $assoc AssociationMapping */ if ($assoc['mappedBy']) { if ($targetMetadata->hasField($assoc['mappedBy'])) { $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the owning side ". "field " . $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " which is not defined as association."; } if (!$targetMetadata->hasAssociation($assoc['mappedBy'])) { $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the owning side ". "field " . $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " which does not exist."; } else if ($targetMetadata->associationMappings[$assoc['mappedBy']]['inversedBy'] == null) { $ce[] = "The field " . $class->name . "#" . $fieldName . " is on the inverse side of a ". "bi-directional relationship, but the specified mappedBy association on the target-entity ". $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " does not contain the required ". "'inversedBy=".$fieldName."' attribute."; } else if ($targetMetadata->associationMappings[$assoc['mappedBy']]['inversedBy'] != $fieldName) { $ce[] = "The mappings " . $class->name . "#" . $fieldName . " and " . $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " are ". "incosistent with each other."; } } if ($assoc['inversedBy']) { if ($targetMetadata->hasField($assoc['inversedBy'])) { $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the inverse side ". "field " . $assoc['targetEntity'] . "#" . $assoc['inversedBy'] . " which is not defined as association."; } if (!$targetMetadata->hasAssociation($assoc['inversedBy'])) { $ce[] = "The association " . $class->name . "#" . $fieldName . " refers to the inverse side ". "field " . $assoc['targetEntity'] . "#" . $assoc['inversedBy'] . " which does not exist."; } else if ($targetMetadata->associationMappings[$assoc['inversedBy']]['mappedBy'] == null) { $ce[] = "The field " . $class->name . "#" . $fieldName . " is on the owning side of a ". "bi-directional relationship, but the specified mappedBy association on the target-entity ". $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " does not contain the required ". "'inversedBy' attribute."; } else if ($targetMetadata->associationMappings[$assoc['inversedBy']]['mappedBy'] != $fieldName) { $ce[] = "The mappings " . $class->name . "#" . $fieldName . " and " . $assoc['targetEntity'] . "#" . $assoc['inversedBy'] . " are ". "incosistent with each other."; } // Verify inverse side/owning side match each other if (array_key_exists($assoc['inversedBy'], $targetMetadata->associationMappings)) { $targetAssoc = $targetMetadata->associationMappings[$assoc['inversedBy']]; if ($assoc['type'] == ClassMetadataInfo::ONE_TO_ONE && $targetAssoc['type'] !== ClassMetadataInfo::ONE_TO_ONE){ $ce[] = "If association " . $class->name . "#" . $fieldName . " is one-to-one, then the inversed " . "side " . $targetMetadata->name . "#" . $assoc['inversedBy'] . " has to be one-to-one as well."; } else if ($assoc['type'] == ClassMetadataInfo::MANY_TO_ONE && $targetAssoc['type'] !== ClassMetadataInfo::ONE_TO_MANY){ $ce[] = "If association " . $class->name . "#" . $fieldName . " is many-to-one, then the inversed " . "side " . $targetMetadata->name . "#" . $assoc['inversedBy'] . " has to be one-to-many."; } else if ($assoc['type'] == ClassMetadataInfo::MANY_TO_MANY && $targetAssoc['type'] !== ClassMetadataInfo::MANY_TO_MANY){ $ce[] = "If association " . $class->name . "#" . $fieldName . " is many-to-many, then the inversed " . "side " . $targetMetadata->name . "#" . $assoc['inversedBy'] . " has to be many-to-many as well."; } } } if ($assoc['isOwningSide']) { if ($assoc['type'] == ClassMetadataInfo::MANY_TO_MANY) { $identifierColumns = $class->getIdentifierColumnNames(); foreach ($assoc['joinTable']['joinColumns'] as $joinColumn) { if (!in_array($joinColumn['referencedColumnName'], $identifierColumns)) { $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . "has to be a primary key column on the target entity class '".$class->name."'."; break; } } $identifierColumns = $targetMetadata->getIdentifierColumnNames(); foreach ($assoc['joinTable']['inverseJoinColumns'] as $inverseJoinColumn) { if (!in_array($inverseJoinColumn['referencedColumnName'], $identifierColumns)) { $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . "has to be a primary key column on the target entity class '".$targetMetadata->name."'."; break; } } if (count($targetMetadata->getIdentifierColumnNames()) != count($assoc['joinTable']['inverseJoinColumns'])) { $ce[] = "The inverse join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . "have to contain to ALL identifier columns of the target entity '". $targetMetadata->name . "', " . "however '" . implode(", ", array_diff($targetMetadata->getIdentifierColumnNames(), array_values($assoc['relationToTargetKeyColumns']))) . "' are missing."; } if (count($class->getIdentifierColumnNames()) != count($assoc['joinTable']['joinColumns'])) { $ce[] = "The join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . "have to contain to ALL identifier columns of the source entity '". $class->name . "', " . "however '" . implode(", ", array_diff($class->getIdentifierColumnNames(), array_values($assoc['relationToSourceKeyColumns']))) . "' are missing."; } } else if ($assoc['type'] & ClassMetadataInfo::TO_ONE) { $identifierColumns = $targetMetadata->getIdentifierColumnNames(); foreach ($assoc['joinColumns'] as $joinColumn) { if (!in_array($joinColumn['referencedColumnName'], $identifierColumns)) { $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . "has to be a primary key column on the target entity class '".$targetMetadata->name."'."; } } if (count($identifierColumns) != count($assoc['joinColumns'])) { $ids = array(); foreach ($assoc['joinColumns'] as $joinColumn) { $ids[] = $joinColumn['name']; } $ce[] = "The join columns of the association '" . $assoc['fieldName'] . "' " . "have to match to ALL identifier columns of the target entity '". $class->name . "', " . "however '" . implode(", ", array_diff($targetMetadata->getIdentifierColumnNames(), $ids)) . "' are missing."; } } } if (isset($assoc['orderBy']) && $assoc['orderBy'] !== null) { foreach ($assoc['orderBy'] as $orderField => $orientation) { if (!$targetMetadata->hasField($orderField)) { $ce[] = "The association " . $class->name."#".$fieldName." is ordered by a foreign field " . $orderField . " that is not a field on the target entity " . $targetMetadata->name; } } } } foreach ($class->reflClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $publicAttr) { if ($publicAttr->isStatic()) { continue; } $ce[] = "Field '".$publicAttr->getName()."' in class '".$class->name."' must be private ". "or protected. Public fields may break lazy-loading."; } foreach ($class->subClasses as $subClass) { if (!in_array($class->name, class_parents($subClass))) { $ce[] = "According to the discriminator map class '" . $subClass . "' has to be a child ". "of '" . $class->name . "' but these entities are not related through inheritance."; } } return $ce; } /** * @param string $columnName * @param ClassMetadataInfo $class * @return bool */ private function columnExistsOnEntity($columnName, $class) { if (isset($class->fieldNames[$columnName])) { return true; } foreach ($class->associationMappings as $assoc) { if ($assoc['isOwningSide']) { foreach ($assoc['joinColumns'] as $columnMapping) { if ($columnMapping['name'] == $columnName) { return true; } } } } return false; } /** * Check if the Database Schema is in sync with the current metadata state. * * @return bool */ public function schemaInSyncWithMetadata() { $schemaTool = new SchemaTool($this->em); $allMetadata = $this->em->getMetadataFactory()->getAllMetadata(); return (count($schemaTool->getUpdateSchemaSql($allMetadata, true)) == 0); } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Setup.php0000644000175100017510000001613712143374607021106 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools; use Doctrine\Common\ClassLoader; use Doctrine\Common\Cache\Cache; use Doctrine\Common\Cache\ArrayCache; use Doctrine\ORM\Configuration; use Doctrine\ORM\Mapping\Driver\XmlDriver; use Doctrine\ORM\Mapping\Driver\YamlDriver; /** * Convenience class for setting up Doctrine from different installations and configurations. * * @author Benjamin Eberlei */ class Setup { /** * Use this method to register all autoloaders for a setup where Doctrine is checked out from * its github repository at {@link http://github.com/doctrine/doctrine2} * * @param string $gitCheckoutRootPath * @return void */ static public function registerAutoloadGit($gitCheckoutRootPath) { if (!class_exists('Doctrine\Common\ClassLoader', false)) { require_once $gitCheckoutRootPath . "/lib/vendor/doctrine-common/lib/Doctrine/Common/ClassLoader.php"; } $loader = new ClassLoader("Doctrine\Common", $gitCheckoutRootPath . "/lib/vendor/doctrine-common/lib"); $loader->register(); $loader = new ClassLoader("Doctrine\DBAL", $gitCheckoutRootPath . "/lib/vendor/doctrine-dbal/lib"); $loader->register(); $loader = new ClassLoader("Doctrine\ORM", $gitCheckoutRootPath . "/lib"); $loader->register(); $loader = new ClassLoader("Symfony\Component", $gitCheckoutRootPath . "/lib/vendor"); $loader->register(); } /** * Use this method to register all autoloaders for a setup where Doctrine is installed * though {@link http://pear.doctrine-project.org}. * * This method registers autoloaders for both Doctrine and Symfony top * level namespaces. * * @return void */ static public function registerAutoloadPEAR() { if (!class_exists('Doctrine\Common\ClassLoader', false)) { require_once "Doctrine/Common/ClassLoader.php"; } $loader = new ClassLoader("Doctrine"); $loader->register(); $loader = new ClassLoader("Symfony"); $loader->register(); } /** * Use this method to register all autoloads for a downloaded Doctrine library. * Pick the directory the library was uncompressed into. * * @param string $directory */ static public function registerAutoloadDirectory($directory) { if (!class_exists('Doctrine\Common\ClassLoader', false)) { require_once $directory . "/Doctrine/Common/ClassLoader.php"; } $loader = new ClassLoader("Doctrine", $directory); $loader->register(); $loader = new ClassLoader("Symfony\Component", $directory . "/Doctrine"); $loader->register(); } /** * Create a configuration with an annotation metadata driver. * * @param array $paths * @param boolean $isDevMode * @param string $proxyDir * @param Cache $cache * @param bool $useSimpleAnnotationReader * @return Configuration */ static public function createAnnotationMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null, $useSimpleAnnotationReader = true) { $config = self::createConfiguration($isDevMode, $proxyDir, $cache); $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver($paths, $useSimpleAnnotationReader)); return $config; } /** * Create a configuration with a xml metadata driver. * * @param array $paths * @param boolean $isDevMode * @param string $proxyDir * @param Cache $cache * @return Configuration */ static public function createXMLMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null) { $config = self::createConfiguration($isDevMode, $proxyDir, $cache); $config->setMetadataDriverImpl(new XmlDriver($paths)); return $config; } /** * Create a configuration with a yaml metadata driver. * * @param array $paths * @param boolean $isDevMode * @param string $proxyDir * @param Cache $cache * @return Configuration */ static public function createYAMLMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null) { $config = self::createConfiguration($isDevMode, $proxyDir, $cache); $config->setMetadataDriverImpl(new YamlDriver($paths)); return $config; } /** * Create a configuration without a metadata driver. * * @param bool $isDevMode * @param string $proxyDir * @param Cache $cache * @return Configuration */ static public function createConfiguration($isDevMode = false, $proxyDir = null, Cache $cache = null) { $proxyDir = $proxyDir ?: sys_get_temp_dir(); if ($isDevMode === false && $cache === null) { if (extension_loaded('apc')) { $cache = new \Doctrine\Common\Cache\ApcCache(); } else if (extension_loaded('xcache')) { $cache = new \Doctrine\Common\Cache\XcacheCache(); } else if (extension_loaded('memcache')) { $memcache = new \Memcache(); $memcache->connect('127.0.0.1'); $cache = new \Doctrine\Common\Cache\MemcacheCache(); $cache->setMemcache($memcache); } else if (extension_loaded('redis')) { $redis = new \Redis(); $redis->connect('127.0.0.1'); $cache = new \Doctrine\Common\Cache\RedisCache(); $cache->setRedis($redis); } else { $cache = new ArrayCache(); } } else if ($cache === null) { $cache = new ArrayCache(); } $cache->setNamespace("dc2_" . md5($proxyDir) . "_"); // to avoid collisions $config = new Configuration(); $config->setMetadataCacheImpl($cache); $config->setQueryCacheImpl($cache); $config->setResultCacheImpl($cache); $config->setProxyDir($proxyDir); $config->setProxyNamespace('DoctrineProxies'); $config->setAutoGenerateProxyClasses($isDevMode); return $config; } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/ToolEvents.php0000644000175100017510000000342312143374607022102 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools; class ToolEvents { /** * The postGenerateSchemaTable event occurs in SchemaTool#getSchemaFromMetadata() * whenever an entity class is transformed into its table representation. It recieves * the current non-complete Schema instance, the Entity Metadata Class instance and * the Schema Table instance of this entity. * * @var string */ const postGenerateSchemaTable = 'postGenerateSchemaTable'; /** * The postGenerateSchema event is triggered in SchemaTool#getSchemaFromMetadata() * after all entity classes have been transformed into the related Schema structure. * The EventArgs contain the EntityManager and the created Schema instance. * * @var string */ const postGenerateSchema = 'postGenerateSchema'; } DoctrineORM-2.3.3/Doctrine/ORM/Tools/ToolsException.php0000644000175100017510000000276712143374607022771 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools; use Doctrine\ORM\ORMException; /** * Tools related Exceptions * * @author Benjamin Eberlei */ class ToolsException extends ORMException { public static function schemaToolFailure($sql, \Exception $e) { return new self("Schema-Tool failed with Error '" . $e->getMessage() . "' while executing DDL: " . $sql, "0", $e); } public static function couldNotMapDoctrine1Type($type) { return new self("Could not map doctrine 1 type '$type'!"); } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Console/ConsoleRunner.php0000644000175100017510000000630412143374607024177 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Console; use Symfony\Component\Console\Application; use Symfony\Component\Console\Helper\HelperSet; class ConsoleRunner { /** * Run console with the given helperset. * * @param \Symfony\Component\Console\Helper\HelperSet $helperSet * @param \Symfony\Component\Console\Command\Command[] $commands * @return void */ static public function run(HelperSet $helperSet, $commands = array()) { $cli = new Application('Doctrine Command Line Interface', \Doctrine\ORM\Version::VERSION); $cli->setCatchExceptions(true); $cli->setHelperSet($helperSet); self::addCommands($cli); $cli->addCommands($commands); $cli->run(); } /** * @param Application $cli */ static public function addCommands(Application $cli) { $cli->addCommands(array( // DBAL Commands new \Doctrine\DBAL\Tools\Console\Command\RunSqlCommand(), new \Doctrine\DBAL\Tools\Console\Command\ImportCommand(), // ORM Commands new \Doctrine\ORM\Tools\Console\Command\ClearCache\MetadataCommand(), new \Doctrine\ORM\Tools\Console\Command\ClearCache\ResultCommand(), new \Doctrine\ORM\Tools\Console\Command\ClearCache\QueryCommand(), new \Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand(), new \Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand(), new \Doctrine\ORM\Tools\Console\Command\SchemaTool\DropCommand(), new \Doctrine\ORM\Tools\Console\Command\EnsureProductionSettingsCommand(), new \Doctrine\ORM\Tools\Console\Command\ConvertDoctrine1SchemaCommand(), new \Doctrine\ORM\Tools\Console\Command\GenerateRepositoriesCommand(), new \Doctrine\ORM\Tools\Console\Command\GenerateEntitiesCommand(), new \Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand(), new \Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand(), new \Doctrine\ORM\Tools\Console\Command\RunDqlCommand(), new \Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand(), new \Doctrine\ORM\Tools\Console\Command\InfoCommand() )); } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Console/MetadataFilter.php0000644000175100017510000000501512143374607024267 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Console; /** * Used by CLI Tools to restrict entity-based commands to given patterns. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class MetadataFilter extends \FilterIterator implements \Countable { /** * Filter Metadatas by one or more filter options. * * @param array $metadatas * @param array|string $filter * @return array */ static public function filter(array $metadatas, $filter) { $metadatas = new MetadataFilter(new \ArrayIterator($metadatas), $filter); return iterator_to_array($metadatas); } private $_filter = array(); public function __construct(\ArrayIterator $metadata, $filter) { $this->_filter = (array)$filter; parent::__construct($metadata); } public function accept() { if (count($this->_filter) == 0) { return true; } $it = $this->getInnerIterator(); $metadata = $it->current(); foreach ($this->_filter as $filter) { if (strpos($metadata->name, $filter) !== false) { return true; } } return false; } public function count() { return count($this->getInnerIterator()); } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Console/Command/ConvertDoctrine1SchemaCommand.php0000644000175100017510000001670512143374607030600 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Console\Command; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Symfony\Component\Console, Doctrine\ORM\Tools\Export\ClassMetadataExporter, Doctrine\ORM\Tools\ConvertDoctrine1Schema, Doctrine\ORM\Tools\EntityGenerator; /** * Command to convert a Doctrine 1 schema to a Doctrine 2 mapping file. * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ConvertDoctrine1SchemaCommand extends Console\Command\Command { /** * @var EntityGenerator */ private $entityGenerator = null; /** * @var ClassMetadataExporter */ private $metadataExporter = null; /** * @return EntityGenerator */ public function getEntityGenerator() { if ($this->entityGenerator == null) { $this->entityGenerator = new EntityGenerator(); } return $this->entityGenerator; } /** * @param EntityGenerator $entityGenerator */ public function setEntityGenerator(EntityGenerator $entityGenerator) { $this->entityGenerator = $entityGenerator; } /** * @return ClassMetadataExporter */ public function getMetadataExporter() { if ($this->metadataExporter == null) { $this->metadataExporter = new ClassMetadataExporter(); } return $this->metadataExporter; } /** * @param ClassMetadataExporter $metadataExporter */ public function setMetadataExporter(ClassMetadataExporter $metadataExporter) { $this->metadataExporter = $metadataExporter; } /** * @see Console\Command\Command */ protected function configure() { $this ->setName('orm:convert-d1-schema') ->setDescription('Converts Doctrine 1.X schema into a Doctrine 2.X schema.') ->setDefinition(array( new InputArgument( 'from-path', InputArgument::REQUIRED, 'The path of Doctrine 1.X schema information.' ), new InputArgument( 'to-type', InputArgument::REQUIRED, 'The destination Doctrine 2.X mapping type.' ), new InputArgument( 'dest-path', InputArgument::REQUIRED, 'The path to generate your Doctrine 2.X mapping information.' ), new InputOption( 'from', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Optional paths of Doctrine 1.X schema information.', array() ), new InputOption( 'extend', null, InputOption::VALUE_OPTIONAL, 'Defines a base class to be extended by generated entity classes.' ), new InputOption( 'num-spaces', null, InputOption::VALUE_OPTIONAL, 'Defines the number of indentation spaces', 4 ) )) ->setHelp(<<getHelper('em')->getEntityManager(); // Process source directories $fromPaths = array_merge(array($input->getArgument('from-path')), $input->getOption('from')); // Process destination directory $destPath = realpath($input->getArgument('dest-path')); $toType = $input->getArgument('to-type'); $extend = $input->getOption('extend'); $numSpaces = $input->getOption('num-spaces'); $this->convertDoctrine1Schema($em, $fromPaths, $destPath, $toType, $numSpaces, $extend, $output); } /** * @param \Doctrine\ORM\EntityManager $em * @param array $fromPaths * @param string $destPath * @param string $toType * @param int $numSpaces * @param string|null $extend * @param Console\Output\OutputInterface $output */ public function convertDoctrine1Schema($em, $fromPaths, $destPath, $toType, $numSpaces, $extend, $output) { foreach ($fromPaths as &$dirName) { $dirName = realpath($dirName); if ( ! file_exists($dirName)) { throw new \InvalidArgumentException( sprintf("Doctrine 1.X schema directory '%s' does not exist.", $dirName) ); } else if ( ! is_readable($dirName)) { throw new \InvalidArgumentException( sprintf("Doctrine 1.X schema directory '%s' does not have read permissions.", $dirName) ); } } if ( ! file_exists($destPath)) { throw new \InvalidArgumentException( sprintf("Doctrine 2.X mapping destination directory '%s' does not exist.", $destPath) ); } else if ( ! is_writable($destPath)) { throw new \InvalidArgumentException( sprintf("Doctrine 2.X mapping destination directory '%s' does not have write permissions.", $destPath) ); } $cme = $this->getMetadataExporter(); $exporter = $cme->getExporter($toType, $destPath); if (strtolower($toType) === 'annotation') { $entityGenerator = $this->getEntityGenerator(); $exporter->setEntityGenerator($entityGenerator); $entityGenerator->setNumSpaces($numSpaces); if ($extend !== null) { $entityGenerator->setClassToExtend($extend); } } $converter = new ConvertDoctrine1Schema($fromPaths); $metadata = $converter->getMetadata(); if ($metadata) { $output->write(PHP_EOL); foreach ($metadata as $class) { $output->write(sprintf('Processing entity "%s"', $class->name) . PHP_EOL); } $exporter->setMetadata($metadata); $exporter->export(); $output->write(PHP_EOL . sprintf( 'Converting Doctrine 1.X schema to "%s" mapping type in "%s"', $toType, $destPath )); } else { $output->write('No Metadata Classes to process.' . PHP_EOL); } } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php0000644000175100017510000001612512143374607027216 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Console\Command; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Symfony\Component\Console, Doctrine\ORM\Tools\Console\MetadataFilter, Doctrine\ORM\Tools\Export\ClassMetadataExporter, Doctrine\ORM\Tools\EntityGenerator, Doctrine\ORM\Tools\DisconnectedClassMetadataFactory; /** * Command to convert your mapping information between the various formats. * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ConvertMappingCommand extends Console\Command\Command { /** * @see Console\Command\Command */ protected function configure() { $this ->setName('orm:convert-mapping') ->setDescription('Convert mapping information between supported formats.') ->setDefinition(array( new InputOption( 'filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A string pattern used to match entities that should be processed.' ), new InputArgument( 'to-type', InputArgument::REQUIRED, 'The mapping type to be converted.' ), new InputArgument( 'dest-path', InputArgument::REQUIRED, 'The path to generate your entities classes.' ), new InputOption( 'force', null, InputOption::VALUE_NONE, 'Force to overwrite existing mapping files.' ), new InputOption( 'from-database', null, null, 'Whether or not to convert mapping information from existing database.' ), new InputOption( 'extend', null, InputOption::VALUE_OPTIONAL, 'Defines a base class to be extended by generated entity classes.' ), new InputOption( 'num-spaces', null, InputOption::VALUE_OPTIONAL, 'Defines the number of indentation spaces', 4 ), new InputOption( 'namespace', null, InputOption::VALUE_OPTIONAL, 'Defines a namespace for the generated entity classes, if converted from database.' ), )) ->setHelp(<<one-time command. It should not be necessary for you to call this method multiple times, escpecially when using the --from-database flag. Converting an existing databsae schema into mapping files only solves about 70-80% of the necessary mapping information. Additionally the detection from an existing database cannot detect inverse associations, inheritance types, entities with foreign keys as primary keys and many of the semantical operations on associations such as cascade. Hint: There is no need to convert YAML or XML mapping files to annotations every time you make changes. All mapping drivers are first class citizens in Doctrine 2 and can be used as runtime mapping for the ORM. EOT ); } /** * @see Console\Command\Command */ protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output) { $em = $this->getHelper('em')->getEntityManager(); if ($input->getOption('from-database') === true) { $databaseDriver = new \Doctrine\ORM\Mapping\Driver\DatabaseDriver( $em->getConnection()->getSchemaManager() ); $em->getConfiguration()->setMetadataDriverImpl( $databaseDriver ); if (($namespace = $input->getOption('namespace')) !== null) { $databaseDriver->setNamespace($namespace); } } $cmf = new DisconnectedClassMetadataFactory(); $cmf->setEntityManager($em); $metadata = $cmf->getAllMetadata(); $metadata = MetadataFilter::filter($metadata, $input->getOption('filter')); // Process destination directory if ( ! is_dir($destPath = $input->getArgument('dest-path'))) { mkdir($destPath, 0777, true); } $destPath = realpath($destPath); if ( ! file_exists($destPath)) { throw new \InvalidArgumentException( sprintf("Mapping destination directory '%s' does not exist.", $input->getArgument('dest-path')) ); } else if ( ! is_writable($destPath)) { throw new \InvalidArgumentException( sprintf("Mapping destination directory '%s' does not have write permissions.", $destPath) ); } $toType = strtolower($input->getArgument('to-type')); $exporter = $this->getExporter($toType, $destPath); $exporter->setOverwriteExistingFiles( ($input->getOption('force') !== false) ); if ($toType == 'annotation') { $entityGenerator = new EntityGenerator(); $exporter->setEntityGenerator($entityGenerator); $entityGenerator->setNumSpaces($input->getOption('num-spaces')); if (($extend = $input->getOption('extend')) !== null) { $entityGenerator->setClassToExtend($extend); } } if (count($metadata)) { foreach ($metadata as $class) { $output->write(sprintf('Processing entity "%s"', $class->name) . PHP_EOL); } $exporter->setMetadata($metadata); $exporter->export(); $output->write(PHP_EOL . sprintf( 'Exporting "%s" mapping information to "%s"' . PHP_EOL, $toType, $destPath )); } else { $output->write('No Metadata Classes to process.' . PHP_EOL); } } protected function getExporter($toType, $destPath) { $cme = new ClassMetadataExporter(); return $cme->getExporter($toType, $destPath); } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Console/Command/EnsureProductionSettingsCommand.php0000644000175100017510000000563612143374607031320 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Console\Command; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Symfony\Component\Console; /** * Command to ensure that Doctrine is properly configured for a production environment. * * * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class EnsureProductionSettingsCommand extends Console\Command\Command { /** * @see Console\Command\Command */ protected function configure() { $this ->setName('orm:ensure-production-settings') ->setDescription('Verify that Doctrine is properly configured for a production environment.') ->setDefinition(array( new InputOption( 'complete', null, InputOption::VALUE_NONE, 'Flag to also inspect database connection existance.' ) )) ->setHelp(<<getHelper('em')->getEntityManager(); $error = false; try { $em->getConfiguration()->ensureProductionSettings(); if ($input->getOption('complete') !== null) { $em->getConnection()->connect(); } } catch (\Exception $e) { $error = true; $output->writeln('' . $e->getMessage() . ''); } if ($error === false) { $output->write('Environment is correctly configured for production.' . PHP_EOL); } } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Console/Command/GenerateEntitiesCommand.php0000644000175100017510000001556212143374607027525 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Console\Command; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Symfony\Component\Console, Doctrine\ORM\Tools\Console\MetadataFilter, Doctrine\ORM\Tools\EntityGenerator, Doctrine\ORM\Tools\DisconnectedClassMetadataFactory; /** * Command to generate entity classes and method stubs from your mapping information. * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class GenerateEntitiesCommand extends Console\Command\Command { /** * @see Console\Command\Command */ protected function configure() { $this ->setName('orm:generate-entities') ->setDescription('Generate entity classes and method stubs from your mapping information.') ->setDefinition(array( new InputOption( 'filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A string pattern used to match entities that should be processed.' ), new InputArgument( 'dest-path', InputArgument::REQUIRED, 'The path to generate your entity classes.' ), new InputOption( 'generate-annotations', null, InputOption::VALUE_OPTIONAL, 'Flag to define if generator should generate annotation metadata on entities.', false ), new InputOption( 'generate-methods', null, InputOption::VALUE_OPTIONAL, 'Flag to define if generator should generate stub methods on entities.', true ), new InputOption( 'regenerate-entities', null, InputOption::VALUE_OPTIONAL, 'Flag to define if generator should regenerate entity if it exists.', false ), new InputOption( 'update-entities', null, InputOption::VALUE_OPTIONAL, 'Flag to define if generator should only update entity if it exists.', true ), new InputOption( 'extend', null, InputOption::VALUE_OPTIONAL, 'Defines a base class to be extended by generated entity classes.' ), new InputOption( 'num-spaces', null, InputOption::VALUE_OPTIONAL, 'Defines the number of indentation spaces', 4 ) )) ->setHelp(<<--update-entities or --regenerate-entities flags your existing code gets overwritten. The EntityGenerator will only append new code to your file and will not delete the old code. However this approach may still be prone to error and we suggest you use code repositories such as GIT or SVN to make backups of your code. It makes sense to generate the entity code if you are using entities as Data Access Objects only and dont put much additional logic on them. If you are however putting much more logic on the entities you should refrain from using the entity-generator and code your entities manually. Important: Even if you specified Inheritance options in your XML or YAML Mapping files the generator cannot generate the base and child classes for you correctly, because it doesn't know which class is supposed to extend which. You have to adjust the entity code manually for inheritance to work! EOT ); } /** * @see Console\Command\Command */ protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output) { $em = $this->getHelper('em')->getEntityManager(); $cmf = new DisconnectedClassMetadataFactory(); $cmf->setEntityManager($em); $metadatas = $cmf->getAllMetadata(); $metadatas = MetadataFilter::filter($metadatas, $input->getOption('filter')); // Process destination directory $destPath = realpath($input->getArgument('dest-path')); if ( ! file_exists($destPath)) { throw new \InvalidArgumentException( sprintf("Entities destination directory '%s' does not exist.", $input->getArgument('dest-path')) ); } else if ( ! is_writable($destPath)) { throw new \InvalidArgumentException( sprintf("Entities destination directory '%s' does not have write permissions.", $destPath) ); } if (count($metadatas)) { // Create EntityGenerator $entityGenerator = new EntityGenerator(); $entityGenerator->setGenerateAnnotations($input->getOption('generate-annotations')); $entityGenerator->setGenerateStubMethods($input->getOption('generate-methods')); $entityGenerator->setRegenerateEntityIfExists($input->getOption('regenerate-entities')); $entityGenerator->setUpdateEntityIfExists($input->getOption('update-entities')); $entityGenerator->setNumSpaces($input->getOption('num-spaces')); if (($extend = $input->getOption('extend')) !== null) { $entityGenerator->setClassToExtend($extend); } foreach ($metadatas as $metadata) { $output->write( sprintf('Processing entity "%s"', $metadata->name) . PHP_EOL ); } // Generating Entities $entityGenerator->generate($metadatas, $destPath); // Outputting information message $output->write(PHP_EOL . sprintf('Entity classes generated to "%s"', $destPath) . PHP_EOL); } else { $output->write('No Metadata Classes to process.' . PHP_EOL); } } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Console/Command/GenerateProxiesCommand.php0000644000175100017510000001017112143374607027361 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Console\Command; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Symfony\Component\Console, Doctrine\ORM\Tools\Console\MetadataFilter; /** * Command to (re)generate the proxy classes used by doctrine. * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class GenerateProxiesCommand extends Console\Command\Command { /** * @see Console\Command\Command */ protected function configure() { $this ->setName('orm:generate-proxies') ->setDescription('Generates proxy classes for entity classes.') ->setDefinition(array( new InputOption( 'filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A string pattern used to match entities that should be processed.' ), new InputArgument( 'dest-path', InputArgument::OPTIONAL, 'The path to generate your proxy classes. If none is provided, it will attempt to grab from configuration.' ), )) ->setHelp(<<getHelper('em')->getEntityManager(); $metadatas = $em->getMetadataFactory()->getAllMetadata(); $metadatas = MetadataFilter::filter($metadatas, $input->getOption('filter')); // Process destination directory if (($destPath = $input->getArgument('dest-path')) === null) { $destPath = $em->getConfiguration()->getProxyDir(); } if ( ! is_dir($destPath)) { mkdir($destPath, 0777, true); } $destPath = realpath($destPath); if ( ! file_exists($destPath)) { throw new \InvalidArgumentException( sprintf("Proxies destination directory '%s' does not exist.", $em->getConfiguration()->getProxyDir()) ); } else if ( ! is_writable($destPath)) { throw new \InvalidArgumentException( sprintf("Proxies destination directory '%s' does not have write permissions.", $destPath) ); } if ( count($metadatas)) { foreach ($metadatas as $metadata) { $output->write( sprintf('Processing entity "%s"', $metadata->name) . PHP_EOL ); } // Generating Proxies $em->getProxyFactory()->generateProxyClasses($metadatas, $destPath); // Outputting information message $output->write(PHP_EOL . sprintf('Proxy classes generated to "%s"', $destPath) . PHP_EOL); } else { $output->write('No Metadata Classes to process.' . PHP_EOL); } } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Console/Command/GenerateRepositoriesCommand.php0000644000175100017510000001052612143374607030423 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Console\Command; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Symfony\Component\Console, Doctrine\ORM\Tools\Console\MetadataFilter, Doctrine\ORM\Tools\EntityRepositoryGenerator; /** * Command to generate repository classes for mapping information. * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class GenerateRepositoriesCommand extends Console\Command\Command { /** * @see Console\Command\Command */ protected function configure() { $this ->setName('orm:generate-repositories') ->setDescription('Generate repository classes from your mapping information.') ->setDefinition(array( new InputOption( 'filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A string pattern used to match entities that should be processed.' ), new InputArgument( 'dest-path', InputArgument::REQUIRED, 'The path to generate your repository classes.' ) )) ->setHelp(<<getHelper('em')->getEntityManager(); $metadatas = $em->getMetadataFactory()->getAllMetadata(); $metadatas = MetadataFilter::filter($metadatas, $input->getOption('filter')); // Process destination directory $destPath = realpath($input->getArgument('dest-path')); if ( ! file_exists($destPath)) { throw new \InvalidArgumentException( sprintf("Entities destination directory '%s' does not exist.", $input->getArgument('dest-path')) ); } else if ( ! is_writable($destPath)) { throw new \InvalidArgumentException( sprintf("Entities destination directory '%s' does not have write permissions.", $destPath) ); } if (count($metadatas)) { $numRepositories = 0; $generator = new EntityRepositoryGenerator(); foreach ($metadatas as $metadata) { if ($metadata->customRepositoryClassName) { $output->write( sprintf('Processing repository "%s"', $metadata->customRepositoryClassName) . PHP_EOL ); $generator->writeEntityRepositoryClass($metadata->customRepositoryClassName, $destPath); $numRepositories++; } } if ($numRepositories) { // Outputting information message $output->write(PHP_EOL . sprintf('Repository classes generated to "%s"', $destPath) . PHP_EOL); } else { $output->write('No Repository classes were found to be processed.' . PHP_EOL); } } else { $output->write('No Metadata Classes to process.' . PHP_EOL); } } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Console/Command/InfoCommand.php0000644000175100017510000000610312143374607025150 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Console\Command; use Doctrine\ORM\Mapping\MappingException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Command\Command; /** * Show information about mapped entities * * * @link www.doctrine-project.org * @since 2.1 * @author Benjamin Eberlei */ class InfoCommand extends Command { protected function configure() { $this ->setName('orm:info') ->setDescription('Show basic information about all mapped entities') ->setHelp(<<%command.name% shows basic information about which entities exist and possibly if their mapping information contains errors or not. EOT ); } protected function execute(InputInterface $input, OutputInterface $output) { /* @var $entityManager \Doctrine\ORM\EntityManager */ $entityManager = $this->getHelper('em')->getEntityManager(); $entityClassNames = $entityManager->getConfiguration() ->getMetadataDriverImpl() ->getAllClassNames(); if (!$entityClassNames) { throw new \Exception( 'You do not have any mapped Doctrine ORM entities according to the current configuration. '. 'If you have entities or mapping files you should check your mapping configuration for errors.' ); } $output->writeln(sprintf("Found %d mapped entities:", count($entityClassNames))); foreach ($entityClassNames as $entityClassName) { try { $entityManager->getClassMetadata($entityClassName); $output->writeln(sprintf("[OK] %s", $entityClassName)); } catch (MappingException $e) { $output->writeln("[FAIL] ".$entityClassName); $output->writeln(sprintf("%s", $e->getMessage())); $output->writeln(''); } } } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Console/Command/RunDqlCommand.php0000644000175100017510000001071112143374607025462 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Console\Command; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Symfony\Component\Console; /** * Command to execute DQL queries in a given EntityManager. * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class RunDqlCommand extends Console\Command\Command { /** * @see Console\Command\Command */ protected function configure() { $this ->setName('orm:run-dql') ->setDescription('Executes arbitrary DQL directly from the command line.') ->setDefinition(array( new InputArgument('dql', InputArgument::REQUIRED, 'The DQL to execute.'), new InputOption( 'hydrate', null, InputOption::VALUE_REQUIRED, 'Hydration mode of result set. Should be either: object, array, scalar or single-scalar.', 'object' ), new InputOption( 'first-result', null, InputOption::VALUE_REQUIRED, 'The first result in the result set.' ), new InputOption( 'max-result', null, InputOption::VALUE_REQUIRED, 'The maximum number of results in the result set.' ), new InputOption( 'depth', null, InputOption::VALUE_REQUIRED, 'Dumping depth of Entity graph.', 7 ) )) ->setHelp(<<getHelper('em')->getEntityManager(); if (($dql = $input->getArgument('dql')) === null) { throw new \RuntimeException("Argument 'DQL' is required in order to execute this command correctly."); } $depth = $input->getOption('depth'); if ( ! is_numeric($depth)) { throw new \LogicException("Option 'depth' must contains an integer value"); } $hydrationModeName = $input->getOption('hydrate'); $hydrationMode = 'Doctrine\ORM\Query::HYDRATE_' . strtoupper(str_replace('-', '_', $hydrationModeName)); if ( ! defined($hydrationMode)) { throw new \RuntimeException( "Hydration mode '$hydrationModeName' does not exist. It should be either: object. array, scalar or single-scalar." ); } $query = $em->createQuery($dql); if (($firstResult = $input->getOption('first-result')) !== null) { if ( ! is_numeric($firstResult)) { throw new \LogicException("Option 'first-result' must contains an integer value"); } $query->setFirstResult((int) $firstResult); } if (($maxResult = $input->getOption('max-result')) !== null) { if ( ! is_numeric($maxResult)) { throw new \LogicException("Option 'max-result' must contains an integer value"); } $query->setMaxResults((int) $maxResult); } $resultSet = $query->execute(array(), constant($hydrationMode)); \Doctrine\Common\Util\Debug::dump($resultSet, $input->getOption('depth')); } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Console/Command/ValidateSchemaCommand.php0000644000175100017510000000633212143374607027133 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Console\Command; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Symfony\Component\Console; /** * Validate that the current mapping is valid * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ValidateSchemaCommand extends Console\Command\Command { /** * @see Console\Command\Command */ protected function configure() { $this ->setName('orm:validate-schema') ->setDescription('Validate the mapping files.') ->setHelp(<<getHelper('em')->getEntityManager(); $validator = new \Doctrine\ORM\Tools\SchemaValidator($em); $errors = $validator->validateMapping(); $exit = 0; if ($errors) { foreach ($errors as $className => $errorMessages) { $output->write("[Mapping] FAIL - The entity-class '" . $className . "' mapping is invalid:\n"); foreach ($errorMessages as $errorMessage) { $output->write('* ' . $errorMessage . "\n"); } $output->write("\n"); } $exit += 1; } else { $output->write('[Mapping] OK - The mapping files are correct.' . "\n"); } if (!$validator->schemaInSyncWithMetadata()) { $output->write('[Database] FAIL - The database schema is not in sync with the current mapping file.' . "\n"); $exit += 2; } else { $output->write('[Database] OK - The database schema is in sync with the mapping files.' . "\n"); } return $exit; } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Console/Command/ClearCache/MetadataCommand.php0000644000175100017510000000761012143374607027753 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Console\Command\ClearCache; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Symfony\Component\Console, Doctrine\Common\Cache; /** * Command to clear the metadata cache of the various cache drivers. * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class MetadataCommand extends Console\Command\Command { /** * @see Console\Command\Command */ protected function configure() { $this ->setName('orm:clear-cache:metadata') ->setDescription('Clear all metadata cache of the various cache drivers.') ->setDefinition(array( new InputOption( 'flush', null, InputOption::VALUE_NONE, 'If defined, cache entries will be flushed instead of deleted/invalidated.' ) )); $this->setHelp(<<%command.name% command is meant to clear the metadata cache of associated Entity Manager. It is possible to invalidate all cache entries at once - called delete -, or flushes the cache provider instance completely. The execution type differ on how you execute the command. If you want to invalidate the entries (and not delete from cache instance), this command would do the work: %command.name% Alternatively, if you want to flush the cache provider using this command: %command.name% --flush Finally, be aware that if --flush option is passed, not all cache providers are able to flush entries, because of a limitation of its execution nature. EOT ); } /** * @see Console\Command\Command */ protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output) { $em = $this->getHelper('em')->getEntityManager(); $cacheDriver = $em->getConfiguration()->getMetadataCacheImpl(); if ( ! $cacheDriver) { throw new \InvalidArgumentException('No Metadata cache driver is configured on given EntityManager.'); } if ($cacheDriver instanceof Cache\ApcCache) { throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI."); } $output->write('Clearing ALL Metadata cache entries' . PHP_EOL); $result = $cacheDriver->deleteAll(); $message = ($result) ? 'Successfully deleted cache entries.' : 'No cache entries were deleted.'; if (true === $input->getOption('flush')) { $result = $cacheDriver->flushAll(); $message = ($result) ? 'Successfully flushed cache entries.' : $message; } $output->write($message . PHP_EOL); } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Console/Command/ClearCache/QueryCommand.php0000644000175100017510000000756012143374607027344 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Console\Command\ClearCache; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Symfony\Component\Console, Doctrine\Common\Cache; /** * Command to clear the query cache of the various cache drivers. * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class QueryCommand extends Console\Command\Command { /** * @see Console\Command\Command */ protected function configure() { $this ->setName('orm:clear-cache:query') ->setDescription('Clear all query cache of the various cache drivers.') ->setDefinition(array( new InputOption( 'flush', null, InputOption::VALUE_NONE, 'If defined, cache entries will be flushed instead of deleted/invalidated.' ) )); $this->setHelp(<<%command.name% command is meant to clear the query cache of associated Entity Manager. It is possible to invalidate all cache entries at once - called delete -, or flushes the cache provider instance completely. The execution type differ on how you execute the command. If you want to invalidate the entries (and not delete from cache instance), this command would do the work: %command.name% Alternatively, if you want to flush the cache provider using this command: %command.name% --flush Finally, be aware that if --flush option is passed, not all cache providers are able to flush entries, because of a limitation of its execution nature. EOT ); } /** * @see Console\Command\Command */ protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output) { $em = $this->getHelper('em')->getEntityManager(); $cacheDriver = $em->getConfiguration()->getQueryCacheImpl(); if ( ! $cacheDriver) { throw new \InvalidArgumentException('No Query cache driver is configured on given EntityManager.'); } if ($cacheDriver instanceof Cache\ApcCache) { throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI."); } $output->write('Clearing ALL Query cache entries' . PHP_EOL); $result = $cacheDriver->deleteAll(); $message = ($result) ? 'Successfully deleted cache entries.' : 'No cache entries were deleted.'; if (true === $input->getOption('flush')) { $result = $cacheDriver->flushAll(); $message = ($result) ? 'Successfully flushed cache entries.' : $message; } $output->write($message . PHP_EOL); } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Console/Command/ClearCache/ResultCommand.php0000644000175100017510000000757012143374607027516 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Console\Command\ClearCache; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Symfony\Component\Console, Doctrine\Common\Cache; /** * Command to clear the result cache of the various cache drivers. * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ResultCommand extends Console\Command\Command { /** * @see Console\Command\Command */ protected function configure() { $this ->setName('orm:clear-cache:result') ->setDescription('Clear all result cache of the various cache drivers.') ->setDefinition(array( new InputOption( 'flush', null, InputOption::VALUE_NONE, 'If defined, cache entries will be flushed instead of deleted/invalidated.' ) )); $this->setHelp(<<%command.name% command is meant to clear the result cache of associated Entity Manager. It is possible to invalidate all cache entries at once - called delete -, or flushes the cache provider instance completely. The execution type differ on how you execute the command. If you want to invalidate the entries (and not delete from cache instance), this command would do the work: %command.name% Alternatively, if you want to flush the cache provider using this command: %command.name% --flush Finally, be aware that if --flush option is passed, not all cache providers are able to flush entries, because of a limitation of its execution nature. EOT ); } /** * @see Console\Command\Command */ protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output) { $em = $this->getHelper('em')->getEntityManager(); $cacheDriver = $em->getConfiguration()->getResultCacheImpl(); if ( ! $cacheDriver) { throw new \InvalidArgumentException('No Result cache driver is configured on given EntityManager.'); } if ($cacheDriver instanceof Cache\ApcCache) { throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI."); } $output->write('Clearing ALL Result cache entries' . PHP_EOL); $result = $cacheDriver->deleteAll(); $message = ($result) ? 'Successfully deleted cache entries.' : 'No cache entries were deleted.'; if (true === $input->getOption('flush')) { $result = $cacheDriver->flushAll(); $message = ($result) ? 'Successfully flushed cache entries.' : $message; } $output->write($message . PHP_EOL); } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Console/Command/SchemaTool/AbstractCommand.php0000644000175100017510000000431212143374607030056 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Console\Command\SchemaTool; use Symfony\Component\Console\Input\InputInterface, Symfony\Component\Console\Output\OutputInterface, Symfony\Component\Console\Command\Command, Doctrine\ORM\Tools\SchemaTool; abstract class AbstractCommand extends Command { /** * @param InputInterface $input * @param OutputInterface $output * @param SchemaTool $schemaTool * @param array $metadatas */ abstract protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas); /** * @see Console\Command\Command */ protected function execute(InputInterface $input, OutputInterface $output) { $emHelper = $this->getHelper('em'); /* @var $em \Doctrine\ORM\EntityManager */ $em = $emHelper->getEntityManager(); $metadatas = $em->getMetadataFactory()->getAllMetadata(); if ( ! empty($metadatas)) { // Create SchemaTool $tool = new \Doctrine\ORM\Tools\SchemaTool($em); $this->executeSchemaCommand($input, $output, $tool, $metadatas); } else { $output->write('No Metadata Classes to process.' . PHP_EOL); } } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Console/Command/SchemaTool/CreateCommand.php0000644000175100017510000000606712143374607027527 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Console\Command\SchemaTool; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Symfony\Component\Console\Input\InputInterface, Symfony\Component\Console\Output\OutputInterface, Doctrine\ORM\Tools\SchemaTool; /** * Command to create the database schema for a set of classes based on their mappings. * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class CreateCommand extends AbstractCommand { /** * @see Console\Command\Command */ protected function configure() { $this ->setName('orm:schema-tool:create') ->setDescription( 'Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output.' ) ->setDefinition(array( new InputOption( 'dump-sql', null, InputOption::VALUE_NONE, 'Instead of try to apply generated SQLs into EntityManager Storage Connection, output them.' ) )) ->setHelp(<<getOption('dump-sql') === true) { $sqls = $schemaTool->getCreateSchemaSql($metadatas); $output->write(implode(';' . PHP_EOL, $sqls) . ';' . PHP_EOL); } else { $output->write('ATTENTION: This operation should not be executed in a production environment.' . PHP_EOL . PHP_EOL); $output->write('Creating database schema...' . PHP_EOL); $schemaTool->createSchema($metadatas); $output->write('Database schema created successfully!' . PHP_EOL); } } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Console/Command/SchemaTool/DropCommand.php0000644000175100017510000001110112143374607027211 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Console\Command\SchemaTool; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Symfony\Component\Console\Input\InputInterface, Symfony\Component\Console\Output\OutputInterface, Doctrine\ORM\Tools\SchemaTool; /** * Command to drop the database schema for a set of classes based on their mappings. * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class DropCommand extends AbstractCommand { /** * @see Console\Command\Command */ protected function configure() { $this ->setName('orm:schema-tool:drop') ->setDescription( 'Drop the complete database schema of EntityManager Storage Connection or generate the corresponding SQL output.' ) ->setDefinition(array( new InputOption( 'dump-sql', null, InputOption::VALUE_NONE, 'Instead of try to apply generated SQLs into EntityManager Storage Connection, output them.' ), new InputOption( 'force', null, InputOption::VALUE_NONE, "Don't ask for the deletion of the database, but force the operation to run." ), new InputOption( 'full-database', null, InputOption::VALUE_NONE, 'Instead of using the Class Metadata to detect the database table schema, drop ALL assets that the database contains.' ), )) ->setHelp(<<getOption('full-database')); if ($input->getOption('dump-sql') === true) { if ($isFullDatabaseDrop) { $sqls = $schemaTool->getDropDatabaseSQL(); } else { $sqls = $schemaTool->getDropSchemaSQL($metadatas); } $output->write(implode(';' . PHP_EOL, $sqls) . PHP_EOL); } else if ($input->getOption('force') === true) { $output->write('Dropping database schema...' . PHP_EOL); if ($isFullDatabaseDrop) { $schemaTool->dropDatabase(); } else { $schemaTool->dropSchema($metadatas); } $output->write('Database schema dropped successfully!' . PHP_EOL); } else { $output->write('ATTENTION: This operation should not be executed in a production environment.' . PHP_EOL . PHP_EOL); if ($isFullDatabaseDrop) { $sqls = $schemaTool->getDropDatabaseSQL(); } else { $sqls = $schemaTool->getDropSchemaSQL($metadatas); } if (count($sqls)) { $output->write('Schema-Tool would execute ' . count($sqls) . ' queries to drop the database.' . PHP_EOL); $output->write('Please run the operation with --force to execute these queries or use --dump-sql to see them.' . PHP_EOL); } else { $output->write('Nothing to drop. The database is empty!' . PHP_EOL); } } } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Console/Command/SchemaTool/UpdateCommand.php0000644000175100017510000001320112143374607027532 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Console\Command\SchemaTool; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Symfony\Component\Console\Input\InputInterface, Symfony\Component\Console\Output\OutputInterface, Doctrine\ORM\Tools\SchemaTool; /** * Command to generate the SQL needed to update the database schema to match * the current mapping information. * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Ryan Weaver */ class UpdateCommand extends AbstractCommand { protected $name = 'orm:schema-tool:update'; /** * @see Console\Command\Command */ protected function configure() { $this ->setName($this->name) ->setDescription( 'Executes (or dumps) the SQL needed to update the database schema to match the current mapping metadata.' ) ->setDefinition(array( new InputOption( 'complete', null, InputOption::VALUE_NONE, 'If defined, all assets of the database which are not relevant to the current metadata will be dropped.' ), new InputOption( 'dump-sql', null, InputOption::VALUE_NONE, 'Dumps the generated SQL statements to the screen (does not execute them).' ), new InputOption( 'force', null, InputOption::VALUE_NONE, 'Causes the generated SQL statements to be physically executed against your database.' ), )); $this->setHelp(<<%command.name% command generates the SQL needed to synchronize the database schema with the current mapping metadata of the default entity manager. For example, if you add metadata for a new column to an entity, this command would generate and output the SQL needed to add the new column to the database: %command.name% --dump-sql Alternatively, you can execute the generated queries: %command.name% --force Finally, be aware that if the --complete option is passed, this task will drop all database assets (e.g. tables, etc) that are *not* described by the current metadata. In other words, without this option, this task leaves untouched any "extra" tables that exist in the database, but which aren't described by any metadata. EOT ); } protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas) { // Defining if update is complete or not (--complete not defined means $saveMode = true) $saveMode = ($input->getOption('complete') !== true); $sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode); if (0 == count($sqls)) { $output->writeln('Nothing to update - your database is already in sync with the current entity metadata.'); return; } $dumpSql = (true === $input->getOption('dump-sql')); $force = (true === $input->getOption('force')); if ($dumpSql && $force) { throw new \InvalidArgumentException('You can pass either the --dump-sql or the --force option (but not both simultaneously).'); } if ($dumpSql) { $output->writeln(implode(';' . PHP_EOL, $sqls)); } else if ($force) { $output->writeln('Updating database schema...'); $schemaTool->updateSchema($metadatas, $saveMode); $output->writeln(sprintf('Database schema updated successfully! "%s" queries were executed', count($sqls))); } else { $output->writeln('ATTENTION: This operation should not be executed in a production environment.'); $output->writeln(' Use the incremental update to detect changes during development and use'); $output->writeln(' the SQL DDL provided to manually update your database in production.'); $output->writeln(''); $output->writeln(sprintf('The Schema-Tool would execute "%s" queries to update the database.', count($sqls))); $output->writeln('Please run the operation by passing one of the following options:'); $output->writeln(sprintf(' %s --force to execute the command', $this->getName())); $output->writeln(sprintf(' %s --dump-sql to dump the SQL statements to the screen', $this->getName())); } } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Console/Helper/EntityManagerHelper.php0000644000175100017510000000400012143374607026520 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Console\Helper; use Symfony\Component\Console\Helper\Helper, Doctrine\ORM\EntityManager; /** * Doctrine CLI Connection Helper. * * * @link www.doctrine-project.org * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class EntityManagerHelper extends Helper { /** * Doctrine ORM EntityManager * @var EntityManager */ protected $_em; /** * Constructor * * @param Connection $connection Doctrine Database Connection */ public function __construct(EntityManager $em) { $this->_em = $em; } /** * Retrieves Doctrine ORM EntityManager * * @return EntityManager */ public function getEntityManager() { return $this->_em; } /** * @see Helper */ public function getName() { return 'entityManager'; } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Event/GenerateSchemaEventArgs.php0000644000175100017510000000365612143374607025563 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Event; use Doctrine\DBAL\Schema\Schema; use Doctrine\ORM\EntityManager; /** * Event Args used for the Events::postGenerateSchema event. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @author Benjamin Eberlei */ class GenerateSchemaEventArgs extends \Doctrine\Common\EventArgs { private $_em = null; private $_schema = null; /** * @param ClassMetadata $classMetadata * @param Schema $schema * @param Table $classTable */ public function __construct(EntityManager $em, Schema $schema) { $this->_em = $em; $this->_schema = $schema; } /** * @return EntityManager */ public function getEntityManager() { return $this->_em; } /** * @return Schema */ public function getSchema() { return $this->_schema; } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Event/GenerateSchemaTableEventArgs.php0000644000175100017510000000435112143374607026524 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Event; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Table; /** * Event Args used for the Events::postGenerateSchemaTable event. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @author Benjamin Eberlei */ class GenerateSchemaTableEventArgs extends \Doctrine\Common\EventArgs { private $_classMetadata = null; private $_schema = null; private $_classTable = null; /** * @param ClassMetadata $classMetadata * @param Schema $schema * @param Table $classTable */ public function __construct(ClassMetadata $classMetadata, Schema $schema, Table $classTable) { $this->_classMetadata = $classMetadata; $this->_schema = $schema; $this->_classTable = $classTable; } /** * @return ClassMetadata */ public function getClassMetadata() { return $this->_classMetadata; } /** * @return Schema */ public function getSchema() { return $this->_schema; } /** * @return Table */ public function getClassTable() { return $this->_classTable; } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Export/ClassMetadataExporter.php0000644000175100017510000000500712143374607025520 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Export; use Doctrine\ORM\Tools\Export\ExportException, Doctrine\ORM\EntityManager; /** * Class used for converting your mapping information between the * supported formats: yaml, xml, and php/annotation. * * * @link www.doctrine-project.org * @since 2.0 * @author Jonathan Wage */ class ClassMetadataExporter { private static $_exporterDrivers = array( 'xml' => 'Doctrine\ORM\Tools\Export\Driver\XmlExporter', 'yaml' => 'Doctrine\ORM\Tools\Export\Driver\YamlExporter', 'yml' => 'Doctrine\ORM\Tools\Export\Driver\YamlExporter', 'php' => 'Doctrine\ORM\Tools\Export\Driver\PhpExporter', 'annotation' => 'Doctrine\ORM\Tools\Export\Driver\AnnotationExporter' ); /** * Register a new exporter driver class under a specified name * * @param string $name * @param string $class */ public static function registerExportDriver($name, $class) { self::$_exporterDrivers[$name] = $class; } /** * Get a exporter driver instance * * @param string $type The type to get (yml, xml, etc.) * @param string $source The directory where the exporter will export to * @return AbstractExporter $exporter */ public function getExporter($type, $dest = null) { if ( ! isset(self::$_exporterDrivers[$type])) { throw ExportException::invalidExporterDriverType($type); } $class = self::$_exporterDrivers[$type]; return new $class($dest); } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Export/ExportException.php0000644000175100017510000000107612143374607024423 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Export\Driver; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Tools\Export\ExportException; /** * Abstract base class which is to be used for the Exporter drivers * which can be found in \Doctrine\ORM\Tools\Export\Driver * * * @link www.doctrine-project.org * @since 2.0 * @author Jonathan Wage */ abstract class AbstractExporter { protected $_metadata = array(); protected $_outputDir; protected $_extension; protected $_overwriteExistingFiles = false; public function __construct($dir = null) { $this->_outputDir = $dir; } public function setOverwriteExistingFiles($overwrite) { $this->_overwriteExistingFiles = $overwrite; } /** * Converts a single ClassMetadata instance to the exported format * and returns it * * @param ClassMetadataInfo $metadata * @return mixed $exported */ abstract public function exportClassMetadata(ClassMetadataInfo $metadata); /** * Set the array of ClassMetadataInfo instances to export * * @param array $metadata * @return void */ public function setMetadata(array $metadata) { $this->_metadata = $metadata; } /** * Get the extension used to generated the path to a class * * @return string $extension */ public function getExtension() { return $this->_extension; } /** * Set the directory to output the mapping files to * * [php] * $exporter = new YamlExporter($metadata); * $exporter->setOutputDir(__DIR__ . '/yaml'); * $exporter->export(); * * @param string $dir * @return void */ public function setOutputDir($dir) { $this->_outputDir = $dir; } /** * Export each ClassMetadata instance to a single Doctrine Mapping file * named after the entity * * @return void */ public function export() { if ( ! is_dir($this->_outputDir)) { mkdir($this->_outputDir, 0777, true); } foreach ($this->_metadata as $metadata) { //In case output is returned, write it to a file, skip otherwise if($output = $this->exportClassMetadata($metadata)){ $path = $this->_generateOutputPath($metadata); $dir = dirname($path); if ( ! is_dir($dir)) { mkdir($dir, 0777, true); } if (file_exists($path) && !$this->_overwriteExistingFiles) { throw ExportException::attemptOverwriteExistingFile($path); } file_put_contents($path, $output); } } } /** * Generate the path to write the class for the given ClassMetadataInfo instance * * @param ClassMetadataInfo $metadata * @return string $path */ protected function _generateOutputPath(ClassMetadataInfo $metadata) { return $this->_outputDir . '/' . str_replace('\\', '.', $metadata->name) . $this->_extension; } /** * Set the directory to output the mapping files to * * [php] * $exporter = new YamlExporter($metadata, __DIR__ . '/yaml'); * $exporter->setExtension('.yml'); * $exporter->export(); * * @param string $extension * @return void */ public function setExtension($extension) { $this->_extension = $extension; } protected function _getInheritanceTypeString($type) { switch ($type) { case ClassMetadataInfo::INHERITANCE_TYPE_NONE: return 'NONE'; break; case ClassMetadataInfo::INHERITANCE_TYPE_JOINED: return 'JOINED'; break; case ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE: return 'SINGLE_TABLE'; break; case ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS: return 'PER_CLASS'; break; } } protected function _getChangeTrackingPolicyString($policy) { switch ($policy) { case ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT: return 'DEFERRED_IMPLICIT'; break; case ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT: return 'DEFERRED_EXPLICIT'; break; case ClassMetadataInfo::CHANGETRACKING_NOTIFY: return 'NOTIFY'; break; } } protected function _getIdGeneratorTypeString($type) { switch ($type) { case ClassMetadataInfo::GENERATOR_TYPE_AUTO: return 'AUTO'; break; case ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE: return 'SEQUENCE'; break; case ClassMetadataInfo::GENERATOR_TYPE_TABLE: return 'TABLE'; break; case ClassMetadataInfo::GENERATOR_TYPE_IDENTITY: return 'IDENTITY'; break; } } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Export/Driver/AnnotationExporter.php0000644000175100017510000000476212143374607026366 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Export\Driver; use Doctrine\ORM\Mapping\ClassMetadataInfo, Doctrine\ORM\Tools\EntityGenerator; /** * ClassMetadata exporter for PHP classes with annotations * * * @link www.doctrine-project.org * @since 2.0 * @author Jonathan Wage */ class AnnotationExporter extends AbstractExporter { protected $_extension = '.php'; private $_entityGenerator; /** * Converts a single ClassMetadata instance to the exported format * and returns it * * @param ClassMetadataInfo $metadata * @return string $exported */ public function exportClassMetadata(ClassMetadataInfo $metadata) { if ( ! $this->_entityGenerator) { throw new \RuntimeException('For the AnnotationExporter you must set an EntityGenerator instance with the setEntityGenerator() method.'); } $this->_entityGenerator->setGenerateAnnotations(true); $this->_entityGenerator->setGenerateStubMethods(false); $this->_entityGenerator->setRegenerateEntityIfExists(false); $this->_entityGenerator->setUpdateEntityIfExists(false); return $this->_entityGenerator->generateEntityClass($metadata); } protected function _generateOutputPath(ClassMetadataInfo $metadata) { return $this->_outputDir . '/' . str_replace('\\', '/', $metadata->name) . $this->_extension; } public function setEntityGenerator(EntityGenerator $entityGenerator) { $this->_entityGenerator = $entityGenerator; } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Export/Driver/PhpExporter.php0000644000175100017510000001545212143374607025001 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Export\Driver; use Doctrine\ORM\Mapping\ClassMetadataInfo; /** * ClassMetadata exporter for PHP code * * * @link www.doctrine-project.org * @since 2.0 * @author Jonathan Wage */ class PhpExporter extends AbstractExporter { protected $_extension = '.php'; /** * Converts a single ClassMetadata instance to the exported format * and returns it * * @param ClassMetadataInfo $metadata * @return mixed $exported */ public function exportClassMetadata(ClassMetadataInfo $metadata) { $lines = array(); $lines[] = 'isMappedSuperclass) { $lines[] = '$metadata->isMappedSuperclass = true;'; } if ($metadata->inheritanceType) { $lines[] = '$metadata->setInheritanceType(ClassMetadataInfo::INHERITANCE_TYPE_' . $this->_getInheritanceTypeString($metadata->inheritanceType) . ');'; } if ($metadata->customRepositoryClassName) { $lines[] = "\$metadata->customRepositoryClassName = '" . $metadata->customRepositoryClassName . "';"; } if ($metadata->table) { $lines[] = '$metadata->setPrimaryTable(' . $this->_varExport($metadata->table) . ');'; } if ($metadata->discriminatorColumn) { $lines[] = '$metadata->setDiscriminatorColumn(' . $this->_varExport($metadata->discriminatorColumn) . ');'; } if ($metadata->discriminatorMap) { $lines[] = '$metadata->setDiscriminatorMap(' . $this->_varExport($metadata->discriminatorMap) . ');'; } if ($metadata->changeTrackingPolicy) { $lines[] = '$metadata->setChangeTrackingPolicy(ClassMetadataInfo::CHANGETRACKING_' . $this->_getChangeTrackingPolicyString($metadata->changeTrackingPolicy) . ');'; } if ($metadata->lifecycleCallbacks) { foreach ($metadata->lifecycleCallbacks as $event => $callbacks) { foreach ($callbacks as $callback) { $lines[] = "\$metadata->addLifecycleCallback('$callback', '$event');"; } } } foreach ($metadata->fieldMappings as $fieldMapping) { $lines[] = '$metadata->mapField(' . $this->_varExport($fieldMapping) . ');'; } if ( ! $metadata->isIdentifierComposite && $generatorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) { $lines[] = '$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_' . $generatorType . ');'; } foreach ($metadata->associationMappings as $associationMapping) { $cascade = array('remove', 'persist', 'refresh', 'merge', 'detach'); foreach ($cascade as $key => $value) { if ( ! $associationMapping['isCascade'.ucfirst($value)]) { unset($cascade[$key]); } } $associationMappingArray = array( 'fieldName' => $associationMapping['fieldName'], 'targetEntity' => $associationMapping['targetEntity'], 'cascade' => $cascade, ); if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { $method = 'mapOneToOne'; $oneToOneMappingArray = array( 'mappedBy' => $associationMapping['mappedBy'], 'inversedBy' => $associationMapping['inversedBy'], 'joinColumns' => $associationMapping['joinColumns'], 'orphanRemoval' => $associationMapping['orphanRemoval'], ); $associationMappingArray = array_merge($associationMappingArray, $oneToOneMappingArray); } else if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_MANY) { $method = 'mapOneToMany'; $potentialAssociationMappingIndexes = array( 'mappedBy', 'orphanRemoval', 'orderBy', ); foreach ($potentialAssociationMappingIndexes as $index) { if (isset($associationMapping[$index])) { $oneToManyMappingArray[$index] = $associationMapping[$index]; } } $associationMappingArray = array_merge($associationMappingArray, $oneToManyMappingArray); } else if ($associationMapping['type'] == ClassMetadataInfo::MANY_TO_MANY) { $method = 'mapManyToMany'; $potentialAssociationMappingIndexes = array( 'mappedBy', 'joinTable', 'orderBy', ); foreach ($potentialAssociationMappingIndexes as $index) { if (isset($associationMapping[$index])) { $manyToManyMappingArray[$index] = $associationMapping[$index]; } } $associationMappingArray = array_merge($associationMappingArray, $manyToManyMappingArray); } $lines[] = '$metadata->' . $method . '(' . $this->_varExport($associationMappingArray) . ');'; } return implode("\n", $lines); } protected function _varExport($var) { $export = var_export($var, true); $export = str_replace("\n", PHP_EOL . str_repeat(' ', 8), $export); $export = str_replace(' ', ' ', $export); $export = str_replace('array (', 'array(', $export); $export = str_replace('array( ', 'array(', $export); $export = str_replace(',)', ')', $export); $export = str_replace(', )', ')', $export); $export = str_replace(' ', ' ', $export); return $export; } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php0000644000175100017510000003755112143374607025016 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Export\Driver; use Doctrine\ORM\Mapping\ClassMetadataInfo; /** * ClassMetadata exporter for Doctrine XML mapping files * * * @link www.doctrine-project.org * @since 2.0 * @author Jonathan Wage */ class XmlExporter extends AbstractExporter { protected $_extension = '.dcm.xml'; /** * Converts a single ClassMetadata instance to the exported format * and returns it * * @param ClassMetadataInfo $metadata * @return mixed $exported */ public function exportClassMetadata(ClassMetadataInfo $metadata) { $xml = new \SimpleXmlElement(""); /*$xml->addAttribute('xmlns', 'http://doctrine-project.org/schemas/orm/doctrine-mapping'); $xml->addAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); $xml->addAttribute('xsi:schemaLocation', 'http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd');*/ if ($metadata->isMappedSuperclass) { $root = $xml->addChild('mapped-superclass'); } else { $root = $xml->addChild('entity'); } if ($metadata->customRepositoryClassName) { $root->addAttribute('repository-class', $metadata->customRepositoryClassName); } $root->addAttribute('name', $metadata->name); if (isset($metadata->table['name'])) { $root->addAttribute('table', $metadata->table['name']); } if (isset($metadata->table['schema'])) { $root->addAttribute('schema', $metadata->table['schema']); } if (isset($metadata->table['inheritance-type'])) { $root->addAttribute('inheritance-type', $metadata->table['inheritance-type']); } if ($metadata->discriminatorColumn) { $discriminatorColumnXml = $root->addChild('discriminator-column'); $discriminatorColumnXml->addAttribute('name', $metadata->discriminatorColumn['name']); $discriminatorColumnXml->addAttribute('type', $metadata->discriminatorColumn['type']); if (isset($metadata->discriminatorColumn['length'])) { $discriminatorColumnXml->addAttribute('length', $metadata->discriminatorColumn['length']); } } if ($metadata->discriminatorMap) { $discriminatorMapXml = $root->addChild('discriminator-map'); foreach ($metadata->discriminatorMap as $value => $className) { $discriminatorMappingXml = $discriminatorMapXml->addChild('discriminator-mapping'); $discriminatorMappingXml->addAttribute('value', $value); $discriminatorMappingXml->addAttribute('class', $className); } } $trackingPolicy = $this->_getChangeTrackingPolicyString($metadata->changeTrackingPolicy); if ( $trackingPolicy != 'DEFERRED_IMPLICIT') { $root->addChild('change-tracking-policy', $trackingPolicy); } if (isset($metadata->table['indexes'])) { $indexesXml = $root->addChild('indexes'); foreach ($metadata->table['indexes'] as $name => $index) { $indexXml = $indexesXml->addChild('index'); $indexXml->addAttribute('name', $name); $indexXml->addAttribute('columns', implode(',', $index['columns'])); } } if (isset($metadata->table['uniqueConstraints'])) { $uniqueConstraintsXml = $root->addChild('unique-constraints'); foreach ($metadata->table['uniqueConstraints'] as $name => $unique) { $uniqueConstraintXml = $uniqueConstraintsXml->addChild('unique-constraint'); $uniqueConstraintXml->addAttribute('name', $name); $uniqueConstraintXml->addAttribute('columns', implode(',', $unique['columns'])); } } $fields = $metadata->fieldMappings; $id = array(); foreach ($fields as $name => $field) { if (isset($field['id']) && $field['id']) { $id[$name] = $field; unset($fields[$name]); } } if ( ! $metadata->isIdentifierComposite && $idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) { $id[$metadata->getSingleIdentifierFieldName()]['generator']['strategy'] = $idGeneratorType; } if ($id) { foreach ($id as $field) { $idXml = $root->addChild('id'); $idXml->addAttribute('name', $field['fieldName']); $idXml->addAttribute('type', $field['type']); if (isset($field['columnName'])) { $idXml->addAttribute('column', $field['columnName']); } if (isset($field['associationKey']) && $field['associationKey']) { $idXml->addAttribute('association-key', 'true'); } if ($idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) { $generatorXml = $idXml->addChild('generator'); $generatorXml->addAttribute('strategy', $idGeneratorType); } } } if ($fields) { foreach ($fields as $field) { $fieldXml = $root->addChild('field'); $fieldXml->addAttribute('name', $field['fieldName']); $fieldXml->addAttribute('type', $field['type']); if (isset($field['columnName'])) { $fieldXml->addAttribute('column', $field['columnName']); } if (isset($field['length'])) { $fieldXml->addAttribute('length', $field['length']); } if (isset($field['precision'])) { $fieldXml->addAttribute('precision', $field['precision']); } if (isset($field['scale'])) { $fieldXml->addAttribute('scale', $field['scale']); } if (isset($field['unique']) && $field['unique']) { $fieldXml->addAttribute('unique', $field['unique']); } if (isset($field['options'])) { $optionsXml = $fieldXml->addChild('options'); foreach ($field['options'] as $key => $value) { $optionsXml->addAttribute($key, $value); } } if (isset($field['version'])) { $fieldXml->addAttribute('version', $field['version']); } if (isset($field['columnDefinition'])) { $fieldXml->addAttribute('column-definition', $field['columnDefinition']); } if (isset($field['nullable'])) { $fieldXml->addAttribute('nullable', $field['nullable'] ? 'true' : 'false'); } } } $orderMap = array( ClassMetadataInfo::ONE_TO_ONE, ClassMetadataInfo::ONE_TO_MANY, ClassMetadataInfo::MANY_TO_ONE, ClassMetadataInfo::MANY_TO_MANY, ); uasort($metadata->associationMappings, function($m1, $m2)use(&$orderMap){ $a1 = array_search($m1['type'],$orderMap); $a2 = array_search($m2['type'],$orderMap); return strcmp($a1, $a2); }); foreach ($metadata->associationMappings as $name => $associationMapping) { if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_ONE) { $associationMappingXml = $root->addChild('one-to-one'); } else if ($associationMapping['type'] == ClassMetadataInfo::MANY_TO_ONE) { $associationMappingXml = $root->addChild('many-to-one'); } else if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_MANY) { $associationMappingXml = $root->addChild('one-to-many'); } else if ($associationMapping['type'] == ClassMetadataInfo::MANY_TO_MANY) { $associationMappingXml = $root->addChild('many-to-many'); } $associationMappingXml->addAttribute('field', $associationMapping['fieldName']); $associationMappingXml->addAttribute('target-entity', $associationMapping['targetEntity']); if (isset($associationMapping['mappedBy'])) { $associationMappingXml->addAttribute('mapped-by', $associationMapping['mappedBy']); } if (isset($associationMapping['inversedBy'])) { $associationMappingXml->addAttribute('inversed-by', $associationMapping['inversedBy']); } if (isset($associationMapping['indexBy'])) { $associationMappingXml->addAttribute('index-by', $associationMapping['indexBy']); } if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']!==false) { $associationMappingXml->addAttribute('orphan-removal', 'true'); } if (isset($associationMapping['joinTable']) && $associationMapping['joinTable']) { $joinTableXml = $associationMappingXml->addChild('join-table'); $joinTableXml->addAttribute('name', $associationMapping['joinTable']['name']); $joinColumnsXml = $joinTableXml->addChild('join-columns'); foreach ($associationMapping['joinTable']['joinColumns'] as $joinColumn) { $joinColumnXml = $joinColumnsXml->addChild('join-column'); $joinColumnXml->addAttribute('name', $joinColumn['name']); $joinColumnXml->addAttribute('referenced-column-name', $joinColumn['referencedColumnName']); if (isset($joinColumn['onDelete'])) { $joinColumnXml->addAttribute('on-delete', $joinColumn['onDelete']); } } $inverseJoinColumnsXml = $joinTableXml->addChild('inverse-join-columns'); foreach ($associationMapping['joinTable']['inverseJoinColumns'] as $inverseJoinColumn) { $inverseJoinColumnXml = $inverseJoinColumnsXml->addChild('join-column'); $inverseJoinColumnXml->addAttribute('name', $inverseJoinColumn['name']); $inverseJoinColumnXml->addAttribute('referenced-column-name', $inverseJoinColumn['referencedColumnName']); if (isset($inverseJoinColumn['onDelete'])) { $inverseJoinColumnXml->addAttribute('on-delete', $inverseJoinColumn['onDelete']); } if (isset($inverseJoinColumn['columnDefinition'])) { $inverseJoinColumnXml->addAttribute('column-definition', $inverseJoinColumn['columnDefinition']); } if (isset($inverseJoinColumn['nullable'])) { $inverseJoinColumnXml->addAttribute('nullable', $inverseJoinColumn['nullable']); } if (isset($inverseJoinColumn['orderBy'])) { $inverseJoinColumnXml->addAttribute('order-by', $inverseJoinColumn['orderBy']); } } } if (isset($associationMapping['joinColumns'])) { $joinColumnsXml = $associationMappingXml->addChild('join-columns'); foreach ($associationMapping['joinColumns'] as $joinColumn) { $joinColumnXml = $joinColumnsXml->addChild('join-column'); $joinColumnXml->addAttribute('name', $joinColumn['name']); $joinColumnXml->addAttribute('referenced-column-name', $joinColumn['referencedColumnName']); if (isset($joinColumn['onDelete'])) { $joinColumnXml->addAttribute('on-delete', $joinColumn['onDelete']); } if (isset($joinColumn['columnDefinition'])) { $joinColumnXml->addAttribute('column-definition', $joinColumn['columnDefinition']); } if (isset($joinColumn['nullable'])) { $joinColumnXml->addAttribute('nullable', $joinColumn['nullable']); } } } if (isset($associationMapping['orderBy'])) { $orderByXml = $associationMappingXml->addChild('order-by'); foreach ($associationMapping['orderBy'] as $name => $direction) { $orderByFieldXml = $orderByXml->addChild('order-by-field'); $orderByFieldXml->addAttribute('name', $name); $orderByFieldXml->addAttribute('direction', $direction); } } $cascade = array(); if ($associationMapping['isCascadeRemove']) { $cascade[] = 'cascade-remove'; } if ($associationMapping['isCascadePersist']) { $cascade[] = 'cascade-persist'; } if ($associationMapping['isCascadeRefresh']) { $cascade[] = 'cascade-refresh'; } if ($associationMapping['isCascadeMerge']) { $cascade[] = 'cascade-merge'; } if ($associationMapping['isCascadeDetach']) { $cascade[] = 'cascade-detach'; } if (count($cascade) === 5) { $cascade = array('cascade-all'); } if ($cascade) { $cascadeXml = $associationMappingXml->addChild('cascade'); foreach ($cascade as $type) { $cascadeXml->addChild($type); } } } if (isset($metadata->lifecycleCallbacks) && count($metadata->lifecycleCallbacks)>0) { $lifecycleCallbacksXml = $root->addChild('lifecycle-callbacks'); foreach ($metadata->lifecycleCallbacks as $name => $methods) { foreach ($methods as $method) { $lifecycleCallbackXml = $lifecycleCallbacksXml->addChild('lifecycle-callback'); $lifecycleCallbackXml->addAttribute('type', $name); $lifecycleCallbackXml->addAttribute('method', $method); } } } return $this->_asXml($xml); } /** * @param \SimpleXMLElement $simpleXml * @return string $xml */ private function _asXml($simpleXml) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->loadXML($simpleXml->asXML()); $dom->formatOutput = true; $result = $dom->saveXML(); return $result; } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php0000644000175100017510000002013012143374607025141 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Export\Driver; use Doctrine\ORM\Mapping\ClassMetadataInfo; /** * ClassMetadata exporter for Doctrine YAML mapping files * * * @link www.doctrine-project.org * @since 2.0 * @author Jonathan Wage */ class YamlExporter extends AbstractExporter { protected $_extension = '.dcm.yml'; /** * Converts a single ClassMetadata instance to the exported format * and returns it * * TODO: Should this code be pulled out in to a toArray() method in ClassMetadata * * @param ClassMetadataInfo $metadata * @return mixed $exported */ public function exportClassMetadata(ClassMetadataInfo $metadata) { $array = array(); if ($metadata->isMappedSuperclass) { $array['type'] = 'mappedSuperclass'; } else { $array['type'] = 'entity'; } $array['table'] = $metadata->table['name']; if (isset($metadata->table['schema'])) { $array['schema'] = $metadata->table['schema']; } $inheritanceType = $metadata->inheritanceType; if ($inheritanceType !== ClassMetadataInfo::INHERITANCE_TYPE_NONE) { $array['inheritanceType'] = $this->_getInheritanceTypeString($inheritanceType); } if ($column = $metadata->discriminatorColumn) { $array['discriminatorColumn'] = $column; } if ($map = $metadata->discriminatorMap) { $array['discriminatorMap'] = $map; } if ($metadata->changeTrackingPolicy !== ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT) { $array['changeTrackingPolicy'] = $this->_getChangeTrackingPolicyString($metadata->changeTrackingPolicy); } if (isset($metadata->table['indexes'])) { $array['indexes'] = $metadata->table['indexes']; } if ($metadata->customRepositoryClassName) { $array['repositoryClass'] = $metadata->customRepositoryClassName; } if (isset($metadata->table['uniqueConstraints'])) { $array['uniqueConstraints'] = $metadata->table['uniqueConstraints']; } $fieldMappings = $metadata->fieldMappings; $ids = array(); foreach ($fieldMappings as $name => $fieldMapping) { $fieldMapping['column'] = $fieldMapping['columnName']; unset( $fieldMapping['columnName'], $fieldMapping['fieldName'] ); if ($fieldMapping['column'] == $name) { unset($fieldMapping['column']); } if (isset($fieldMapping['id']) && $fieldMapping['id']) { $ids[$name] = $fieldMapping; unset($fieldMappings[$name]); continue; } $fieldMappings[$name] = $fieldMapping; } if ( ! $metadata->isIdentifierComposite && $idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) { $ids[$metadata->getSingleIdentifierFieldName()]['generator']['strategy'] = $idGeneratorType; } if ($ids) { $array['fields'] = $ids; } if ($fieldMappings) { if ( ! isset($array['fields'])) { $array['fields'] = array(); } $array['fields'] = array_merge($array['fields'], $fieldMappings); } foreach ($metadata->associationMappings as $name => $associationMapping) { $cascade = array(); if ($associationMapping['isCascadeRemove']) { $cascade[] = 'remove'; } if ($associationMapping['isCascadePersist']) { $cascade[] = 'persist'; } if ($associationMapping['isCascadeRefresh']) { $cascade[] = 'refresh'; } if ($associationMapping['isCascadeMerge']) { $cascade[] = 'merge'; } if ($associationMapping['isCascadeDetach']) { $cascade[] = 'detach'; } if (count($cascade) === 5) { $cascade = array('all'); } $associationMappingArray = array( 'targetEntity' => $associationMapping['targetEntity'], 'cascade' => $cascade, ); if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { $joinColumns = $associationMapping['joinColumns']; $newJoinColumns = array(); foreach ($joinColumns as $joinColumn) { $newJoinColumns[$joinColumn['name']]['referencedColumnName'] = $joinColumn['referencedColumnName']; if (isset($joinColumn['onDelete'])) { $newJoinColumns[$joinColumn['name']]['onDelete'] = $joinColumn['onDelete']; } } $oneToOneMappingArray = array( 'mappedBy' => $associationMapping['mappedBy'], 'inversedBy' => $associationMapping['inversedBy'], 'joinColumns' => $newJoinColumns, 'orphanRemoval' => $associationMapping['orphanRemoval'], ); $associationMappingArray = array_merge($associationMappingArray, $oneToOneMappingArray); if ($associationMapping['type'] & ClassMetadataInfo::ONE_TO_ONE) { $array['oneToOne'][$name] = $associationMappingArray; } else { $array['manyToOne'][$name] = $associationMappingArray; } } else if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_MANY) { $oneToManyMappingArray = array( 'mappedBy' => $associationMapping['mappedBy'], 'inversedBy' => $associationMapping['inversedBy'], 'orphanRemoval' => $associationMapping['orphanRemoval'], 'orderBy' => isset($associationMapping['orderBy']) ? $associationMapping['orderBy'] : null ); $associationMappingArray = array_merge($associationMappingArray, $oneToManyMappingArray); $array['oneToMany'][$name] = $associationMappingArray; } else if ($associationMapping['type'] == ClassMetadataInfo::MANY_TO_MANY) { $manyToManyMappingArray = array( 'mappedBy' => $associationMapping['mappedBy'], 'inversedBy' => $associationMapping['inversedBy'], 'joinTable' => isset($associationMapping['joinTable']) ? $associationMapping['joinTable'] : null, 'orderBy' => isset($associationMapping['orderBy']) ? $associationMapping['orderBy'] : null ); $associationMappingArray = array_merge($associationMappingArray, $manyToManyMappingArray); $array['manyToMany'][$name] = $associationMappingArray; } } if (isset($metadata->lifecycleCallbacks)) { $array['lifecycleCallbacks'] = $metadata->lifecycleCallbacks; } return \Symfony\Component\Yaml\Yaml::dump(array($metadata->name => $array), 10); } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Pagination/CountOutputWalker.php0000644000175100017510000001146112143374607025551 0ustar beberleibeberlei FROM ()) * * Works with composite keys but cannot deal with queries that have multiple * root entities (e.g. `SELECT f, b from Foo, Bar`) * * @author Sander Marechal */ class CountOutputWalker extends SqlWalker { /** * @var Doctrine\DBAL\Platforms\AbstractPlatform */ private $platform; /** * @var Doctrine\ORM\Query\ResultSetMapping */ private $rsm; /** * @var array */ private $queryComponents; /** * Constructor. Stores various parameters that are otherwise unavailable * because Doctrine\ORM\Query\SqlWalker keeps everything private without * accessors. * * @param Doctrine\ORM\Query $query * @param Doctrine\ORM\Query\ParserResult $parserResult * @param array $queryComponents */ public function __construct($query, $parserResult, array $queryComponents) { $this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform(); $this->rsm = $parserResult->getResultSetMapping(); $this->queryComponents = $queryComponents; parent::__construct($query, $parserResult, $queryComponents); } /** * Walks down a SelectStatement AST node, wrapping it in a COUNT (SELECT DISTINCT) * * Note that the ORDER BY clause is not removed. Many SQL implementations (e.g. MySQL) * are able to cache subqueries. By keeping the ORDER BY clause intact, the limitSubQuery * that will most likely be executed next can be read from the native SQL cache. * * @param SelectStatement $AST * @return string */ public function walkSelectStatement(SelectStatement $AST) { if ($this->platform->getName() === "mssql") { $AST->orderByClause = null; } $sql = parent::walkSelectStatement($AST); // Find out the SQL alias of the identifier column of the root entity // It may be possible to make this work with multiple root entities but that // would probably require issuing multiple queries or doing a UNION SELECT // so for now, It's not supported. // Get the root entity and alias from the AST fromClause $from = $AST->fromClause->identificationVariableDeclarations; if (count($from) > 1) { throw new \RuntimeException("Cannot count query which selects two FROM components, cannot make distinction"); } $rootAlias = $from[0]->rangeVariableDeclaration->aliasIdentificationVariable; $rootClass = $this->queryComponents[$rootAlias]['metadata']; $rootIdentifier = $rootClass->identifier; // For every identifier, find out the SQL alias by combing through the ResultSetMapping $sqlIdentifier = array(); foreach ($rootIdentifier as $property) { if (isset($rootClass->fieldMappings[$property])) { foreach (array_keys($this->rsm->fieldMappings, $property) as $alias) { if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) { $sqlIdentifier[$property] = $alias; } } } if (isset($rootClass->associationMappings[$property])) { $joinColumn = $rootClass->associationMappings[$property]['joinColumns'][0]['name']; foreach (array_keys($this->rsm->metaMappings, $joinColumn) as $alias) { if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) { $sqlIdentifier[$property] = $alias; } } } } if (count($rootIdentifier) != count($sqlIdentifier)) { throw new \RuntimeException(sprintf( 'Not all identifier properties can be found in the ResultSetMapping: %s', implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier))) )); } // Build the counter query return sprintf('SELECT %s AS dctrn_count FROM (SELECT DISTINCT %s FROM (%s) dctrn_result) dctrn_table', $this->platform->getCountExpression('*'), implode(', ', $sqlIdentifier), $sql ); } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Pagination/CountWalker.php0000644000175100017510000000615612143374607024335 0ustar beberleibeberlei * @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/) * @license http://hobodave.com/license.txt New BSD License */ class CountWalker extends TreeWalkerAdapter { /** * Distinct mode hint name */ const HINT_DISTINCT = 'doctrine_paginator.distinct'; /** * Walks down a SelectStatement AST node, modifying it to retrieve a COUNT * * @param SelectStatement $AST * @return void */ public function walkSelectStatement(SelectStatement $AST) { if ($AST->havingClause) { throw new \RuntimeException('Cannot count query that uses a HAVING clause. Use the output walkers for pagination'); } $rootComponents = array(); foreach ($this->_getQueryComponents() as $dqlAlias => $qComp) { $isParent = array_key_exists('parent', $qComp) && $qComp['parent'] === null && $qComp['nestingLevel'] == 0 ; if ($isParent) { $rootComponents[] = array($dqlAlias => $qComp); } } if (count($rootComponents) > 1) { throw new \RuntimeException("Cannot count query which selects two FROM components, cannot make distinction"); } $root = reset($rootComponents); $parentName = key($root); $parent = current($root); $identifierFieldName = $parent['metadata']->getSingleIdentifierFieldName(); $pathType = PathExpression::TYPE_STATE_FIELD; if (isset($parent['metadata']->associationMappings[$identifierFieldName])) { $pathType = PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION; } $pathExpression = new PathExpression( PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName, $identifierFieldName ); $pathExpression->type = $pathType; $distinct = $this->_getQuery()->getHint(self::HINT_DISTINCT); $AST->selectClause->selectExpressions = array( new SelectExpression( new AggregateExpression('count', $pathExpression, $distinct), null ) ); // ORDER BY is not needed, only increases query execution through unnecessary sorting. $AST->orderByClause = null; } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Pagination/LimitSubqueryOutputWalker.php0000644000175100017510000001620612143374607027301 0ustar beberleibeberlei FROM () LIMIT x OFFSET y * * Works with composite keys but cannot deal with queries that have multiple * root entities (e.g. `SELECT f, b from Foo, Bar`) * * @author Sander Marechal */ class LimitSubqueryOutputWalker extends SqlWalker { /** * @var Doctrine\DBAL\Platforms\AbstractPlatform */ private $platform; /** * @var Doctrine\ORM\Query\ResultSetMapping */ private $rsm; /** * @var array */ private $queryComponents; /** * @var int */ private $firstResult; /** * @var int */ private $maxResults; /** * Constructor. Stores various parameters that are otherwise unavailable * because Doctrine\ORM\Query\SqlWalker keeps everything private without * accessors. * * @param Doctrine\ORM\Query $query * @param Doctrine\ORM\Query\ParserResult $parserResult * @param array $queryComponents */ public function __construct($query, $parserResult, array $queryComponents) { $this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform(); $this->rsm = $parserResult->getResultSetMapping(); $this->queryComponents = $queryComponents; // Reset limit and offset $this->firstResult = $query->getFirstResult(); $this->maxResults = $query->getMaxResults(); $query->setFirstResult(null)->setMaxResults(null); parent::__construct($query, $parserResult, $queryComponents); } /** * Walks down a SelectStatement AST node, wrapping it in a SELECT DISTINCT * * @param SelectStatement $AST * @return string */ public function walkSelectStatement(SelectStatement $AST) { $innerSql = parent::walkSelectStatement($AST); // Find out the SQL alias of the identifier column of the root entity // It may be possible to make this work with multiple root entities but that // would probably require issuing multiple queries or doing a UNION SELECT // so for now, It's not supported. // Get the root entity and alias from the AST fromClause $from = $AST->fromClause->identificationVariableDeclarations; if (count($from) !== 1) { throw new \RuntimeException("Cannot count query which selects two FROM components, cannot make distinction"); } $rootAlias = $from[0]->rangeVariableDeclaration->aliasIdentificationVariable; $rootClass = $this->queryComponents[$rootAlias]['metadata']; $rootIdentifier = $rootClass->identifier; // For every identifier, find out the SQL alias by combing through the ResultSetMapping $sqlIdentifier = array(); foreach ($rootIdentifier as $property) { if (isset($rootClass->fieldMappings[$property])) { foreach (array_keys($this->rsm->fieldMappings, $property) as $alias) { if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) { $sqlIdentifier[$property] = $alias; } } } if (isset($rootClass->associationMappings[$property])) { $joinColumn = $rootClass->associationMappings[$property]['joinColumns'][0]['name']; foreach (array_keys($this->rsm->metaMappings, $joinColumn) as $alias) { if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) { $sqlIdentifier[$property] = $alias; } } } } if (count($rootIdentifier) != count($sqlIdentifier)) { throw new \RuntimeException(sprintf( 'Not all identifier properties can be found in the ResultSetMapping: %s', implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier))) )); } // Build the counter query $sql = sprintf('SELECT DISTINCT %s FROM (%s) dctrn_result', implode(', ', $sqlIdentifier), $innerSql); if ($this->platform instanceof PostgreSqlPlatform) { //http://www.doctrine-project.org/jira/browse/DDC-1958 $this->getPostgresqlSql($AST, $sqlIdentifier, $innerSql, $sql); } // Apply the limit and offset $sql = $this->platform->modifyLimitQuery( $sql, $this->maxResults, $this->firstResult ); // Add the columns to the ResultSetMapping. It's not really nice but // it works. Preferably I'd clear the RSM or simply create a new one // but that is not possible from inside the output walker, so we dirty // up the one we have. foreach ($sqlIdentifier as $property => $alias) { $this->rsm->addScalarResult($alias, $property); } return $sql; } /** * Generate new SQL for postgresql if necessary * * @param SelectStatement $AST * @param array sqlIdentifier * @param string $sql */ public function getPostgresqlSql(SelectStatement $AST, array $sqlIdentifier, $innerSql, &$sql) { // For every order by, find out the SQL alias by inspecting the ResultSetMapping $sqlOrderColumns = array(); $orderBy = array(); if (isset($AST->orderByClause)) { foreach ($AST->orderByClause->orderByItems as $item) { $possibleAliases = array_keys($this->rsm->fieldMappings, $item->expression->field); foreach ($possibleAliases as $alias) { if ($this->rsm->columnOwnerMap[$alias] == $item->expression->identificationVariable) { $sqlOrderColumns[] = $alias; $orderBy[] = $alias . ' ' . $item->type; break; } } } //remove identifier aliases $sqlOrderColumns = array_diff($sqlOrderColumns, $sqlIdentifier); } //we don't need orderBy in inner query //However at least on 5.4.6 I'm getting a segmentation fault and thus we don't clear it for now /*$AST->orderByClause = null; $innerSql = parent::walkSelectStatement($AST);*/ if (count($orderBy)) { $sql = sprintf( 'SELECT DISTINCT %s FROM (%s) dctrn_result ORDER BY %s', implode(', ', array_merge($sqlIdentifier, $sqlOrderColumns)), $innerSql, implode(', ', $orderBy) ); } } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Pagination/LimitSubqueryWalker.php0000644000175100017510000001005012143374607026047 0ustar beberleibeberlei * @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/) * @license http://hobodave.com/license.txt New BSD License */ namespace Doctrine\ORM\Tools\Pagination; use Doctrine\DBAL\Types\Type, Doctrine\ORM\Query\TreeWalkerAdapter, Doctrine\ORM\Query\AST\SelectStatement, Doctrine\ORM\Query\AST\SelectExpression, Doctrine\ORM\Query\AST\PathExpression, Doctrine\ORM\Query\AST\AggregateExpression; /** * Replaces the selectClause of the AST with a SELECT DISTINCT root.id equivalent * * @category DoctrineExtensions * @package DoctrineExtensions\Paginate * @author David Abdemoulaie * @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/) * @license http://hobodave.com/license.txt New BSD License */ class LimitSubqueryWalker extends TreeWalkerAdapter { /** * ID type hint */ const IDENTIFIER_TYPE = 'doctrine_paginator.id.type'; /** * @var int Counter for generating unique order column aliases */ private $_aliasCounter = 0; /** * Walks down a SelectStatement AST node, modifying it to retrieve DISTINCT ids * of the root Entity * * @param SelectStatement $AST * @return void */ public function walkSelectStatement(SelectStatement $AST) { $parent = null; $parentName = null; $selectExpressions = array(); foreach ($this->_getQueryComponents() as $dqlAlias => $qComp) { // preserve mixed data in query for ordering if (isset($qComp['resultVariable'])) { $selectExpressions[] = new SelectExpression($qComp['resultVariable'], $dqlAlias); continue; } if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) { $parent = $qComp; $parentName = $dqlAlias; continue; } } $identifier = $parent['metadata']->getSingleIdentifierFieldName(); if (isset($parent['metadata']->associationMappings[$identifier])) { throw new \RuntimeException("Paginating an entity with foreign key as identifier only works when using the Output Walkers. Call Paginator#setUseOutputWalkers(true) before iterating the paginator."); } $this->_getQuery()->setHint( self::IDENTIFIER_TYPE, Type::getType($parent['metadata']->getTypeOfField($identifier)) ); $pathExpression = new PathExpression( PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName, $identifier ); $pathExpression->type = PathExpression::TYPE_STATE_FIELD; array_unshift($selectExpressions, new SelectExpression($pathExpression, '_dctrn_id')); $AST->selectClause->selectExpressions = $selectExpressions; if (isset($AST->orderByClause)) { foreach ($AST->orderByClause->orderByItems as $item) { if ($item->expression instanceof PathExpression) { $pathExpression = new PathExpression( PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $item->expression->identificationVariable, $item->expression->field ); $pathExpression->type = PathExpression::TYPE_STATE_FIELD; $AST->selectClause->selectExpressions[] = new SelectExpression( $pathExpression, '_dctrn_ord' . $this->_aliasCounter++ ); } } } $AST->selectClause->isDistinct = true; } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Pagination/Paginator.php0000644000175100017510000001563412143374607024024 0ustar beberleibeberlei. */ namespace Doctrine\ORM\Tools\Pagination; use Doctrine\ORM\QueryBuilder, Doctrine\ORM\Query, Doctrine\ORM\Query\ResultSetMapping, Doctrine\ORM\NoResultException; /** * Paginator * * The paginator can handle various complex scenarios with DQL. * * @author Pablo Díez * @author Benjamin Eberlei * @license New BSD */ class Paginator implements \Countable, \IteratorAggregate { /** * @var Query */ private $query; /** * @var bool */ private $fetchJoinCollection; /** * @var bool|null */ private $useOutputWalkers; /** * @var int */ private $count; /** * Constructor. * * @param Query|QueryBuilder $query A Doctrine ORM query or query builder. * @param Boolean $fetchJoinCollection Whether the query joins a collection (true by default). */ public function __construct($query, $fetchJoinCollection = true) { if ($query instanceof QueryBuilder) { $query = $query->getQuery(); } $this->query = $query; $this->fetchJoinCollection = (Boolean) $fetchJoinCollection; } /** * Returns the query * * @return Query */ public function getQuery() { return $this->query; } /** * Returns whether the query joins a collection. * * @return Boolean Whether the query joins a collection. */ public function getFetchJoinCollection() { return $this->fetchJoinCollection; } /** * Returns whether the paginator will use an output walker * * @return bool|null */ public function getUseOutputWalkers() { return $this->useOutputWalkers; } /** * Set whether the paginator will use an output walker * * @param bool|null $useOutputWalkers * @return $this */ public function setUseOutputWalkers($useOutputWalkers) { $this->useOutputWalkers = $useOutputWalkers; return $this; } /** * {@inheritdoc} */ public function count() { if ($this->count === null) { /* @var $countQuery Query */ $countQuery = $this->cloneQuery($this->query); if ( ! $countQuery->getHint(CountWalker::HINT_DISTINCT)) { $countQuery->setHint(CountWalker::HINT_DISTINCT, true); } if ($this->useOutputWalker($countQuery)) { $platform = $countQuery->getEntityManager()->getConnection()->getDatabasePlatform(); // law of demeter win $rsm = new ResultSetMapping(); $rsm->addScalarResult($platform->getSQLResultCasing('dctrn_count'), 'count'); $countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'Doctrine\ORM\Tools\Pagination\CountOutputWalker'); $countQuery->setResultSetMapping($rsm); } else { $countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\CountWalker')); } $countQuery->setFirstResult(null)->setMaxResults(null); try { $data = $countQuery->getScalarResult(); $data = array_map('current', $data); $this->count = array_sum($data); } catch(NoResultException $e) { $this->count = 0; } } return $this->count; } /** * {@inheritdoc} */ public function getIterator() { $offset = $this->query->getFirstResult(); $length = $this->query->getMaxResults(); if ($this->fetchJoinCollection) { $subQuery = $this->cloneQuery($this->query); if ($this->useOutputWalker($subQuery)) { $subQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'Doctrine\ORM\Tools\Pagination\LimitSubqueryOutputWalker'); } else { $subQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\LimitSubqueryWalker')); } $subQuery->setFirstResult($offset)->setMaxResults($length); $ids = array_map('current', $subQuery->getScalarResult()); $whereInQuery = $this->cloneQuery($this->query); // don't do this for an empty id array if (count($ids) == 0) { return new \ArrayIterator(array()); } $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\WhereInWalker')); $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, count($ids)); $whereInQuery->setFirstResult(null)->setMaxResults(null); $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, $ids); $result = $whereInQuery->getResult($this->query->getHydrationMode()); } else { $result = $this->cloneQuery($this->query) ->setMaxResults($length) ->setFirstResult($offset) ->getResult($this->query->getHydrationMode()) ; } return new \ArrayIterator($result); } /** * Clones a query. * * @param Query $query The query. * * @return Query The cloned query. */ private function cloneQuery(Query $query) { /* @var $cloneQuery Query */ $cloneQuery = clone $query; $cloneQuery->setParameters(clone $query->getParameters()); foreach ($query->getHints() as $name => $value) { $cloneQuery->setHint($name, $value); } return $cloneQuery; } /** * Determine whether to use an output walker for the query * * @param Query $query The query. * * @return bool */ private function useOutputWalker(Query $query) { if ($this->useOutputWalkers === null) { return (Boolean) $query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER) == false; } return $this->useOutputWalkers; } } DoctrineORM-2.3.3/Doctrine/ORM/Tools/Pagination/WhereInWalker.php0000644000175100017510000001277412143374607024611 0ustar beberleibeberlei * @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/) * @license http://hobodave.com/license.txt New BSD License */ namespace Doctrine\ORM\Tools\Pagination; use Doctrine\ORM\Query\AST\ArithmeticExpression, Doctrine\ORM\Query\AST\SimpleArithmeticExpression, Doctrine\ORM\Query\TreeWalkerAdapter, Doctrine\ORM\Query\AST\SelectStatement, Doctrine\ORM\Query\AST\PathExpression, Doctrine\ORM\Query\AST\InExpression, Doctrine\ORM\Query\AST\NullComparisonExpression, Doctrine\ORM\Query\AST\InputParameter, Doctrine\ORM\Query\AST\ConditionalPrimary, Doctrine\ORM\Query\AST\ConditionalTerm, Doctrine\ORM\Query\AST\ConditionalExpression, Doctrine\ORM\Query\AST\ConditionalFactor, Doctrine\ORM\Query\AST\WhereClause; /** * Replaces the whereClause of the AST with a WHERE id IN (:foo_1, :foo_2) equivalent * * @category DoctrineExtensions * @package DoctrineExtensions\Paginate * @author David Abdemoulaie * @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/) * @license http://hobodave.com/license.txt New BSD License */ class WhereInWalker extends TreeWalkerAdapter { /** * ID Count hint name */ const HINT_PAGINATOR_ID_COUNT = 'doctrine.id.count'; /** * Primary key alias for query */ const PAGINATOR_ID_ALIAS = 'dpid'; /** * Replaces the whereClause in the AST * * Generates a clause equivalent to WHERE IN (:dpid_1, :dpid_2, ...) * * The parameter namespace (dpid) is defined by * the PAGINATOR_ID_ALIAS * * The total number of parameters is retrieved from * the HINT_PAGINATOR_ID_COUNT query hint * * @param SelectStatement $AST * @return void */ public function walkSelectStatement(SelectStatement $AST) { $rootComponents = array(); foreach ($this->_getQueryComponents() as $dqlAlias => $qComp) { $isParent = array_key_exists('parent', $qComp) && $qComp['parent'] === null && $qComp['nestingLevel'] == 0 ; if ($isParent) { $rootComponents[] = array($dqlAlias => $qComp); } } if (count($rootComponents) > 1) { throw new \RuntimeException("Cannot count query which selects two FROM components, cannot make distinction"); } $root = reset($rootComponents); $parentName = key($root); $parent = current($root); $identifierFieldName = $parent['metadata']->getSingleIdentifierFieldName(); $pathType = PathExpression::TYPE_STATE_FIELD; if (isset($parent['metadata']->associationMappings[$identifierFieldName])) { $pathType = PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION; } $pathExpression = new PathExpression(PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName, $identifierFieldName); $pathExpression->type = $pathType; $count = $this->_getQuery()->getHint(self::HINT_PAGINATOR_ID_COUNT); if ($count > 0) { $arithmeticExpression = new ArithmeticExpression(); $arithmeticExpression->simpleArithmeticExpression = new SimpleArithmeticExpression( array($pathExpression) ); $expression = new InExpression($arithmeticExpression); $expression->literals[] = new InputParameter(":" . self::PAGINATOR_ID_ALIAS); } else { $expression = new NullComparisonExpression($pathExpression); $expression->not = false; } $conditionalPrimary = new ConditionalPrimary; $conditionalPrimary->simpleConditionalExpression = $expression; if ($AST->whereClause) { if ($AST->whereClause->conditionalExpression instanceof ConditionalTerm) { $AST->whereClause->conditionalExpression->conditionalFactors[] = $conditionalPrimary; } elseif ($AST->whereClause->conditionalExpression instanceof ConditionalPrimary) { $AST->whereClause->conditionalExpression = new ConditionalExpression(array( new ConditionalTerm(array( $AST->whereClause->conditionalExpression, $conditionalPrimary )) )); } elseif ($AST->whereClause->conditionalExpression instanceof ConditionalExpression || $AST->whereClause->conditionalExpression instanceof ConditionalFactor ) { $tmpPrimary = new ConditionalPrimary; $tmpPrimary->conditionalExpression = $AST->whereClause->conditionalExpression; $AST->whereClause->conditionalExpression = new ConditionalTerm(array( $tmpPrimary, $conditionalPrimary )); } } else { $AST->whereClause = new WhereClause( new ConditionalExpression(array( new ConditionalTerm(array( $conditionalPrimary )) )) ); } } } DoctrineORM-2.3.3/doctrine-mapping.xsd0000644000175100017510000005406212143374607017750 0ustar beberleibeberlei DoctrineORM-2.3.3/LICENSE0000644000175100017510000000205112143374607014764 0ustar beberleibeberleiCopyright (c) 2006-2012 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. DoctrineORM-2.3.3/UPGRADE.md0000644000175100017510000005206712143374607015404 0ustar beberleibeberlei# Upgrade to 2.3 ## EntityManager#find() not calls EntityRepository#find() anymore Previous to 2.3, calling ``EntityManager#find()`` would be delegated to ``EntityRepository#find()``. This has lead to some unexpected behavior in the core of Doctrine when people have overwritten the find method in their repositories. That is why this behavior has been reversed in 2.3, and ``EntityRepository#find()`` calls ``EntityManager#find()`` instead. ## EntityGenerator add*() method generation When generating an add*() method for a collection the EntityGenerator will now not use the Type-Hint to get the singular for the collection name, but use the field-name and strip a trailing "s" character if there is one. ## Merge copies non persisted properties too When merging an entity in UoW not only mapped properties are copied, but also others. ## Query, QueryBuilder and NativeQuery parameters *BC break* From now on, parameters in queries is an ArrayCollection instead of a simple array. This affects heavily the usage of setParameters(), because it will not append anymore parameters to query, but will actually override the already defined ones. Whenever you are retrieving a parameter (ie. $query->getParameter(1)), you will receive an instance of Query\Parameter, which contains the methods "getName", "getValue" and "getType". Parameters are also only converted to when necessary, and not when they are set. Also, related functions were affected: * execute($parameters, $hydrationMode) the argument $parameters can be either an key=>value array or an ArrayCollection instance * iterate($parameters, $hydrationMode) the argument $parameters can be either an key=>value array or an ArrayCollection instance * setParameters($parameters) the argument $parameters can be either an key=>value array or an ArrayCollection instance * getParameters() now returns ArrayCollection instead of array * getParameter($key) now returns Parameter instance instead of parameter value ## Query TreeWalker method renamed Internal changes were made to DQL and SQL generation. If you have implemented your own TreeWalker, you probably need to update it. The method walkJoinVariableDeclaration is now named walkJoin. ## Metadata Drivers Metadata drivers have been rewritten to reuse code from Doctrine\Common. Anyone who is using the `Doctrine\ORM\Mapping\Driver\Driver` interface should instead refer to `Doctrine\Common\Persistence\Mapping\Driver\MappingDriver`. Same applies to `Doctrine\ORM\Mapping\Driver\AbstractFileDriver`: you should now refer to `Doctrine\Common\Persistence\Mapping\Driver\FileDriver`. Also, following mapping drivers have been deprecated, please use their replacements in Doctrine\Common as listed: * `Doctrine\ORM\Mapping\Driver\DriverChain` => `Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain` * `Doctrine\ORM\Mapping\Driver\PHPDriver` => `Doctrine\Common\Persistence\Mapping\Driver\PHPDriver` * `Doctrine\ORM\Mapping\Driver\StaticPHPDriver` => `Doctrine\Common\Persistence\Mapping\Driver\StaticPHPDriver` # Upgrade to 2.2 ## ResultCache implementation rewritten The result cache is completely rewritten and now works on the database result level, not inside the ORM AbstractQuery anymore. This means that for result cached queries the hydration will now always be performed again, regardless of the hydration mode. Affected areas are: 1. Fixes the problem that entities coming from the result cache were not registered in the UnitOfWork leading to problems during EntityManager#flush. Calls to EntityManager#merge are not necessary anymore. 2. Affects the array hydrator which now includes the overhead of hydration compared to caching the final result. The API is backwards compatible however most of the getter methods on the `AbstractQuery` object are now deprecated in favor of calling AbstractQuery#getQueryCacheProfile(). This method returns a `Doctrine\DBAL\Cache\QueryCacheProfile` instance with access to result cache driver, lifetime and cache key. ## EntityManager#getPartialReference() creates read-only entity Entities returned from EntityManager#getPartialReference() are now marked as read-only if they haven't been in the identity map before. This means objects of this kind never lead to changes in the UnitOfWork. ## Fields omitted in a partial DQL query or a native query are never updated Fields of an entity that are not returned from a partial DQL Query or native SQL query will never be updated through an UPDATE statement. ## Removed support for onUpdate in @JoinColumn The onUpdate foreign key handling makes absolutely no sense in an ORM. Additionally Oracle doesn't even support it. Support for it is removed. ## Changes in Annotation Handling There have been some changes to the annotation handling in Common 2.2 again, that affect how people with old configurations from 2.0 have to configure the annotation driver if they don't use `Configuration::newDefaultAnnotationDriver()`: // Register the ORM Annotations in the AnnotationRegistry AnnotationRegistry::registerFile('path/to/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php'); $reader = new \Doctrine\Common\Annotations\SimpleAnnotationReader(); $reader->addNamespace('Doctrine\ORM\Mapping'); $reader = new \Doctrine\Common\Annotations\CachedReader($reader, new ArrayCache()); $driver = new AnnotationDriver($reader, (array)$paths); $config->setMetadataDriverImpl($driver); ## Scalar mappings can now be ommitted from DQL result You are now allowed to mark scalar SELECT expressions as HIDDEN an they are not hydrated anymore. Example: SELECT u, SUM(a.id) AS HIDDEN numArticles FROM User u LEFT JOIN u.Articles a ORDER BY numArticles DESC HAVING numArticles > 10 Your result will be a collection of Users, and not an array with key 0 as User object instance and "numArticles" as the number of articles per user ## Map entities as scalars in DQL result When hydrating to array or even a mixed result in object hydrator, previously you had the 0 index holding you entity instance. You are now allowed to alias this, providing more flexibility for you code. Example: SELECT u AS user FROM User u Will now return a collection of arrays with index "user" pointing to the User object instance. ## Performance optimizations Thousands of lines were completely reviewed and optimized for best performance. Removed redundancy and improved code readability made now internal Doctrine code easier to understand. Also, Doctrine 2.2 now is around 10-15% faster than 2.1. ## EntityManager#find(null) Previously EntityManager#find(null) returned null. It now throws an exception. # Upgrade to 2.1 ## Interface for EntityRepository The EntityRepository now has an interface Doctrine\Common\Persistence\ObjectRepository. This means that your classes that override EntityRepository and extend find(), findOneBy() or findBy() must be adjusted to follow this interface. ## AnnotationReader changes The annotation reader was heavily refactored between 2.0 and 2.1-RC1. In theory the operation of the new reader should be backwards compatible, but it has to be setup differently to work that way: // new call to the AnnotationRegistry \Doctrine\Common\Annotations\AnnotationRegistry::registerFile('/doctrine-src/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php'); $reader = new \Doctrine\Common\Annotations\AnnotationReader(); $reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\'); // new code necessary starting here $reader->setIgnoreNotImportedAnnotations(true); $reader->setEnableParsePhpImports(false); $reader = new \Doctrine\Common\Annotations\CachedReader( new \Doctrine\Common\Annotations\IndexedReader($reader), new ArrayCache() ); This is already done inside the ``$config->newDefaultAnnotationDriver``, so everything should automatically work if you are using this method. You can verify if everything still works by executing a console command such as schema-validate that loads all metadata into memory. # Update from 2.0-BETA3 to 2.0-BETA4 ## XML Driver element demoted to attribute We changed how the XML Driver allows to define the change-tracking-policy. The working case is now: # Update from 2.0-BETA2 to 2.0-BETA3 ## Serialization of Uninitialized Proxies As of Beta3 you can now serialize uninitialized proxies, an exception will only be thrown when trying to access methods on the unserialized proxy as long as it has not been re-attached to the EntityManager using `EntityManager#merge()`. See this example: $proxy = $em->getReference('User', 1); $serializedProxy = serialize($proxy); $detachedProxy = unserialized($serializedProxy); echo $em->contains($detachedProxy); // FALSE try { $detachedProxy->getId(); // uninitialized detached proxy } catch(Exception $e) { } $attachedProxy = $em->merge($detachedProxy); echo $attackedProxy->getId(); // works! ## Changed SQL implementation of Postgres and Oracle DateTime types The DBAL Type "datetime" included the Timezone Offset in both Postgres and Oracle. As of this version they are now generated without Timezone (TIMESTAMP WITHOUT TIME ZONE instead of TIMESTAMP WITH TIME ZONE). See [this comment to Ticket DBAL-22](http://www.doctrine-project.org/jira/browse/DBAL-22?focusedCommentId=13396&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#action_13396) for more details as well as migration issues for PostgreSQL and Oracle. Both Postgres and Oracle will throw Exceptions during hydration of Objects with "DateTime" fields unless migration steps are taken! ## Removed multi-dot/deep-path expressions in DQL The support for implicit joins in DQL through the multi-dot/Deep Path Expressions was dropped. For example: SELECT u FROM User u WHERE u.group.name = ?1 See the "u.group.id" here is using multi dots (deep expression) to walk through the graph of objects and properties. Internally the DQL parser would rewrite these queries to: SELECT u FROM User u JOIN u.group g WHERE g.name = ?1 This explicit notation will be the only supported notation as of now. The internal handling of multi-dots in the DQL Parser was very complex, error prone in edge cases and required special treatment for several features we added. Additionally it had edge cases that could not be solved without making the DQL Parser even much more complex. For this reason we will drop the support for the deep path expressions to increase maintainability and overall performance of the DQL parsing process. This will benefit any DQL query being parsed, even those not using deep path expressions. Note that the generated SQL of both notations is exactly the same! You don't loose anything through this. ## Default Allocation Size for Sequences The default allocation size for sequences has been changed from 10 to 1. This step was made to not cause confusion with users and also because it is partly some kind of premature optimization. # Update from 2.0-BETA1 to 2.0-BETA2 There are no backwards incompatible changes in this release. # Upgrade from 2.0-ALPHA4 to 2.0-BETA1 ## EntityRepository deprecates access to protected variables Instead of accessing protected variables for the EntityManager in a custom EntityRepository it is now required to use the getter methods for all the three instance variables: * `$this->_em` now accessible through `$this->getEntityManager()` * `$this->_class` now accessible through `$this->getClassMetadata()` * `$this->_entityName` now accessible through `$this->getEntityName()` Important: For Beta 2 the protected visibility of these three properties will be changed to private! ## Console migrated to Symfony Console The Doctrine CLI has been replaced by Symfony Console Configuration Instead of having to specify: [php] $cliConfig = new CliConfiguration(); $cliConfig->setAttribute('em', $entityManager); You now have to configure the script like: [php] $helperSet = new \Symfony\Components\Console\Helper\HelperSet(array( 'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()), 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) )); ## Console: No need for Mapping Paths anymore In previous versions you had to specify the --from and --from-path options to show where your mapping paths are from the console. However this information is already known from the Mapping Driver configuration, so the requirement for this options were dropped. Instead for each console command all the entities are loaded and to restrict the operation to one or more sub-groups you can use the --filter flag. ## AnnotationDriver is not a default mapping driver anymore In conjunction with the recent changes to Console we realized that the annotations driver being a default metadata driver lead to lots of glue code in the console components to detect where entities lie and how to load them for batch updates like SchemaTool and other commands. However the annotations driver being a default driver does not really help that much anyways. Therefore we decided to break backwards compability in this issue and drop the support for Annotations as Default Driver and require our users to specify the driver explicitly (which allows us to ask for the path to all entities). If you are using the annotations metadata driver as default driver, you have to add the following lines to your bootstrap code: $driverImpl = $config->newDefaultAnnotationDriver(array(__DIR__."/Entities")); $config->setMetadataDriverImpl($driverImpl); You have to specify the path to your entities as either string of a single path or array of multiple paths to your entities. This information will be used by all console commands to access all entities. Xml and Yaml Drivers work as before! ## New inversedBy attribute It is now *mandatory* that the owning side of a bidirectional association specifies the 'inversedBy' attribute that points to the name of the field on the inverse side that completes the association. Example: [php] // BEFORE (ALPHA4 AND EARLIER) class User { //... /** @OneToOne(targetEntity="Address", mappedBy="user") */ private $address; //... } class Address { //... /** @OneToOne(targetEntity="User") */ private $user; //... } // SINCE BETA1 // User class DOES NOT CHANGE class Address { //... /** @OneToOne(targetEntity="User", inversedBy="address") */ private $user; //... } Thus, the inversedBy attribute is the counterpart to the mappedBy attribute. This change was necessary to enable some simplifications and further performance improvements. We apologize for the inconvenience. ## Default Property for Field Mappings The "default" option for database column defaults has been removed. If desired, database column defaults can be implemented by using the columnDefinition attribute of the @Column annotation (or the approriate XML and YAML equivalents). Prefer PHP default values, if possible. ## Selecting Partial Objects Querying for partial objects now has a new syntax. The old syntax to query for partial objects now has a different meaning. This is best illustrated by an example. If you previously had a DQL query like this: [sql] SELECT u.id, u.name FROM User u Since BETA1, simple state field path expressions in the select clause are used to select object fields as plain scalar values (something that was not possible before). To achieve the same result as previously (that is, a partial object with only id and name populated) you need to use the following, explicit syntax: [sql] SELECT PARTIAL u.{id,name} FROM User u ## XML Mapping Driver The 'inheritance-type' attribute changed to take last bit of ClassMetadata constant names, i.e. NONE, SINGLE_TABLE, INHERITANCE_TYPE_JOINED ## YAML Mapping Driver The way to specify lifecycle callbacks in YAML Mapping driver was changed to allow for multiple callbacks per event. The Old syntax ways: [yaml] lifecycleCallbacks: doStuffOnPrePersist: prePersist doStuffOnPostPersist: postPersist The new syntax is: [yaml] lifecycleCallbacks: prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ] postPersist: [ doStuffOnPostPersist ] ## PreUpdate Event Listeners Event Listeners listening to the 'preUpdate' event can only affect the primitive values of entity changesets by using the API on the `PreUpdateEventArgs` instance passed to the preUpdate listener method. Any changes to the state of the entitys properties won't affect the database UPDATE statement anymore. This gives drastic performance benefits for the preUpdate event. ## Collection API The Collection interface in the Common package has been updated with some missing methods that were present only on the default implementation, ArrayCollection. Custom collection implementations need to be updated to adhere to the updated interface. # Upgrade from 2.0-ALPHA3 to 2.0-ALPHA4 ## CLI Controller changes CLI main object changed its name and namespace. Renamed from Doctrine\ORM\Tools\Cli to Doctrine\Common\Cli\CliController. Doctrine\Common\Cli\CliController now only deals with namespaces. Ready to go, Core, Dbal and Orm are available and you can subscribe new tasks by retrieving the namespace and including new task. Example: [php] $cli->getNamespace('Core')->addTask('my-example', '\MyProject\Tools\Cli\Tasks\MyExampleTask'); ## CLI Tasks documentation Tasks have implemented a new way to build documentation. Although it is still possible to define the help manually by extending the basicHelp and extendedHelp, they are now optional. With new required method AbstractTask::buildDocumentation, its implementation defines the TaskDocumentation instance (accessible through AbstractTask::getDocumentation()), basicHelp and extendedHelp are now not necessary to be implemented. ## Changes in Method Signatures * A bunch of Methods on both Doctrine\DBAL\Platforms\AbstractPlatform and Doctrine\DBAL\Schema\AbstractSchemaManager have changed quite significantly by adopting the new Schema instance objects. ## Renamed Methods * Doctrine\ORM\AbstractQuery::setExpireResultCache() -> expireResultCache() * Doctrine\ORM\Query::setExpireQueryCache() -> expireQueryCache() ## SchemaTool Changes * "doctrine schema-tool --drop" now always drops the complete database instead of only those tables defined by the current database model. The previous method had problems when foreign keys of orphaned tables pointed to tables that were schedulded for deletion. * Use "doctrine schema-tool --update" to get a save incremental update for your database schema without deleting any unused tables, sequences or foreign keys. * Use "doctrine schema-tool --complete-update" to do a full incremental update of your schema. # Upgrade from 2.0-ALPHA2 to 2.0-ALPHA3 This section details the changes made to Doctrine 2.0-ALPHA3 to make it easier for you to upgrade your projects to use this version. ## CLI Changes The $args variable used in the cli-config.php for configuring the Doctrine CLI has been renamed to $globalArguments. ## Proxy class changes You are now required to make supply some minimalist configuration with regards to proxy objects. That involves 2 new configuration options. First, the directory where generated proxy classes should be placed needs to be specified. Secondly, you need to configure the namespace used for proxy classes. The following snippet shows an example: [php] // step 1: configure directory for proxy classes // $config instanceof Doctrine\ORM\Configuration $config->setProxyDir('/path/to/myproject/lib/MyProject/Generated/Proxies'); $config->setProxyNamespace('MyProject\Generated\Proxies'); Note that proxy classes behave exactly like any other classes when it comes to class loading. Therefore you need to make sure the proxy classes can be loaded by some class loader. If you place the generated proxy classes in a namespace and directory under your projects class files, like in the example above, it would be sufficient to register the MyProject namespace on a class loader. Since the proxy classes are contained in that namespace and adhere to the standards for class loading, no additional work is required. Generating the proxy classes into a namespace within your class library is the recommended setup. Entities with initialized proxy objects can now be serialized and unserialized properly from within the same application. For more details refer to the Configuration section of the manual. ## Removed allowPartialObjects configuration option The allowPartialObjects configuration option together with the `Configuration#getAllowPartialObjects` and `Configuration#setAllowPartialObjects` methods have been removed. The new behavior is as if the option were set to FALSE all the time, basically disallowing partial objects globally. However, you can still use the `Query::HINT_FORCE_PARTIAL_LOAD` query hint to force a query to return partial objects for optimization purposes. ## Renamed Methods * Doctrine\ORM\Configuration#getCacheDir() to getProxyDir() * Doctrine\ORM\Configuration#setCacheDir($dir) to setProxyDir($dir)