--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Annotations;
+
+/**
+ * Annotations class
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class Annotation
+{
+ /**
+ * Value property. Common among all derived classes.
+ *
+ * @var string
+ */
+ public $value;
+
+ /**
+ * Constructor
+ *
+ * @param array $data Key-value for properties to be defined in this class
+ */
+ public final function __construct(array $data)
+ {
+ foreach ($data as $key => $value) {
+ $this->$key = $value;
+ }
+ }
+
+ /**
+ * Error handler for unknown property accessor in Annotation class.
+ *
+ * @param string $name Unknown property name
+ */
+ public function __get($name)
+ {
+ throw new \BadMethodCallException(
+ sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this))
+ );
+ }
+
+ /**
+ * Error handler for unknown property mutator in Annotation class.
+ *
+ * @param string $name Unkown property name
+ * @param mixed $value Property value
+ */
+ public function __set($name, $value)
+ {
+ throw new \BadMethodCallException(
+ sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this))
+ );
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Annotations;
+
+/**
+ * Description of AnnotationException
+ *
+ * @since 2.0
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class AnnotationException extends \Exception
+{
+ /**
+ * Creates a new AnnotationException describing a Syntax error.
+ *
+ * @param string $message Exception message
+ * @return AnnotationException
+ */
+ public static function syntaxError($message)
+ {
+ return new self('[Syntax Error] ' . $message);
+ }
+
+ /**
+ * Creates a new AnnotationException describing a Semantical error.
+ *
+ * @param string $message Exception message
+ * @return AnnotationException
+ */
+ public static function semanticalError($message)
+ {
+ return new self('[Semantical Error] ' . $message);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Annotations;
+
+use Closure,
+ ReflectionClass,
+ ReflectionMethod,
+ ReflectionProperty,
+ Doctrine\Common\Cache\Cache;
+
+/**
+ * A reader for docblock annotations.
+ *
+ * @since 2.0
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class AnnotationReader
+{
+ /**
+ * Cache salt
+ *
+ * @var string
+ * @static
+ */
+ private static $CACHE_SALT = '@<Annot>';
+
+ /**
+ * Annotations Parser
+ *
+ * @var Doctrine\Common\Annotations\Parser
+ */
+ private $parser;
+
+ /**
+ * Cache mechanism to store processed Annotations
+ *
+ * @var Doctrine\Common\Cache\Cache
+ */
+ private $cache;
+
+ /**
+ * Constructor. Initializes a new AnnotationReader that uses the given Cache provider.
+ *
+ * @param Cache $cache The cache provider to use. If none is provided, ArrayCache is used.
+ * @param Parser $parser The parser to use. If none is provided, the default parser is used.
+ */
+ public function __construct(Cache $cache = null, Parser $parser = null)
+ {
+ $this->parser = $parser ?: new Parser;
+ $this->cache = $cache ?: new \Doctrine\Common\Cache\ArrayCache;
+ }
+
+ /**
+ * Sets the default namespace that the AnnotationReader should assume for annotations
+ * with not fully qualified names.
+ *
+ * @param string $defaultNamespace
+ */
+ public function setDefaultAnnotationNamespace($defaultNamespace)
+ {
+ $this->parser->setDefaultAnnotationNamespace($defaultNamespace);
+ }
+
+ /**
+ * Sets the custom function to use for creating new annotations on the
+ * underlying parser.
+ *
+ * The function is supplied two arguments. The first argument is the name
+ * of the annotation and the second argument an array of values for this
+ * annotation. The function is assumed to return an object or NULL.
+ * Whenever the function returns NULL for an annotation, the implementation falls
+ * back to the default annotation creation process of the underlying parser.
+ *
+ * @param Closure $func
+ */
+ public function setAnnotationCreationFunction(Closure $func)
+ {
+ $this->parser->setAnnotationCreationFunction($func);
+ }
+
+ /**
+ * Sets an alias for an annotation namespace.
+ *
+ * @param $namespace
+ * @param $alias
+ */
+ public function setAnnotationNamespaceAlias($namespace, $alias)
+ {
+ $this->parser->setAnnotationNamespaceAlias($namespace, $alias);
+ }
+
+ /**
+ * Sets a flag whether to try to autoload annotation classes, as well as to distinguish
+ * between what is an annotation and what not by triggering autoloading.
+ *
+ * NOTE: Autoloading of annotation classes is inefficient and requires silently failing
+ * autoloaders. In particular, setting this option to TRUE renders this AnnotationReader
+ * incompatible with a {@link ClassLoader}.
+ * @param boolean $bool Boolean flag.
+ */
+ public function setAutoloadAnnotations($bool)
+ {
+ $this->parser->setAutoloadAnnotations($bool);
+ }
+
+ /**
+ * Gets a flag whether to try to autoload annotation classes.
+ *
+ * @see setAutoloadAnnotations
+ * @return boolean
+ */
+ public function getAutoloadAnnotations()
+ {
+ return $this->parser->getAutoloadAnnotations();
+ }
+
+ /**
+ * Gets the annotations applied to a class.
+ *
+ * @param string|ReflectionClass $class The name or ReflectionClass of the class from which
+ * the class annotations should be read.
+ * @return array An array of Annotations.
+ */
+ public function getClassAnnotations(ReflectionClass $class)
+ {
+ $cacheKey = $class->getName() . self::$CACHE_SALT;
+
+ // Attempt to grab data from cache
+ if (($data = $this->cache->fetch($cacheKey)) !== false) {
+ return $data;
+ }
+
+ $annotations = $this->parser->parse($class->getDocComment(), 'class ' . $class->getName());
+ $this->cache->save($cacheKey, $annotations, null);
+
+ return $annotations;
+ }
+
+ /**
+ * Gets a class annotation.
+ *
+ * @param $class
+ * @param string $annotation The name of the annotation.
+ * @return The Annotation or NULL, if the requested annotation does not exist.
+ */
+ public function getClassAnnotation(ReflectionClass $class, $annotation)
+ {
+ $annotations = $this->getClassAnnotations($class);
+
+ return isset($annotations[$annotation]) ? $annotations[$annotation] : null;
+ }
+
+ /**
+ * Gets the annotations applied to a property.
+ *
+ * @param string|ReflectionClass $class The name or ReflectionClass of the class that owns the property.
+ * @param string|ReflectionProperty $property The name or ReflectionProperty of the property
+ * from which the annotations should be read.
+ * @return array An array of Annotations.
+ */
+ public function getPropertyAnnotations(ReflectionProperty $property)
+ {
+ $cacheKey = $property->getDeclaringClass()->getName() . '$' . $property->getName() . self::$CACHE_SALT;
+
+ // Attempt to grab data from cache
+ if (($data = $this->cache->fetch($cacheKey)) !== false) {
+ return $data;
+ }
+
+ $context = 'property ' . $property->getDeclaringClass()->getName() . "::\$" . $property->getName();
+ $annotations = $this->parser->parse($property->getDocComment(), $context);
+ $this->cache->save($cacheKey, $annotations, null);
+
+ return $annotations;
+ }
+
+ /**
+ * Gets a property annotation.
+ *
+ * @param ReflectionProperty $property
+ * @param string $annotation The name of the annotation.
+ * @return The Annotation or NULL, if the requested annotation does not exist.
+ */
+ public function getPropertyAnnotation(ReflectionProperty $property, $annotation)
+ {
+ $annotations = $this->getPropertyAnnotations($property);
+
+ return isset($annotations[$annotation]) ? $annotations[$annotation] : null;
+ }
+
+ /**
+ * Gets the annotations applied to a method.
+ *
+ * @param string|ReflectionClass $class The name or ReflectionClass of the class that owns the method.
+ * @param string|ReflectionMethod $property The name or ReflectionMethod of the method from which
+ * the annotations should be read.
+ * @return array An array of Annotations.
+ */
+ public function getMethodAnnotations(ReflectionMethod $method)
+ {
+ $cacheKey = $method->getDeclaringClass()->getName() . '#' . $method->getName() . self::$CACHE_SALT;
+
+ // Attempt to grab data from cache
+ if (($data = $this->cache->fetch($cacheKey)) !== false) {
+ return $data;
+ }
+
+ $context = 'method ' . $method->getDeclaringClass()->getName() . '::' . $method->getName() . '()';
+ $annotations = $this->parser->parse($method->getDocComment(), $context);
+ $this->cache->save($cacheKey, $annotations, null);
+
+ return $annotations;
+ }
+
+ /**
+ * Gets a method annotation.
+ *
+ * @param ReflectionMethod $method
+ * @param string $annotation The name of the annotation.
+ * @return The Annotation or NULL, if the requested annotation does not exist.
+ */
+ public function getMethodAnnotation(ReflectionMethod $method, $annotation)
+ {
+ $annotations = $this->getMethodAnnotations($method);
+
+ return isset($annotations[$annotation]) ? $annotations[$annotation] : null;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Annotations;
+
+/**
+ * Simple lexer for docblock annotations.
+ *
+ * This Lexer can be subclassed to customize certain aspects of the annotation
+ * lexing (token recognition) process. Note though that currently no special care
+ * is taken to maintain full backwards compatibility for subclasses. Implementation
+ * details of the default Lexer can change without explicit notice.
+ *
+ * @since 2.0
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class Lexer extends \Doctrine\Common\Lexer
+{
+ const T_NONE = 1;
+ const T_IDENTIFIER = 2;
+ const T_INTEGER = 3;
+ const T_STRING = 4;
+ const T_FLOAT = 5;
+
+ const T_AT = 101;
+ const T_CLOSE_CURLY_BRACES = 102;
+ const T_CLOSE_PARENTHESIS = 103;
+ const T_COMMA = 104;
+ const T_EQUALS = 105;
+ const T_FALSE = 106;
+ const T_NAMESPACE_SEPARATOR = 107;
+ const T_OPEN_CURLY_BRACES = 108;
+ const T_OPEN_PARENTHESIS = 109;
+ const T_TRUE = 110;
+
+ /**
+ * @inheritdoc
+ */
+ protected function getCatchablePatterns()
+ {
+ return array(
+ '[a-z_][a-z0-9_:]*',
+ '(?:[0-9]+(?:[\.][0-9]+)*)(?:e[+-]?[0-9]+)?',
+ '"(?:[^"]|"")*"'
+ );
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function getNonCatchablePatterns()
+ {
+ return array('\s+', '\*+', '(.)');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function getType(&$value)
+ {
+ $type = self::T_NONE;
+ $newVal = $this->getNumeric($value);
+
+ // Checking numeric value
+ if ($newVal !== false) {
+ $value = $newVal;
+
+ return (strpos($value, '.') !== false || stripos($value, 'e') !== false)
+ ? self::T_FLOAT : self::T_INTEGER;
+ }
+
+ if ($value[0] === '"') {
+ $value = str_replace('""', '"', substr($value, 1, strlen($value) - 2));
+
+ return self::T_STRING;
+ } else {
+ switch (strtolower($value)) {
+ case '@':
+ return self::T_AT;
+
+ case ',':
+ return self::T_COMMA;
+
+ case '(':
+ return self::T_OPEN_PARENTHESIS;
+
+ case ')':
+ return self::T_CLOSE_PARENTHESIS;
+
+ case '{':
+ return self::T_OPEN_CURLY_BRACES;
+
+ case '}': return self::T_CLOSE_CURLY_BRACES;
+ case '=':
+ return self::T_EQUALS;
+
+ case '\\':
+ return self::T_NAMESPACE_SEPARATOR;
+
+ case 'true':
+ return self::T_TRUE;
+
+ case 'false':
+ return self::T_FALSE;
+
+ default:
+ if (ctype_alpha($value[0]) || $value[0] === '_') {
+ return self::T_IDENTIFIER;
+ }
+
+ break;
+ }
+ }
+
+ return $type;
+ }
+
+ /**
+ * Checks if a value is numeric or not
+ *
+ * @param mixed $value Value to be inspected
+ * @return boolean|integer|float Processed value
+ * @todo Inline
+ */
+ private function getNumeric($value)
+ {
+ if ( ! is_scalar($value)) {
+ return false;
+ }
+
+ // Checking for valid numeric numbers: 1.234, -1.234e-2
+ if (is_numeric($value)) {
+ return $value;
+ }
+
+ return false;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Annotations;
+
+use Closure, Doctrine\Common\ClassLoader;
+
+/**
+ * A simple parser for docblock annotations.
+ *
+ * This Parser can be subclassed to customize certain aspects of the annotation
+ * parsing and/or creation process. Note though that currently no special care
+ * is taken to maintain full backwards compatibility for subclasses. Implementation
+ * details of the default Parser can change without explicit notice.
+ *
+ * @since 2.0
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class Parser
+{
+ /**
+ * Some common tags that are stripped prior to parsing in order to reduce parsing overhead.
+ *
+ * @var array
+ */
+ private static $strippedTags = array(
+ "{@internal", "{@inheritdoc", "{@link"
+ );
+
+ /**
+ * The lexer.
+ *
+ * @var Doctrine\Common\Annotations\Lexer
+ */
+ private $lexer;
+
+ /**
+ * Flag to control if the current annotation is nested or not.
+ *
+ * @var boolean
+ */
+ protected $isNestedAnnotation = false;
+
+ /**
+ * Default namespace for annotations.
+ *
+ * @var string
+ */
+ private $defaultAnnotationNamespace = '';
+
+ /**
+ * Hashmap to store namespace aliases.
+ *
+ * @var array
+ */
+ private $namespaceAliases = array();
+
+ /**
+ * @var string
+ */
+ private $context = '';
+
+ /**
+ * @var boolean Whether to try to autoload annotations that are not yet defined.
+ */
+ private $autoloadAnnotations = false;
+
+ /**
+ * @var Closure The custom function used to create new annotations, if any.
+ */
+ private $annotationCreationFunction;
+
+ /**
+ * Constructs a new AnnotationParser.
+ */
+ public function __construct(Lexer $lexer = null)
+ {
+ $this->lexer = $lexer ?: new Lexer;
+ }
+
+ /**
+ * Gets the lexer used by this parser.
+ *
+ * @return Lexer The lexer.
+ */
+ public function getLexer()
+ {
+ return $this->lexer;
+ }
+
+ /**
+ * Sets a flag whether to try to autoload annotation classes, as well as to distinguish
+ * between what is an annotation and what not by triggering autoloading.
+ *
+ * NOTE: Autoloading of annotation classes is inefficient and requires silently failing
+ * autoloaders. In particular, setting this option to TRUE renders the Parser
+ * incompatible with a {@link ClassLoader}.
+ * @param boolean $bool Boolean flag.
+ */
+ public function setAutoloadAnnotations($bool)
+ {
+ $this->autoloadAnnotations = $bool;
+ }
+
+ /**
+ * Sets the custom function to use for creating new annotations.
+ *
+ * The function is supplied two arguments. The first argument is the name
+ * of the annotation and the second argument an array of values for this
+ * annotation. The function is assumed to return an object or NULL.
+ * Whenever the function returns NULL for an annotation, the parser falls
+ * back to the default annotation creation process.
+ *
+ * Whenever the function returns NULL for an annotation, the implementation falls
+ * back to the default annotation creation process.
+ *
+ * @param Closure $func
+ */
+ public function setAnnotationCreationFunction(Closure $func)
+ {
+ $this->annotationCreationFunction = $func;
+ }
+
+ /**
+ * Gets a flag whether to try to autoload annotation classes.
+ *
+ * @see setAutoloadAnnotations
+ * @return boolean
+ */
+ public function getAutoloadAnnotations()
+ {
+ return $this->autoloadAnnotations;
+ }
+
+ /**
+ * Sets the default namespace that is assumed for an annotation that does not
+ * define a namespace prefix.
+ *
+ * @param string $defaultNamespace
+ */
+ public function setDefaultAnnotationNamespace($defaultNamespace)
+ {
+ $this->defaultAnnotationNamespace = $defaultNamespace;
+ }
+
+ /**
+ * Sets an alias for an annotation namespace.
+ *
+ * @param string $namespace
+ * @param string $alias
+ */
+ public function setAnnotationNamespaceAlias($namespace, $alias)
+ {
+ $this->namespaceAliases[$alias] = $namespace;
+ }
+
+ /**
+ * Gets the namespace alias mappings used by this parser.
+ *
+ * @return array The namespace alias mappings.
+ */
+ public function getNamespaceAliases()
+ {
+ return $this->namespaceAliases;
+ }
+
+ /**
+ * Parses the given docblock string for annotations.
+ *
+ * @param string $docBlockString The docblock string to parse.
+ * @param string $context The parsing context.
+ * @return array Array of annotations. If no annotations are found, an empty array is returned.
+ */
+ public function parse($docBlockString, $context='')
+ {
+ $this->context = $context;
+
+ // Strip out some known inline tags.
+ $input = str_replace(self::$strippedTags, '', $docBlockString);
+
+ // Cut of the beginning of the input until the first '@'.
+ $input = substr($input, strpos($input, '@'));
+
+ $this->lexer->reset();
+ $this->lexer->setInput(trim($input, '* /'));
+ $this->lexer->moveNext();
+
+ if ($this->lexer->isNextToken(Lexer::T_AT)) {
+ return $this->Annotations();
+ }
+
+ return array();
+ }
+
+ /**
+ * 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 bool True if tokens match; false otherwise.
+ */
+ public function match($token)
+ {
+ if ( ! ($this->lexer->lookahead['type'] === $token)) {
+ $this->syntaxError($this->lexer->getLiteral($token));
+ }
+ $this->lexer->moveNext();
+ }
+
+ /**
+ * Generates a new syntax error.
+ *
+ * @param string $expected Expected string.
+ * @param array $token Optional token.
+ * @throws AnnotationException
+ */
+ private function syntaxError($expected, $token = null)
+ {
+ if ($token === null) {
+ $token = $this->lexer->lookahead;
+ }
+
+ $message = "Expected {$expected}, got ";
+
+ if ($this->lexer->lookahead === null) {
+ $message .= 'end of string';
+ } else {
+ $message .= "'{$token['value']}' at position {$token['position']}";
+ }
+
+ if (strlen($this->context)) {
+ $message .= ' in ' . $this->context;
+ }
+
+ $message .= '.';
+
+ throw AnnotationException::syntaxError($message);
+ }
+
+ /**
+ * Annotations ::= Annotation {[ "*" ]* [Annotation]}*
+ *
+ * @return array
+ */
+ public function Annotations()
+ {
+ $this->isNestedAnnotation = false;
+
+ $annotations = array();
+ $annot = $this->Annotation();
+
+ if ($annot !== false) {
+ $annotations[get_class($annot)] = $annot;
+ $this->lexer->skipUntil(Lexer::T_AT);
+ }
+
+ while ($this->lexer->lookahead !== null && $this->lexer->isNextToken(Lexer::T_AT)) {
+ $this->isNestedAnnotation = false;
+ $annot = $this->Annotation();
+
+ if ($annot !== false) {
+ $annotations[get_class($annot)] = $annot;
+ $this->lexer->skipUntil(Lexer::T_AT);
+ }
+ }
+
+ return $annotations;
+ }
+
+ /**
+ * Annotation ::= "@" AnnotationName ["(" [Values] ")"]
+ * AnnotationName ::= QualifiedName | SimpleName | AliasedName
+ * QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName
+ * AliasedName ::= Alias ":" SimpleName
+ * NameSpacePart ::= identifier
+ * SimpleName ::= identifier
+ * Alias ::= identifier
+ *
+ * @return mixed False if it is not a valid annotation.
+ */
+ public function Annotation()
+ {
+ $values = array();
+ $nameParts = array();
+
+ $this->match(Lexer::T_AT);
+ $this->match(Lexer::T_IDENTIFIER);
+ $nameParts[] = $this->lexer->token['value'];
+
+ while ($this->lexer->isNextToken(Lexer::T_NAMESPACE_SEPARATOR)) {
+ $this->match(Lexer::T_NAMESPACE_SEPARATOR);
+ $this->match(Lexer::T_IDENTIFIER);
+ $nameParts[] = $this->lexer->token['value'];
+ }
+
+ // Effectively pick the name of the class (append default NS if none, grab from NS alias, etc)
+ if (strpos($nameParts[0], ':')) {
+ list ($alias, $nameParts[0]) = explode(':', $nameParts[0]);
+
+ // If the namespace alias doesnt exist, skip until next annotation
+ if ( ! isset($this->namespaceAliases[$alias])) {
+ $this->lexer->skipUntil(Lexer::T_AT);
+ return false;
+ }
+
+ $name = $this->namespaceAliases[$alias] . implode('\\', $nameParts);
+ } else if (count($nameParts) == 1) {
+ $name = $this->defaultAnnotationNamespace . $nameParts[0];
+ } else {
+ $name = implode('\\', $nameParts);
+ }
+
+ // Does the annotation class exist?
+ if ( ! class_exists($name, $this->autoloadAnnotations)) {
+ $this->lexer->skipUntil(Lexer::T_AT);
+ return false;
+ }
+
+ // Next will be nested
+ $this->isNestedAnnotation = true;
+
+ if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
+ $this->match(Lexer::T_OPEN_PARENTHESIS);
+
+ if ( ! $this->lexer->isNextToken(Lexer::T_CLOSE_PARENTHESIS)) {
+ $values = $this->Values();
+ }
+
+ $this->match(Lexer::T_CLOSE_PARENTHESIS);
+ }
+
+ if ($this->annotationCreationFunction !== null) {
+ $func = $this->annotationCreationFunction;
+ $annot = $func($name, $values);
+ }
+
+ return isset($annot) ? $annot : $this->newAnnotation($name, $values);
+ }
+
+ /**
+ * Values ::= Array | Value {"," Value}*
+ *
+ * @return array
+ */
+ public function Values()
+ {
+ $values = array();
+
+ // Handle the case of a single array as value, i.e. @Foo({....})
+ if ($this->lexer->isNextToken(Lexer::T_OPEN_CURLY_BRACES)) {
+ $values['value'] = $this->Value();
+ return $values;
+ }
+
+ $values[] = $this->Value();
+
+ while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
+ $this->match(Lexer::T_COMMA);
+ $value = $this->Value();
+
+ if ( ! is_array($value)) {
+ $this->syntaxError('Value', $value);
+ }
+
+ $values[] = $value;
+ }
+
+ foreach ($values as $k => $value) {
+ if (is_array($value) && is_string(key($value))) {
+ $key = key($value);
+ $values[$key] = $value[$key];
+ } else {
+ $values['value'] = $value;
+ }
+
+ unset($values[$k]);
+ }
+
+ return $values;
+ }
+
+ /**
+ * Value ::= PlainValue | FieldAssignment
+ *
+ * @return mixed
+ */
+ public function Value()
+ {
+ $peek = $this->lexer->glimpse();
+
+ if ($peek['value'] === '=') {
+ return $this->FieldAssignment();
+ }
+
+ return $this->PlainValue();
+ }
+
+ /**
+ * PlainValue ::= integer | string | float | boolean | Array | Annotation
+ *
+ * @return mixed
+ */
+ public function PlainValue()
+ {
+ if ($this->lexer->isNextToken(Lexer::T_OPEN_CURLY_BRACES)) {
+ return $this->Arrayx();
+ }
+
+ if ($this->lexer->isNextToken(Lexer::T_AT)) {
+ return $this->Annotation();
+ }
+
+ switch ($this->lexer->lookahead['type']) {
+ case Lexer::T_STRING:
+ $this->match(Lexer::T_STRING);
+ return $this->lexer->token['value'];
+
+ case Lexer::T_INTEGER:
+ $this->match(Lexer::T_INTEGER);
+ return $this->lexer->token['value'];
+
+ case Lexer::T_FLOAT:
+ $this->match(Lexer::T_FLOAT);
+ return $this->lexer->token['value'];
+
+ case Lexer::T_TRUE:
+ $this->match(Lexer::T_TRUE);
+ return true;
+
+ case Lexer::T_FALSE:
+ $this->match(Lexer::T_FALSE);
+ return false;
+
+ default:
+ $this->syntaxError('PlainValue');
+ }
+ }
+
+ /**
+ * FieldAssignment ::= FieldName "=" PlainValue
+ * FieldName ::= identifier
+ *
+ * @return array
+ */
+ public function FieldAssignment()
+ {
+ $this->match(Lexer::T_IDENTIFIER);
+ $fieldName = $this->lexer->token['value'];
+ $this->match(Lexer::T_EQUALS);
+
+ return array($fieldName => $this->PlainValue());
+ }
+
+ /**
+ * Array ::= "{" ArrayEntry {"," ArrayEntry}* "}"
+ *
+ * @return array
+ */
+ public function Arrayx()
+ {
+ $array = $values = array();
+
+ $this->match(Lexer::T_OPEN_CURLY_BRACES);
+ $values[] = $this->ArrayEntry();
+
+ while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
+ $this->match(Lexer::T_COMMA);
+ $values[] = $this->ArrayEntry();
+ }
+
+ $this->match(Lexer::T_CLOSE_CURLY_BRACES);
+
+ foreach ($values as $value) {
+ list ($key, $val) = $value;
+
+ if ($key !== null) {
+ $array[$key] = $val;
+ } else {
+ $array[] = $val;
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * ArrayEntry ::= Value | KeyValuePair
+ * KeyValuePair ::= Key "=" PlainValue
+ * Key ::= string | integer
+ *
+ * @return array
+ */
+ public function ArrayEntry()
+ {
+ $peek = $this->lexer->glimpse();
+
+ if ($peek['value'] == '=') {
+ $this->match(
+ $this->lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_STRING
+ );
+
+ $key = $this->lexer->token['value'];
+ $this->match(Lexer::T_EQUALS);
+
+ return array($key, $this->PlainValue());
+ }
+
+ return array(null, $this->Value());
+ }
+
+ /**
+ * Constructs a new annotation with a given map of values.
+ *
+ * The default construction procedure is to instantiate a new object of a class
+ * with the same name as the annotation. Subclasses can override this method to
+ * change the construction process of new annotations.
+ *
+ * @param string The name of the annotation.
+ * @param array The map of annotation values.
+ * @return mixed The new annotation with the given values.
+ */
+ protected function newAnnotation($name, array $values)
+ {
+ return new $name($values);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Cache;
+
+/**
+ * Base class for cache driver implementations.
+ *
+ * @since 2.0
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+abstract class AbstractCache implements Cache
+{
+ /** @var string The cache id to store the index of cache ids under */
+ private $_cacheIdsIndexId = 'doctrine_cache_ids';
+
+ /** @var string The namespace to prefix all cache ids with */
+ private $_namespace = null;
+
+ /**
+ * Set the namespace to prefix all cache ids with.
+ *
+ * @param string $namespace
+ * @return void
+ */
+ public function setNamespace($namespace)
+ {
+ $this->_namespace = $namespace;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function fetch($id)
+ {
+ return $this->_doFetch($this->_getNamespacedId($id));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function contains($id)
+ {
+ return $this->_doContains($this->_getNamespacedId($id));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save($id, $data, $lifeTime = 0)
+ {
+ return $this->_doSave($this->_getNamespacedId($id), $data, $lifeTime);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete($id)
+ {
+ $id = $this->_getNamespacedId($id);
+
+ if (strpos($id, '*') !== false) {
+ return $this->deleteByRegex('/' . str_replace('*', '.*', $id) . '/');
+ }
+
+ return $this->_doDelete($id);
+ }
+
+ /**
+ * Delete all cache entries.
+ *
+ * @return array $deleted Array of the deleted cache ids
+ */
+ public function deleteAll()
+ {
+ $ids = $this->getIds();
+
+ foreach ($ids as $id) {
+ $this->delete($id);
+ }
+
+ return $ids;
+ }
+
+ /**
+ * Delete cache entries where the id matches a PHP regular expressions
+ *
+ * @param string $regex
+ * @return array $deleted Array of the deleted cache ids
+ */
+ public function deleteByRegex($regex)
+ {
+ $deleted = array();
+
+ $ids = $this->getIds();
+
+ foreach ($ids as $id) {
+ if (preg_match($regex, $id)) {
+ $this->delete($id);
+ $deleted[] = $id;
+ }
+ }
+
+ return $deleted;
+ }
+
+ /**
+ * Delete cache entries where the id has the passed prefix
+ *
+ * @param string $prefix
+ * @return array $deleted Array of the deleted cache ids
+ */
+ public function deleteByPrefix($prefix)
+ {
+ $deleted = array();
+
+ $prefix = $this->_getNamespacedId($prefix);
+ $ids = $this->getIds();
+
+ foreach ($ids as $id) {
+ if (strpos($id, $prefix) === 0) {
+ $this->delete($id);
+ $deleted[] = $id;
+ }
+ }
+
+ return $deleted;
+ }
+
+ /**
+ * Delete cache entries where the id has the passed suffix
+ *
+ * @param string $suffix
+ * @return array $deleted Array of the deleted cache ids
+ */
+ public function deleteBySuffix($suffix)
+ {
+ $deleted = array();
+
+ $ids = $this->getIds();
+
+ foreach ($ids as $id) {
+ if (substr($id, -1 * strlen($suffix)) === $suffix) {
+ $this->delete($id);
+ $deleted[] = $id;
+ }
+ }
+
+ return $deleted;
+ }
+
+ /**
+ * Prefix the passed id with the configured namespace value
+ *
+ * @param string $id The id to namespace
+ * @return string $id The namespaced id
+ */
+ private function _getNamespacedId($id)
+ {
+ if ( ! $this->_namespace || strpos($id, $this->_namespace) === 0) {
+ return $id;
+ } else {
+ return $this->_namespace . $id;
+ }
+ }
+
+ /**
+ * Fetches an entry from the cache.
+ *
+ * @param string $id cache id The id of the cache entry to fetch.
+ * @return string The cached data or FALSE, if no cache entry exists for the given id.
+ */
+ abstract protected function _doFetch($id);
+
+ /**
+ * Test if an entry exists in the cache.
+ *
+ * @param string $id cache id The cache id of the entry to check for.
+ * @return boolean TRUE if a cache entry exists for the given cache id, FALSE otherwise.
+ */
+ abstract protected function _doContains($id);
+
+ /**
+ * Puts data into the cache.
+ *
+ * @param string $id The cache id.
+ * @param string $data The cache entry/data.
+ * @param int $lifeTime The lifetime. If != false, sets a specific lifetime for this cache entry (null => infinite lifeTime).
+ * @return boolean TRUE if the entry was successfully stored in the cache, FALSE otherwise.
+ */
+ abstract protected function _doSave($id, $data, $lifeTime = false);
+
+ /**
+ * Deletes a cache entry.
+ *
+ * @param string $id cache id
+ * @return boolean TRUE if the cache entry was successfully deleted, FALSE otherwise.
+ */
+ abstract protected function _doDelete($id);
+
+ /**
+ * Get an array of all the cache ids stored
+ *
+ * @return array $ids
+ */
+ abstract public function getIds();
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Cache;
+
+/**
+ * APC cache driver.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author David Abdemoulaie <dave@hobodave.com>
+ * @todo Rename: APCCache
+ */
+class ApcCache extends AbstractCache
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getIds()
+ {
+ $ci = apc_cache_info('user');
+ $keys = array();
+
+ foreach ($ci['cache_list'] as $entry) {
+ $keys[] = $entry['info'];
+ }
+
+ return $keys;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _doFetch($id)
+ {
+ return apc_fetch($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _doContains($id)
+ {
+ $found = false;
+
+ apc_fetch($id, $found);
+
+ return $found;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _doSave($id, $data, $lifeTime = 0)
+ {
+ return (bool) apc_store($id, $data, (int) $lifeTime);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _doDelete($id)
+ {
+ return apc_delete($id);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Cache;
+
+/**
+ * Array cache driver.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author David Abdemoulaie <dave@hobodave.com>
+ */
+class ArrayCache extends AbstractCache
+{
+ /**
+ * @var array $data
+ */
+ private $data = array();
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getIds()
+ {
+ return array_keys($this->data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _doFetch($id)
+ {
+ if (isset($this->data[$id])) {
+ return $this->data[$id];
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _doContains($id)
+ {
+ return isset($this->data[$id]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _doSave($id, $data, $lifeTime = 0)
+ {
+ $this->data[$id] = $data;
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _doDelete($id)
+ {
+ unset($this->data[$id]);
+
+ return true;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Cache;
+
+/**
+ * Interface for cache drivers.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+interface Cache
+{
+ /**
+ * Fetches an entry from the cache.
+ *
+ * @param string $id cache id The id of the cache entry to fetch.
+ * @return string The cached data or FALSE, if no cache entry exists for the given id.
+ */
+ function fetch($id);
+
+ /**
+ * Test if an entry exists in the cache.
+ *
+ * @param string $id cache id The cache id of the entry to check for.
+ * @return boolean TRUE if a cache entry exists for the given cache id, FALSE otherwise.
+ */
+ function contains($id);
+
+ /**
+ * Puts data into the cache.
+ *
+ * @param string $id The cache id.
+ * @param string $data The cache entry/data.
+ * @param int $lifeTime The lifetime. If != 0, sets a specific lifetime for this cache entry (0 => infinite lifeTime).
+ * @return boolean TRUE if the entry was successfully stored in the cache, FALSE otherwise.
+ */
+ function save($id, $data, $lifeTime = 0);
+
+ /**
+ * Deletes a cache entry.
+ *
+ * @param string $id cache id
+ * @return boolean TRUE if the cache entry was successfully deleted, FALSE otherwise.
+ */
+ function delete($id);
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Cache;
+
+use \Memcache;
+
+/**
+ * Memcache cache driver.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author David Abdemoulaie <dave@hobodave.com>
+ */
+class MemcacheCache extends AbstractCache
+{
+ /**
+ * @var Memcache
+ */
+ private $_memcache;
+
+ /**
+ * Sets the memcache instance to use.
+ *
+ * @param Memcache $memcache
+ */
+ public function setMemcache(Memcache $memcache)
+ {
+ $this->_memcache = $memcache;
+ }
+
+ /**
+ * Gets the memcache instance used by the cache.
+ *
+ * @return Memcache
+ */
+ public function getMemcache()
+ {
+ return $this->_memcache;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getIds()
+ {
+ $keys = array();
+ $allSlabs = $this->_memcache->getExtendedStats('slabs');
+
+ foreach ($allSlabs as $server => $slabs) {
+ if (is_array($slabs)) {
+ foreach (array_keys($slabs) as $slabId) {
+ $dump = $this->_memcache->getExtendedStats('cachedump', (int) $slabId);
+
+ if ($dump) {
+ foreach ($dump as $entries) {
+ if ($entries) {
+ $keys = array_merge($keys, array_keys($entries));
+ }
+ }
+ }
+ }
+ }
+ }
+ return $keys;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _doFetch($id)
+ {
+ return $this->_memcache->get($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _doContains($id)
+ {
+ return (bool) $this->_memcache->get($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _doSave($id, $data, $lifeTime = 0)
+ {
+ return $this->_memcache->set($id, $data, 0, (int) $lifeTime);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _doDelete($id)
+ {
+ return $this->_memcache->delete($id);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Cache;
+
+/**
+ * Xcache cache driver.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author David Abdemoulaie <dave@hobodave.com>
+ */
+class XcacheCache extends AbstractCache
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getIds()
+ {
+ $this->_checkAuth();
+ $keys = array();
+
+ for ($i = 0, $count = xcache_count(XC_TYPE_VAR); $i < $count; $i++) {
+ $entries = xcache_list(XC_TYPE_VAR, $i);
+
+ if (is_array($entries['cache_list'])) {
+ foreach ($entries['cache_list'] as $entry) {
+ $keys[] = $entry['name'];
+ }
+ }
+ }
+
+ return $keys;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _doFetch($id)
+ {
+ return $this->_doContains($id) ? unserialize(xcache_get($id)) : false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _doContains($id)
+ {
+ return xcache_isset($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _doSave($id, $data, $lifeTime = 0)
+ {
+ return xcache_set($id, serialize($data), (int) $lifeTime);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _doDelete($id)
+ {
+ return xcache_unset($id);
+ }
+
+
+ /**
+ * Checks that xcache.admin.enable_auth is Off
+ *
+ * @throws \BadMethodCallException When xcache.admin.enable_auth is On
+ * @return void
+ */
+ protected function _checkAuth()
+ {
+ if (ini_get('xcache.admin.enable_auth')) {
+ throw new \BadMethodCallException('To use all features of \Doctrine\Common\Cache\XcacheCache, you must set "xcache.admin.enable_auth" to "Off" in your php.ini.');
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common;
+
+/**
+ * A <tt>ClassLoader</tt> is an autoloader for class files that can be
+ * installed on the SPL autoload stack. It is a class loader that either loads only classes
+ * of a specific namespace or all namespaces and it is suitable for working together
+ * with other autoloaders in the SPL autoload stack.
+ *
+ * If no include path is configured through the constructor or {@link setIncludePath}, a ClassLoader
+ * relies on the PHP <code>include_path</code>.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @since 2.0
+ */
+class ClassLoader
+{
+ private $fileExtension = '.php';
+ private $namespace;
+ private $includePath;
+ private $namespaceSeparator = '\\';
+
+ /**
+ * Creates a new <tt>ClassLoader</tt> that loads classes of the
+ * specified namespace from the specified include path.
+ *
+ * If no include path is given, the ClassLoader relies on the PHP include_path.
+ * If neither a namespace nor an include path is given, the ClassLoader will
+ * be responsible for loading all classes, thereby relying on the PHP include_path.
+ *
+ * @param string $ns The namespace of the classes to load.
+ * @param string $includePath The base include path to use.
+ */
+ public function __construct($ns = null, $includePath = null)
+ {
+ $this->namespace = $ns;
+ $this->includePath = $includePath;
+ }
+
+ /**
+ * Sets the namespace separator used by classes in the namespace of this ClassLoader.
+ *
+ * @param string $sep The separator to use.
+ */
+ public function setNamespaceSeparator($sep)
+ {
+ $this->namespaceSeparator = $sep;
+ }
+
+ /**
+ * Gets the namespace separator used by classes in the namespace of this ClassLoader.
+ *
+ * @return string
+ */
+ public function getNamespaceSeparator()
+ {
+ return $this->namespaceSeparator;
+ }
+
+ /**
+ * Sets the base include path for all class files in the namespace of this ClassLoader.
+ *
+ * @param string $includePath
+ */
+ public function setIncludePath($includePath)
+ {
+ $this->includePath = $includePath;
+ }
+
+ /**
+ * Gets the base include path for all class files in the namespace of this ClassLoader.
+ *
+ * @return string
+ */
+ public function getIncludePath()
+ {
+ return $this->includePath;
+ }
+
+ /**
+ * Sets the file extension of class files in the namespace of this ClassLoader.
+ *
+ * @param string $fileExtension
+ */
+ public function setFileExtension($fileExtension)
+ {
+ $this->fileExtension = $fileExtension;
+ }
+
+ /**
+ * Gets the file extension of class files in the namespace of this ClassLoader.
+ *
+ * @return string
+ */
+ public function getFileExtension()
+ {
+ return $this->fileExtension;
+ }
+
+ /**
+ * Registers this ClassLoader on the SPL autoload stack.
+ */
+ public function register()
+ {
+ spl_autoload_register(array($this, 'loadClass'));
+ }
+
+ /**
+ * Removes this ClassLoader from the SPL autoload stack.
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $classname The name of the class to load.
+ * @return boolean TRUE if the class has been successfully loaded, FALSE otherwise.
+ */
+ public function loadClass($className)
+ {
+ if ($this->namespace !== null && strpos($className, $this->namespace.$this->namespaceSeparator) !== 0) {
+ return false;
+ }
+
+ require ($this->includePath !== null ? $this->includePath . DIRECTORY_SEPARATOR : '')
+ . str_replace($this->namespaceSeparator, DIRECTORY_SEPARATOR, $className)
+ . $this->fileExtension;
+
+ return true;
+ }
+
+ /**
+ * Asks this ClassLoader whether it can potentially load the class (file) with
+ * the given name.
+ *
+ * @param string $className The fully-qualified name of the class.
+ * @return boolean TRUE if this ClassLoader can load the class, FALSE otherwise.
+ */
+ public function canLoadClass($className)
+ {
+ if ($this->namespace !== null && strpos($className, $this->namespace.$this->namespaceSeparator) !== 0) {
+ return false;
+ }
+ return file_exists(($this->includePath !== null ? $this->includePath . DIRECTORY_SEPARATOR : '')
+ . str_replace($this->namespaceSeparator, DIRECTORY_SEPARATOR, $className)
+ . $this->fileExtension);
+ }
+
+ /**
+ * Checks whether a class with a given name exists. A class "exists" if it is either
+ * already defined in the current request or if there is an autoloader on the SPL
+ * autoload stack that is a) responsible for the class in question and b) is able to
+ * load a class file in which the class definition resides.
+ *
+ * If the class is not already defined, each autoloader in the SPL autoload stack
+ * is asked whether it is able to tell if the class exists. If the autoloader is
+ * a <tt>ClassLoader</tt>, {@link canLoadClass} is used, otherwise the autoload
+ * function of the autoloader is invoked and expected to return a value that
+ * evaluates to TRUE if the class (file) exists. As soon as one autoloader reports
+ * that the class exists, TRUE is returned.
+ *
+ * Note that, depending on what kinds of autoloaders are installed on the SPL
+ * autoload stack, the class (file) might already be loaded as a result of checking
+ * for its existence. This is not the case with a <tt>ClassLoader</tt>, who separates
+ * these responsibilities.
+ *
+ * @param string $className The fully-qualified name of the class.
+ * @return boolean TRUE if the class exists as per the definition given above, FALSE otherwise.
+ */
+ public static function classExists($className)
+ {
+ if (class_exists($className, false)) {
+ return true;
+ }
+
+ foreach (spl_autoload_functions() as $loader) {
+ if (is_array($loader)) { // array(???, ???)
+ if (is_object($loader[0])) {
+ if ($loader[0] instanceof ClassLoader) { // array($obj, 'methodName')
+ if ($loader[0]->canLoadClass($className)) {
+ return true;
+ }
+ } else if ($loader[0]->{$loader[1]}($className)) {
+ return true;
+ }
+ } else if ($loader[0]::$loader[1]($className)) { // array('ClassName', 'methodName')
+ return true;
+ }
+ } else if ($loader instanceof \Closure) { // function($className) {..}
+ if ($loader($className)) {
+ return true;
+ }
+ } else if (is_string($loader) && $loader($className)) { // "MyClass::loadClass"
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets the <tt>ClassLoader</tt> from the SPL autoload stack that is responsible
+ * for (and is able to load) the class with the given name.
+ *
+ * @param string $className The name of the class.
+ * @return The <tt>ClassLoader</tt> for the class or NULL if no such <tt>ClassLoader</tt> exists.
+ */
+ public static function getClassLoader($className)
+ {
+ foreach (spl_autoload_functions() as $loader) {
+ if (is_array($loader) && $loader[0] instanceof ClassLoader &&
+ $loader[0]->canLoadClass($className)) {
+ return $loader[0];
+ }
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Collections;
+
+use Closure, ArrayIterator;
+
+/**
+ * An ArrayCollection is a Collection implementation that wraps a regular PHP array.
+ *
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class ArrayCollection implements Collection
+{
+ /**
+ * An array containing the entries of this collection.
+ *
+ * @var array
+ */
+ private $_elements;
+
+ /**
+ * Initializes a new ArrayCollection.
+ *
+ * @param array $elements
+ */
+ public function __construct(array $elements = array())
+ {
+ $this->_elements = $elements;
+ }
+
+ /**
+ * Gets the PHP array representation of this collection.
+ *
+ * @return array The PHP array representation of this collection.
+ */
+ public function toArray()
+ {
+ return $this->_elements;
+ }
+
+ /**
+ * Sets the internal iterator to the first element in the collection and
+ * returns this element.
+ *
+ * @return mixed
+ */
+ public function first()
+ {
+ return reset($this->_elements);
+ }
+
+ /**
+ * Sets the internal iterator to the last element in the collection and
+ * returns this element.
+ *
+ * @return mixed
+ */
+ public function last()
+ {
+ return end($this->_elements);
+ }
+
+ /**
+ * Gets the current key/index at the current internal iterator position.
+ *
+ * @return mixed
+ */
+ public function key()
+ {
+ return key($this->_elements);
+ }
+
+ /**
+ * Moves the internal iterator position to the next element.
+ *
+ * @return mixed
+ */
+ public function next()
+ {
+ return next($this->_elements);
+ }
+
+ /**
+ * Gets the element of the collection at the current internal iterator position.
+ *
+ * @return mixed
+ */
+ public function current()
+ {
+ return current($this->_elements);
+ }
+
+ /**
+ * Removes an element with a specific key/index from the collection.
+ *
+ * @param mixed $key
+ * @return mixed The removed element or NULL, if no element exists for the given key.
+ */
+ public function remove($key)
+ {
+ if (isset($this->_elements[$key])) {
+ $removed = $this->_elements[$key];
+ unset($this->_elements[$key]);
+
+ return $removed;
+ }
+
+ return null;
+ }
+
+ /**
+ * Removes the specified element from the collection, if it is found.
+ *
+ * @param mixed $element The element to remove.
+ * @return boolean TRUE if this collection contained the specified element, FALSE otherwise.
+ */
+ public function removeElement($element)
+ {
+ $key = array_search($element, $this->_elements, true);
+
+ if ($key !== false) {
+ unset($this->_elements[$key]);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * ArrayAccess implementation of offsetExists()
+ *
+ * @see containsKey()
+ */
+ public function offsetExists($offset)
+ {
+ return $this->containsKey($offset);
+ }
+
+ /**
+ * ArrayAccess implementation of offsetGet()
+ *
+ * @see get()
+ */
+ public function offsetGet($offset)
+ {
+ return $this->get($offset);
+ }
+
+ /**
+ * ArrayAccess implementation of offsetGet()
+ *
+ * @see add()
+ * @see set()
+ */
+ public function offsetSet($offset, $value)
+ {
+ if ( ! isset($offset)) {
+ return $this->add($value);
+ }
+ return $this->set($offset, $value);
+ }
+
+ /**
+ * ArrayAccess implementation of offsetUnset()
+ *
+ * @see remove()
+ */
+ public function offsetUnset($offset)
+ {
+ return $this->remove($offset);
+ }
+
+ /**
+ * Checks whether the collection contains a specific key/index.
+ *
+ * @param mixed $key The key to check for.
+ * @return boolean TRUE if the given key/index exists, FALSE otherwise.
+ */
+ public function containsKey($key)
+ {
+ return isset($this->_elements[$key]);
+ }
+
+ /**
+ * Checks whether the given element is contained in the collection.
+ * Only element values are compared, not keys. The comparison of two elements
+ * is strict, that means not only the value but also the type must match.
+ * For objects this means reference equality.
+ *
+ * @param mixed $element
+ * @return boolean TRUE if the given element is contained in the collection,
+ * FALSE otherwise.
+ */
+ public function contains($element)
+ {
+ return in_array($element, $this->_elements, true);
+ }
+
+ /**
+ * Tests for the existance of an element that satisfies the given predicate.
+ *
+ * @param Closure $p The predicate.
+ * @return boolean TRUE if the predicate is TRUE for at least one element, FALSE otherwise.
+ */
+ public function exists(Closure $p)
+ {
+ foreach ($this->_elements as $key => $element)
+ if ($p($key, $element)) return true;
+ return false;
+ }
+
+ /**
+ * Searches for a given element and, if found, returns the corresponding key/index
+ * of that element. The comparison of two elements is strict, that means not
+ * only the value but also the type must match.
+ * For objects this means reference equality.
+ *
+ * @param mixed $element The element to search for.
+ * @return mixed The key/index of the element or FALSE if the element was not found.
+ */
+ public function indexOf($element)
+ {
+ return array_search($element, $this->_elements, true);
+ }
+
+ /**
+ * Gets the element with the given key/index.
+ *
+ * @param mixed $key The key.
+ * @return mixed The element or NULL, if no element exists for the given key.
+ */
+ public function get($key)
+ {
+ if (isset($this->_elements[$key])) {
+ return $this->_elements[$key];
+ }
+ return null;
+ }
+
+ /**
+ * Gets all keys/indexes of the collection elements.
+ *
+ * @return array
+ */
+ public function getKeys()
+ {
+ return array_keys($this->_elements);
+ }
+
+ /**
+ * Gets all elements.
+ *
+ * @return array
+ */
+ public function getValues()
+ {
+ return array_values($this->_elements);
+ }
+
+ /**
+ * Returns the number of elements in the collection.
+ *
+ * Implementation of the Countable interface.
+ *
+ * @return integer The number of elements in the collection.
+ */
+ public function count()
+ {
+ return count($this->_elements);
+ }
+
+ /**
+ * Adds/sets an element in the collection at the index / with the specified key.
+ *
+ * When the collection is a Map this is like put(key,value)/add(key,value).
+ * When the collection is a List this is like add(position,value).
+ *
+ * @param mixed $key
+ * @param mixed $value
+ */
+ public function set($key, $value)
+ {
+ $this->_elements[$key] = $value;
+ }
+
+ /**
+ * Adds an element to the collection.
+ *
+ * @param mixed $value
+ * @return boolean Always TRUE.
+ */
+ public function add($value)
+ {
+ $this->_elements[] = $value;
+ return true;
+ }
+
+ /**
+ * Checks whether the collection is empty.
+ *
+ * Note: This is preferrable over count() == 0.
+ *
+ * @return boolean TRUE if the collection is empty, FALSE otherwise.
+ */
+ public function isEmpty()
+ {
+ return ! $this->_elements;
+ }
+
+ /**
+ * Gets an iterator for iterating over the elements in the collection.
+ *
+ * @return ArrayIterator
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->_elements);
+ }
+
+ /**
+ * Applies the given function to each element in the collection and returns
+ * a new collection with the elements returned by the function.
+ *
+ * @param Closure $func
+ * @return Collection
+ */
+ public function map(Closure $func)
+ {
+ return new ArrayCollection(array_map($func, $this->_elements));
+ }
+
+ /**
+ * Returns all the elements of this collection that satisfy the predicate p.
+ * The order of the elements is preserved.
+ *
+ * @param Closure $p The predicate used for filtering.
+ * @return Collection A collection with the results of the filter operation.
+ */
+ public function filter(Closure $p)
+ {
+ return new ArrayCollection(array_filter($this->_elements, $p));
+ }
+
+ /**
+ * Applies the given predicate p to all elements of this collection,
+ * returning true, if the predicate yields true for all elements.
+ *
+ * @param Closure $p The predicate.
+ * @return boolean TRUE, if the predicate yields TRUE for all elements, FALSE otherwise.
+ */
+ public function forAll(Closure $p)
+ {
+ foreach ($this->_elements as $key => $element) {
+ if ( ! $p($key, $element)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Partitions this collection in two collections according to a predicate.
+ * Keys are preserved in the resulting collections.
+ *
+ * @param Closure $p The predicate on which to partition.
+ * @return array An array with two elements. The first element contains the collection
+ * of elements where the predicate returned TRUE, the second element
+ * contains the collection of elements where the predicate returned FALSE.
+ */
+ public function partition(Closure $p)
+ {
+ $coll1 = $coll2 = array();
+ foreach ($this->_elements as $key => $element) {
+ if ($p($key, $element)) {
+ $coll1[$key] = $element;
+ } else {
+ $coll2[$key] = $element;
+ }
+ }
+ return array(new ArrayCollection($coll1), new ArrayCollection($coll2));
+ }
+
+ /**
+ * Returns a string representation of this object.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return __CLASS__ . '@' . spl_object_hash($this);
+ }
+
+ /**
+ * Clears the collection.
+ */
+ public function clear()
+ {
+ $this->_elements = array();
+ }
+
+ /**
+ * 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)
+ {
+ return array_slice($this->_elements, $offset, $length, true);
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Collections;
+
+use Closure, Countable, IteratorAggregate, ArrayAccess;
+
+/**
+ * The missing (SPL) Collection/Array/OrderedMap interface.
+ *
+ * A Collection resembles the nature of a regular PHP array. That is,
+ * it is essentially an <b>ordered map</b> that can also be used
+ * like a list.
+ *
+ * A Collection has an internal iterator just like a PHP array. In addition,
+ * a Collection can be iterated with external iterators, which is preferrable.
+ * To use an external iterator simply use the foreach language construct to
+ * iterate over the collection (which calls {@link getIterator()} internally) or
+ * explicitly retrieve an iterator though {@link getIterator()} which can then be
+ * used to iterate over the collection.
+ * You can not rely on the internal iterator of the collection being at a certain
+ * position unless you explicitly positioned it before. Prefer iteration with
+ * external iterators.
+ *
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+interface Collection extends Countable, IteratorAggregate, ArrayAccess
+{
+ /**
+ * Adds an element at the end of the collection.
+ *
+ * @param mixed $element The element to add.
+ * @return boolean Always TRUE.
+ */
+ function add($element);
+
+ /**
+ * Clears the collection, removing all elements.
+ */
+ function clear();
+
+ /**
+ * Checks whether an element is contained in the collection.
+ * This is an O(n) operation, where n is the size of the collection.
+ *
+ * @param mixed $element The element to search for.
+ * @return boolean TRUE if the collection contains the element, FALSE otherwise.
+ */
+ function contains($element);
+
+ /**
+ * Checks whether the collection is empty (contains no elements).
+ *
+ * @return boolean TRUE if the collection is empty, FALSE otherwise.
+ */
+ function isEmpty();
+
+ /**
+ * Removes the element at the specified index from the collection.
+ *
+ * @param string|integer $key The kex/index of the element to remove.
+ * @return mixed The removed element or NULL, if the collection did not contain the element.
+ */
+ function remove($key);
+
+ /**
+ * Removes the specified element from the collection, if it is found.
+ *
+ * @param mixed $element The element to remove.
+ * @return boolean TRUE if this collection contained the specified element, FALSE otherwise.
+ */
+ function removeElement($element);
+
+ /**
+ * Checks whether the collection contains an element with the specified key/index.
+ *
+ * @param string|integer $key The key/index to check for.
+ * @return boolean TRUE if the collection contains an element with the specified key/index,
+ * FALSE otherwise.
+ */
+ function containsKey($key);
+
+ /**
+ * Gets the element at the specified key/index.
+ *
+ * @param string|integer $key The key/index of the element to retrieve.
+ * @return mixed
+ */
+ function get($key);
+
+ /**
+ * Gets all keys/indices of the collection.
+ *
+ * @return array The keys/indices of the collection, in the order of the corresponding
+ * elements in the collection.
+ */
+ function getKeys();
+
+ /**
+ * Gets all values of the collection.
+ *
+ * @return array The values of all elements in the collection, in the order they
+ * appear in the collection.
+ */
+ function getValues();
+
+ /**
+ * Sets an element in the collection at the specified key/index.
+ *
+ * @param string|integer $key The key/index of the element to set.
+ * @param mixed $value The element to set.
+ */
+ function set($key, $value);
+
+ /**
+ * Gets a native PHP array representation of the collection.
+ *
+ * @return array
+ */
+ function toArray();
+
+ /**
+ * Sets the internal iterator to the first element in the collection and
+ * returns this element.
+ *
+ * @return mixed
+ */
+ function first();
+
+ /**
+ * Sets the internal iterator to the last element in the collection and
+ * returns this element.
+ *
+ * @return mixed
+ */
+ function last();
+
+ /**
+ * Gets the key/index of the element at the current iterator position.
+ *
+ */
+ function key();
+
+ /**
+ * Gets the element of the collection at the current iterator position.
+ *
+ */
+ function current();
+
+ /**
+ * Moves the internal iterator position to the next element.
+ *
+ */
+ function next();
+
+ /**
+ * Tests for the existence of an element that satisfies the given predicate.
+ *
+ * @param Closure $p The predicate.
+ * @return boolean TRUE if the predicate is TRUE for at least one element, FALSE otherwise.
+ */
+ function exists(Closure $p);
+
+ /**
+ * Returns all the elements of this collection that satisfy the predicate p.
+ * The order of the elements is preserved.
+ *
+ * @param Closure $p The predicate used for filtering.
+ * @return Collection A collection with the results of the filter operation.
+ */
+ function filter(Closure $p);
+
+ /**
+ * Applies the given predicate p to all elements of this collection,
+ * returning true, if the predicate yields true for all elements.
+ *
+ * @param Closure $p The predicate.
+ * @return boolean TRUE, if the predicate yields TRUE for all elements, FALSE otherwise.
+ */
+ function forAll(Closure $p);
+
+ /**
+ * Applies the given function to each element in the collection and returns
+ * a new collection with the elements returned by the function.
+ *
+ * @param Closure $func
+ * @return Collection
+ */
+ function map(Closure $func);
+
+ /**
+ * Partitions this collection in two collections according to a predicate.
+ * Keys are preserved in the resulting collections.
+ *
+ * @param Closure $p The predicate on which to partition.
+ * @return array An array with two elements. The first element contains the collection
+ * of elements where the predicate returned TRUE, the second element
+ * contains the collection of elements where the predicate returned FALSE.
+ */
+ function partition(Closure $p);
+
+ /**
+ * Gets the index/key of a given element. The comparison of two elements is strict,
+ * that means not only the value but also the type must match.
+ * For objects this means reference equality.
+ *
+ * @param mixed $element The element to search for.
+ * @return mixed The key/index of the element or FALSE if the element was not found.
+ */
+ function indexOf($element);
+
+ /**
+ * 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);
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common;
+
+/**
+ * Base exception class for package Doctrine\Common
+ * @author heinrich
+ *
+ */
+class CommonException extends \Exception {
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common;
+
+/**
+ * EventArgs is the base class for classes containing event data.
+ *
+ * This class contains no event data. It is used by events that do not pass state
+ * information to an event handler when an event is raised. The single empty EventArgs
+ * instance can be obtained through {@link getEmptyInstance}.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class EventArgs
+{
+ /**
+ * @var EventArgs Single instance of EventArgs
+ * @static
+ */
+ private static $_emptyEventArgsInstance;
+
+ /**
+ * Gets the single, empty and immutable EventArgs instance.
+ *
+ * This instance will be used when events are dispatched without any parameter,
+ * like this: EventManager::dispatchEvent('eventname');
+ *
+ * The benefit from this is that only one empty instance is instantiated and shared
+ * (otherwise there would be instances for every dispatched in the abovementioned form)
+ *
+ * @see EventManager::dispatchEvent
+ * @link http://msdn.microsoft.com/en-us/library/system.eventargs.aspx
+ * @static
+ * @return EventArgs
+ */
+ public static function getEmptyInstance()
+ {
+ if ( ! self::$_emptyEventArgsInstance) {
+ self::$_emptyEventArgsInstance = new EventArgs;
+ }
+
+ return self::$_emptyEventArgsInstance;
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common;
+
+use Doctrine\Common\Events\Event;
+
+/**
+ * The EventManager is the central point of Doctrine's event listener system.
+ * Listeners are registered on the manager and events are dispatched through the
+ * manager.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class EventManager
+{
+ /**
+ * Map of registered listeners.
+ * <event> => <listeners>
+ *
+ * @var array
+ */
+ private $_listeners = array();
+
+ /**
+ * Dispatches an event to all registered listeners.
+ *
+ * @param string $eventName The name of the event to dispatch. The name of the event is
+ * the name of the method that is invoked on listeners.
+ * @param EventArgs $eventArgs The event arguments to pass to the event handlers/listeners.
+ * If not supplied, the single empty EventArgs instance is used.
+ * @return boolean
+ */
+ public function dispatchEvent($eventName, EventArgs $eventArgs = null)
+ {
+ if (isset($this->_listeners[$eventName])) {
+ $eventArgs = $eventArgs === null ? EventArgs::getEmptyInstance() : $eventArgs;
+
+ foreach ($this->_listeners[$eventName] as $listener) {
+ $listener->$eventName($eventArgs);
+ }
+ }
+ }
+
+ /**
+ * Gets the listeners of a specific event or all listeners.
+ *
+ * @param string $event The name of the event.
+ * @return array The event listeners for the specified event, or all event listeners.
+ */
+ public function getListeners($event = null)
+ {
+ return $event ? $this->_listeners[$event] : $this->_listeners;
+ }
+
+ /**
+ * Checks whether an event has any registered listeners.
+ *
+ * @param string $event
+ * @return boolean TRUE if the specified event has any listeners, FALSE otherwise.
+ */
+ public function hasListeners($event)
+ {
+ return isset($this->_listeners[$event]) && $this->_listeners[$event];
+ }
+
+ /**
+ * Adds an event listener that listens on the specified events.
+ *
+ * @param string|array $events The event(s) to listen on.
+ * @param object $listener The listener object.
+ */
+ public function addEventListener($events, $listener)
+ {
+ // Picks the hash code related to that listener
+ $hash = spl_object_hash($listener);
+
+ foreach ((array) $events as $event) {
+ // Overrides listener if a previous one was associated already
+ // Prevents duplicate listeners on same event (same instance only)
+ $this->_listeners[$event][$hash] = $listener;
+ }
+ }
+
+ /**
+ * Removes an event listener from the specified events.
+ *
+ * @param string|array $events
+ * @param object $listener
+ */
+ public function removeEventListener($events, $listener)
+ {
+ // Picks the hash code related to that listener
+ $hash = spl_object_hash($listener);
+
+ foreach ((array) $events as $event) {
+ // Check if actually have this listener associated
+ if (isset($this->_listeners[$event][$hash])) {
+ unset($this->_listeners[$event][$hash]);
+ }
+ }
+ }
+
+ /**
+ * Adds an EventSubscriber. The subscriber is asked for all the events he is
+ * interested in and added as a listener for these events.
+ *
+ * @param Doctrine\Common\EventSubscriber $subscriber The subscriber.
+ */
+ public function addEventSubscriber(EventSubscriber $subscriber)
+ {
+ $this->addEventListener($subscriber->getSubscribedEvents(), $subscriber);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id: EventListener.php 4653 2008-07-10 17:17:58Z romanb $
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common;
+
+/**
+ * An EventSubscriber knows himself what events he is interested in.
+ * If an EventSubscriber is added to an EventManager, the manager invokes
+ * {@link getSubscribedEvents} and registers the subscriber as a listener for all
+ * returned events.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+interface EventSubscriber
+{
+ /**
+ * Returns an array of events this subscriber wants to listen to.
+ *
+ * @return array
+ */
+ public function getSubscribedEvents();
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common;
+
+/**
+ * Base class for writing simple lexers, i.e. for creating small DSLs.
+ *
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @todo Rename: AbstractLexer
+ */
+abstract class Lexer
+{
+ /**
+ * @var array Array of scanned tokens
+ */
+ private $tokens = array();
+
+ /**
+ * @var integer Current lexer position in input string
+ */
+ private $position = 0;
+
+ /**
+ * @var integer Current peek of current lexer position
+ */
+ private $peek = 0;
+
+ /**
+ * @var array The next token in the input.
+ */
+ public $lookahead;
+
+ /**
+ * @var array The last matched/seen token.
+ */
+ public $token;
+
+ /**
+ * Sets the input data to be tokenized.
+ *
+ * The Lexer is immediately reset and the new input tokenized.
+ * Any unprocessed tokens from any previous input are lost.
+ *
+ * @param string $input The input to be tokenized.
+ */
+ public function setInput($input)
+ {
+ $this->tokens = array();
+ $this->reset();
+ $this->scan($input);
+ }
+
+ /**
+ * Resets the lexer.
+ */
+ public function reset()
+ {
+ $this->lookahead = null;
+ $this->token = null;
+ $this->peek = 0;
+ $this->position = 0;
+ }
+
+ /**
+ * Resets the peek pointer to 0.
+ */
+ public function resetPeek()
+ {
+ $this->peek = 0;
+ }
+
+ /**
+ * Resets the lexer position on the input to the given position.
+ *
+ * @param integer $position Position to place the lexical scanner
+ */
+ public function resetPosition($position = 0)
+ {
+ $this->position = $position;
+ }
+
+ /**
+ * Checks whether a given token matches the current lookahead.
+ *
+ * @param integer|string $token
+ * @return boolean
+ */
+ public function isNextToken($token)
+ {
+ return $this->lookahead['type'] === $token;
+ }
+
+ /**
+ * Moves to the next token in the input string.
+ *
+ * A token is an associative array containing three items:
+ * - 'value' : the string value of the token in the input string
+ * - 'type' : the type of the token (identifier, numeric, string, input
+ * parameter, none)
+ * - 'position' : the position of the token in the input string
+ *
+ * @return array|null the next token; null if there is no more tokens left
+ */
+ public function moveNext()
+ {
+ $this->peek = 0;
+ $this->token = $this->lookahead;
+ $this->lookahead = (isset($this->tokens[$this->position]))
+ ? $this->tokens[$this->position++] : null;
+
+ return $this->lookahead !== null;
+ }
+
+ /**
+ * Tells the lexer to skip input tokens until it sees a token with the given value.
+ *
+ * @param $type The token type to skip until.
+ */
+ public function skipUntil($type)
+ {
+ while ($this->lookahead !== null && $this->lookahead['type'] !== $type) {
+ $this->moveNext();
+ }
+ }
+
+ /**
+ * Checks if given value is identical to the given token
+ *
+ * @param mixed $value
+ * @param integer $token
+ * @return boolean
+ */
+ public function isA($value, $token)
+ {
+ return $this->getType($value) === $token;
+ }
+
+ /**
+ * Moves the lookahead token forward.
+ *
+ * @return array | null The next token or NULL if there are no more tokens ahead.
+ */
+ public function peek()
+ {
+ if (isset($this->tokens[$this->position + $this->peek])) {
+ return $this->tokens[$this->position + $this->peek++];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Peeks at the next token, returns it and immediately resets the peek.
+ *
+ * @return array|null The next token or NULL if there are no more tokens ahead.
+ */
+ public function glimpse()
+ {
+ $peek = $this->peek();
+ $this->peek = 0;
+ return $peek;
+ }
+
+ /**
+ * Scans the input string for tokens.
+ *
+ * @param string $input a query string
+ */
+ protected function scan($input)
+ {
+ static $regex;
+
+ if ( ! isset($regex)) {
+ $regex = '/(' . implode(')|(', $this->getCatchablePatterns()) . ')|'
+ . implode('|', $this->getNonCatchablePatterns()) . '/i';
+ }
+
+ $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE;
+ $matches = preg_split($regex, $input, -1, $flags);
+
+ foreach ($matches as $match) {
+ // Must remain before 'value' assignment since it can change content
+ $type = $this->getType($match[0]);
+
+ $this->tokens[] = array(
+ 'value' => $match[0],
+ 'type' => $type,
+ 'position' => $match[1],
+ );
+ }
+ }
+
+ /**
+ * Gets the literal for a given token.
+ *
+ * @param integer $token
+ * @return string
+ */
+ public function getLiteral($token)
+ {
+ $className = get_class($this);
+ $reflClass = new \ReflectionClass($className);
+ $constants = $reflClass->getConstants();
+
+ foreach ($constants as $name => $value) {
+ if ($value === $token) {
+ return $className . '::' . $name;
+ }
+ }
+
+ return $token;
+ }
+
+ /**
+ * Lexical catchable patterns.
+ *
+ * @return array
+ */
+ abstract protected function getCatchablePatterns();
+
+ /**
+ * Lexical non-catchable patterns.
+ *
+ * @return array
+ */
+ abstract protected function getNonCatchablePatterns();
+
+ /**
+ * Retrieve token type. Also processes the token value if necessary.
+ *
+ * @param string $value
+ * @return integer
+ */
+ abstract protected function getType(&$value);
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common;
+
+/**
+ * Contract for classes that provide the service of notifying listeners of
+ * changes to their properties.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+interface NotifyPropertyChanged
+{
+ /**
+ * Adds a listener that wants to be notified about property changes.
+ *
+ * @param PropertyChangedListener $listener
+ */
+ function addPropertyChangedListener(PropertyChangedListener $listener);
+}
+
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common;
+
+/**
+ * Contract for classes that are potential listeners of a <tt>NotifyPropertyChanged</tt>
+ * implementor.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+interface PropertyChangedListener
+{
+ /**
+ * Notifies the listener of a property change.
+ *
+ * @param object $sender The object on which the property changed.
+ * @param string $propertyName The name of the property that changed.
+ * @param mixed $oldValue The old value of the property that changed.
+ * @param mixed $newValue The new value of the property that changed.
+ */
+ function propertyChanged($sender, $propertyName, $oldValue, $newValue);
+}
+
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Util;
+
+/**
+ * Static class containing most used debug methods.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
+ */
+final class Debug
+{
+ /**
+ * Private constructor (prevents from instantiation)
+ *
+ */
+ private function __construct() {}
+
+ /**
+ * Prints a dump of the public, protected and private properties of $var.
+ *
+ * @static
+ * @link http://xdebug.org/
+ * @param mixed $var
+ * @param integer $maxDepth Maximum nesting level for object properties
+ */
+ public static function dump($var, $maxDepth = 2)
+ {
+ ini_set('html_errors', 'On');
+
+ if (extension_loaded('xdebug')) {
+ ini_set('xdebug.var_display_max_depth', $maxDepth);
+ }
+
+ $var = self::export($var, $maxDepth++);
+
+ ob_start();
+ var_dump($var);
+ $dump = ob_get_contents();
+ ob_end_clean();
+
+ echo strip_tags(html_entity_decode($dump));
+
+ ini_set('html_errors', 'Off');
+ }
+
+ public static function export($var, $maxDepth)
+ {
+ $return = null;
+ $isObj = is_object($var);
+
+ if ($isObj && in_array('Doctrine\Common\Collections\Collection', class_implements($var))) {
+ $var = $var->toArray();
+ }
+
+ if ($maxDepth) {
+ if (is_array($var)) {
+ $return = array();
+
+ foreach ($var as $k => $v) {
+ $return[$k] = self::export($v, $maxDepth - 1);
+ }
+ } else if ($isObj) {
+ if ($var instanceof \DateTime) {
+ $return = $var->format('c');
+ } else {
+ $reflClass = new \ReflectionClass(get_class($var));
+ $return = new \stdclass();
+ $return->{'__CLASS__'} = get_class($var);
+
+ if ($var instanceof \Doctrine\ORM\Proxy\Proxy && ! $var->__isInitialized__) {
+ $reflProperty = $reflClass->getProperty('_identifier');
+ $reflProperty->setAccessible(true);
+
+ foreach ($reflProperty->getValue($var) as $name => $value) {
+ $return->$name = self::export($value, $maxDepth - 1);
+ }
+ } else {
+ $excludeProperties = array();
+
+ if ($var instanceof \Doctrine\ORM\Proxy\Proxy) {
+ $excludeProperties = array('_entityPersister', '__isInitialized__', '_identifier');
+ }
+
+ foreach ($reflClass->getProperties() as $reflProperty) {
+ $name = $reflProperty->getName();
+
+ if ( ! in_array($name, $excludeProperties)) {
+ $reflProperty->setAccessible(true);
+
+ $return->$name = self::export($reflProperty->getValue($var), $maxDepth - 1);
+ }
+ }
+ }
+ }
+ } else {
+ $return = $var;
+ }
+ } else {
+ $return = is_object($var) ? get_class($var)
+ : (is_array($var) ? 'Array(' . count($var) . ')' : $var);
+ }
+
+ return $return;
+ }
+
+ public static function toString($obj)
+ {
+ return method_exists('__toString', $obj) ? (string) $obj : get_class($obj) . '@' . spl_object_hash($obj);
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id: Inflector.php 3189 2007-11-18 20:37:44Z meus $
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Util;
+
+/**
+ * Doctrine inflector has static methods for inflecting text
+ *
+ * The methods in these classes are from several different sources collected
+ * across several different php projects and several different authors. The
+ * original author names and emails are not known
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 1.0
+ * @version $Revision: 3189 $
+ * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class Inflector
+{
+ /**
+ * Convert word in to the format for a Doctrine table name. Converts 'ModelName' to 'model_name'
+ *
+ * @param string $word Word to tableize
+ * @return string $word Tableized word
+ */
+ public static function tableize($word)
+ {
+ return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $word));
+ }
+
+ /**
+ * Convert a word in to the format for a Doctrine class name. Converts 'table_name' to 'TableName'
+ *
+ * @param string $word Word to classify
+ * @return string $word Classified word
+ */
+ public static function classify($word)
+ {
+ return str_replace(" ", "", ucwords(strtr($word, "_-", " ")));
+ }
+
+ /**
+ * Camelize a word. This uses the classify() method and turns the first character to lowercase
+ *
+ * @param string $word
+ * @return string $word
+ */
+ public static function camelize($word)
+ {
+ return lcfirst(self::classify($word));
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common;
+
+/**
+ * Class to store and retrieve the version of Doctrine
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class Version
+{
+ /**
+ * Current Doctrine Version
+ */
+ const VERSION = '2.0.0RC2-DEV';
+
+ /**
+ * 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);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL;
+
+use Doctrine\DBAL\Logging\SQLLogger;
+
+/**
+ * Configuration container for the Doctrine DBAL.
+ *
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @internal When adding a new configuration option just write a getter/setter
+ * pair and add the option to the _attributes array with a proper default value.
+ */
+class Configuration
+{
+ /**
+ * The attributes that are contained in the configuration.
+ * Values are default values.
+ *
+ * @var array
+ */
+ protected $_attributes = array();
+
+ /**
+ * Sets the SQL logger to use. Defaults to NULL which means SQL logging is disabled.
+ *
+ * @param SQLLogger $logger
+ */
+ public function setSQLLogger(SQLLogger $logger)
+ {
+ $this->_attributes['sqlLogger'] = $logger;
+ }
+
+ /**
+ * Gets the SQL logger that is used.
+ *
+ * @return SQLLogger
+ */
+ public function getSQLLogger()
+ {
+ return isset($this->_attributes['sqlLogger']) ?
+ $this->_attributes['sqlLogger'] : null;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL;
+
+use PDO, Closure, Exception,
+ Doctrine\DBAL\Types\Type,
+ Doctrine\DBAL\Driver\Connection as DriverConnection,
+ Doctrine\Common\EventManager,
+ Doctrine\DBAL\DBALException;
+
+/**
+ * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like
+ * events, transaction isolation levels, configuration, emulated transaction nesting,
+ * lazy connecting and more.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
+ * @author Lukas Smith <smith@pooteeweet.org> (MDB2 library)
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class Connection implements DriverConnection
+{
+ /**
+ * Constant for transaction isolation level READ UNCOMMITTED.
+ */
+ const TRANSACTION_READ_UNCOMMITTED = 1;
+
+ /**
+ * Constant for transaction isolation level READ COMMITTED.
+ */
+ const TRANSACTION_READ_COMMITTED = 2;
+
+ /**
+ * Constant for transaction isolation level REPEATABLE READ.
+ */
+ const TRANSACTION_REPEATABLE_READ = 3;
+
+ /**
+ * Constant for transaction isolation level SERIALIZABLE.
+ */
+ const TRANSACTION_SERIALIZABLE = 4;
+
+ /**
+ * The wrapped driver connection.
+ *
+ * @var Doctrine\DBAL\Driver\Connection
+ */
+ protected $_conn;
+
+ /**
+ * @var Doctrine\DBAL\Configuration
+ */
+ protected $_config;
+
+ /**
+ * @var Doctrine\Common\EventManager
+ */
+ protected $_eventManager;
+
+ /**
+ * Whether or not a connection has been established.
+ *
+ * @var boolean
+ */
+ private $_isConnected = false;
+
+ /**
+ * The transaction nesting level.
+ *
+ * @var integer
+ */
+ private $_transactionNestingLevel = 0;
+
+ /**
+ * The currently active transaction isolation level.
+ *
+ * @var integer
+ */
+ private $_transactionIsolationLevel;
+
+ /**
+ * If nested transations should use savepoints
+ *
+ * @var integer
+ */
+ private $_nestTransactionsWithSavepoints;
+
+ /**
+ * The parameters used during creation of the Connection instance.
+ *
+ * @var array
+ */
+ private $_params = array();
+
+ /**
+ * The DatabasePlatform object that provides information about the
+ * database platform used by the connection.
+ *
+ * @var Doctrine\DBAL\Platforms\AbstractPlatform
+ */
+ protected $_platform;
+
+ /**
+ * The schema manager.
+ *
+ * @var Doctrine\DBAL\Schema\SchemaManager
+ */
+ protected $_schemaManager;
+
+ /**
+ * The used DBAL driver.
+ *
+ * @var Doctrine\DBAL\Driver
+ */
+ protected $_driver;
+
+ /**
+ * Flag that indicates whether the current transaction is marked for rollback only.
+ *
+ * @var boolean
+ */
+ private $_isRollbackOnly = false;
+
+ /**
+ * Initializes a new instance of the Connection class.
+ *
+ * @param array $params The connection parameters.
+ * @param Driver $driver
+ * @param Configuration $config
+ * @param EventManager $eventManager
+ */
+ public function __construct(array $params, Driver $driver, Configuration $config = null,
+ EventManager $eventManager = null)
+ {
+ $this->_driver = $driver;
+ $this->_params = $params;
+
+ if (isset($params['pdo'])) {
+ $this->_conn = $params['pdo'];
+ $this->_isConnected = true;
+ }
+
+ // Create default config and event manager if none given
+ if ( ! $config) {
+ $config = new Configuration();
+ }
+
+ if ( ! $eventManager) {
+ $eventManager = new EventManager();
+ }
+
+ $this->_config = $config;
+ $this->_eventManager = $eventManager;
+ if ( ! isset($params['platform'])) {
+ $this->_platform = $driver->getDatabasePlatform();
+ } else if ($params['platform'] instanceof Platforms\AbstractPlatform) {
+ $this->_platform = $params['platform'];
+ } else {
+ throw DBALException::invalidPlatformSpecified();
+ }
+ $this->_transactionIsolationLevel = $this->_platform->getDefaultTransactionIsolationLevel();
+ }
+
+ /**
+ * Gets the parameters used during instantiation.
+ *
+ * @return array $params
+ */
+ public function getParams()
+ {
+ return $this->_params;
+ }
+
+ /**
+ * Gets the name of the database this Connection is connected to.
+ *
+ * @return string $database
+ */
+ public function getDatabase()
+ {
+ return $this->_driver->getDatabase($this);
+ }
+
+ /**
+ * Gets the hostname of the currently connected database.
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ return isset($this->_params['host']) ? $this->_params['host'] : null;
+ }
+
+ /**
+ * Gets the port of the currently connected database.
+ *
+ * @return mixed
+ */
+ public function getPort()
+ {
+ return isset($this->_params['port']) ? $this->_params['port'] : null;
+ }
+
+ /**
+ * Gets the username used by this connection.
+ *
+ * @return string
+ */
+ public function getUsername()
+ {
+ return isset($this->_params['user']) ? $this->_params['user'] : null;
+ }
+
+ /**
+ * Gets the password used by this connection.
+ *
+ * @return string
+ */
+ public function getPassword()
+ {
+ return isset($this->_params['password']) ? $this->_params['password'] : null;
+ }
+
+ /**
+ * Gets the DBAL driver instance.
+ *
+ * @return Doctrine\DBAL\Driver
+ */
+ public function getDriver()
+ {
+ return $this->_driver;
+ }
+
+ /**
+ * Gets the Configuration used by the Connection.
+ *
+ * @return Doctrine\DBAL\Configuration
+ */
+ public function getConfiguration()
+ {
+ return $this->_config;
+ }
+
+ /**
+ * Gets the EventManager used by the Connection.
+ *
+ * @return Doctrine\Common\EventManager
+ */
+ public function getEventManager()
+ {
+ return $this->_eventManager;
+ }
+
+ /**
+ * Gets the DatabasePlatform for the connection.
+ *
+ * @return Doctrine\DBAL\Platforms\AbstractPlatform
+ */
+ public function getDatabasePlatform()
+ {
+ return $this->_platform;
+ }
+
+ /**
+ * Establishes the connection with the database.
+ *
+ * @return boolean TRUE if the connection was successfully established, FALSE if
+ * the connection is already open.
+ */
+ public function connect()
+ {
+ if ($this->_isConnected) return false;
+
+ $driverOptions = isset($this->_params['driverOptions']) ?
+ $this->_params['driverOptions'] : array();
+ $user = isset($this->_params['user']) ? $this->_params['user'] : null;
+ $password = isset($this->_params['password']) ?
+ $this->_params['password'] : null;
+
+ $this->_conn = $this->_driver->connect($this->_params, $user, $password, $driverOptions);
+ $this->_isConnected = true;
+
+ if ($this->_eventManager->hasListeners(Events::postConnect)) {
+ $eventArgs = new Event\ConnectionEventArgs($this);
+ $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
+ }
+
+ return true;
+ }
+
+ /**
+ * Prepares and executes an SQL query and returns the first row of the result
+ * as an associative array.
+ *
+ * @param string $statement The SQL query.
+ * @param array $params The query parameters.
+ * @return array
+ */
+ public function fetchAssoc($statement, array $params = array())
+ {
+ return $this->executeQuery($statement, $params)->fetch(PDO::FETCH_ASSOC);
+ }
+
+ /**
+ * Prepares and executes an SQL query and returns the first row of the result
+ * as a numerically indexed array.
+ *
+ * @param string $statement sql query to be executed
+ * @param array $params prepared statement params
+ * @return array
+ */
+ public function fetchArray($statement, array $params = array())
+ {
+ return $this->executeQuery($statement, $params)->fetch(PDO::FETCH_NUM);
+ }
+
+ /**
+ * Prepares and executes an SQL query and returns the value of a single column
+ * of the first row of the result.
+ *
+ * @param string $statement sql query to be executed
+ * @param array $params prepared statement params
+ * @param int $colnum 0-indexed column number to retrieve
+ * @return mixed
+ */
+ public function fetchColumn($statement, array $params = array(), $colnum = 0)
+ {
+ return $this->executeQuery($statement, $params)->fetchColumn($colnum);
+ }
+
+ /**
+ * Whether an actual connection to the database is established.
+ *
+ * @return boolean
+ */
+ public function isConnected()
+ {
+ return $this->_isConnected;
+ }
+
+ /**
+ * Checks whether a transaction is currently active.
+ *
+ * @return boolean TRUE if a transaction is currently active, FALSE otherwise.
+ */
+ public function isTransactionActive()
+ {
+ return $this->_transactionNestingLevel > 0;
+ }
+
+ /**
+ * Executes an SQL DELETE statement on a table.
+ *
+ * @param string $table The name of the table on which to delete.
+ * @param array $identifier The deletion criteria. An associateve array containing column-value pairs.
+ * @return integer The number of affected rows.
+ */
+ public function delete($tableName, array $identifier)
+ {
+ $this->connect();
+
+ $criteria = array();
+
+ foreach (array_keys($identifier) as $columnName) {
+ $criteria[] = $columnName . ' = ?';
+ }
+
+ $query = 'DELETE FROM ' . $tableName . ' WHERE ' . implode(' AND ', $criteria);
+
+ return $this->executeUpdate($query, array_values($identifier));
+ }
+
+ /**
+ * Closes the connection.
+ *
+ * @return void
+ */
+ public function close()
+ {
+ unset($this->_conn);
+
+ $this->_isConnected = false;
+ }
+
+ /**
+ * Sets the transaction isolation level.
+ *
+ * @param integer $level The level to set.
+ */
+ public function setTransactionIsolation($level)
+ {
+ $this->_transactionIsolationLevel = $level;
+
+ return $this->executeUpdate($this->_platform->getSetTransactionIsolationSQL($level));
+ }
+
+ /**
+ * Gets the currently active transaction isolation level.
+ *
+ * @return integer The current transaction isolation level.
+ */
+ public function getTransactionIsolation()
+ {
+ return $this->_transactionIsolationLevel;
+ }
+
+ /**
+ * Executes an SQL UPDATE statement on a table.
+ *
+ * @param string $table The name of the table to update.
+ * @param array $identifier The update criteria. An associative array containing column-value pairs.
+ * @return integer The number of affected rows.
+ */
+ public function update($tableName, array $data, array $identifier)
+ {
+ $this->connect();
+ $set = array();
+ foreach ($data as $columnName => $value) {
+ $set[] = $columnName . ' = ?';
+ }
+
+ $params = array_merge(array_values($data), array_values($identifier));
+
+ $sql = 'UPDATE ' . $tableName . ' SET ' . implode(', ', $set)
+ . ' WHERE ' . implode(' = ? AND ', array_keys($identifier))
+ . ' = ?';
+
+ return $this->executeUpdate($sql, $params);
+ }
+
+ /**
+ * Inserts a table row with specified data.
+ *
+ * @param string $table The name of the table to insert data into.
+ * @param array $data An associative array containing column-value pairs.
+ * @return integer The number of affected rows.
+ */
+ public function insert($tableName, array $data)
+ {
+ $this->connect();
+
+ // column names are specified as array keys
+ $cols = array();
+ $placeholders = array();
+
+ foreach ($data as $columnName => $value) {
+ $cols[] = $columnName;
+ $placeholders[] = '?';
+ }
+
+ $query = 'INSERT INTO ' . $tableName
+ . ' (' . implode(', ', $cols) . ')'
+ . ' VALUES (' . implode(', ', $placeholders) . ')';
+
+ return $this->executeUpdate($query, array_values($data));
+ }
+
+ /**
+ * Sets the given charset on the current connection.
+ *
+ * @param string $charset The charset to set.
+ */
+ public function setCharset($charset)
+ {
+ $this->executeUpdate($this->_platform->getSetCharsetSQL($charset));
+ }
+
+ /**
+ * Quote a string so it can be safely used as a table or column name, even if
+ * it is a reserved name.
+ *
+ * Delimiting style depends on the underlying database platform that is being used.
+ *
+ * NOTE: Just because you CAN use quoted identifiers does not mean
+ * you SHOULD use them. In general, they end up causing way more
+ * problems than they solve.
+ *
+ * @param string $str The name to be quoted.
+ * @return string The quoted name.
+ */
+ public function quoteIdentifier($str)
+ {
+ return $this->_platform->quoteIdentifier($str);
+ }
+
+ /**
+ * Quotes a given input parameter.
+ *
+ * @param mixed $input Parameter to be quoted.
+ * @param string $type Type of the parameter.
+ * @return string The quoted parameter.
+ */
+ public function quote($input, $type = null)
+ {
+ $this->connect();
+
+ return $this->_conn->quote($input, $type);
+ }
+
+ /**
+ * Prepares and executes an SQL query and returns the result as an associative array.
+ *
+ * @param string $sql The SQL query.
+ * @param array $params The query parameters.
+ * @return array
+ */
+ public function fetchAll($sql, array $params = array())
+ {
+ return $this->executeQuery($sql, $params)->fetchAll(PDO::FETCH_ASSOC);
+ }
+
+ /**
+ * Prepares an SQL statement.
+ *
+ * @param string $statement The SQL statement to prepare.
+ * @return Doctrine\DBAL\Driver\Statement The prepared statement.
+ */
+ public function prepare($statement)
+ {
+ $this->connect();
+
+ return new Statement($statement, $this);
+ }
+
+ /**
+ * Executes an, optionally parameterized, SQL query.
+ *
+ * If the query is parameterized, a prepared statement is used.
+ * If an SQLLogger is configured, the execution is logged.
+ *
+ * @param string $query The SQL query to execute.
+ * @param array $params The parameters to bind to the query, if any.
+ * @return Doctrine\DBAL\Driver\Statement The executed statement.
+ * @internal PERF: Directly prepares a driver statement, not a wrapper.
+ */
+ public function executeQuery($query, array $params = array(), $types = array())
+ {
+ $this->connect();
+
+ $hasLogger = $this->_config->getSQLLogger() !== null;
+ if ($hasLogger) {
+ $this->_config->getSQLLogger()->startQuery($query, $params, $types);
+ }
+
+ if ($params) {
+ $stmt = $this->_conn->prepare($query);
+ if ($types) {
+ $this->_bindTypedValues($stmt, $params, $types);
+ $stmt->execute();
+ } else {
+ $stmt->execute($params);
+ }
+ } else {
+ $stmt = $this->_conn->query($query);
+ }
+
+ if ($hasLogger) {
+ $this->_config->getSQLLogger()->stopQuery();
+ }
+
+ return $stmt;
+ }
+
+ /**
+ * Executes an, optionally parameterized, SQL query and returns the result,
+ * applying a given projection/transformation function on each row of the result.
+ *
+ * @param string $query The SQL query to execute.
+ * @param array $params The parameters, if any.
+ * @param Closure $mapper The transformation function that is applied on each row.
+ * The function receives a single paramater, an array, that
+ * represents a row of the result set.
+ * @return mixed The projected result of the query.
+ */
+ public function project($query, array $params, Closure $function)
+ {
+ $result = array();
+ $stmt = $this->executeQuery($query, $params ?: array());
+
+ while ($row = $stmt->fetch()) {
+ $result[] = $function($row);
+ }
+
+ $stmt->closeCursor();
+
+ return $result;
+ }
+
+ /**
+ * Executes an SQL statement, returning a result set as a Statement object.
+ *
+ * @param string $statement
+ * @param integer $fetchType
+ * @return Doctrine\DBAL\Driver\Statement
+ */
+ public function query()
+ {
+ $this->connect();
+
+ return call_user_func_array(array($this->_conn, 'query'), func_get_args());
+ }
+
+ /**
+ * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
+ * and returns the number of affected rows.
+ *
+ * This method supports PDO binding types as well as DBAL mapping types.
+ *
+ * @param string $query The SQL query.
+ * @param array $params The query parameters.
+ * @param array $types The parameter types.
+ * @return integer The number of affected rows.
+ * @internal PERF: Directly prepares a driver statement, not a wrapper.
+ */
+ public function executeUpdate($query, array $params = array(), array $types = array())
+ {
+ $this->connect();
+
+ $hasLogger = $this->_config->getSQLLogger() !== null;
+ if ($hasLogger) {
+ $this->_config->getSQLLogger()->startQuery($query, $params, $types);
+ }
+
+ if ($params) {
+ $stmt = $this->_conn->prepare($query);
+ if ($types) {
+ $this->_bindTypedValues($stmt, $params, $types);
+ $stmt->execute();
+ } else {
+ $stmt->execute($params);
+ }
+ $result = $stmt->rowCount();
+ } else {
+ $result = $this->_conn->exec($query);
+ }
+
+ if ($hasLogger) {
+ $this->_config->getSQLLogger()->stopQuery();
+ }
+
+ return $result;
+ }
+
+ /**
+ * Execute an SQL statement and return the number of affected rows.
+ *
+ * @param string $statement
+ * @return integer The number of affected rows.
+ */
+ public function exec($statement)
+ {
+ $this->connect();
+ return $this->_conn->exec($statement);
+ }
+
+ /**
+ * Returns the current transaction nesting level.
+ *
+ * @return integer The nesting level. A value of 0 means there's no active transaction.
+ */
+ public function getTransactionNestingLevel()
+ {
+ return $this->_transactionNestingLevel;
+ }
+
+ /**
+ * Fetch the SQLSTATE associated with the last database operation.
+ *
+ * @return integer The last error code.
+ */
+ public function errorCode()
+ {
+ $this->connect();
+ return $this->_conn->errorCode();
+ }
+
+ /**
+ * Fetch extended error information associated with the last database operation.
+ *
+ * @return array The last error information.
+ */
+ public function errorInfo()
+ {
+ $this->connect();
+ return $this->_conn->errorInfo();
+ }
+
+ /**
+ * Returns the ID of the last inserted row, or the last value from a sequence object,
+ * depending on the underlying driver.
+ *
+ * Note: This method may not return a meaningful or consistent result across different drivers,
+ * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
+ * columns or sequences.
+ *
+ * @param string $seqName Name of the sequence object from which the ID should be returned.
+ * @return string A string representation of the last inserted ID.
+ */
+ public function lastInsertId($seqName = null)
+ {
+ $this->connect();
+ return $this->_conn->lastInsertId($seqName);
+ }
+
+ /**
+ * Executes a function in a transaction.
+ *
+ * The function gets passed this Connection instance as an (optional) parameter.
+ *
+ * If an exception occurs during execution of the function or transaction commit,
+ * the transaction is rolled back and the exception re-thrown.
+ *
+ * @param Closure $func The function to execute transactionally.
+ */
+ public function transactional(Closure $func)
+ {
+ $this->beginTransaction();
+ try {
+ $func($this);
+ $this->commit();
+ } catch (Exception $e) {
+ $this->rollback();
+ throw $e;
+ }
+ }
+
+ /**
+ * Set if nested transactions should use savepoints
+ *
+ * @param boolean
+ * @return void
+ */
+ public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints)
+ {
+ if ($this->_transactionNestingLevel > 0) {
+ throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction();
+ }
+
+ if (!$this->_platform->supportsSavepoints()) {
+ throw ConnectionException::savepointsNotSupported();
+ }
+
+ $this->_nestTransactionsWithSavepoints = $nestTransactionsWithSavepoints;
+ }
+
+ /**
+ * Get if nested transactions should use savepoints
+ *
+ * @return boolean
+ */
+ public function getNestTransactionsWithSavepoints()
+ {
+ return $this->_nestTransactionsWithSavepoints;
+ }
+
+ /**
+ * Returns the savepoint name to use for nested transactions are false if they are not supported
+ * "savepointFormat" parameter is not set
+ *
+ * @return mixed a string with the savepoint name or false
+ */
+ protected function _getNestedTransactionSavePointName()
+ {
+ return 'DOCTRINE2_SAVEPOINT_'.$this->_transactionNestingLevel;
+ }
+
+ /**
+ * Starts a transaction by suspending auto-commit mode.
+ *
+ * @return void
+ */
+ public function beginTransaction()
+ {
+ $this->connect();
+
+ ++$this->_transactionNestingLevel;
+
+ if ($this->_transactionNestingLevel == 1) {
+ $this->_conn->beginTransaction();
+ } else if ($this->_nestTransactionsWithSavepoints) {
+ $this->createSavepoint($this->_getNestedTransactionSavePointName());
+ }
+ }
+
+ /**
+ * Commits the current transaction.
+ *
+ * @return void
+ * @throws ConnectionException If the commit failed due to no active transaction or
+ * because the transaction was marked for rollback only.
+ */
+ public function commit()
+ {
+ if ($this->_transactionNestingLevel == 0) {
+ throw ConnectionException::noActiveTransaction();
+ }
+ if ($this->_isRollbackOnly) {
+ throw ConnectionException::commitFailedRollbackOnly();
+ }
+
+ $this->connect();
+
+ if ($this->_transactionNestingLevel == 1) {
+ $this->_conn->commit();
+ } else if ($this->_nestTransactionsWithSavepoints) {
+ $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
+ }
+
+ --$this->_transactionNestingLevel;
+ }
+
+ /**
+ * Cancel any database changes done during the current transaction.
+ *
+ * this method can be listened with onPreTransactionRollback and onTransactionRollback
+ * eventlistener methods
+ *
+ * @throws ConnectionException If the rollback operation failed.
+ */
+ public function rollback()
+ {
+ if ($this->_transactionNestingLevel == 0) {
+ throw ConnectionException::noActiveTransaction();
+ }
+
+ $this->connect();
+
+ if ($this->_transactionNestingLevel == 1) {
+ $this->_transactionNestingLevel = 0;
+ $this->_conn->rollback();
+ $this->_isRollbackOnly = false;
+ } else if ($this->_nestTransactionsWithSavepoints) {
+ $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
+ --$this->_transactionNestingLevel;
+ } else {
+ $this->_isRollbackOnly = true;
+ --$this->_transactionNestingLevel;
+ }
+ }
+
+ /**
+ * createSavepoint
+ * creates a new savepoint
+ *
+ * @param string $savepoint name of a savepoint to set
+ * @return void
+ */
+ public function createSavepoint($savepoint)
+ {
+ if (!$this->_platform->supportsSavepoints()) {
+ throw ConnectionException::savepointsNotSupported();
+ }
+
+ $this->_conn->exec($this->_platform->createSavePoint($savepoint));
+ }
+
+ /**
+ * releaseSavePoint
+ * releases given savepoint
+ *
+ * @param string $savepoint name of a savepoint to release
+ * @return void
+ */
+ public function releaseSavepoint($savepoint)
+ {
+ if (!$this->_platform->supportsSavepoints()) {
+ throw ConnectionException::savepointsNotSupported();
+ }
+
+ if ($this->_platform->supportsReleaseSavepoints()) {
+ $this->_conn->exec($this->_platform->releaseSavePoint($savepoint));
+ }
+ }
+
+ /**
+ * rollbackSavePoint
+ * releases given savepoint
+ *
+ * @param string $savepoint name of a savepoint to rollback to
+ * @return void
+ */
+ public function rollbackSavepoint($savepoint)
+ {
+ if (!$this->_platform->supportsSavepoints()) {
+ throw ConnectionException::savepointsNotSupported();
+ }
+
+ $this->_conn->exec($this->_platform->rollbackSavePoint($savepoint));
+ }
+
+ /**
+ * Gets the wrapped driver connection.
+ *
+ * @return Doctrine\DBAL\Driver\Connection
+ */
+ public function getWrappedConnection()
+ {
+ $this->connect();
+
+ return $this->_conn;
+ }
+
+ /**
+ * Gets the SchemaManager that can be used to inspect or change the
+ * database schema through the connection.
+ *
+ * @return Doctrine\DBAL\Schema\SchemaManager
+ */
+ public function getSchemaManager()
+ {
+ if ( ! $this->_schemaManager) {
+ $this->_schemaManager = $this->_driver->getSchemaManager($this);
+ }
+
+ return $this->_schemaManager;
+ }
+
+ /**
+ * Marks the current transaction so that the only possible
+ * outcome for the transaction to be rolled back.
+ *
+ * @throws ConnectionException If no transaction is active.
+ */
+ public function setRollbackOnly()
+ {
+ if ($this->_transactionNestingLevel == 0) {
+ throw ConnectionException::noActiveTransaction();
+ }
+ $this->_isRollbackOnly = true;
+ }
+
+ /**
+ * Check whether the current transaction is marked for rollback only.
+ *
+ * @return boolean
+ * @throws ConnectionException If no transaction is active.
+ */
+ public function isRollbackOnly()
+ {
+ if ($this->_transactionNestingLevel == 0) {
+ throw ConnectionException::noActiveTransaction();
+ }
+ return $this->_isRollbackOnly;
+ }
+
+ /**
+ * Converts a given value to its database representation according to the conversion
+ * rules of a specific DBAL mapping type.
+ *
+ * @param mixed $value The value to convert.
+ * @param string $type The name of the DBAL mapping type.
+ * @return mixed The converted value.
+ */
+ public function convertToDatabaseValue($value, $type)
+ {
+ return Type::getType($type)->convertToDatabaseValue($value, $this->_platform);
+ }
+
+ /**
+ * Converts a given value to its PHP representation according to the conversion
+ * rules of a specific DBAL mapping type.
+ *
+ * @param mixed $value The value to convert.
+ * @param string $type The name of the DBAL mapping type.
+ * @return mixed The converted type.
+ */
+ public function convertToPHPValue($value, $type)
+ {
+ return Type::getType($type)->convertToPHPValue($value, $this->_platform);
+ }
+
+ /**
+ * Binds a set of parameters, some or all of which are typed with a PDO binding type
+ * or DBAL mapping type, to a given statement.
+ *
+ * @param $stmt The statement to bind the values to.
+ * @param array $params The map/list of named/positional parameters.
+ * @param array $types The parameter types (PDO binding types or DBAL mapping types).
+ * @internal Duck-typing used on the $stmt parameter to support driver statements as well as
+ * raw PDOStatement instances.
+ */
+ private function _bindTypedValues($stmt, array $params, array $types)
+ {
+ // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
+ if (is_int(key($params))) {
+ // Positional parameters
+ $typeOffset = isset($types[0]) ? -1 : 0;
+ $bindIndex = 1;
+ foreach ($params as $position => $value) {
+ $typeIndex = $bindIndex + $typeOffset;
+ if (isset($types[$typeIndex])) {
+ $type = $types[$typeIndex];
+ if (is_string($type)) {
+ $type = Type::getType($type);
+ }
+ if ($type instanceof Type) {
+ $value = $type->convertToDatabaseValue($value, $this->_platform);
+ $bindingType = $type->getBindingType();
+ } else {
+ $bindingType = $type; // PDO::PARAM_* constants
+ }
+ $stmt->bindValue($bindIndex, $value, $bindingType);
+ } else {
+ $stmt->bindValue($bindIndex, $value);
+ }
+ ++$bindIndex;
+ }
+ } else {
+ // Named parameters
+ foreach ($params as $name => $value) {
+ if (isset($types[$name])) {
+ $type = $types[$name];
+ if (is_string($type)) {
+ $type = Type::getType($type);
+ }
+ if ($type instanceof Type) {
+ $value = $type->convertToDatabaseValue($value, $this->_platform);
+ $bindingType = $type->getBindingType();
+ } else {
+ $bindingType = $type; // PDO::PARAM_* constants
+ }
+ $stmt->bindValue($name, $value, $bindingType);
+ } else {
+ $stmt->bindValue($name, $value);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id: Exception.php 4628 2008-07-04 16:32:19Z romanb $
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL;
+
+/**
+ * Doctrine\DBAL\ConnectionException
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 4628 $
+ * @author Jonathan H. Wage <jonwage@gmail.com
+ */
+class ConnectionException extends DBALException
+{
+ public static function commitFailedRollbackOnly()
+ {
+ return new self("Transaction commit failed because the transaction has been marked for rollback only.");
+ }
+
+ public static function noActiveTransaction()
+ {
+ return new self("There is no active transaction.");
+ }
+
+ public static function savepointsNotSupported()
+ {
+ return new self("Savepoints are not supported by this driver.");
+ }
+
+ public static function mayNotAlterNestedTransactionWithSavepointsInTransaction()
+ {
+ return new self("May not alter the nested transaction with savepoints behavior while a transaction is open.");
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace Doctrine\DBAL;
+
+class DBALException extends \Exception
+{
+ public static function notSupported($method)
+ {
+ return new self("Operation '$method' is not supported by platform.");
+ }
+
+ public static function invalidPlatformSpecified()
+ {
+ return new self(
+ "Invalid 'platform' option specified, need to give an instance of ".
+ "\Doctrine\DBAL\Platforms\AbstractPlatform.");
+ }
+
+ public static function invalidPdoInstance()
+ {
+ return new self(
+ "The 'pdo' option was used in DriverManager::getConnection() but no ".
+ "instance of PDO was given."
+ );
+ }
+
+ public static function driverRequired()
+ {
+ return new self("The options 'driver' or 'driverClass' are mandatory if no PDO ".
+ "instance is given to DriverManager::getConnection().");
+ }
+
+ public static function unknownDriver($unknownDriverName, array $knownDrivers)
+ {
+ return new self("The given 'driver' ".$unknownDriverName." is unknown, ".
+ "Doctrine currently supports only the following drivers: ".implode(", ", $knownDrivers));
+ }
+
+ public static function invalidWrapperClass($wrapperClass)
+ {
+ return new self("The given 'wrapperClass' ".$wrapperClass." has to be a ".
+ "subtype of \Doctrine\DBAL\Connection.");
+ }
+
+ public static function invalidDriverClass($driverClass)
+ {
+ return new self("The given 'driverClass' ".$driverClass." has to implement the ".
+ "\Doctrine\DBAL\Driver interface.");
+ }
+
+ /**
+ * @param string $tableName
+ * @return DBALException
+ */
+ public static function invalidTableName($tableName)
+ {
+ return new self("Invalid table name specified: ".$tableName);
+ }
+
+ /**
+ * @param string $tableName
+ * @return DBALException
+ */
+ public static function noColumnsSpecifiedForTable($tableName)
+ {
+ return new self("No columns specified for table ".$tableName);
+ }
+
+ public static function limitOffsetInvalid()
+ {
+ return new self("Invalid Offset in Limit Query, it has to be larger or equal to 0.");
+ }
+
+ public static function typeExists($name)
+ {
+ return new self('Type '.$name.' already exists.');
+ }
+
+ public static function unknownColumnType($name)
+ {
+ return new self('Unknown column type '.$name.' requested.');
+ }
+
+ public static function typeNotFound($name)
+ {
+ return new self('Type to be overwritten '.$name.' does not exist.');
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL;
+
+/**
+ * Driver interface.
+ * Interface that all DBAL drivers must implement.
+ *
+ * @since 2.0
+ */
+interface Driver
+{
+ /**
+ * Attempts to create a connection with the database.
+ *
+ * @param array $params All connection parameters passed by the user.
+ * @param string $username The username to use when connecting.
+ * @param string $password The password to use when connecting.
+ * @param array $driverOptions The driver options to use when connecting.
+ * @return Doctrine\DBAL\Driver\Connection The database connection.
+ */
+ public function connect(array $params, $username = null, $password = null, array $driverOptions = array());
+
+ /**
+ * Gets the DatabasePlatform instance that provides all the metadata about
+ * the platform this driver connects to.
+ *
+ * @return Doctrine\DBAL\Platforms\AbstractPlatform The database platform.
+ */
+ public function getDatabasePlatform();
+
+ /**
+ * Gets the SchemaManager that can be used to inspect and change the underlying
+ * database schema of the platform this driver connects to.
+ *
+ * @param Doctrine\DBAL\Connection $conn
+ * @return Doctrine\DBAL\SchemaManager
+ */
+ public function getSchemaManager(Connection $conn);
+
+ /**
+ * Gets the name of the driver.
+ *
+ * @return string The name of the driver.
+ */
+ public function getName();
+
+ /**
+ * Get the name of the database connected to for this driver.
+ *
+ * @param Doctrine\DBAL\Connection $conn
+ * @return string $database
+ */
+ public function getDatabase(Connection $conn);
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Driver;
+
+/**
+ * Connection interface.
+ * Driver connections must implement this interface.
+ *
+ * This resembles (a subset of) the PDO interface.
+ *
+ * @since 2.0
+ */
+interface Connection
+{
+ function prepare($prepareString);
+ function query();
+ function quote($input, $type=\PDO::PARAM_STR);
+ function exec($statement);
+ function lastInsertId($name = null);
+ function beginTransaction();
+ function commit();
+ function rollBack();
+ function errorCode();
+ function errorInfo();
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\DBAL\Driver\IBMDB2;
+
+class DB2Connection implements \Doctrine\DBAL\Driver\Connection
+{
+ private $_conn = null;
+
+ public function __construct(array $params, $username, $password, $driverOptions = array())
+ {
+ $isPersistant = (isset($params['persistent']) && $params['persistent'] == true);
+
+ if ($isPersistant) {
+ $this->_conn = db2_pconnect($params['dbname'], $username, $password, $driverOptions);
+ } else {
+ $this->_conn = db2_connect($params['dbname'], $username, $password, $driverOptions);
+ }
+ if (!$this->_conn) {
+ throw new DB2Exception(db2_conn_errormsg());
+ }
+ }
+
+ function prepare($sql)
+ {
+ $stmt = @db2_prepare($this->_conn, $sql);
+ if (!$stmt) {
+ throw new DB2Exception(db2_stmt_errormsg());
+ }
+ return new DB2Statement($stmt);
+ }
+
+ function query()
+ {
+ $args = func_get_args();
+ $sql = $args[0];
+ $stmt = $this->prepare($sql);
+ $stmt->execute();
+ return $stmt;
+ }
+
+ function quote($input, $type=\PDO::PARAM_STR)
+ {
+ $input = db2_escape_string($input);
+ if ($type == \PDO::PARAM_INT ) {
+ return $input;
+ } else {
+ return "'".$input."'";
+ }
+ }
+
+ function exec($statement)
+ {
+ $stmt = $this->prepare($statement);
+ $stmt->execute();
+ return $stmt->rowCount();
+ }
+
+ function lastInsertId($name = null)
+ {
+ return db2_last_insert_id($this->_conn);
+ }
+
+ function beginTransaction()
+ {
+ db2_autocommit($this->_conn, DB2_AUTOCOMMIT_OFF);
+ }
+
+ function commit()
+ {
+ if (!db2_commit($this->_conn)) {
+ throw new DB2Exception(db2_conn_errormsg($this->_conn));
+ }
+ db2_autocommit($this->_conn, DB2_AUTOCOMMIT_ON);
+ }
+
+ function rollBack()
+ {
+ if (!db2_rollback($this->_conn)) {
+ throw new DB2Exception(db2_conn_errormsg($this->_conn));
+ }
+ db2_autocommit($this->_conn, DB2_AUTOCOMMIT_ON);
+ }
+
+ function errorCode()
+ {
+ return db2_conn_error($this->_conn);
+ }
+
+ function errorInfo()
+ {
+ return array(
+ 0 => db2_conn_errormsg($this->_conn),
+ 1 => $this->errorCode(),
+ );
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\DBAL\Driver\IBMDB2;
+
+use Doctrine\DBAL\Driver,
+ Doctrine\DBAL\Connection;
+
+/**
+ * IBM DB2 Driver
+ *
+ * @since 2.0
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class DB2Driver implements Driver
+{
+ /**
+ * Attempts to create a connection with the database.
+ *
+ * @param array $params All connection parameters passed by the user.
+ * @param string $username The username to use when connecting.
+ * @param string $password The password to use when connecting.
+ * @param array $driverOptions The driver options to use when connecting.
+ * @return Doctrine\DBAL\Driver\Connection The database connection.
+ */
+ public function connect(array $params, $username = null, $password = null, array $driverOptions = array())
+ {
+ if ( !isset($params['schema']) ) {
+
+ }
+
+ if ($params['host'] !== 'localhost' && $params['host'] != '127.0.0.1') {
+ // if the host isn't localhost, use extended connection params
+ $params['dbname'] = 'DRIVER={IBM DB2 ODBC DRIVER}' .
+ ';DATABASE=' . $params['dbname'] .
+ ';HOSTNAME=' . $params['host'] .
+ ';PORT=' . $params['port'] .
+ ';PROTOCOL=' . $params['protocol'] .
+ ';UID=' . $username .
+ ';PWD=' . $password .';';
+ $username = null;
+ $password = null;
+ }
+
+ return new DB2Connection($params, $username, $password, $driverOptions);
+ }
+
+ /**
+ * Gets the DatabasePlatform instance that provides all the metadata about
+ * the platform this driver connects to.
+ *
+ * @return Doctrine\DBAL\Platforms\AbstractPlatform The database platform.
+ */
+ public function getDatabasePlatform()
+ {
+ return new \Doctrine\DBAL\Platforms\DB2Platform;
+ }
+
+ /**
+ * Gets the SchemaManager that can be used to inspect and change the underlying
+ * database schema of the platform this driver connects to.
+ *
+ * @param Doctrine\DBAL\Connection $conn
+ * @return Doctrine\DBAL\SchemaManager
+ */
+ public function getSchemaManager(Connection $conn)
+ {
+ return new \Doctrine\DBAL\Schema\DB2SchemaManager($conn);
+ }
+
+ /**
+ * Gets the name of the driver.
+ *
+ * @return string The name of the driver.
+ */
+ public function getName()
+ {
+ return 'ibm_db2';
+ }
+
+ /**
+ * Get the name of the database connected to for this driver.
+ *
+ * @param Doctrine\DBAL\Connection $conn
+ * @return string $database
+ */
+ public function getDatabase(\Doctrine\DBAL\Connection $conn)
+ {
+ $params = $conn->getParams();
+ return $params['dbname'];
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\DBAL\Driver\IBMDB2;
+
+class DB2Exception extends \Exception
+{
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\DBAL\Driver\IBMDB2;
+
+class DB2Statement implements \Doctrine\DBAL\Driver\Statement
+{
+ private $_stmt = null;
+
+ private $_bindParam = array();
+
+ /**
+ * DB2_BINARY, DB2_CHAR, DB2_DOUBLE, or DB2_LONG
+ * @var <type>
+ */
+ static private $_typeMap = array(
+ \PDO::PARAM_INT => DB2_LONG,
+ \PDO::PARAM_STR => DB2_CHAR,
+ );
+
+ public function __construct($stmt)
+ {
+ $this->_stmt = $stmt;
+ }
+
+ /**
+ * Binds a value to a corresponding named or positional
+ * placeholder in the SQL statement that was used to prepare the statement.
+ *
+ * @param mixed $param Parameter identifier. For a prepared statement using named placeholders,
+ * this will be a parameter name of the form :name. For a prepared statement
+ * using question mark placeholders, this will be the 1-indexed position of the parameter
+ *
+ * @param mixed $value The value to bind to the parameter.
+ * @param integer $type Explicit data type for the parameter using the PDO::PARAM_* constants.
+ *
+ * @return boolean Returns TRUE on success or FALSE on failure.
+ */
+ function bindValue($param, $value, $type = null)
+ {
+ return $this->bindParam($param, $value, $type);
+ }
+
+ /**
+ * Binds a PHP variable to a corresponding named or question mark placeholder in the
+ * SQL statement that was use to prepare the statement. Unlike PDOStatement->bindValue(),
+ * the variable is bound as a reference and will only be evaluated at the time
+ * that PDOStatement->execute() is called.
+ *
+ * Most parameters are input parameters, that is, parameters that are
+ * used in a read-only fashion to build up the query. Some drivers support the invocation
+ * of stored procedures that return data as output parameters, and some also as input/output
+ * parameters that both send in data and are updated to receive it.
+ *
+ * @param mixed $param Parameter identifier. For a prepared statement using named placeholders,
+ * this will be a parameter name of the form :name. For a prepared statement
+ * using question mark placeholders, this will be the 1-indexed position of the parameter
+ *
+ * @param mixed $variable Name of the PHP variable to bind to the SQL statement parameter.
+ *
+ * @param integer $type Explicit data type for the parameter using the PDO::PARAM_* constants. To return
+ * an INOUT parameter from a stored procedure, use the bitwise OR operator to set the
+ * PDO::PARAM_INPUT_OUTPUT bits for the data_type parameter.
+ * @return boolean Returns TRUE on success or FALSE on failure.
+ */
+ function bindParam($column, &$variable, $type = null)
+ {
+ $this->_bindParam[$column] =& $variable;
+
+ if ($type && isset(self::$_typeMap[$type])) {
+ $type = self::$_typeMap[$type];
+ } else {
+ $type = DB2_CHAR;
+ }
+
+ if (!db2_bind_param($this->_stmt, $column, "variable", DB2_PARAM_IN, $type)) {
+ throw new DB2Exception(db2_stmt_errormsg());
+ }
+ return true;
+ }
+
+ /**
+ * Closes the cursor, enabling the statement to be executed again.
+ *
+ * @return boolean Returns TRUE on success or FALSE on failure.
+ */
+ function closeCursor()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $this->_bindParam = array();
+ db2_free_result($this->_stmt);
+ $ret = db2_free_stmt($this->_stmt);
+ $this->_stmt = false;
+ return $ret;
+ }
+
+ /**
+ * columnCount
+ * Returns the number of columns in the result set
+ *
+ * @return integer Returns the number of columns in the result set represented
+ * by the PDOStatement object. If there is no result set,
+ * this method should return 0.
+ */
+ function columnCount()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+ return db2_num_fields($this->_stmt);
+ }
+
+ /**
+ * errorCode
+ * Fetch the SQLSTATE associated with the last operation on the statement handle
+ *
+ * @see Doctrine_Adapter_Interface::errorCode()
+ * @return string error code string
+ */
+ function errorCode()
+ {
+ return db2_stmt_error();
+ }
+
+ /**
+ * errorInfo
+ * Fetch extended error information associated with the last operation on the statement handle
+ *
+ * @see Doctrine_Adapter_Interface::errorInfo()
+ * @return array error info array
+ */
+ function errorInfo()
+ {
+ return array(
+ 0 => db2_stmt_errormsg(),
+ 1 => db2_stmt_error(),
+ );
+ }
+
+ /**
+ * Executes a prepared statement
+ *
+ * If the prepared statement included parameter markers, you must either:
+ * call PDOStatement->bindParam() to bind PHP variables to the parameter markers:
+ * bound variables pass their value as input and receive the output value,
+ * if any, of their associated parameter markers or pass an array of input-only
+ * parameter values
+ *
+ *
+ * @param array $params An array of values with as many elements as there are
+ * bound parameters in the SQL statement being executed.
+ * @return boolean Returns TRUE on success or FALSE on failure.
+ */
+ function execute($params = null)
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ /*$retval = true;
+ if ($params !== null) {
+ $retval = @db2_execute($this->_stmt, $params);
+ } else {
+ $retval = @db2_execute($this->_stmt);
+ }*/
+ if ($params === null) {
+ ksort($this->_bindParam);
+ $params = array_values($this->_bindParam);
+ }
+ $retval = @db2_execute($this->_stmt, $params);
+
+ if ($retval === false) {
+ throw new DB2Exception(db2_stmt_errormsg());
+ }
+ return $retval;
+ }
+
+ /**
+ * fetch
+ *
+ * @see Query::HYDRATE_* constants
+ * @param integer $fetchStyle Controls how the next row will be returned to the caller.
+ * This value must be one of the Query::HYDRATE_* constants,
+ * defaulting to Query::HYDRATE_BOTH
+ *
+ * @param integer $cursorOrientation For a PDOStatement object representing a scrollable cursor,
+ * this value determines which row will be returned to the caller.
+ * This value must be one of the Query::HYDRATE_ORI_* constants, defaulting to
+ * Query::HYDRATE_ORI_NEXT. To request a scrollable cursor for your
+ * PDOStatement object,
+ * you must set the PDO::ATTR_CURSOR attribute to Doctrine::CURSOR_SCROLL when you
+ * prepare the SQL statement with Doctrine_Adapter_Interface->prepare().
+ *
+ * @param integer $cursorOffset For a PDOStatement object representing a scrollable cursor for which the
+ * $cursorOrientation parameter is set to Query::HYDRATE_ORI_ABS, this value specifies
+ * the absolute number of the row in the result set that shall be fetched.
+ *
+ * For a PDOStatement object representing a scrollable cursor for
+ * which the $cursorOrientation parameter is set to Query::HYDRATE_ORI_REL, this value
+ * specifies the row to fetch relative to the cursor position before
+ * PDOStatement->fetch() was called.
+ *
+ * @return mixed
+ */
+ function fetch($fetchStyle = \PDO::FETCH_BOTH)
+ {
+ switch ($fetchStyle) {
+ case \PDO::FETCH_BOTH:
+ return db2_fetch_both($this->_stmt);
+ case \PDO::FETCH_ASSOC:
+ return db2_fetch_assoc($this->_stmt);
+ case \PDO::FETCH_NUM:
+ return db2_fetch_array($this->_stmt);
+ default:
+ throw new DB2Exception("Given Fetch-Style " . $fetchStyle . " is not supported.");
+ }
+ }
+
+ /**
+ * Returns an array containing all of the result set rows
+ *
+ * @param integer $fetchStyle Controls how the next row will be returned to the caller.
+ * This value must be one of the Query::HYDRATE_* constants,
+ * defaulting to Query::HYDRATE_BOTH
+ *
+ * @param integer $columnIndex Returns the indicated 0-indexed column when the value of $fetchStyle is
+ * Query::HYDRATE_COLUMN. Defaults to 0.
+ *
+ * @return array
+ */
+ function fetchAll($fetchStyle = \PDO::FETCH_BOTH)
+ {
+ $rows = array();
+ while ($row = $this->fetch($fetchStyle)) {
+ $rows[] = $row;
+ }
+ return $rows;
+ }
+
+ /**
+ * fetchColumn
+ * Returns a single column from the next row of a
+ * result set or FALSE if there are no more rows.
+ *
+ * @param integer $columnIndex 0-indexed number of the column you wish to retrieve from the row. If no
+ * value is supplied, PDOStatement->fetchColumn()
+ * fetches the first column.
+ *
+ * @return string returns a single column in the next row of a result set.
+ */
+ function fetchColumn($columnIndex = 0)
+ {
+ $row = $this->fetch(\PDO::FETCH_NUM);
+ if ($row && isset($row[$columnIndex])) {
+ return $row[$columnIndex];
+ }
+ return false;
+ }
+
+ /**
+ * rowCount
+ * rowCount() returns the number of rows affected by the last DELETE, INSERT, or UPDATE statement
+ * executed by the corresponding object.
+ *
+ * If the last SQL statement executed by the associated Statement object was a SELECT statement,
+ * some databases may return the number of rows returned by that statement. However,
+ * this behaviour is not guaranteed for all databases and should not be
+ * relied on for portable applications.
+ *
+ * @return integer Returns the number of rows.
+ */
+ function rowCount()
+ {
+ return (@db2_num_rows($this->_stmt))?:0;
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Driver\OCI8;
+
+use Doctrine\DBAL\Platforms;
+
+/**
+ * A Doctrine DBAL driver for the Oracle OCI8 PHP extensions.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @since 2.0
+ */
+class Driver implements \Doctrine\DBAL\Driver
+{
+ public function connect(array $params, $username = null, $password = null, array $driverOptions = array())
+ {
+ return new OCI8Connection(
+ $username,
+ $password,
+ $this->_constructDsn($params),
+ isset($params['charset']) ? $params['charset'] : null,
+ isset($params['sessionMode']) ? $params['sessionMode'] : OCI_DEFAULT
+ );
+ }
+
+ /**
+ * Constructs the Oracle DSN.
+ *
+ * @return string The DSN.
+ */
+ private function _constructDsn(array $params)
+ {
+ $dsn = '';
+ if (isset($params['host'])) {
+ $dsn .= '(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)' .
+ '(HOST=' . $params['host'] . ')';
+
+ if (isset($params['port'])) {
+ $dsn .= '(PORT=' . $params['port'] . ')';
+ } else {
+ $dsn .= '(PORT=1521)';
+ }
+
+ $dsn .= '))';
+ if (isset($params['dbname'])) {
+ $dsn .= '(CONNECT_DATA=(SID=' . $params['dbname'] . ')';
+ }
+ $dsn .= '))';
+ } else {
+ $dsn .= $params['dbname'];
+ }
+
+ return $dsn;
+ }
+
+ public function getDatabasePlatform()
+ {
+ return new \Doctrine\DBAL\Platforms\OraclePlatform();
+ }
+
+ public function getSchemaManager(\Doctrine\DBAL\Connection $conn)
+ {
+ return new \Doctrine\DBAL\Schema\OracleSchemaManager($conn);
+ }
+
+ public function getName()
+ {
+ return 'oci8';
+ }
+
+ public function getDatabase(\Doctrine\DBAL\Connection $conn)
+ {
+ $params = $conn->getParams();
+ return $params['user'];
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Driver\OCI8;
+
+/**
+ * OCI8 implementation of the Connection interface.
+ *
+ * @since 2.0
+ */
+class OCI8Connection implements \Doctrine\DBAL\Driver\Connection
+{
+ private $_dbh;
+
+ private $_executeMode = OCI_COMMIT_ON_SUCCESS;
+
+ /**
+ * Create a Connection to an Oracle Database using oci8 extension.
+ *
+ * @param string $username
+ * @param string $password
+ * @param string $db
+ */
+ public function __construct($username, $password, $db, $charset = null, $sessionMode = OCI_DEFAULT)
+ {
+ if (!defined('OCI_NO_AUTO_COMMIT')) {
+ define('OCI_NO_AUTO_COMMIT', 0);
+ }
+
+ $this->_dbh = @oci_connect($username, $password, $db, $charset, $sessionMode);
+ if (!$this->_dbh) {
+ throw OCI8Exception::fromErrorInfo(oci_error());
+ }
+ }
+
+ /**
+ * Create a non-executed prepared statement.
+ *
+ * @param string $prepareString
+ * @return OCI8Statement
+ */
+ public function prepare($prepareString)
+ {
+ return new OCI8Statement($this->_dbh, $prepareString, $this->_executeMode);
+ }
+
+ /**
+ * @param string $sql
+ * @return OCI8Statement
+ */
+ public function query()
+ {
+ $args = func_get_args();
+ $sql = $args[0];
+ //$fetchMode = $args[1];
+ $stmt = $this->prepare($sql);
+ $stmt->execute();
+ return $stmt;
+ }
+
+ /**
+ * Quote input value.
+ *
+ * @param mixed $input
+ * @param int $type PDO::PARAM*
+ * @return mixed
+ */
+ public function quote($input, $type=\PDO::PARAM_STR)
+ {
+ return is_numeric($input) ? $input : "'$input'";
+ }
+
+ /**
+ *
+ * @param string $statement
+ * @return int
+ */
+ public function exec($statement)
+ {
+ $stmt = $this->prepare($statement);
+ $stmt->execute();
+ return $stmt->rowCount();
+ }
+
+ public function lastInsertId($name = null)
+ {
+ //TODO: throw exception or support sequences?
+ }
+
+ /**
+ * Start a transactiom
+ *
+ * Oracle has to explicitly set the autocommit mode off. That means
+ * after connection, a commit or rollback there is always automatically
+ * opened a new transaction.
+ *
+ * @return bool
+ */
+ public function beginTransaction()
+ {
+ $this->_executeMode = OCI_NO_AUTO_COMMIT;
+ return true;
+ }
+
+ /**
+ * @throws OCI8Exception
+ * @return bool
+ */
+ public function commit()
+ {
+ if (!oci_commit($this->_dbh)) {
+ throw OCI8Exception::fromErrorInfo($this->errorInfo());
+ }
+ $this->_executeMode = OCI_COMMIT_ON_SUCCESS;
+ return true;
+ }
+
+ /**
+ * @throws OCI8Exception
+ * @return bool
+ */
+ public function rollBack()
+ {
+ if (!oci_rollback($this->_dbh)) {
+ throw OCI8Exception::fromErrorInfo($this->errorInfo());
+ }
+ $this->_executeMode = OCI_COMMIT_ON_SUCCESS;
+ return true;
+ }
+
+ public function errorCode()
+ {
+ $error = oci_error($this->_dbh);
+ if ($error !== false) {
+ $error = $error['code'];
+ }
+ return $error;
+ }
+
+ public function errorInfo()
+ {
+ return oci_error($this->_dbh);
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\DBAL\Driver\OCI8;
+
+class OCI8Exception extends \Exception
+{
+ static public function fromErrorInfo($error)
+ {
+ return new self($error['message'], $error['code']);
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Driver\OCI8;
+
+use \PDO;
+
+/**
+ * The OCI8 implementation of the Statement interface.
+ *
+ * @since 2.0
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class OCI8Statement implements \Doctrine\DBAL\Driver\Statement
+{
+ /** Statement handle. */
+ private $_sth;
+ private $_executeMode;
+ private static $_PARAM = ':param';
+ private static $fetchStyleMap = array(
+ PDO::FETCH_BOTH => OCI_BOTH,
+ PDO::FETCH_ASSOC => OCI_ASSOC,
+ PDO::FETCH_NUM => OCI_NUM
+ );
+ private $_paramMap = array();
+
+ /**
+ * Creates a new OCI8Statement that uses the given connection handle and SQL statement.
+ *
+ * @param resource $dbh The connection handle.
+ * @param string $statement The SQL statement.
+ */
+ public function __construct($dbh, $statement, $executeMode)
+ {
+ list($statement, $paramMap) = self::convertPositionalToNamedPlaceholders($statement);
+ $this->_sth = oci_parse($dbh, $statement);
+ $this->_paramMap = $paramMap;
+ $this->_executeMode = $executeMode;
+ }
+
+ /**
+ * Convert positional (?) into named placeholders (:param<num>)
+ *
+ * Oracle does not support positional parameters, hence this method converts all
+ * positional parameters into artificially named parameters. Note that this conversion
+ * is not perfect. All question marks (?) in the original statement are treated as
+ * placeholders and converted to a named parameter.
+ *
+ * The algorithm uses a state machine with two possible states: InLiteral and NotInLiteral.
+ * Question marks inside literal strings are therefore handled correctly by this method.
+ * This comes at a cost, the whole sql statement has to be looped over.
+ *
+ * @todo extract into utility class in Doctrine\DBAL\Util namespace
+ * @todo review and test for lost spaces. we experienced missing spaces with oci8 in some sql statements.
+ * @param string $statement The SQL statement to convert.
+ * @return string
+ */
+ static public function convertPositionalToNamedPlaceholders($statement)
+ {
+ $count = 1;
+ $inLiteral = false; // a valid query never starts with quotes
+ $stmtLen = strlen($statement);
+ $paramMap = array();
+ for ($i = 0; $i < $stmtLen; $i++) {
+ if ($statement[$i] == '?' && !$inLiteral) {
+ // real positional parameter detected
+ $paramMap[$count] = ":param$count";
+ $len = strlen($paramMap[$count]);
+ $statement = substr_replace($statement, ":param$count", $i, 1);
+ $i += $len-1; // jump ahead
+ $stmtLen = strlen($statement); // adjust statement length
+ ++$count;
+ } else if ($statement[$i] == "'" || $statement[$i] == '"') {
+ $inLiteral = ! $inLiteral; // switch state!
+ }
+ }
+
+ return array($statement, $paramMap);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function bindValue($param, $value, $type = null)
+ {
+ return $this->bindParam($param, $value, $type);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function bindParam($column, &$variable, $type = null)
+ {
+ $column = isset($this->_paramMap[$column]) ? $this->_paramMap[$column] : $column;
+
+ return oci_bind_by_name($this->_sth, $column, $variable);
+ }
+
+ /**
+ * Closes the cursor, enabling the statement to be executed again.
+ *
+ * @return boolean Returns TRUE on success or FALSE on failure.
+ */
+ public function closeCursor()
+ {
+ return oci_free_statement($this->_sth);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function columnCount()
+ {
+ return oci_num_fields($this->_sth);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function errorCode()
+ {
+ $error = oci_error($this->_sth);
+ if ($error !== false) {
+ $error = $error['code'];
+ }
+ return $error;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function errorInfo()
+ {
+ return oci_error($this->_sth);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute($params = null)
+ {
+ if ($params) {
+ $hasZeroIndex = isset($params[0]);
+ foreach ($params as $key => $val) {
+ if ($hasZeroIndex && is_numeric($key)) {
+ $this->bindValue($key + 1, $val);
+ } else {
+ $this->bindValue($key, $val);
+ }
+ }
+ }
+
+ $ret = @oci_execute($this->_sth, $this->_executeMode);
+ if ( ! $ret) {
+ throw OCI8Exception::fromErrorInfo($this->errorInfo());
+ }
+ return $ret;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function fetch($fetchStyle = PDO::FETCH_BOTH)
+ {
+ if ( ! isset(self::$fetchStyleMap[$fetchStyle])) {
+ throw new \InvalidArgumentException("Invalid fetch style: " . $fetchStyle);
+ }
+
+ return oci_fetch_array($this->_sth, self::$fetchStyleMap[$fetchStyle] | OCI_RETURN_NULLS | OCI_RETURN_LOBS);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function fetchAll($fetchStyle = PDO::FETCH_BOTH)
+ {
+ if ( ! isset(self::$fetchStyleMap[$fetchStyle])) {
+ throw new \InvalidArgumentException("Invalid fetch style: " . $fetchStyle);
+ }
+
+ $result = array();
+ oci_fetch_all($this->_sth, $result, 0, -1,
+ self::$fetchStyleMap[$fetchStyle] | OCI_RETURN_NULLS | OCI_FETCHSTATEMENT_BY_ROW | OCI_RETURN_LOBS);
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function fetchColumn($columnIndex = 0)
+ {
+ $row = oci_fetch_array($this->_sth, OCI_NUM | OCI_RETURN_NULLS | OCI_RETURN_LOBS);
+ return $row[$columnIndex];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function rowCount()
+ {
+ return oci_num_rows($this->_sth);
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Driver;
+
+use \PDO;
+
+/**
+ * PDO implementation of the Connection interface.
+ * Used by all PDO-based drivers.
+ *
+ * @since 2.0
+ */
+class PDOConnection extends PDO implements Connection
+{
+ public function __construct($dsn, $user = null, $password = null, array $options = null)
+ {
+ parent::__construct($dsn, $user, $password, $options);
+ $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('Doctrine\DBAL\Driver\PDOStatement', array()));
+ $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\DBAL\Driver\PDOIbm;
+
+use Doctrine\DBAL\Connection;
+
+/**
+ * Driver for the PDO IBM extension
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 1.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class Driver implements \Doctrine\DBAL\Driver
+{
+ /**
+ * Attempts to establish a connection with the underlying driver.
+ *
+ * @param array $params
+ * @param string $username
+ * @param string $password
+ * @param array $driverOptions
+ * @return Doctrine\DBAL\Driver\Connection
+ */
+ public function connect(array $params, $username = null, $password = null, array $driverOptions = array())
+ {
+ $conn = new \Doctrine\DBAL\Driver\PDOConnection(
+ $this->_constructPdoDsn($params),
+ $username,
+ $password,
+ $driverOptions
+ );
+ return $conn;
+ }
+
+ /**
+ * Constructs the MySql PDO DSN.
+ *
+ * @return string The DSN.
+ */
+ private function _constructPdoDsn(array $params)
+ {
+ $dsn = 'ibm:';
+ if (isset($params['host'])) {
+ $dsn .= 'HOSTNAME=' . $params['host'] . ';';
+ }
+ if (isset($params['port'])) {
+ $dsn .= 'PORT=' . $params['port'] . ';';
+ }
+ $dsn .= 'PROTOCOL=TCPIP;';
+ if (isset($params['dbname'])) {
+ $dsn .= 'DATABASE=' . $params['dbname'] . ';';
+ }
+
+ return $dsn;
+ }
+
+ /**
+ * Gets the DatabasePlatform instance that provides all the metadata about
+ * the platform this driver connects to.
+ *
+ * @return Doctrine\DBAL\Platforms\AbstractPlatform The database platform.
+ */
+ public function getDatabasePlatform()
+ {
+ return new \Doctrine\DBAL\Platforms\DB2Platform;
+ }
+
+ /**
+ * Gets the SchemaManager that can be used to inspect and change the underlying
+ * database schema of the platform this driver connects to.
+ *
+ * @param Doctrine\DBAL\Connection $conn
+ * @return Doctrine\DBAL\SchemaManager
+ */
+ public function getSchemaManager(Connection $conn)
+ {
+ return new \Doctrine\DBAL\Schema\DB2SchemaManager($conn);
+ }
+
+ /**
+ * Gets the name of the driver.
+ *
+ * @return string The name of the driver.
+ */
+ public function getName()
+ {
+ return 'pdo_ibm';
+ }
+
+ /**
+ * Get the name of the database connected to for this driver.
+ *
+ * @param Doctrine\DBAL\Connection $conn
+ * @return string $database
+ */
+ public function getDatabase(\Doctrine\DBAL\Connection $conn)
+ {
+ $params = $conn->getParams();
+ return $params['dbname'];
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Driver\PDOMySql;
+
+use Doctrine\DBAL\Connection;
+
+/**
+ * PDO MySql driver.
+ *
+ * @since 2.0
+ */
+class Driver implements \Doctrine\DBAL\Driver
+{
+ /**
+ * Attempts to establish a connection with the underlying driver.
+ *
+ * @param array $params
+ * @param string $username
+ * @param string $password
+ * @param array $driverOptions
+ * @return Doctrine\DBAL\Driver\Connection
+ */
+ public function connect(array $params, $username = null, $password = null, array $driverOptions = array())
+ {
+ $conn = new \Doctrine\DBAL\Driver\PDOConnection(
+ $this->_constructPdoDsn($params),
+ $username,
+ $password,
+ $driverOptions
+ );
+ return $conn;
+ }
+
+ /**
+ * Constructs the MySql PDO DSN.
+ *
+ * @return string The DSN.
+ */
+ private function _constructPdoDsn(array $params)
+ {
+ $dsn = 'mysql:';
+ if (isset($params['host'])) {
+ $dsn .= 'host=' . $params['host'] . ';';
+ }
+ if (isset($params['port'])) {
+ $dsn .= 'port=' . $params['port'] . ';';
+ }
+ if (isset($params['dbname'])) {
+ $dsn .= 'dbname=' . $params['dbname'] . ';';
+ }
+ if (isset($params['unix_socket'])) {
+ $dsn .= 'unix_socket=' . $params['unix_socket'] . ';';
+ }
+
+ return $dsn;
+ }
+
+ public function getDatabasePlatform()
+ {
+ return new \Doctrine\DBAL\Platforms\MySqlPlatform();
+ }
+
+ public function getSchemaManager(\Doctrine\DBAL\Connection $conn)
+ {
+ return new \Doctrine\DBAL\Schema\MySqlSchemaManager($conn);
+ }
+
+ public function getName()
+ {
+ return 'pdo_mysql';
+ }
+
+ public function getDatabase(\Doctrine\DBAL\Connection $conn)
+ {
+ $params = $conn->getParams();
+ return $params['dbname'];
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Driver\PDOOracle;
+
+use Doctrine\DBAL\Platforms;
+
+class Driver implements \Doctrine\DBAL\Driver
+{
+ public function connect(array $params, $username = null, $password = null, array $driverOptions = array())
+ {
+ return new \Doctrine\DBAL\Driver\PDOConnection(
+ $this->_constructPdoDsn($params),
+ $username,
+ $password,
+ $driverOptions
+ );
+ }
+
+ /**
+ * Constructs the Oracle PDO DSN.
+ *
+ * @return string The DSN.
+ */
+ private function _constructPdoDsn(array $params)
+ {
+ $dsn = 'oci:';
+ if (isset($params['host'])) {
+ $dsn .= 'dbname=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)' .
+ '(HOST=' . $params['host'] . ')';
+
+ if (isset($params['port'])) {
+ $dsn .= '(PORT=' . $params['port'] . ')';
+ } else {
+ $dsn .= '(PORT=1521)';
+ }
+
+ $dsn .= '))(CONNECT_DATA=(SID=' . $params['dbname'] . ')))';
+ } else {
+ $dsn .= 'dbname=' . $params['dbname'];
+ }
+
+ if (isset($params['charset'])) {
+ $dsn .= ';charset=' . $params['charset'];
+ }
+
+ return $dsn;
+ }
+
+ public function getDatabasePlatform()
+ {
+ return new \Doctrine\DBAL\Platforms\OraclePlatform();
+ }
+
+ public function getSchemaManager(\Doctrine\DBAL\Connection $conn)
+ {
+ return new \Doctrine\DBAL\Schema\OracleSchemaManager($conn);
+ }
+
+ public function getName()
+ {
+ return 'pdo_oracle';
+ }
+
+ public function getDatabase(\Doctrine\DBAL\Connection $conn)
+ {
+ $params = $conn->getParams();
+ return $params['user'];
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace Doctrine\DBAL\Driver\PDOPgSql;
+
+use Doctrine\DBAL\Platforms;
+
+/**
+ * Driver that connects through pdo_pgsql.
+ *
+ * @since 2.0
+ */
+class Driver implements \Doctrine\DBAL\Driver
+{
+ /**
+ * Attempts to connect to the database and returns a driver connection on success.
+ *
+ * @return Doctrine\DBAL\Driver\Connection
+ */
+ public function connect(array $params, $username = null, $password = null, array $driverOptions = array())
+ {
+ return new \Doctrine\DBAL\Driver\PDOConnection(
+ $this->_constructPdoDsn($params),
+ $username,
+ $password,
+ $driverOptions
+ );
+ }
+
+ /**
+ * Constructs the Postgres PDO DSN.
+ *
+ * @return string The DSN.
+ */
+ private function _constructPdoDsn(array $params)
+ {
+ $dsn = 'pgsql:';
+ if (isset($params['host'])) {
+ $dsn .= 'host=' . $params['host'] . ' ';
+ }
+ if (isset($params['port'])) {
+ $dsn .= 'port=' . $params['port'] . ' ';
+ }
+ if (isset($params['dbname'])) {
+ $dsn .= 'dbname=' . $params['dbname'] . ' ';
+ }
+
+ return $dsn;
+ }
+
+ public function getDatabasePlatform()
+ {
+ return new \Doctrine\DBAL\Platforms\PostgreSqlPlatform();
+ }
+
+ public function getSchemaManager(\Doctrine\DBAL\Connection $conn)
+ {
+ return new \Doctrine\DBAL\Schema\PostgreSqlSchemaManager($conn);
+ }
+
+ public function getName()
+ {
+ return 'pdo_pgsql';
+ }
+
+ public function getDatabase(\Doctrine\DBAL\Connection $conn)
+ {
+ $params = $conn->getParams();
+ return $params['dbname'];
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Driver\PDOSqlite;
+
+/**
+ * The PDO Sqlite driver.
+ *
+ * @since 2.0
+ */
+class Driver implements \Doctrine\DBAL\Driver
+{
+ /**
+ * @var array
+ */
+ protected $_userDefinedFunctions = array(
+ 'sqrt' => array('callback' => array('Doctrine\DBAL\Platforms\SqlitePlatform', 'udfSqrt'), 'numArgs' => 1),
+ 'mod' => array('callback' => array('Doctrine\DBAL\Platforms\SqlitePlatform', 'udfMod'), 'numArgs' => 2),
+ 'locate' => array('callback' => array('Doctrine\DBAL\Platforms\SqlitePlatform', 'udfLocate'), 'numArgs' => -1),
+ );
+
+ /**
+ * Tries to establish a database connection to SQLite.
+ *
+ * @param array $params
+ * @param string $username
+ * @param string $password
+ * @param array $driverOptions
+ * @return Connection
+ */
+ public function connect(array $params, $username = null, $password = null, array $driverOptions = array())
+ {
+ if (isset($driverOptions['userDefinedFunctions'])) {
+ $this->_userDefinedFunctions = array_merge(
+ $this->_userDefinedFunctions, $driverOptions['userDefinedFunctions']);
+ unset($driverOptions['userDefinedFunctions']);
+ }
+
+ $pdo = new \Doctrine\DBAL\Driver\PDOConnection(
+ $this->_constructPdoDsn($params),
+ $username,
+ $password,
+ $driverOptions
+ );
+
+ foreach ($this->_userDefinedFunctions AS $fn => $data) {
+ $pdo->sqliteCreateFunction($fn, $data['callback'], $data['numArgs']);
+ }
+
+ return $pdo;
+ }
+
+ /**
+ * Constructs the Sqlite PDO DSN.
+ *
+ * @return string The DSN.
+ * @override
+ */
+ protected function _constructPdoDsn(array $params)
+ {
+ $dsn = 'sqlite:';
+ if (isset($params['path'])) {
+ $dsn .= $params['path'];
+ } else if (isset($params['memory'])) {
+ $dsn .= ':memory:';
+ }
+
+ return $dsn;
+ }
+
+ /**
+ * Gets the database platform that is relevant for this driver.
+ */
+ public function getDatabasePlatform()
+ {
+ return new \Doctrine\DBAL\Platforms\SqlitePlatform();
+ }
+
+ /**
+ * Gets the schema manager that is relevant for this driver.
+ *
+ * @param Doctrine\DBAL\Connection $conn
+ * @return Doctrine\DBAL\Schema\SqliteSchemaManager
+ */
+ public function getSchemaManager(\Doctrine\DBAL\Connection $conn)
+ {
+ return new \Doctrine\DBAL\Schema\SqliteSchemaManager($conn);
+ }
+
+ public function getName()
+ {
+ return 'pdo_sqlite';
+ }
+
+ public function getDatabase(\Doctrine\DBAL\Connection $conn)
+ {
+ $params = $conn->getParams();
+ return isset($params['path']) ? $params['path'] : null;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Driver\PDOSqlsrv;
+
+/**
+ * Sqlsrv Connection implementation.
+ *
+ * @since 2.0
+ */
+class Connection extends \Doctrine\DBAL\Driver\PDOConnection implements \Doctrine\DBAL\Driver\Connection
+{
+ /**
+ * @override
+ */
+ public function quote($value, $type=\PDO::PARAM_STR)
+ {
+ $val = parent::quote($value, $type);
+
+ // Fix for a driver version terminating all values with null byte
+ if (strpos($val, "\0") !== false) {
+ $val = substr($val, 0, -1);
+ }
+
+ return $val;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Driver\PDOSqlsrv;
+
+/**
+ * The PDO-based Sqlsrv driver.
+ *
+ * @since 2.0
+ */
+class Driver implements \Doctrine\DBAL\Driver
+{
+ public function connect(array $params, $username = null, $password = null, array $driverOptions = array())
+ {
+ return new Connection(
+ $this->_constructPdoDsn($params),
+ $username,
+ $password,
+ $driverOptions
+ );
+ }
+
+ /**
+ * Constructs the Sqlsrv PDO DSN.
+ *
+ * @return string The DSN.
+ */
+ private function _constructPdoDsn(array $params)
+ {
+ $dsn = 'sqlsrv:server=';
+
+ if (isset($params['host'])) {
+ $dsn .= $params['host'];
+ }
+
+ if (isset($params['port']) && !empty($params['port'])) {
+ $dsn .= ',' . $params['port'];
+ }
+
+ if (isset($params['dbname'])) {
+ $dsn .= ';Database=' . $params['dbname'];
+ }
+
+ return $dsn;
+ }
+
+
+ public function getDatabasePlatform()
+ {
+ return new \Doctrine\DBAL\Platforms\MsSqlPlatform();
+ }
+
+ public function getSchemaManager(\Doctrine\DBAL\Connection $conn)
+ {
+ return new \Doctrine\DBAL\Schema\MsSqlSchemaManager($conn);
+ }
+
+ public function getName()
+ {
+ return 'pdo_sqlsrv';
+ }
+
+ public function getDatabase(\Doctrine\DBAL\Connection $conn)
+ {
+ $params = $conn->getParams();
+ return $params['dbname'];
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id: Interface.php 3882 2008-02-22 18:11:35Z jwage $
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Driver;
+
+/**
+ * The PDO implementation of the Statement interface.
+ * Used by all PDO-based drivers.
+ *
+ * @since 2.0
+ */
+class PDOStatement extends \PDOStatement implements Statement
+{
+ private function __construct() {}
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Driver;
+
+use \PDO;
+
+/**
+ * Statement interface.
+ * Drivers must implement this interface.
+ *
+ * This resembles (a subset of) the PDOStatement interface.
+ *
+ * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ */
+interface Statement
+{
+ /**
+ * Binds a value to a corresponding named or positional
+ * placeholder in the SQL statement that was used to prepare the statement.
+ *
+ * @param mixed $param Parameter identifier. For a prepared statement using named placeholders,
+ * this will be a parameter name of the form :name. For a prepared statement
+ * using question mark placeholders, this will be the 1-indexed position of the parameter
+ *
+ * @param mixed $value The value to bind to the parameter.
+ * @param integer $type Explicit data type for the parameter using the PDO::PARAM_* constants.
+ *
+ * @return boolean Returns TRUE on success or FALSE on failure.
+ */
+ function bindValue($param, $value, $type = null);
+
+ /**
+ * Binds a PHP variable to a corresponding named or question mark placeholder in the
+ * SQL statement that was use to prepare the statement. Unlike PDOStatement->bindValue(),
+ * the variable is bound as a reference and will only be evaluated at the time
+ * that PDOStatement->execute() is called.
+ *
+ * Most parameters are input parameters, that is, parameters that are
+ * used in a read-only fashion to build up the query. Some drivers support the invocation
+ * of stored procedures that return data as output parameters, and some also as input/output
+ * parameters that both send in data and are updated to receive it.
+ *
+ * @param mixed $param Parameter identifier. For a prepared statement using named placeholders,
+ * this will be a parameter name of the form :name. For a prepared statement
+ * using question mark placeholders, this will be the 1-indexed position of the parameter
+ *
+ * @param mixed $variable Name of the PHP variable to bind to the SQL statement parameter.
+ *
+ * @param integer $type Explicit data type for the parameter using the PDO::PARAM_* constants. To return
+ * an INOUT parameter from a stored procedure, use the bitwise OR operator to set the
+ * PDO::PARAM_INPUT_OUTPUT bits for the data_type parameter.
+ * @return boolean Returns TRUE on success or FALSE on failure.
+ */
+ function bindParam($column, &$variable, $type = null);
+
+ /**
+ * Closes the cursor, enabling the statement to be executed again.
+ *
+ * @return boolean Returns TRUE on success or FALSE on failure.
+ */
+ function closeCursor();
+
+ /**
+ * columnCount
+ * Returns the number of columns in the result set
+ *
+ * @return integer Returns the number of columns in the result set represented
+ * by the PDOStatement object. If there is no result set,
+ * this method should return 0.
+ */
+ function columnCount();
+
+ /**
+ * errorCode
+ * Fetch the SQLSTATE associated with the last operation on the statement handle
+ *
+ * @see Doctrine_Adapter_Interface::errorCode()
+ * @return string error code string
+ */
+ function errorCode();
+
+ /**
+ * errorInfo
+ * Fetch extended error information associated with the last operation on the statement handle
+ *
+ * @see Doctrine_Adapter_Interface::errorInfo()
+ * @return array error info array
+ */
+ function errorInfo();
+
+ /**
+ * Executes a prepared statement
+ *
+ * If the prepared statement included parameter markers, you must either:
+ * call PDOStatement->bindParam() to bind PHP variables to the parameter markers:
+ * bound variables pass their value as input and receive the output value,
+ * if any, of their associated parameter markers or pass an array of input-only
+ * parameter values
+ *
+ *
+ * @param array $params An array of values with as many elements as there are
+ * bound parameters in the SQL statement being executed.
+ * @return boolean Returns TRUE on success or FALSE on failure.
+ */
+ function execute($params = null);
+
+ /**
+ * fetch
+ *
+ * @see Query::HYDRATE_* constants
+ * @param integer $fetchStyle Controls how the next row will be returned to the caller.
+ * This value must be one of the Query::HYDRATE_* constants,
+ * defaulting to Query::HYDRATE_BOTH
+ *
+ * @param integer $cursorOrientation For a PDOStatement object representing a scrollable cursor,
+ * this value determines which row will be returned to the caller.
+ * This value must be one of the Query::HYDRATE_ORI_* constants, defaulting to
+ * Query::HYDRATE_ORI_NEXT. To request a scrollable cursor for your
+ * PDOStatement object,
+ * you must set the PDO::ATTR_CURSOR attribute to Doctrine::CURSOR_SCROLL when you
+ * prepare the SQL statement with Doctrine_Adapter_Interface->prepare().
+ *
+ * @param integer $cursorOffset For a PDOStatement object representing a scrollable cursor for which the
+ * $cursorOrientation parameter is set to Query::HYDRATE_ORI_ABS, this value specifies
+ * the absolute number of the row in the result set that shall be fetched.
+ *
+ * For a PDOStatement object representing a scrollable cursor for
+ * which the $cursorOrientation parameter is set to Query::HYDRATE_ORI_REL, this value
+ * specifies the row to fetch relative to the cursor position before
+ * PDOStatement->fetch() was called.
+ *
+ * @return mixed
+ */
+ function fetch($fetchStyle = PDO::FETCH_BOTH);
+
+ /**
+ * Returns an array containing all of the result set rows
+ *
+ * @param integer $fetchStyle Controls how the next row will be returned to the caller.
+ * This value must be one of the Query::HYDRATE_* constants,
+ * defaulting to Query::HYDRATE_BOTH
+ *
+ * @param integer $columnIndex Returns the indicated 0-indexed column when the value of $fetchStyle is
+ * Query::HYDRATE_COLUMN. Defaults to 0.
+ *
+ * @return array
+ */
+ function fetchAll($fetchStyle = PDO::FETCH_BOTH);
+
+ /**
+ * fetchColumn
+ * Returns a single column from the next row of a
+ * result set or FALSE if there are no more rows.
+ *
+ * @param integer $columnIndex 0-indexed number of the column you wish to retrieve from the row. If no
+ * value is supplied, PDOStatement->fetchColumn()
+ * fetches the first column.
+ *
+ * @return string returns a single column in the next row of a result set.
+ */
+ function fetchColumn($columnIndex = 0);
+
+ /**
+ * rowCount
+ * rowCount() returns the number of rows affected by the last DELETE, INSERT, or UPDATE statement
+ * executed by the corresponding object.
+ *
+ * If the last SQL statement executed by the associated Statement object was a SELECT statement,
+ * some databases may return the number of rows returned by that statement. However,
+ * this behaviour is not guaranteed for all databases and should not be
+ * relied on for portable applications.
+ *
+ * @return integer Returns the number of rows.
+ */
+ function rowCount();
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL;
+
+use Doctrine\Common\EventManager;
+
+/**
+ * Factory for creating Doctrine\DBAL\Connection instances.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @since 2.0
+ */
+final class DriverManager
+{
+ /**
+ * List of supported drivers and their mappings to the driver classes.
+ *
+ * @var array
+ * @todo REMOVE. Users should directly supply class names instead.
+ */
+ private static $_driverMap = array(
+ 'pdo_mysql' => 'Doctrine\DBAL\Driver\PDOMySql\Driver',
+ 'pdo_sqlite' => 'Doctrine\DBAL\Driver\PDOSqlite\Driver',
+ 'pdo_pgsql' => 'Doctrine\DBAL\Driver\PDOPgSql\Driver',
+ 'pdo_oci' => 'Doctrine\DBAL\Driver\PDOOracle\Driver',
+ 'oci8' => 'Doctrine\DBAL\Driver\OCI8\Driver',
+ 'ibm_db2' => 'Doctrine\DBAL\Driver\IBMDB2\DB2Driver',
+ 'pdo_ibm' => 'Doctrine\DBAL\Driver\PDOIbm\Driver',
+ 'pdo_sqlsrv' => 'Doctrine\DBAL\Driver\PDOSqlsrv\Driver',
+ );
+
+ /** Private constructor. This class cannot be instantiated. */
+ private function __construct() { }
+
+ /**
+ * Creates a connection object based on the specified parameters.
+ * This method returns a Doctrine\DBAL\Connection which wraps the underlying
+ * driver connection.
+ *
+ * $params must contain at least one of the following.
+ *
+ * Either 'driver' with one of the following values:
+ * pdo_mysql
+ * pdo_sqlite
+ * pdo_pgsql
+ * pdo_oracle
+ * pdo_sqlsrv
+ *
+ * OR 'driverClass' that contains the full class name (with namespace) of the
+ * driver class to instantiate.
+ *
+ * Other (optional) parameters:
+ *
+ * <b>user (string)</b>:
+ * The username to use when connecting.
+ *
+ * <b>password (string)</b>:
+ * The password to use when connecting.
+ *
+ * <b>driverOptions (array)</b>:
+ * Any additional driver-specific options for the driver. These are just passed
+ * through to the driver.
+ *
+ * <b>pdo</b>:
+ * You can pass an existing PDO instance through this parameter. The PDO
+ * instance will be wrapped in a Doctrine\DBAL\Connection.
+ *
+ * <b>wrapperClass</b>:
+ * You may specify a custom wrapper class through the 'wrapperClass'
+ * parameter but this class MUST inherit from Doctrine\DBAL\Connection.
+ *
+ * @param array $params The parameters.
+ * @param Doctrine\DBAL\Configuration The configuration to use.
+ * @param Doctrine\Common\EventManager The event manager to use.
+ * @return Doctrine\DBAL\Connection
+ */
+ public static function getConnection(
+ array $params,
+ Configuration $config = null,
+ EventManager $eventManager = null)
+ {
+ // create default config and event manager, if not set
+ if ( ! $config) {
+ $config = new Configuration();
+ }
+ if ( ! $eventManager) {
+ $eventManager = new EventManager();
+ }
+
+ // check for existing pdo object
+ if (isset($params['pdo']) && ! $params['pdo'] instanceof \PDO) {
+ throw DBALException::invalidPdoInstance();
+ } else if (isset($params['pdo'])) {
+ $params['pdo']->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+ $params['driver'] = 'pdo_' . $params['pdo']->getAttribute(\PDO::ATTR_DRIVER_NAME);
+ } else {
+ self::_checkParams($params);
+ }
+ if (isset($params['driverClass'])) {
+ $className = $params['driverClass'];
+ } else {
+ $className = self::$_driverMap[$params['driver']];
+ }
+
+ $driver = new $className();
+
+ $wrapperClass = 'Doctrine\DBAL\Connection';
+ if (isset($params['wrapperClass'])) {
+ if (is_subclass_of($params['wrapperClass'], $wrapperClass)) {
+ $wrapperClass = $params['wrapperClass'];
+ } else {
+ throw DBALException::invalidWrapperClass($params['wrapperClass']);
+ }
+ }
+
+ return new $wrapperClass($params, $driver, $config, $eventManager);
+ }
+
+ /**
+ * Checks the list of parameters.
+ *
+ * @param array $params
+ */
+ private static function _checkParams(array $params)
+ {
+ // check existance of mandatory parameters
+
+ // driver
+ if ( ! isset($params['driver']) && ! isset($params['driverClass'])) {
+ throw DBALException::driverRequired();
+ }
+
+ // check validity of parameters
+
+ // driver
+ if ( isset($params['driver']) && ! isset(self::$_driverMap[$params['driver']])) {
+ throw DBALException::unknownDriver($params['driver'], array_keys(self::$_driverMap));
+ }
+
+ if (isset($params['driverClass']) && ! in_array('Doctrine\DBAL\Driver', class_implements($params['driverClass'], true))) {
+ throw DBALException::invalidDriverClass($params['driverClass']);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\DBAL\Event;
+
+use Doctrine\Common\EventArgs,
+ Doctrine\DBAL\Connection;
+
+/**
+ * Event Arguments used when a Driver connection is established inside Doctrine\DBAL\Connection.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 1.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class ConnectionEventArgs extends EventArgs
+{
+ /**
+ * @var Connection
+ */
+ private $_connection = null;
+
+ public function __construct(Connection $connection)
+ {
+ $this->_connection = $connection;
+ }
+
+ /**
+ * @return Doctrine\DBAL\Connection
+ */
+ public function getConnection()
+ {
+ return $this->_connection;
+ }
+
+ /**
+ * @return Doctrine\DBAL\Driver
+ */
+ public function getDriver()
+ {
+ return $this->_connection->getDriver();
+ }
+
+ /**
+ * @return Doctrine\DBAL\Platforms\AbstractPlatform
+ */
+ public function getDatabasePlatform()
+ {
+ return $this->_connection->getDatabasePlatform();
+ }
+
+ /**
+ * @return Doctrine\DBAL\Schema\AbstractSchemaManager
+ */
+ public function getSchemaManager()
+ {
+ return $this->_connection->getSchemaManager();
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\DBAL\Event\Listeners;
+
+use Doctrine\DBAL\Event\ConnectionEventArgs;
+use Doctrine\DBAL\Events;
+use Doctrine\Common\EventSubscriber;
+
+/**
+ * MySQL Session Init Event Subscriber which allows to set the Client Encoding of the Connection
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 1.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class MysqlSessionInit implements EventSubscriber
+{
+ /**
+ * @var string
+ */
+ private $_charset;
+
+ /**
+ * @var string
+ */
+ private $_collation;
+
+ /**
+ * Configure Charset and Collation options of MySQL Client for each Connection
+ *
+ * @param string $charset
+ * @param string $collation
+ */
+ public function __construct($charset = 'utf8', $collation = false)
+ {
+ $this->_charset = $charset;
+ $this->_collation = $collation;
+ }
+
+ /**
+ * @param ConnectionEventArgs $args
+ * @return void
+ */
+ public function postConnect(ConnectionEventArgs $args)
+ {
+ $collation = ($this->_collation) ? " COLLATE ".$this->_collation : "";
+ $args->getConnection()->executeUpdate("SET NAMES ".$this->_charset . $collation);
+ }
+
+ public function getSubscribedEvents()
+ {
+ return array(Events::postConnect);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\DBAL\Event\Listeners;
+
+use Doctrine\DBAL\Event\ConnectionEventArgs;
+use Doctrine\DBAL\Events;
+use Doctrine\Common\EventSubscriber;
+
+/**
+ * Should be used when Oracle Server default enviroment does not match the Doctrine requirements.
+ *
+ * The following enviroment variables are required for the Doctrine default date format:
+ *
+ * NLS_TIME_FORMAT="HH24:MI:SS"
+ * NLS_DATE_FORMAT="YYYY-MM-DD"
+ * NLS_TIMESTAMP_FORMAT="YYYY-MM-DD HH24:MI:SS"
+ * NLS_TIMESTAMP_TZ_FORMAT="YYYY-MM-DD HH24:MI:SS TZH:TZM"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 1.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class OracleSessionInit implements EventSubscriber
+{
+ protected $_defaultSessionVars = array(
+ 'NLS_TIME_FORMAT' => "HH24:MI:SS",
+ 'NLS_DATE_FORMAT' => "YYYY-MM-DD HH24:MI:SS",
+ 'NLS_TIMESTAMP_FORMAT' => "YYYY-MM-DD HH24:MI:SS",
+ 'NLS_TIMESTAMP_TZ_FORMAT' => "YYYY-MM-DD HH24:MI:SS TZH:TZM",
+ );
+
+ /**
+ * @param array $oracleSessionVars
+ */
+ public function __construct(array $oracleSessionVars = array())
+ {
+ $this->_defaultSessionVars = array_merge($this->_defaultSessionVars, $oracleSessionVars);
+ }
+
+ /**
+ * @param ConnectionEventArgs $args
+ * @return void
+ */
+ public function postConnect(ConnectionEventArgs $args)
+ {
+ if (count($this->_defaultSessionVars)) {
+ array_change_key_case($this->_defaultSessionVars, \CASE_UPPER);
+ $vars = array();
+ foreach ($this->_defaultSessionVars AS $option => $value) {
+ $vars[] = $option." = '".$value."'";
+ }
+ $sql = "ALTER SESSION SET ".implode(" ", $vars);
+ $args->getConnection()->executeUpdate($sql);
+ }
+ }
+
+ public function getSubscribedEvents()
+ {
+ return array(Events::postConnect);
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL;
+
+/**
+ * Container for all DBAL events.
+ *
+ * This class cannot be instantiated.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @since 2.0
+ */
+final class Events
+{
+ private function __construct() {}
+
+ const postConnect = 'postConnect';
+}
+
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\DBAL;
+
+/**
+ * Contains all DBAL LockModes
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 1.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class LockMode
+{
+ const NONE = 0;
+ const OPTIMISTIC = 1;
+ const PESSIMISTIC_READ = 2;
+ const PESSIMISTIC_WRITE = 4;
+
+ final private function __construct() { }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Logging;
+
+/**
+ * Includes executed SQLs in a Debug Stack
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class DebugStack implements SQLLogger
+{
+ /** @var array $queries Executed SQL queries. */
+ public $queries = array();
+
+ /** @var boolean $enabled If Debug Stack is enabled (log queries) or not. */
+ public $enabled = true;
+
+ public $start = null;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function startQuery($sql, array $params = null, array $types = null)
+ {
+ if ($this->enabled) {
+ $this->start = microtime(true);
+ $this->queries[] = array('sql' => $sql, 'params' => $params, 'types' => $types, 'executionMS' => 0);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function stopQuery()
+ {
+ $this->queries[(count($this->queries)-1)]['executionMS'] = microtime(true) - $this->start;
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Logging;
+
+/**
+ * A SQL logger that logs to the standard output using echo/var_dump.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class EchoSQLLogger implements SQLLogger
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function startQuery($sql, array $params = null, array $types = null)
+ {
+ echo $sql . PHP_EOL;
+
+ if ($params) {
+ var_dump($params);
+ }
+
+ if ($types) {
+ var_dump($types);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function stopQuery()
+ {
+
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Logging;
+
+/**
+ * Interface for SQL loggers.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+interface SQLLogger
+{
+ /**
+ * Logs a SQL statement somewhere.
+ *
+ * @param string $sql The SQL to be executed.
+ * @param array $params The SQL parameters.
+ * @param float $executionMS The microtime difference it took to execute this query.
+ * @return void
+ */
+ public function startQuery($sql, array $params = null, array $types = null);
+
+ /**
+ * Mark the last started query as stopped. This can be used for timing of queries.
+ *
+ * @return void
+ */
+ public function stopQuery();
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Platforms;
+
+use Doctrine\DBAL\DBALException,
+ Doctrine\DBAL\Connection,
+ Doctrine\DBAL\Types,
+ Doctrine\DBAL\Schema\Table,
+ Doctrine\DBAL\Schema\Index,
+ Doctrine\DBAL\Schema\ForeignKeyConstraint,
+ Doctrine\DBAL\Schema\TableDiff;
+
+/**
+ * Base class for all DatabasePlatforms. The DatabasePlatforms are the central
+ * point of abstraction of platform-specific behaviors, features and SQL dialects.
+ * They are a passive source of information.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @todo Remove any unnecessary methods.
+ */
+abstract class AbstractPlatform
+{
+ /**
+ * @var int
+ */
+ const CREATE_INDEXES = 1;
+
+ /**
+ * @var int
+ */
+ const CREATE_FOREIGNKEYS = 2;
+
+ /**
+ * @var int
+ */
+ const TRIM_UNSPECIFIED = 0;
+
+ /**
+ * @var int
+ */
+ const TRIM_LEADING = 1;
+
+ /**
+ * @var int
+ */
+ const TRIM_TRAILING = 2;
+
+ /**
+ * @var int
+ */
+ const TRIM_BOTH = 3;
+
+ /**
+ * @var array
+ */
+ protected $doctrineTypeMapping = null;
+
+ /**
+ * Constructor.
+ */
+ public function __construct() {}
+
+ /**
+ * Gets the SQL snippet that declares a boolean column.
+ *
+ * @param array $columnDef
+ * @return string
+ */
+ abstract public function getBooleanTypeDeclarationSQL(array $columnDef);
+
+ /**
+ * Gets the SQL snippet that declares a 4 byte integer column.
+ *
+ * @param array $columnDef
+ * @return string
+ */
+ abstract public function getIntegerTypeDeclarationSQL(array $columnDef);
+
+ /**
+ * Gets the SQL snippet that declares an 8 byte integer column.
+ *
+ * @param array $columnDef
+ * @return string
+ */
+ abstract public function getBigIntTypeDeclarationSQL(array $columnDef);
+
+ /**
+ * Gets the SQL snippet that declares a 2 byte integer column.
+ *
+ * @param array $columnDef
+ * @return string
+ */
+ abstract public function getSmallIntTypeDeclarationSQL(array $columnDef);
+
+ /**
+ * Gets the SQL snippet that declares common properties of an integer column.
+ *
+ * @param array $columnDef
+ * @return string
+ */
+ abstract protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef);
+
+ /**
+ * Lazy load Doctrine Type Mappings
+ *
+ * @return void
+ */
+ abstract protected function initializeDoctrineTypeMappings();
+
+ /**
+ * Gets the SQL snippet used to declare a VARCHAR column type.
+ *
+ * @param array $field
+ */
+ abstract public function getVarcharTypeDeclarationSQL(array $field);
+
+ /**
+ * Gets the SQL snippet used to declare a CLOB column type.
+ *
+ * @param array $field
+ */
+ abstract public function getClobTypeDeclarationSQL(array $field);
+
+ /**
+ * Gets the name of the platform.
+ *
+ * @return string
+ */
+ abstract public function getName();
+
+ /**
+ * Register a doctrine type to be used in conjunction with a column type of this platform.
+ *
+ * @param string $dbType
+ * @param string $doctrineType
+ */
+ public function registerDoctrineTypeMapping($dbType, $doctrineType)
+ {
+ if ($this->doctrineTypeMapping === null) {
+ $this->initializeDoctrineTypeMappings();
+ }
+
+ if (!Types\Type::hasType($doctrineType)) {
+ throw DBALException::typeNotFound($doctrineType);
+ }
+
+ $dbType = strtolower($dbType);
+ $this->doctrineTypeMapping[$dbType] = $doctrineType;
+ }
+
+ /**
+ * Get the Doctrine type that is mapped for the given database column type.
+ *
+ * @param string $dbType
+ * @return string
+ */
+ public function getDoctrineTypeMapping($dbType)
+ {
+ if ($this->doctrineTypeMapping === null) {
+ $this->initializeDoctrineTypeMappings();
+ }
+
+ $dbType = strtolower($dbType);
+ if (isset($this->doctrineTypeMapping[$dbType])) {
+ return $this->doctrineTypeMapping[$dbType];
+ } else {
+ throw new \Doctrine\DBAL\DBALException("Unknown database type ".$dbType." requested, " . get_class($this) . " may not support it.");
+ }
+ }
+
+ /**
+ * Check if a database type is currently supported by this platform.
+ *
+ * @param string $dbType
+ * @return bool
+ */
+ public function hasDoctrineTypeMappingFor($dbType)
+ {
+ if ($this->doctrineTypeMapping === null) {
+ $this->initializeDoctrineTypeMappings();
+ }
+
+ $dbType = strtolower($dbType);
+ return isset($this->doctrineTypeMapping[$dbType]);
+ }
+
+ /**
+ * Gets the character used for identifier quoting.
+ *
+ * @return string
+ */
+ public function getIdentifierQuoteCharacter()
+ {
+ return '"';
+ }
+
+ /**
+ * Gets the string portion that starts an SQL comment.
+ *
+ * @return string
+ */
+ public function getSqlCommentStartString()
+ {
+ return "--";
+ }
+
+ /**
+ * Gets the string portion that ends an SQL comment.
+ *
+ * @return string
+ */
+ public function getSqlCommentEndString()
+ {
+ return "\n";
+ }
+
+ /**
+ * Gets the maximum length of a varchar field.
+ *
+ * @return integer
+ */
+ public function getVarcharMaxLength()
+ {
+ return 4000;
+ }
+
+ /**
+ * Gets the default length of a varchar field.
+ *
+ * @return integer
+ */
+ public function getVarcharDefaultLength()
+ {
+ return 255;
+ }
+
+ /**
+ * Gets all SQL wildcard characters of the platform.
+ *
+ * @return array
+ */
+ public function getWildcards()
+ {
+ return array('%', '_');
+ }
+
+ /**
+ * Returns the regular expression operator.
+ *
+ * @return string
+ */
+ public function getRegexpExpression()
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ /**
+ * Returns the average value of a column
+ *
+ * @param string $column the column to use
+ * @return string generated sql including an AVG aggregate function
+ */
+ public function getAvgExpression($column)
+ {
+ return 'AVG(' . $column . ')';
+ }
+
+ /**
+ * Returns the number of rows (without a NULL value) of a column
+ *
+ * If a '*' is used instead of a column the number of selected rows
+ * is returned.
+ *
+ * @param string|integer $column the column to use
+ * @return string generated sql including a COUNT aggregate function
+ */
+ public function getCountExpression($column)
+ {
+ return 'COUNT(' . $column . ')';
+ }
+
+ /**
+ * Returns the highest value of a column
+ *
+ * @param string $column the column to use
+ * @return string generated sql including a MAX aggregate function
+ */
+ public function getMaxExpression($column)
+ {
+ return 'MAX(' . $column . ')';
+ }
+
+ /**
+ * Returns the lowest value of a column
+ *
+ * @param string $column the column to use
+ * @return string
+ */
+ public function getMinExpression($column)
+ {
+ return 'MIN(' . $column . ')';
+ }
+
+ /**
+ * Returns the total sum of a column
+ *
+ * @param string $column the column to use
+ * @return string
+ */
+ public function getSumExpression($column)
+ {
+ return 'SUM(' . $column . ')';
+ }
+
+ // scalar functions
+
+ /**
+ * Returns the md5 sum of a field.
+ *
+ * Note: Not SQL92, but common functionality
+ *
+ * @return string
+ */
+ public function getMd5Expression($column)
+ {
+ return 'MD5(' . $column . ')';
+ }
+
+ /**
+ * Returns the length of a text field.
+ *
+ * @param string $expression1
+ * @param string $expression2
+ * @return string
+ */
+ public function getLengthExpression($column)
+ {
+ return 'LENGTH(' . $column . ')';
+ }
+
+ /**
+ * Rounds a numeric field to the number of decimals specified.
+ *
+ * @param string $expression1
+ * @param string $expression2
+ * @return string
+ */
+ public function getRoundExpression($column, $decimals = 0)
+ {
+ return 'ROUND(' . $column . ', ' . $decimals . ')';
+ }
+
+ /**
+ * Returns the remainder of the division operation
+ * $expression1 / $expression2.
+ *
+ * @param string $expression1
+ * @param string $expression2
+ * @return string
+ */
+ public function getModExpression($expression1, $expression2)
+ {
+ return 'MOD(' . $expression1 . ', ' . $expression2 . ')';
+ }
+
+ /**
+ * Trim a string, leading/trailing/both and with a given char which defaults to space.
+ *
+ * @param string $str
+ * @param int $pos
+ * @param string $char has to be quoted already
+ * @return string
+ */
+ public function getTrimExpression($str, $pos = self::TRIM_UNSPECIFIED, $char = false)
+ {
+ $posStr = '';
+ $trimChar = ($char != false) ? $char . ' FROM ' : '';
+
+ if ($pos == self::TRIM_LEADING) {
+ $posStr = 'LEADING '.$trimChar;
+ } else if($pos == self::TRIM_TRAILING) {
+ $posStr = 'TRAILING '.$trimChar;
+ } else if($pos == self::TRIM_BOTH) {
+ $posStr = 'BOTH '.$trimChar;
+ }
+
+ return 'TRIM(' . $posStr . $str . ')';
+ }
+
+ /**
+ * rtrim
+ * returns the string $str with proceeding space characters removed
+ *
+ * @param string $str literal string or column name
+ * @return string
+ */
+ public function getRtrimExpression($str)
+ {
+ return 'RTRIM(' . $str . ')';
+ }
+
+ /**
+ * ltrim
+ * returns the string $str with leading space characters removed
+ *
+ * @param string $str literal string or column name
+ * @return string
+ */
+ public function getLtrimExpression($str)
+ {
+ return 'LTRIM(' . $str . ')';
+ }
+
+ /**
+ * upper
+ * Returns the string $str with all characters changed to
+ * uppercase according to the current character set mapping.
+ *
+ * @param string $str literal string or column name
+ * @return string
+ */
+ public function getUpperExpression($str)
+ {
+ return 'UPPER(' . $str . ')';
+ }
+
+ /**
+ * lower
+ * Returns the string $str with all characters changed to
+ * lowercase according to the current character set mapping.
+ *
+ * @param string $str literal string or column name
+ * @return string
+ */
+ public function getLowerExpression($str)
+ {
+ return 'LOWER(' . $str . ')';
+ }
+
+ /**
+ * returns the position of the first occurrence of substring $substr in string $str
+ *
+ * @param string $substr literal string to find
+ * @param string $str literal string
+ * @param int $pos position to start at, beginning of string by default
+ * @return integer
+ */
+ public function getLocateExpression($str, $substr, $startPos = false)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ /**
+ * Returns the current system date.
+ *
+ * @return string
+ */
+ public function getNowExpression()
+ {
+ return 'NOW()';
+ }
+
+ /**
+ * return string to call a function to get a substring inside an SQL statement
+ *
+ * Note: Not SQL92, but common functionality.
+ *
+ * SQLite only supports the 2 parameter variant of this function
+ *
+ * @param string $value an sql string literal or column name/alias
+ * @param integer $from where to start the substring portion
+ * @param integer $len the substring portion length
+ * @return string
+ */
+ public function getSubstringExpression($value, $from, $len = null)
+ {
+ if ($len === null)
+ return 'SUBSTRING(' . $value . ' FROM ' . $from . ')';
+ else {
+ return 'SUBSTRING(' . $value . ' FROM ' . $from . ' FOR ' . $len . ')';
+ }
+ }
+
+ /**
+ * Returns a series of strings concatinated
+ *
+ * concat() accepts an arbitrary number of parameters. Each parameter
+ * must contain an expression
+ *
+ * @param string $arg1, $arg2 ... $argN strings that will be concatinated.
+ * @return string
+ */
+ public function getConcatExpression()
+ {
+ return join(' || ' , func_get_args());
+ }
+
+ /**
+ * Returns the SQL for a logical not.
+ *
+ * Example:
+ * <code>
+ * $q = new Doctrine_Query();
+ * $e = $q->expr;
+ * $q->select('*')->from('table')
+ * ->where($e->eq('id', $e->not('null'));
+ * </code>
+ *
+ * @return string a logical expression
+ */
+ public function getNotExpression($expression)
+ {
+ return 'NOT(' . $expression . ')';
+ }
+
+ /**
+ * Returns the SQL to check if a value is one in a set of
+ * given values.
+ *
+ * in() accepts an arbitrary number of parameters. The first parameter
+ * must always specify the value that should be matched against. Successive
+ * must contain a logical expression or an array with logical expressions.
+ * These expressions will be matched against the first parameter.
+ *
+ * @param string $column the value that should be matched against
+ * @param string|array(string) values that will be matched against $column
+ * @return string logical expression
+ */
+ public function getInExpression($column, $values)
+ {
+ if ( ! is_array($values)) {
+ $values = array($values);
+ }
+ $values = $this->getIdentifiers($values);
+
+ if (count($values) == 0) {
+ throw \InvalidArgumentException('Values must not be empty.');
+ }
+ return $column . ' IN (' . implode(', ', $values) . ')';
+ }
+
+ /**
+ * Returns SQL that checks if a expression is null.
+ *
+ * @param string $expression the expression that should be compared to null
+ * @return string logical expression
+ */
+ public function getIsNullExpression($expression)
+ {
+ return $expression . ' IS NULL';
+ }
+
+ /**
+ * Returns SQL that checks if a expression is not null.
+ *
+ * @param string $expression the expression that should be compared to null
+ * @return string logical expression
+ */
+ public function getIsNotNullExpression($expression)
+ {
+ return $expression . ' IS NOT NULL';
+ }
+
+ /**
+ * Returns SQL that checks if an expression evaluates to a value between
+ * two values.
+ *
+ * The parameter $expression is checked if it is between $value1 and $value2.
+ *
+ * Note: There is a slight difference in the way BETWEEN works on some databases.
+ * http://www.w3schools.com/sql/sql_between.asp. If you want complete database
+ * independence you should avoid using between().
+ *
+ * @param string $expression the value to compare to
+ * @param string $value1 the lower value to compare with
+ * @param string $value2 the higher value to compare with
+ * @return string logical expression
+ */
+ public function getBetweenExpression($expression, $value1, $value2)
+ {
+ return $expression . ' BETWEEN ' .$value1 . ' AND ' . $value2;
+ }
+
+ public function getAcosExpression($value)
+ {
+ return 'ACOS(' . $value . ')';
+ }
+
+ public function getSinExpression($value)
+ {
+ return 'SIN(' . $value . ')';
+ }
+
+ public function getPiExpression()
+ {
+ return 'PI()';
+ }
+
+ public function getCosExpression($value)
+ {
+ return 'COS(' . $value . ')';
+ }
+
+ public function getForUpdateSQL()
+ {
+ return 'FOR UPDATE';
+ }
+
+ /**
+ * Honors that some SQL vendors such as MsSql use table hints for locking instead of the ANSI SQL FOR UPDATE specification.
+ *
+ * @param string $fromClause
+ * @param int $lockMode
+ * @return string
+ */
+ public function appendLockHint($fromClause, $lockMode)
+ {
+ return $fromClause;
+ }
+
+ /**
+ * Get the sql snippet to append to any SELECT statement which locks rows in shared read lock.
+ *
+ * This defaults to the ASNI SQL "FOR UPDATE", which is an exclusive lock (Write). Some database
+ * vendors allow to lighten this constraint up to be a real read lock.
+ *
+ * @return string
+ */
+ public function getReadLockSQL()
+ {
+ return $this->getForUpdateSQL();
+ }
+
+ /**
+ * Get the SQL snippet to append to any SELECT statement which obtains an exclusive lock on the rows.
+ *
+ * The semantics of this lock mode should equal the SELECT .. FOR UPDATE of the ASNI SQL standard.
+ *
+ * @return string
+ */
+ public function getWriteLockSQL()
+ {
+ return $this->getForUpdateSQL();
+ }
+
+ public function getDropDatabaseSQL($database)
+ {
+ return 'DROP DATABASE ' . $database;
+ }
+
+ /**
+ * Drop a Table
+ *
+ * @param Table|string $table
+ * @return string
+ */
+ public function getDropTableSQL($table)
+ {
+ if ($table instanceof \Doctrine\DBAL\Schema\Table) {
+ $table = $table->getQuotedName($this);
+ }
+
+ return 'DROP TABLE ' . $table;
+ }
+
+ /**
+ * Drop index from a table
+ *
+ * @param Index|string $name
+ * @param string|Table $table
+ * @return string
+ */
+ public function getDropIndexSQL($index, $table=null)
+ {
+ if($index instanceof \Doctrine\DBAL\Schema\Index) {
+ $index = $index->getQuotedName($this);
+ } else if(!is_string($index)) {
+ throw new \InvalidArgumentException('AbstractPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.');
+ }
+
+ return 'DROP INDEX ' . $index;
+ }
+
+ /**
+ * Get drop constraint sql
+ *
+ * @param \Doctrine\DBAL\Schema\Constraint $constraint
+ * @param string|Table $table
+ * @return string
+ */
+ public function getDropConstraintSQL($constraint, $table)
+ {
+ if ($constraint instanceof \Doctrine\DBAL\Schema\Constraint) {
+ $constraint = $constraint->getQuotedName($this);
+ }
+
+ if ($table instanceof \Doctrine\DBAL\Schema\Table) {
+ $table = $table->getQuotedName($this);
+ }
+
+ return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $constraint;
+ }
+
+ /**
+ * @param ForeignKeyConstraint|string $foreignKey
+ * @param Table|string $table
+ * @return string
+ */
+ public function getDropForeignKeySQL($foreignKey, $table)
+ {
+ if ($foreignKey instanceof \Doctrine\DBAL\Schema\ForeignKeyConstraint) {
+ $foreignKey = $foreignKey->getQuotedName($this);
+ }
+
+ if ($table instanceof \Doctrine\DBAL\Schema\Table) {
+ $table = $table->getQuotedName($this);
+ }
+
+ return 'ALTER TABLE ' . $table . ' DROP FOREIGN KEY ' . $foreignKey;
+ }
+
+ /**
+ * Gets the SQL statement(s) to create a table with the specified name, columns and constraints
+ * on this platform.
+ *
+ * @param string $table The name of the table.
+ * @param int $createFlags
+ * @return array The sequence of SQL statements.
+ */
+ public function getCreateTableSQL(Table $table, $createFlags=self::CREATE_INDEXES)
+ {
+ if ( ! is_int($createFlags)) {
+ throw new \InvalidArgumentException("Second argument of AbstractPlatform::getCreateTableSQL() has to be integer.");
+ }
+
+ if (count($table->getColumns()) == 0) {
+ throw DBALException::noColumnsSpecifiedForTable($table->getName());
+ }
+
+ $tableName = $table->getQuotedName($this);
+ $options = $table->getOptions();
+ $options['uniqueConstraints'] = array();
+ $options['indexes'] = array();
+ $options['primary'] = array();
+
+ if (($createFlags&self::CREATE_INDEXES) > 0) {
+ foreach ($table->getIndexes() AS $index) {
+ /* @var $index Index */
+ if ($index->isPrimary()) {
+ $options['primary'] = $index->getColumns();
+ } else {
+ $options['indexes'][$index->getName()] = $index;
+ }
+ }
+ }
+
+ $columns = array();
+ foreach ($table->getColumns() AS $column) {
+ /* @var \Doctrine\DBAL\Schema\Column $column */
+ $columnData = array();
+ $columnData['name'] = $column->getQuotedName($this);
+ $columnData['type'] = $column->getType();
+ $columnData['length'] = $column->getLength();
+ $columnData['notnull'] = $column->getNotNull();
+ $columnData['unique'] = false; // TODO: what do we do about this?
+ $columnData['version'] = ($column->hasPlatformOption("version"))?$column->getPlatformOption('version'):false;
+ if(strtolower($columnData['type']) == "string" && $columnData['length'] === null) {
+ $columnData['length'] = 255;
+ }
+ $columnData['precision'] = $column->getPrecision();
+ $columnData['scale'] = $column->getScale();
+ $columnData['default'] = $column->getDefault();
+ $columnData['columnDefinition'] = $column->getColumnDefinition();
+ $columnData['autoincrement'] = $column->getAutoincrement();
+
+ if(in_array($column->getName(), $options['primary'])) {
+ $columnData['primary'] = true;
+ }
+
+ $columns[$columnData['name']] = $columnData;
+ }
+
+ if (($createFlags&self::CREATE_FOREIGNKEYS) > 0) {
+ $options['foreignKeys'] = array();
+ foreach ($table->getForeignKeys() AS $fkConstraint) {
+ $options['foreignKeys'][] = $fkConstraint;
+ }
+ }
+
+ return $this->_getCreateTableSQL($tableName, $columns, $options);
+ }
+
+ /**
+ * @param string $tableName
+ * @param array $columns
+ * @param array $options
+ * @return array
+ */
+ protected function _getCreateTableSQL($tableName, array $columns, array $options = array())
+ {
+ $columnListSql = $this->getColumnDeclarationListSQL($columns);
+
+ if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
+ foreach ($options['uniqueConstraints'] as $name => $definition) {
+ $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
+ }
+ }
+
+ if (isset($options['primary']) && ! empty($options['primary'])) {
+ $columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')';
+ }
+
+ if (isset($options['indexes']) && ! empty($options['indexes'])) {
+ foreach($options['indexes'] as $index => $definition) {
+ $columnListSql .= ', ' . $this->getIndexDeclarationSQL($index, $definition);
+ }
+ }
+
+ $query = 'CREATE TABLE ' . $tableName . ' (' . $columnListSql;
+
+ $check = $this->getCheckDeclarationSQL($columns);
+ if ( ! empty($check)) {
+ $query .= ', ' . $check;
+ }
+ $query .= ')';
+
+ $sql[] = $query;
+
+ if (isset($options['foreignKeys'])) {
+ foreach ((array) $options['foreignKeys'] AS $definition) {
+ $sql[] = $this->getCreateForeignKeySQL($definition, $tableName);
+ }
+ }
+
+ return $sql;
+ }
+
+ public function getCreateTemporaryTableSnippetSQL()
+ {
+ return "CREATE TEMPORARY TABLE";
+ }
+
+ /**
+ * Gets the SQL to create a sequence on this platform.
+ *
+ * @param \Doctrine\DBAL\Schema\Sequence $sequence
+ * @throws DBALException
+ */
+ public function getCreateSequenceSQL(\Doctrine\DBAL\Schema\Sequence $sequence)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ /**
+ * Gets the SQL to create a constraint on a table on this platform.
+ *
+ * @param Constraint $constraint
+ * @param string|Table $table
+ * @return string
+ */
+ public function getCreateConstraintSQL(\Doctrine\DBAL\Schema\Constraint $constraint, $table)
+ {
+ if ($table instanceof \Doctrine\DBAL\Schema\Table) {
+ $table = $table->getQuotedName($this);
+ }
+
+ $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $constraint->getQuotedName($this);
+
+ $columns = array();
+ foreach ($constraint->getColumns() as $column) {
+ $columns[] = $column;
+ }
+ $columnList = '('. implode(', ', $columns) . ')';
+
+ $referencesClause = '';
+ if ($constraint instanceof \Doctrine\DBAL\Schema\Index) {
+ if($constraint->isPrimary()) {
+ $query .= ' PRIMARY KEY';
+ } elseif ($constraint->isUnique()) {
+ $query .= ' UNIQUE';
+ } else {
+ throw new \InvalidArgumentException(
+ 'Can only create primary or unique constraints, no common indexes with getCreateConstraintSQL().'
+ );
+ }
+ } else if ($constraint instanceof \Doctrine\DBAL\Schema\ForeignKeyConstraint) {
+ $query .= ' FOREIGN KEY';
+
+ $foreignColumns = array();
+ foreach ($constraint->getForeignColumns() AS $column) {
+ $foreignColumns[] = $column;
+ }
+
+ $referencesClause = ' REFERENCES '.$constraint->getForeignTableName(). ' ('.implode(', ', $foreignColumns).')';
+ }
+ $query .= ' '.$columnList.$referencesClause;
+
+ return $query;
+ }
+
+ /**
+ * Gets the SQL to create an index on a table on this platform.
+ *
+ * @param Index $index
+ * @param string|Table $table name of the table on which the index is to be created
+ * @return string
+ */
+ public function getCreateIndexSQL(Index $index, $table)
+ {
+ if ($table instanceof Table) {
+ $table = $table->getQuotedName($this);
+ }
+ $name = $index->getQuotedName($this);
+ $columns = $index->getColumns();
+
+ if (count($columns) == 0) {
+ throw new \InvalidArgumentException("Incomplete definition. 'columns' required.");
+ }
+
+ $type = '';
+ if ($index->isUnique()) {
+ $type = 'UNIQUE ';
+ }
+
+ $query = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table;
+
+ $query .= ' (' . $this->getIndexFieldDeclarationListSQL($columns) . ')';
+
+ return $query;
+ }
+
+ /**
+ * Quotes a string so that it can be safely used as a table or column name,
+ * even if it is a reserved word of the platform.
+ *
+ * NOTE: Just because you CAN use quoted identifiers doesn't mean
+ * you SHOULD use them. In general, they end up causing way more
+ * problems than they solve.
+ *
+ * @param string $str identifier name to be quoted
+ * @return string quoted identifier string
+ */
+ public function quoteIdentifier($str)
+ {
+ $c = $this->getIdentifierQuoteCharacter();
+
+ return $c . $str . $c;
+ }
+
+ /**
+ * Create a new foreign key
+ *
+ * @param ForeignKeyConstraint $foreignKey ForeignKey instance
+ * @param string|Table $table name of the table on which the foreign key is to be created
+ * @return string
+ */
+ public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table)
+ {
+ if ($table instanceof \Doctrine\DBAL\Schema\Table) {
+ $table = $table->getQuotedName($this);
+ }
+
+ $query = 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclarationSQL($foreignKey);
+
+ return $query;
+ }
+
+ /**
+ * Gets the sql statements for altering an existing table.
+ *
+ * The method returns an array of sql statements, since some platforms need several statements.
+ *
+ * @param TableDiff $diff
+ * @return array
+ */
+ public function getAlterTableSQL(TableDiff $diff)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ /**
+ * Common code for alter table statement generation that updates the changed Index and Foreign Key definitions.
+ *
+ * @param TableDiff $diff
+ * @return array
+ */
+ protected function _getAlterTableIndexForeignKeySQL(TableDiff $diff)
+ {
+ if ($diff->newName !== false) {
+ $tableName = $diff->newName;
+ } else {
+ $tableName = $diff->name;
+ }
+
+ $sql = array();
+ if ($this->supportsForeignKeyConstraints()) {
+ foreach ($diff->removedForeignKeys AS $foreignKey) {
+ $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName);
+ }
+ foreach ($diff->addedForeignKeys AS $foreignKey) {
+ $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName);
+ }
+ foreach ($diff->changedForeignKeys AS $foreignKey) {
+ $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName);
+ $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName);
+ }
+ }
+
+ foreach ($diff->addedIndexes AS $index) {
+ $sql[] = $this->getCreateIndexSQL($index, $tableName);
+ }
+ foreach ($diff->removedIndexes AS $index) {
+ $sql[] = $this->getDropIndexSQL($index, $tableName);
+ }
+ foreach ($diff->changedIndexes AS $index) {
+ $sql[] = $this->getDropIndexSQL($index, $tableName);
+ $sql[] = $this->getCreateIndexSQL($index, $tableName);
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Get declaration of a number of fields in bulk
+ *
+ * @param array $fields a multidimensional associative array.
+ * The first dimension determines the field name, while the second
+ * dimension is keyed with the name of the properties
+ * of the field being declared as array indexes. Currently, the types
+ * of supported field properties are as follows:
+ *
+ * length
+ * Integer value that determines the maximum length of the text
+ * field. If this argument is missing the field should be
+ * declared to have the longest length allowed by the DBMS.
+ *
+ * default
+ * Text value to be used as default for this field.
+ *
+ * notnull
+ * Boolean flag that indicates whether this field is constrained
+ * to not be set to null.
+ * charset
+ * Text value with the default CHARACTER SET for this field.
+ * collation
+ * Text value with the default COLLATION for this field.
+ * unique
+ * unique constraint
+ *
+ * @return string
+ */
+ public function getColumnDeclarationListSQL(array $fields)
+ {
+ $queryFields = array();
+ foreach ($fields as $fieldName => $field) {
+ $query = $this->getColumnDeclarationSQL($fieldName, $field);
+ $queryFields[] = $query;
+ }
+ return implode(', ', $queryFields);
+ }
+
+ /**
+ * Obtain DBMS specific SQL code portion needed to declare a generic type
+ * field to be used in statements like CREATE TABLE.
+ *
+ * @param string $name name the field to be declared.
+ * @param array $field associative array with the name of the properties
+ * of the field being declared as array indexes. Currently, the types
+ * of supported field properties are as follows:
+ *
+ * length
+ * Integer value that determines the maximum length of the text
+ * field. If this argument is missing the field should be
+ * declared to have the longest length allowed by the DBMS.
+ *
+ * default
+ * Text value to be used as default for this field.
+ *
+ * notnull
+ * Boolean flag that indicates whether this field is constrained
+ * to not be set to null.
+ * charset
+ * Text value with the default CHARACTER SET for this field.
+ * collation
+ * Text value with the default COLLATION for this field.
+ * unique
+ * unique constraint
+ * check
+ * column check constraint
+ * columnDefinition
+ * a string that defines the complete column
+ *
+ * @return string DBMS specific SQL code portion that should be used to declare the column.
+ */
+ public function getColumnDeclarationSQL($name, array $field)
+ {
+ if (isset($field['columnDefinition'])) {
+ $columnDef = $this->getCustomTypeDeclarationSQL($field);
+ } else {
+ $default = $this->getDefaultValueDeclarationSQL($field);
+
+ $charset = (isset($field['charset']) && $field['charset']) ?
+ ' ' . $this->getColumnCharsetDeclarationSQL($field['charset']) : '';
+
+ $collation = (isset($field['collation']) && $field['collation']) ?
+ ' ' . $this->getColumnCollationDeclarationSQL($field['collation']) : '';
+
+ $notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : '';
+
+ $unique = (isset($field['unique']) && $field['unique']) ?
+ ' ' . $this->getUniqueFieldDeclarationSQL() : '';
+
+ $check = (isset($field['check']) && $field['check']) ?
+ ' ' . $field['check'] : '';
+
+ $typeDecl = $field['type']->getSqlDeclaration($field, $this);
+ $columnDef = $typeDecl . $charset . $default . $notnull . $unique . $check . $collation;
+ }
+
+ return $name . ' ' . $columnDef;
+ }
+
+ /**
+ * Gets the SQL snippet that declares a floating point column of arbitrary precision.
+ *
+ * @param array $columnDef
+ * @return string
+ */
+ public function getDecimalTypeDeclarationSQL(array $columnDef)
+ {
+ $columnDef['precision'] = ( ! isset($columnDef['precision']) || empty($columnDef['precision']))
+ ? 10 : $columnDef['precision'];
+ $columnDef['scale'] = ( ! isset($columnDef['scale']) || empty($columnDef['scale']))
+ ? 0 : $columnDef['scale'];
+
+ return 'NUMERIC(' . $columnDef['precision'] . ', ' . $columnDef['scale'] . ')';
+ }
+
+ /**
+ * Obtain DBMS specific SQL code portion needed to set a default value
+ * declaration to be used in statements like CREATE TABLE.
+ *
+ * @param array $field field definition array
+ * @return string DBMS specific SQL code portion needed to set a default value
+ */
+ public function getDefaultValueDeclarationSQL($field)
+ {
+ $default = empty($field['notnull']) ? ' DEFAULT NULL' : '';
+
+ if (isset($field['default'])) {
+ $default = " DEFAULT '".$field['default']."'";
+ if (isset($field['type'])) {
+ if (in_array((string)$field['type'], array("Integer", "BigInteger", "SmallInteger"))) {
+ $default = " DEFAULT ".$field['default'];
+ } else if ((string)$field['type'] == 'DateTime' && $field['default'] == $this->getCurrentTimestampSQL()) {
+ $default = " DEFAULT ".$this->getCurrentTimestampSQL();
+ }
+ }
+ }
+ return $default;
+ }
+
+ /**
+ * Obtain DBMS specific SQL code portion needed to set a CHECK constraint
+ * declaration to be used in statements like CREATE TABLE.
+ *
+ * @param array $definition check definition
+ * @return string DBMS specific SQL code portion needed to set a CHECK constraint
+ */
+ public function getCheckDeclarationSQL(array $definition)
+ {
+ $constraints = array();
+ foreach ($definition as $field => $def) {
+ if (is_string($def)) {
+ $constraints[] = 'CHECK (' . $def . ')';
+ } else {
+ if (isset($def['min'])) {
+ $constraints[] = 'CHECK (' . $field . ' >= ' . $def['min'] . ')';
+ }
+
+ if (isset($def['max'])) {
+ $constraints[] = 'CHECK (' . $field . ' <= ' . $def['max'] . ')';
+ }
+ }
+ }
+
+ return implode(', ', $constraints);
+ }
+
+ /**
+ * Obtain DBMS specific SQL code portion needed to set a unique
+ * constraint declaration to be used in statements like CREATE TABLE.
+ *
+ * @param string $name name of the unique constraint
+ * @param Index $index index definition
+ * @return string DBMS specific SQL code portion needed
+ * to set a constraint
+ */
+ public function getUniqueConstraintDeclarationSQL($name, Index $index)
+ {
+ if (count($index->getColumns()) == 0) {
+ throw \InvalidArgumentException("Incomplete definition. 'columns' required.");
+ }
+
+ return 'CONSTRAINT ' . $name . ' UNIQUE ('
+ . $this->getIndexFieldDeclarationListSQL($index->getColumns())
+ . ')';
+ }
+
+ /**
+ * Obtain DBMS specific SQL code portion needed to set an index
+ * declaration to be used in statements like CREATE TABLE.
+ *
+ * @param string $name name of the index
+ * @param Index $index index definition
+ * @return string DBMS specific SQL code portion needed to set an index
+ */
+ public function getIndexDeclarationSQL($name, Index $index)
+ {
+ $type = '';
+
+ if($index->isUnique()) {
+ $type = 'UNIQUE ';
+ }
+
+ if (count($index->getColumns()) == 0) {
+ throw \InvalidArgumentException("Incomplete definition. 'columns' required.");
+ }
+
+ return $type . 'INDEX ' . $name . ' ('
+ . $this->getIndexFieldDeclarationListSQL($index->getColumns())
+ . ')';
+ }
+
+ /**
+ * getCustomTypeDeclarationSql
+ * Obtail SQL code portion needed to create a custom column,
+ * e.g. when a field has the "columnDefinition" keyword.
+ * Only "AUTOINCREMENT" and "PRIMARY KEY" are added if appropriate.
+ *
+ * @return string
+ */
+ public function getCustomTypeDeclarationSQL(array $columnDef)
+ {
+ return $columnDef['columnDefinition'];
+ }
+
+ /**
+ * getIndexFieldDeclarationList
+ * Obtain DBMS specific SQL code portion needed to set an index
+ * declaration to be used in statements like CREATE TABLE.
+ *
+ * @return string
+ */
+ public function getIndexFieldDeclarationListSQL(array $fields)
+ {
+ $ret = array();
+ foreach ($fields as $field => $definition) {
+ if (is_array($definition)) {
+ $ret[] = $field;
+ } else {
+ $ret[] = $definition;
+ }
+ }
+ return implode(', ', $ret);
+ }
+
+ /**
+ * A method to return the required SQL string that fits between CREATE ... TABLE
+ * to create the table as a temporary table.
+ *
+ * Should be overridden in driver classes to return the correct string for the
+ * specific database type.
+ *
+ * The default is to return the string "TEMPORARY" - this will result in a
+ * SQL error for any database that does not support temporary tables, or that
+ * requires a different SQL command from "CREATE TEMPORARY TABLE".
+ *
+ * @return string The string required to be placed between "CREATE" and "TABLE"
+ * to generate a temporary table, if possible.
+ */
+ public function getTemporaryTableSQL()
+ {
+ return 'TEMPORARY';
+ }
+
+ /**
+ * Some vendors require temporary table names to be qualified specially.
+ *
+ * @param string $tableName
+ * @return string
+ */
+ public function getTemporaryTableName($tableName)
+ {
+ return $tableName;
+ }
+
+ /**
+ * Get sql query to show a list of database.
+ *
+ * @return string
+ */
+ public function getShowDatabasesSQL()
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ /**
+ * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
+ * of a field declaration to be used in statements like CREATE TABLE.
+ *
+ * @param array $definition an associative array with the following structure:
+ * name optional constraint name
+ *
+ * local the local field(s)
+ *
+ * foreign the foreign reference field(s)
+ *
+ * foreignTable the name of the foreign table
+ *
+ * onDelete referential delete action
+ *
+ * onUpdate referential update action
+ *
+ * deferred deferred constraint checking
+ *
+ * The onDelete and onUpdate keys accept the following values:
+ *
+ * CASCADE: Delete or update the row from the parent table and automatically delete or
+ * update the matching rows in the child table. Both ON DELETE CASCADE and ON UPDATE CASCADE are supported.
+ * Between two tables, you should not define several ON UPDATE CASCADE clauses that act on the same column
+ * in the parent table or in the child table.
+ *
+ * SET NULL: Delete or update the row from the parent table and set the foreign key column or columns in the
+ * child table to NULL. This is valid only if the foreign key columns do not have the NOT NULL qualifier
+ * specified. Both ON DELETE SET NULL and ON UPDATE SET NULL clauses are supported.
+ *
+ * NO ACTION: In standard SQL, NO ACTION means no action in the sense that an attempt to delete or update a primary
+ * key value is not allowed to proceed if there is a related foreign key value in the referenced table.
+ *
+ * RESTRICT: Rejects the delete or update operation for the parent table. NO ACTION and RESTRICT are the same as
+ * omitting the ON DELETE or ON UPDATE clause.
+ *
+ * SET DEFAULT
+ *
+ * @return string DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
+ * of a field declaration.
+ */
+ public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey)
+ {
+ $sql = $this->getForeignKeyBaseDeclarationSQL($foreignKey);
+ $sql .= $this->getAdvancedForeignKeyOptionsSQL($foreignKey);
+
+ return $sql;
+ }
+
+ /**
+ * Return the FOREIGN KEY query section dealing with non-standard options
+ * as MATCH, INITIALLY DEFERRED, ON UPDATE, ...
+ *
+ * @param ForeignKeyConstraint $foreignKey foreign key definition
+ * @return string
+ */
+ public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
+ {
+ $query = '';
+ if ($this->supportsForeignKeyOnUpdate() && $foreignKey->hasOption('onUpdate')) {
+ $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onUpdate'));
+ }
+ if ($foreignKey->hasOption('onDelete')) {
+ $query .= ' ON DELETE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete'));
+ }
+ return $query;
+ }
+
+ /**
+ * returns given referential action in uppercase if valid, otherwise throws
+ * an exception
+ *
+ * @throws Doctrine_Exception_Exception if unknown referential action given
+ * @param string $action foreign key referential action
+ * @param string foreign key referential action in uppercase
+ */
+ public function getForeignKeyReferentialActionSQL($action)
+ {
+ $upper = strtoupper($action);
+ switch ($upper) {
+ case 'CASCADE':
+ case 'SET NULL':
+ case 'NO ACTION':
+ case 'RESTRICT':
+ case 'SET DEFAULT':
+ return $upper;
+ break;
+ default:
+ throw \InvalidArgumentException('Invalid foreign key action: ' . $upper);
+ }
+ }
+
+ /**
+ * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
+ * of a field declaration to be used in statements like CREATE TABLE.
+ *
+ * @param ForeignKeyConstraint $foreignKey
+ * @return string
+ */
+ public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey)
+ {
+ $sql = '';
+ if (strlen($foreignKey->getName())) {
+ $sql .= 'CONSTRAINT ' . $foreignKey->getQuotedName($this) . ' ';
+ }
+ $sql .= 'FOREIGN KEY (';
+
+ if (count($foreignKey->getLocalColumns()) == 0) {
+ throw new \InvalidArgumentException("Incomplete definition. 'local' required.");
+ }
+ if (count($foreignKey->getForeignColumns()) == 0) {
+ throw new \InvalidArgumentException("Incomplete definition. 'foreign' required.");
+ }
+ if (strlen($foreignKey->getForeignTableName()) == 0) {
+ throw new \InvalidArgumentException("Incomplete definition. 'foreignTable' required.");
+ }
+
+ $sql .= implode(', ', $foreignKey->getLocalColumns())
+ . ') REFERENCES '
+ . $foreignKey->getForeignTableName() . '('
+ . implode(', ', $foreignKey->getForeignColumns()) . ')';
+
+ return $sql;
+ }
+
+ /**
+ * Obtain DBMS specific SQL code portion needed to set the UNIQUE constraint
+ * of a field declaration to be used in statements like CREATE TABLE.
+ *
+ * @return string DBMS specific SQL code portion needed to set the UNIQUE constraint
+ * of a field declaration.
+ */
+ public function getUniqueFieldDeclarationSQL()
+ {
+ return 'UNIQUE';
+ }
+
+ /**
+ * Obtain DBMS specific SQL code portion needed to set the CHARACTER SET
+ * of a field declaration to be used in statements like CREATE TABLE.
+ *
+ * @param string $charset name of the charset
+ * @return string DBMS specific SQL code portion needed to set the CHARACTER SET
+ * of a field declaration.
+ */
+ public function getColumnCharsetDeclarationSQL($charset)
+ {
+ return '';
+ }
+
+ /**
+ * Obtain DBMS specific SQL code portion needed to set the COLLATION
+ * of a field declaration to be used in statements like CREATE TABLE.
+ *
+ * @param string $collation name of the collation
+ * @return string DBMS specific SQL code portion needed to set the COLLATION
+ * of a field declaration.
+ */
+ public function getColumnCollationDeclarationSQL($collation)
+ {
+ return '';
+ }
+
+ /**
+ * Whether the platform prefers sequences for ID generation.
+ * Subclasses should override this method to return TRUE if they prefer sequences.
+ *
+ * @return boolean
+ */
+ public function prefersSequences()
+ {
+ return false;
+ }
+
+ /**
+ * Whether the platform prefers identity columns (eg. autoincrement) for ID generation.
+ * Subclasses should override this method to return TRUE if they prefer identity columns.
+ *
+ * @return boolean
+ */
+ public function prefersIdentityColumns()
+ {
+ return false;
+ }
+
+ /**
+ * Some platforms need the boolean values to be converted.
+ *
+ * The default conversion in this implementation converts to integers (false => 0, true => 1).
+ *
+ * @param mixed $item
+ */
+ public function convertBooleans($item)
+ {
+ if (is_array($item)) {
+ foreach ($item as $k => $value) {
+ if (is_bool($value)) {
+ $item[$k] = (int) $value;
+ }
+ }
+ } else if (is_bool($item)) {
+ $item = (int) $item;
+ }
+ return $item;
+ }
+
+ /**
+ * Gets the SQL statement specific for the platform to set the charset.
+ *
+ * This function is MySQL specific and required by
+ * {@see \Doctrine\DBAL\Connection::setCharset($charset)}
+ *
+ * @param string $charset
+ * @return string
+ */
+ public function getSetCharsetSQL($charset)
+ {
+ return "SET NAMES '".$charset."'";
+ }
+
+ /**
+ * Gets the SQL specific for the platform to get the current date.
+ *
+ * @return string
+ */
+ public function getCurrentDateSQL()
+ {
+ return 'CURRENT_DATE';
+ }
+
+ /**
+ * Gets the SQL specific for the platform to get the current time.
+ *
+ * @return string
+ */
+ public function getCurrentTimeSQL()
+ {
+ return 'CURRENT_TIME';
+ }
+
+ /**
+ * Gets the SQL specific for the platform to get the current timestamp
+ *
+ * @return string
+ */
+ public function getCurrentTimestampSQL()
+ {
+ return 'CURRENT_TIMESTAMP';
+ }
+
+ /**
+ * Get sql for transaction isolation level Connection constant
+ *
+ * @param integer $level
+ */
+ protected function _getTransactionIsolationLevelSQL($level)
+ {
+ switch ($level) {
+ case Connection::TRANSACTION_READ_UNCOMMITTED:
+ return 'READ UNCOMMITTED';
+ case Connection::TRANSACTION_READ_COMMITTED:
+ return 'READ COMMITTED';
+ case Connection::TRANSACTION_REPEATABLE_READ:
+ return 'REPEATABLE READ';
+ case Connection::TRANSACTION_SERIALIZABLE:
+ return 'SERIALIZABLE';
+ default:
+ throw new \InvalidArgumentException('Invalid isolation level:' . $level);
+ }
+ }
+
+ public function getListDatabasesSQL()
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ public function getListSequencesSQL($database)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ public function getListTableConstraintsSQL($table)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ public function getListTableColumnsSQL($table)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ public function getListTablesSQL()
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ public function getListUsersSQL()
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ /**
+ * Get the SQL to list all views of a database or user.
+ *
+ * @param string $database
+ * @return string
+ */
+ public function getListViewsSQL($database)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ public function getListTableIndexesSQL($table)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ public function getListTableForeignKeysSQL($table)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ public function getCreateViewSQL($name, $sql)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ public function getDropViewSQL($name)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ public function getDropSequenceSQL($sequence)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ public function getSequenceNextValSQL($sequenceName)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ public function getCreateDatabaseSQL($database)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ /**
+ * Get sql to set the transaction isolation level
+ *
+ * @param integer $level
+ */
+ public function getSetTransactionIsolationSQL($level)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ /**
+ * Obtain DBMS specific SQL to be used to create datetime fields in
+ * statements like CREATE TABLE
+ *
+ * @param array $fieldDeclaration
+ * @return string
+ */
+ public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ /**
+ * Obtain DBMS specific SQL to be used to create datetime with timezone offset fields.
+ *
+ * @param array $fieldDeclaration
+ */
+ public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ return $this->getDateTimeTypeDeclarationSQL($fieldDeclaration);
+ }
+
+
+ /**
+ * Obtain DBMS specific SQL to be used to create date fields in statements
+ * like CREATE TABLE.
+ *
+ * @param array $fieldDeclaration
+ * @return string
+ */
+ public function getDateTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ /**
+ * Obtain DBMS specific SQL to be used to create time fields in statements
+ * like CREATE TABLE.
+ *
+ * @param array $fieldDeclaration
+ * @return string
+ */
+ public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ public function getFloatDeclarationSQL(array $fieldDeclaration)
+ {
+ return 'DOUBLE PRECISION';
+ }
+
+ /**
+ * Gets the default transaction isolation level of the platform.
+ *
+ * @return integer The default isolation level.
+ * @see Doctrine\DBAL\Connection\TRANSACTION_* constants.
+ */
+ public function getDefaultTransactionIsolationLevel()
+ {
+ return Connection::TRANSACTION_READ_COMMITTED;
+ }
+
+ /* supports*() metods */
+
+ /**
+ * Whether the platform supports sequences.
+ *
+ * @return boolean
+ */
+ public function supportsSequences()
+ {
+ return false;
+ }
+
+ /**
+ * Whether the platform supports identity columns.
+ * Identity columns are columns that recieve an auto-generated value from the
+ * database on insert of a row.
+ *
+ * @return boolean
+ */
+ public function supportsIdentityColumns()
+ {
+ return false;
+ }
+
+ /**
+ * Whether the platform supports indexes.
+ *
+ * @return boolean
+ */
+ public function supportsIndexes()
+ {
+ return true;
+ }
+
+ public function supportsAlterTable()
+ {
+ return true;
+ }
+
+ /**
+ * Whether the platform supports transactions.
+ *
+ * @return boolean
+ */
+ public function supportsTransactions()
+ {
+ return true;
+ }
+
+ /**
+ * Whether the platform supports savepoints.
+ *
+ * @return boolean
+ */
+ public function supportsSavepoints()
+ {
+ return true;
+ }
+
+ /**
+ * Whether the platform supports releasing savepoints.
+ *
+ * @return boolean
+ */
+ public function supportsReleaseSavepoints()
+ {
+ return $this->supportsSavepoints();
+ }
+
+ /**
+ * Whether the platform supports primary key constraints.
+ *
+ * @return boolean
+ */
+ public function supportsPrimaryConstraints()
+ {
+ return true;
+ }
+
+ /**
+ * Does the platform supports foreign key constraints?
+ *
+ * @return boolean
+ */
+ public function supportsForeignKeyConstraints()
+ {
+ return true;
+ }
+
+ /**
+ * Does this platform supports onUpdate in foreign key constraints?
+ *
+ * @return bool
+ */
+ public function supportsForeignKeyOnUpdate()
+ {
+ return ($this->supportsForeignKeyConstraints() && true);
+ }
+
+ /**
+ * Whether the platform supports database schemas.
+ *
+ * @return boolean
+ */
+ public function supportsSchemas()
+ {
+ return false;
+ }
+
+ /**
+ * Some databases don't allow to create and drop databases at all or only with certain tools.
+ *
+ * @return bool
+ */
+ public function supportsCreateDropDatabase()
+ {
+ return true;
+ }
+
+ /**
+ * Whether the platform supports getting the affected rows of a recent
+ * update/delete type query.
+ *
+ * @return boolean
+ */
+ public function supportsGettingAffectedRows()
+ {
+ return true;
+ }
+
+ public function getIdentityColumnNullInsertSQL()
+ {
+ return "";
+ }
+
+ /**
+ * Gets the format string, as accepted by the date() function, that describes
+ * the format of a stored datetime value of this platform.
+ *
+ * @return string The format string.
+ */
+ public function getDateTimeFormatString()
+ {
+ return 'Y-m-d H:i:s';
+ }
+
+ /**
+ * Gets the format string, as accepted by the date() function, that describes
+ * the format of a stored datetime with timezone value of this platform.
+ *
+ * @return string The format string.
+ */
+ public function getDateTimeTzFormatString()
+ {
+ return 'Y-m-d H:i:s';
+ }
+
+ /**
+ * Gets the format string, as accepted by the date() function, that describes
+ * the format of a stored date value of this platform.
+ *
+ * @return string The format string.
+ */
+ public function getDateFormatString()
+ {
+ return 'Y-m-d';
+ }
+
+ /**
+ * Gets the format string, as accepted by the date() function, that describes
+ * the format of a stored time value of this platform.
+ *
+ * @return string The format string.
+ */
+ public function getTimeFormatString()
+ {
+ return 'H:i:s';
+ }
+
+ public function modifyLimitQuery($query, $limit, $offset = null)
+ {
+ if ( ! is_null($limit)) {
+ $query .= ' LIMIT ' . $limit;
+ }
+
+ if ( ! is_null($offset)) {
+ $query .= ' OFFSET ' . $offset;
+ }
+
+ return $query;
+ }
+
+ /**
+ * Gets the character casing of a column in an SQL result set of this platform.
+ *
+ * @param string $column The column name for which to get the correct character casing.
+ * @return string The column name in the character casing used in SQL result sets.
+ */
+ public function getSQLResultCasing($column)
+ {
+ return $column;
+ }
+
+ /**
+ * Makes any fixes to a name of a schema element (table, sequence, ...) that are required
+ * by restrictions of the platform, like a maximum length.
+ *
+ * @param string $schemaName
+ * @return string
+ */
+ public function fixSchemaElementName($schemaElementName)
+ {
+ return $schemaElementName;
+ }
+
+ /**
+ * Maximum length of any given databse identifier, like tables or column names.
+ *
+ * @return int
+ */
+ public function getMaxIdentifierLength()
+ {
+ return 63;
+ }
+
+ /**
+ * Get the insert sql for an empty insert statement
+ *
+ * @param string $tableName
+ * @param string $identifierColumnName
+ * @return string $sql
+ */
+ public function getEmptyIdentityInsertSQL($tableName, $identifierColumnName)
+ {
+ return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (null)';
+ }
+
+ /**
+ * Generate a Truncate Table SQL statement for a given table.
+ *
+ * Cascade is not supported on many platforms but would optionally cascade the truncate by
+ * following the foreign keys.
+ *
+ * @param string $tableName
+ * @param bool $cascade
+ * @return string
+ */
+ public function getTruncateTableSQL($tableName, $cascade = false)
+ {
+ return 'TRUNCATE '.$tableName;
+ }
+
+ /**
+ * This is for test reasons, many vendors have special requirements for dummy statements.
+ *
+ * @return string
+ */
+ public function getDummySelectSQL()
+ {
+ return 'SELECT 1';
+ }
+
+ /**
+ * Generate SQL to create a new savepoint
+ *
+ * @param string $savepoint
+ * @return string
+ */
+ public function createSavePoint($savepoint)
+ {
+ return 'SAVEPOINT ' . $savepoint;
+ }
+
+ /**
+ * Generate SQL to release a savepoint
+ *
+ * @param string $savepoint
+ * @return string
+ */
+ public function releaseSavePoint($savepoint)
+ {
+ return 'RELEASE SAVEPOINT ' . $savepoint;
+ }
+
+ /**
+ * Generate SQL to rollback a savepoint
+ *
+ * @param string $savepoint
+ * @return string
+ */
+ public function rollbackSavePoint($savepoint)
+ {
+ return 'ROLLBACK TO SAVEPOINT ' . $savepoint;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\DBAL\Platforms;
+
+use Doctrine\DBAL\DBALException;
+use Doctrine\DBAL\Schema\Index;
+use Doctrine\DBAL\Schema\TableDiff;
+
+class DB2Platform extends AbstractPlatform
+{
+ public function initializeDoctrineTypeMappings()
+ {
+ $this->doctrineTypeMapping = array(
+ 'smallint' => 'smallint',
+ 'bigint' => 'bigint',
+ 'integer' => 'integer',
+ 'time' => 'time',
+ 'date' => 'date',
+ 'varchar' => 'string',
+ 'character' => 'string',
+ 'clob' => 'text',
+ 'decimal' => 'decimal',
+ 'double' => 'float',
+ 'real' => 'float',
+ 'timestamp' => 'datetime',
+ );
+ }
+
+ /**
+ * Gets the SQL snippet used to declare a VARCHAR column type.
+ *
+ * @param array $field
+ */
+ public function getVarcharTypeDeclarationSQL(array $field)
+ {
+ if ( ! isset($field['length'])) {
+ if (array_key_exists('default', $field)) {
+ $field['length'] = $this->getVarcharDefaultLength();
+ } else {
+ $field['length'] = false;
+ }
+ }
+
+ $length = ($field['length'] <= $this->getVarcharMaxLength()) ? $field['length'] : false;
+ $fixed = (isset($field['fixed'])) ? $field['fixed'] : false;
+
+ return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
+ : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)');
+ }
+
+ /**
+ * Gets the SQL snippet used to declare a CLOB column type.
+ *
+ * @param array $field
+ */
+ public function getClobTypeDeclarationSQL(array $field)
+ {
+ // todo clob(n) with $field['length'];
+ return 'CLOB(1M)';
+ }
+
+ /**
+ * Gets the name of the platform.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return 'db2';
+ }
+
+
+ /**
+ * Gets the SQL snippet that declares a boolean column.
+ *
+ * @param array $columnDef
+ * @return string
+ */
+ public function getBooleanTypeDeclarationSQL(array $columnDef)
+ {
+ return 'SMALLINT';
+ }
+
+ /**
+ * Gets the SQL snippet that declares a 4 byte integer column.
+ *
+ * @param array $columnDef
+ * @return string
+ */
+ public function getIntegerTypeDeclarationSQL(array $columnDef)
+ {
+ return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
+ }
+
+ /**
+ * Gets the SQL snippet that declares an 8 byte integer column.
+ *
+ * @param array $columnDef
+ * @return string
+ */
+ public function getBigIntTypeDeclarationSQL(array $columnDef)
+ {
+ return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
+ }
+
+ /**
+ * Gets the SQL snippet that declares a 2 byte integer column.
+ *
+ * @param array $columnDef
+ * @return string
+ */
+ public function getSmallIntTypeDeclarationSQL(array $columnDef)
+ {
+ return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
+ }
+
+ /**
+ * Gets the SQL snippet that declares common properties of an integer column.
+ *
+ * @param array $columnDef
+ * @return string
+ */
+ protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
+ {
+ $autoinc = '';
+ if ( ! empty($columnDef['autoincrement'])) {
+ $autoinc = ' GENERATED BY DEFAULT AS IDENTITY';
+ }
+ return $autoinc;
+ }
+
+ /**
+ * Obtain DBMS specific SQL to be used to create datetime fields in
+ * statements like CREATE TABLE
+ *
+ * @param array $fieldDeclaration
+ * @return string
+ */
+ public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] == true) {
+ return "TIMESTAMP(0) WITH DEFAULT";
+ }
+
+ return 'TIMESTAMP(0)';
+ }
+
+ /**
+ * Obtain DBMS specific SQL to be used to create date fields in statements
+ * like CREATE TABLE.
+ *
+ * @param array $fieldDeclaration
+ * @return string
+ */
+ public function getDateTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ return 'DATE';
+ }
+
+ /**
+ * Obtain DBMS specific SQL to be used to create time fields in statements
+ * like CREATE TABLE.
+ *
+ * @param array $fieldDeclaration
+ * @return string
+ */
+ public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ return 'TIME';
+ }
+
+ public function getListDatabasesSQL()
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ public function getListSequencesSQL($database)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ public function getListTableConstraintsSQL($table)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ /**
+ * This code fragment is originally from the Zend_Db_Adapter_Db2 class.
+ *
+ * @license New BSD License
+ * @param string $table
+ * @return string
+ */
+ public function getListTableColumnsSQL($table)
+ {
+ return "SELECT DISTINCT c.tabschema, c.tabname, c.colname, c.colno,
+ c.typename, c.default, c.nulls, c.length, c.scale,
+ c.identity, tc.type AS tabconsttype, k.colseq
+ FROM syscat.columns c
+ LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc
+ ON (k.tabschema = tc.tabschema
+ AND k.tabname = tc.tabname
+ AND tc.type = 'P'))
+ ON (c.tabschema = k.tabschema
+ AND c.tabname = k.tabname
+ AND c.colname = k.colname)
+ WHERE UPPER(c.tabname) = UPPER('" . $table . "') ORDER BY c.colno";
+ }
+
+ public function getListTablesSQL()
+ {
+ return "SELECT NAME FROM SYSIBM.SYSTABLES WHERE TYPE = 'T'";
+ }
+
+ public function getListUsersSQL()
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ /**
+ * Get the SQL to list all views of a database or user.
+ *
+ * @param string $database
+ * @return string
+ */
+ public function getListViewsSQL($database)
+ {
+ return "SELECT NAME, TEXT FROM SYSIBM.SYSVIEWS";
+ }
+
+ public function getListTableIndexesSQL($table)
+ {
+ return "SELECT NAME, COLNAMES, UNIQUERULE FROM SYSIBM.SYSINDEXES WHERE TBNAME = UPPER('" . $table . "')";
+ }
+
+ public function getListTableForeignKeysSQL($table)
+ {
+ return "SELECT TBNAME, RELNAME, REFTBNAME, DELETERULE, UPDATERULE, FKCOLNAMES, PKCOLNAMES ".
+ "FROM SYSIBM.SYSRELS WHERE TBNAME = UPPER('".$table."')";
+ }
+
+ public function getCreateViewSQL($name, $sql)
+ {
+ return "CREATE VIEW ".$name." AS ".$sql;
+ }
+
+ public function getDropViewSQL($name)
+ {
+ return "DROP VIEW ".$name;
+ }
+
+ public function getDropSequenceSQL($sequence)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ public function getSequenceNextValSQL($sequenceName)
+ {
+ throw DBALException::notSupported(__METHOD__);
+ }
+
+ public function getCreateDatabaseSQL($database)
+ {
+ return "CREATE DATABASE ".$database;
+ }
+
+ public function getDropDatabaseSQL($database)
+ {
+ return "DROP DATABASE ".$database.";";
+ }
+
+ public function supportsCreateDropDatabase()
+ {
+ return false;
+ }
+
+ /**
+ * Whether the platform supports releasing savepoints.
+ *
+ * @return boolean
+ */
+ public function supportsReleaseSavepoints()
+ {
+ return false;
+ }
+
+ /**
+ * Gets the SQL specific for the platform to get the current date.
+ *
+ * @return string
+ */
+ public function getCurrentDateSQL()
+ {
+ return 'VALUES CURRENT DATE';
+ }
+
+ /**
+ * Gets the SQL specific for the platform to get the current time.
+ *
+ * @return string
+ */
+ public function getCurrentTimeSQL()
+ {
+ return 'VALUES CURRENT TIME';
+ }
+
+ /**
+ * Gets the SQL specific for the platform to get the current timestamp
+ *
+ * @return string
+ */
+
+ public function getCurrentTimestampSQL()
+ {
+ return "VALUES CURRENT TIMESTAMP";
+ }
+
+ /**
+ * Obtain DBMS specific SQL code portion needed to set an index
+ * declaration to be used in statements like CREATE TABLE.
+ *
+ * @param string $name name of the index
+ * @param Index $index index definition
+ * @return string DBMS specific SQL code portion needed to set an index
+ */
+ public function getIndexDeclarationSQL($name, Index $index)
+ {
+ return $this->getUniqueConstraintDeclarationSQL($name, $index);
+ }
+
+ /**
+ * @param string $tableName
+ * @param array $columns
+ * @param array $options
+ * @return array
+ */
+ protected function _getCreateTableSQL($tableName, array $columns, array $options = array())
+ {
+ $indexes = array();
+ if (isset($options['indexes'])) {
+ $indexes = $options['indexes'];
+ }
+ $options['indexes'] = array();
+
+ $sqls = parent::_getCreateTableSQL($tableName, $columns, $options);
+
+ foreach ($indexes as $index => $definition) {
+ $sqls[] = $this->getCreateIndexSQL($definition, $tableName);
+ }
+ return $sqls;
+ }
+
+ /**
+ * Gets the SQL to alter an existing table.
+ *
+ * @param TableDiff $diff
+ * @return array
+ */
+ public function getAlterTableSQL(TableDiff $diff)
+ {
+ $sql = array();
+
+ $queryParts = array();
+ foreach ($diff->addedColumns AS $fieldName => $column) {
+ $queryParts[] = 'ADD COLUMN ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
+ }
+
+ foreach ($diff->removedColumns AS $column) {
+ $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this);
+ }
+
+ foreach ($diff->changedColumns AS $columnDiff) {
+ /* @var $columnDiff Doctrine\DBAL\Schema\ColumnDiff */
+ $column = $columnDiff->column;
+ $queryParts[] = 'ALTER ' . ($columnDiff->oldColumnName) . ' '
+ . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
+ }
+
+ foreach ($diff->renamedColumns AS $oldColumnName => $column) {
+ $queryParts[] = 'RENAME ' . $oldColumnName . ' TO ' . $column->getQuotedName($this);
+ }
+
+ if (count($queryParts) > 0) {
+ $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . implode(" ", $queryParts);
+ }
+
+ $sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySQL($diff));
+
+ if ($diff->newName !== false) {
+ $sql[] = 'RENAME TABLE TO ' . $diff->newName;
+ }
+
+ return $sql;
+ }
+
+ public function getDefaultValueDeclarationSQL($field)
+ {
+ if (isset($field['notnull']) && $field['notnull'] && !isset($field['default'])) {
+ if (in_array((string)$field['type'], array("Integer", "BigInteger", "SmallInteger"))) {
+ $field['default'] = 0;
+ } else if((string)$field['type'] == "DateTime") {
+ $field['default'] = "00-00-00 00:00:00";
+ } else if ((string)$field['type'] == "Date") {
+ $field['default'] = "00-00-00";
+ } else if((string)$field['type'] == "Time") {
+ $field['default'] = "00:00:00";
+ } else {
+ $field['default'] = '';
+ }
+ }
+
+ unset($field['default']); // @todo this needs fixing
+ if (isset($field['version']) && $field['version']) {
+ if ((string)$field['type'] != "DateTime") {
+ $field['default'] = "1";
+ }
+ }
+
+ return parent::getDefaultValueDeclarationSQL($field);
+ }
+
+ /**
+ * Get the insert sql for an empty insert statement
+ *
+ * @param string $tableName
+ * @param string $identifierColumnName
+ * @return string $sql
+ */
+ public function getEmptyIdentityInsertSQL($tableName, $identifierColumnName)
+ {
+ return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (DEFAULT)';
+ }
+
+ public function getCreateTemporaryTableSnippetSQL()
+ {
+ return "DECLARE GLOBAL TEMPORARY TABLE";
+ }
+
+ /**
+ * DB2 automatically moves temporary tables into the SESSION. schema.
+ *
+ * @param string $tableName
+ * @return string
+ */
+ public function getTemporaryTableName($tableName)
+ {
+ return "SESSION." . $tableName;
+ }
+
+ public function modifyLimitQuery($query, $limit, $offset = null)
+ {
+ if ($limit === null && $offset === null) {
+ return $query;
+ }
+
+ $limit = (int)$limit;
+ $offset = (int)(($offset)?:0);
+
+ // Todo OVER() needs ORDER BY data!
+ $sql = 'SELECT db22.* FROM (SELECT ROW_NUMBER() OVER() AS DC_ROWNUM, db21.* '.
+ 'FROM (' . $query . ') db21) db22 WHERE db22.DC_ROWNUM BETWEEN ' . ($offset+1) .' AND ' . ($offset+$limit);
+ return $sql;
+ }
+
+ /**
+ * returns the position of the first occurrence of substring $substr in string $str
+ *
+ * @param string $substr literal string to find
+ * @param string $str literal string
+ * @param int $pos position to start at, beginning of string by default
+ * @return integer
+ */
+ public function getLocateExpression($str, $substr, $startPos = false)
+ {
+ if ($startPos == false) {
+ return 'LOCATE(' . $substr . ', ' . $str . ')';
+ } else {
+ return 'LOCATE(' . $substr . ', ' . $str . ', '.$startPos.')';
+ }
+ }
+
+ /**
+ * return string to call a function to get a substring inside an SQL statement
+ *
+ * Note: Not SQL92, but common functionality.
+ *
+ * SQLite only supports the 2 parameter variant of this function
+ *
+ * @param string $value an sql string literal or column name/alias
+ * @param integer $from where to start the substring portion
+ * @param integer $len the substring portion length
+ * @return string
+ */
+ public function getSubstringExpression($value, $from, $len = null)
+ {
+ if ($len === null)
+ return 'SUBSTR(' . $value . ', ' . $from . ')';
+ else {
+ return 'SUBSTR(' . $value . ', ' . $from . ', ' . $len . ')';
+ }
+ }
+
+ public function supportsIdentityColumns()
+ {
+ return true;
+ }
+
+ public function prefersIdentityColumns()
+ {
+ return true;
+ }
+
+ /**
+ * Gets the character casing of a column in an SQL result set of this platform.
+ *
+ * DB2 returns all column names in SQL result sets in uppercase.
+ *
+ * @param string $column The column name for which to get the correct character casing.
+ * @return string The column name in the character casing used in SQL result sets.
+ */
+ public function getSQLResultCasing($column)
+ {
+ return strtoupper($column);
+ }
+
+ public function getForUpdateSQL()
+ {
+ return ' WITH RR USE AND KEEP UPDATE LOCKS';
+ }
+
+ public function getDummySelectSQL()
+ {
+ return 'SELECT 1 FROM sysibm.sysdummy1';
+ }
+
+ /**
+ * DB2 supports savepoints, but they work semantically different than on other vendor platforms.
+ *
+ * TODO: We have to investigate how to get DB2 up and running with savepoints.
+ *
+ * @return bool
+ */
+ public function supportsSavepoints()
+ {
+ return false;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Platforms;
+
+use Doctrine\DBAL\Schema\TableDiff;
+use Doctrine\DBAL\DBALException;
+use Doctrine\DBAL\Schema\Index, Doctrine\DBAL\Schema\Table;
+
+/**
+ * The MsSqlPlatform provides the behavior, features and SQL dialect of the
+ * MySQL database platform.
+ *
+ * @since 2.0
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @todo Rename: MsSQLPlatform
+ */
+class MsSqlPlatform extends AbstractPlatform
+{
+
+ /**
+ * Whether the platform prefers identity columns for ID generation.
+ * MsSql prefers "autoincrement" identity columns since sequences can only
+ * be emulated with a table.
+ *
+ * @return boolean
+ * @override
+ */
+ public function prefersIdentityColumns()
+ {
+ return true;
+ }
+
+ /**
+ * Whether the platform supports identity columns.
+ * MsSql supports this through AUTO_INCREMENT columns.
+ *
+ * @return boolean
+ * @override
+ */
+ public function supportsIdentityColumns()
+ {
+ return true;
+ }
+
+ /**
+ * Whether the platform supports releasing savepoints.
+ *
+ * @return boolean
+ */
+ public function supportsReleaseSavepoints()
+ {
+ return false;
+ }
+
+ /**
+ * create a new database
+ *
+ * @param string $name name of the database that should be created
+ * @return string
+ * @override
+ */
+ public function getCreateDatabaseSQL($name)
+ {
+ return 'CREATE DATABASE ' . $name;
+ }
+
+ /**
+ * drop an existing database
+ *
+ * @param string $name name of the database that should be dropped
+ * @return string
+ * @override
+ */
+ public function getDropDatabaseSQL($name)
+ {
+ return 'DROP DATABASE ' . $name;
+ }
+
+ /**
+ * @override
+ */
+ public function supportsCreateDropDatabase()
+ {
+ return false;
+ }
+
+ /**
+ * @override
+ */
+ public function getDropForeignKeySQL($foreignKey, $table)
+ {
+ if ($foreignKey instanceof \Doctrine\DBAL\Schema\ForeignKeyConstraint) {
+ $foreignKey = $foreignKey->getQuotedName($this);
+ }
+
+ if ($table instanceof \Doctrine\DBAL\Schema\Table) {
+ $table = $table->getQuotedName($this);
+ }
+
+ return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey;
+ }
+
+ /**
+ * @override
+ */
+ public function getDropIndexSQL($index, $table=null)
+ {
+ if ($index instanceof \Doctrine\DBAL\Schema\Index) {
+ $index_ = $index;
+ $index = $index->getQuotedName($this);
+ } else if (!is_string($index)) {
+ throw new \InvalidArgumentException('AbstractPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.');
+ }
+
+ if (!isset($table)) {
+ return 'DROP INDEX ' . $index;
+ } else {
+ if ($table instanceof \Doctrine\DBAL\Schema\Table) {
+ $table = $table->getQuotedName($this);
+ }
+
+ return "IF EXISTS (SELECT * FROM sysobjects WHERE name = '$index')
+ ALTER TABLE " . $table . " DROP CONSTRAINT " . $index . "
+ ELSE
+ DROP INDEX " . $index . " ON " . $table;
+ }
+ }
+
+ /**
+ * @override
+ */
+ protected function _getCreateTableSQL($tableName, array $columns, array $options = array())
+ {
+ // @todo does other code breaks because of this?
+ // foce primary keys to be not null
+ foreach ($columns as &$column) {
+ if (isset($column['primary']) && $column['primary']) {
+ $column['notnull'] = true;
+ }
+ }
+
+ $columnListSql = $this->getColumnDeclarationListSQL($columns);
+
+ if (isset($options['uniqueConstraints']) && !empty($options['uniqueConstraints'])) {
+ foreach ($options['uniqueConstraints'] as $name => $definition) {
+ $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
+ }
+ }
+
+ if (isset($options['primary']) && !empty($options['primary'])) {
+ $columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')';
+ }
+
+ $query = 'CREATE TABLE ' . $tableName . ' (' . $columnListSql;
+
+ $check = $this->getCheckDeclarationSQL($columns);
+ if (!empty($check)) {
+ $query .= ', ' . $check;
+ }
+ $query .= ')';
+
+ $sql[] = $query;
+
+ if (isset($options['indexes']) && !empty($options['indexes'])) {
+ foreach ($options['indexes'] AS $index) {
+ $sql[] = $this->getCreateIndexSQL($index, $tableName);
+ }
+ }
+
+ if (isset($options['foreignKeys'])) {
+ foreach ((array) $options['foreignKeys'] AS $definition) {
+ $sql[] = $this->getCreateForeignKeySQL($definition, $tableName);
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * @override
+ */
+ public function getUniqueConstraintDeclarationSQL($name, Index $index)
+ {
+ $constraint = parent::getUniqueConstraintDeclarationSQL($name, $index);
+
+ $constraint = $this->_appendUniqueConstraintDefinition($constraint, $index);
+
+ return $constraint;
+ }
+
+ /**
+ * @override
+ */
+ public function getCreateIndexSQL(Index $index, $table)
+ {
+ $constraint = parent::getCreateIndexSQL($index, $table);
+
+ if ($index->isUnique()) {
+ $constraint = $this->_appendUniqueConstraintDefinition($constraint, $index);
+ }
+
+ return $constraint;
+ }
+
+ /**
+ * Extend unique key constraint with required filters
+ *
+ * @param string $sql
+ * @param Index $index
+ * @return string
+ */
+ private function _appendUniqueConstraintDefinition($sql, Index $index)
+ {
+ $fields = array();
+ foreach ($index->getColumns() as $field => $definition) {
+ if (!is_array($definition)) {
+ $field = $definition;
+ }
+
+ $fields[] = $field . ' IS NOT NULL';
+ }
+
+ return $sql . ' WHERE ' . implode(' AND ', $fields);
+ }
+
+ /**
+ * @override
+ */
+ public function getAlterTableSQL(TableDiff $diff)
+ {
+ $queryParts = array();
+ if ($diff->newName !== false) {
+ $queryParts[] = 'RENAME TO ' . $diff->newName;
+ }
+
+ foreach ($diff->addedColumns AS $fieldName => $column) {
+ $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
+ }
+
+ foreach ($diff->removedColumns AS $column) {
+ $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this);
+ }
+
+ foreach ($diff->changedColumns AS $columnDiff) {
+ /* @var $columnDiff Doctrine\DBAL\Schema\ColumnDiff */
+ $column = $columnDiff->column;
+ $queryParts[] = 'CHANGE ' . ($columnDiff->oldColumnName) . ' '
+ . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
+ }
+
+ foreach ($diff->renamedColumns AS $oldColumnName => $column) {
+ $queryParts[] = 'CHANGE ' . $oldColumnName . ' '
+ . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
+ }
+
+ $sql = array();
+
+ foreach ($queryParts as $query) {
+ $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query;
+ }
+
+ $sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySQL($diff));
+
+ return $sql;
+ }
+
+ /**
+ * @override
+ */
+ public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName)
+ {
+ return 'INSERT INTO ' . $quotedTableName . ' DEFAULT VALUES';
+ }
+
+ /**
+ * @override
+ */
+ public function getShowDatabasesSQL()
+ {
+ return 'SHOW DATABASES';
+ }
+
+ /**
+ * @override
+ */
+ public function getListTablesSQL()
+ {
+ return "SELECT name FROM sysobjects WHERE type = 'U' ORDER BY name";
+ }
+
+ /**
+ * @override
+ */
+ public function getListTableColumnsSQL($table)
+ {
+ return 'exec sp_columns @table_name = ' . $table;
+ }
+
+ /**
+ * @override
+ */
+ public function getListTableForeignKeysSQL($table, $database = null)
+ {
+ return "SELECT f.name AS ForeignKey,
+ SCHEMA_NAME (f.SCHEMA_ID) AS SchemaName,
+ OBJECT_NAME (f.parent_object_id) AS TableName,
+ COL_NAME (fc.parent_object_id,fc.parent_column_id) AS ColumnName,
+ SCHEMA_NAME (o.SCHEMA_ID) ReferenceSchemaName,
+ OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName,
+ COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName,
+ f.delete_referential_action_desc,
+ f.update_referential_action_desc
+ FROM sys.foreign_keys AS f
+ INNER JOIN sys.foreign_key_columns AS fc
+ INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id
+ ON f.OBJECT_ID = fc.constraint_object_id
+ WHERE OBJECT_NAME (f.parent_object_id) = '" . $table . "'";
+ }
+
+ /**
+ * @override
+ */
+ public function getListTableIndexesSQL($table)
+ {
+ return "exec sp_helpindex '" . $table . "'";
+ }
+
+ /**
+ * @override
+ */
+ public function getCreateViewSQL($name, $sql)
+ {
+ return 'CREATE VIEW ' . $name . ' AS ' . $sql;
+ }
+
+ /**
+ * @override
+ */
+ public function getListViewsSQL($database)
+ {
+ return "SELECT name FROM sysobjects WHERE type = 'V' ORDER BY name";
+ }
+
+ /**
+ * @override
+ */
+ public function getDropViewSQL($name)
+ {
+ return 'DROP VIEW ' . $name;
+ }
+
+ /**
+ * Returns the regular expression operator.
+ *
+ * @return string
+ * @override
+ */
+ public function getRegexpExpression()
+ {
+ return 'RLIKE';
+ }
+
+ /**
+ * Returns global unique identifier
+ *
+ * @return string to get global unique identifier
+ * @override
+ */
+ public function getGuidExpression()
+ {
+ return 'UUID()';
+ }
+
+ /**
+ * @override
+ */
+ public function getLocateExpression($str, $substr, $startPos = false)
+ {
+ if ($startPos == false) {
+ return 'CHARINDEX(' . $substr . ', ' . $str . ')';
+ } else {
+ return 'CHARINDEX(' . $substr . ', ' . $str . ', ' . $startPos . ')';
+ }
+ }
+
+ /**
+ * @override
+ */
+ public function getModExpression($expression1, $expression2)
+ {
+ return $expression1 . ' % ' . $expression2;
+ }
+
+ /**
+ * @override
+ */
+ public function getTrimExpression($str, $pos = self::TRIM_UNSPECIFIED, $char = false)
+ {
+ $trimFn = '';
+
+ if (!$char) {
+ if ($pos == self::TRIM_LEADING) {
+ $trimFn = 'LTRIM';
+ } else if ($pos == self::TRIM_TRAILING) {
+ $trimFn = 'RTRIM';
+ } else {
+ return 'LTRIM(RTRIM(' . $str . '))';
+ }
+
+ return $trimFn . '(' . $str . ')';
+ } else {
+ /** Original query used to get those expressions
+ declare @c varchar(100) = 'xxxBarxxx', @trim_char char(1) = 'x';
+ declare @pat varchar(10) = '%[^' + @trim_char + ']%';
+ select @c as string
+ , @trim_char as trim_char
+ , stuff(@c, 1, patindex(@pat, @c) - 1, null) as trim_leading
+ , reverse(stuff(reverse(@c), 1, patindex(@pat, reverse(@c)) - 1, null)) as trim_trailing
+ , reverse(stuff(reverse(stuff(@c, 1, patindex(@pat, @c) - 1, null)), 1, patindex(@pat, reverse(stuff(@c, 1, patindex(@pat, @c) - 1, null))) - 1, null)) as trim_both;
+ */
+ $pattern = "'%[^' + $char + ']%'";
+
+ if ($pos == self::TRIM_LEADING) {
+ return 'stuff(' . $str . ', 1, patindex(' . $pattern .', ' . $str . ') - 1, null)';
+ } else if ($pos == self::TRIM_TRAILING) {
+ return 'reverse(stuff(reverse(' . $str . '), 1, patindex(' . $pattern .', reverse(' . $str . ')) - 1, null))';
+ } else {
+ return 'reverse(stuff(reverse(stuff(' . $str . ', 1, patindex(' . $pattern .', ' . $str . ') - 1, null)), 1, patindex(' . $pattern .', reverse(stuff(' . $str . ', 1, patindex(' . $pattern .', ' . $str . ') - 1, null))) - 1, null))';
+ }
+ }
+ }
+
+ /**
+ * @override
+ */
+ public function getConcatExpression()
+ {
+ $args = func_get_args();
+ return '(' . implode(' + ', $args) . ')';
+ }
+
+ public function getListDatabasesSQL()
+ {
+ return 'SELECT * FROM SYS.DATABASES';
+ }
+
+ /**
+ * @override
+ */
+ public function getSubstringExpression($value, $from, $len = null)
+ {
+ if (!is_null($len)) {
+ return 'SUBSTRING(' . $value . ', ' . $from . ', ' . $len . ')';
+ }
+ return 'SUBSTRING(' . $value . ', ' . $from . ', LEN(' . $value . ') - ' . $from . ' + 1)';
+ }
+
+ /**
+ * @override
+ */
+ public function getLengthExpression($column)
+ {
+ return 'LEN(' . $column . ')';
+ }
+
+ /**
+ * @override
+ */
+ public function getSetTransactionIsolationSQL($level)
+ {
+ return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level);
+ }
+
+ /**
+ * @override
+ */
+ public function getIntegerTypeDeclarationSQL(array $field)
+ {
+ return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
+ }
+
+ /**
+ * @override
+ */
+ public function getBigIntTypeDeclarationSQL(array $field)
+ {
+ return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
+ }
+
+ /**
+ * @override
+ */
+ public function getSmallIntTypeDeclarationSQL(array $field)
+ {
+ return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
+ }
+
+ /** @override */
+ public function getVarcharTypeDeclarationSQL(array $field)
+ {
+ if (!isset($field['length'])) {
+ if (array_key_exists('default', $field)) {
+ $field['length'] = $this->getVarcharDefaultLength();
+ } else {
+ $field['length'] = false;
+ }
+ }
+
+ $length = ($field['length'] <= $this->getVarcharMaxLength()) ? $field['length'] : false;
+ $fixed = (isset($field['fixed'])) ? $field['fixed'] : false;
+
+ return $fixed ? ($length ? 'NCHAR(' . $length . ')' : 'CHAR(255)') : ($length ? 'NVARCHAR(' . $length . ')' : 'NTEXT');
+ }
+
+ /** @override */
+ public function getClobTypeDeclarationSQL(array $field)
+ {
+ return 'TEXT';
+ }
+
+ /**
+ * @override
+ */
+ protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
+ {
+ $autoinc = '';
+ if (!empty($columnDef['autoincrement'])) {
+ $autoinc = ' IDENTITY';
+ }
+ $unsigned = (isset($columnDef['unsigned']) && $columnDef['unsigned']) ? ' UNSIGNED' : '';
+
+ return $unsigned . $autoinc;
+ }
+
+ /**
+ * @override
+ */
+ public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ // 6 - microseconds precision length
+ return 'DATETIME2(6)';
+ }
+
+ /**
+ * @override
+ */
+ public function getDateTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ return 'DATE';
+ }
+
+ /**
+ * @override
+ */
+ public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ return 'TIME(0)';
+ }
+
+ /**
+ * @override
+ */
+ public function getBooleanTypeDeclarationSQL(array $field)
+ {
+ return 'BIT';
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $query
+ * @param mixed $limit
+ * @param mixed $offset
+ * @link http://lists.bestpractical.com/pipermail/rt-devel/2005-June/007339.html
+ * @return string
+ */
+ public function modifyLimitQuery($query, $limit, $offset = null)
+ {
+ if ($limit > 0) {
+ $count = intval($limit);
+ $offset = intval($offset);
+
+ if ($offset < 0) {
+ throw new Doctrine_Connection_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ if ($offset == 0) {
+ $query = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . $count . ' ', $query);
+ } else {
+ $orderby = stristr($query, 'ORDER BY');
+
+ if (!$orderby) {
+ $over = 'ORDER BY (SELECT 0)';
+ } else {
+ $over = preg_replace('/\"[^,]*\".\"([^,]*)\"/i', '"inner_tbl"."$1"', $orderby);
+ }
+
+ // Remove ORDER BY clause from $query
+ $query = preg_replace('/\s+ORDER BY(.*)/', '', $query);
+
+ // Add ORDER BY clause as an argument for ROW_NUMBER()
+ $query = "SELECT ROW_NUMBER() OVER ($over) AS \"doctrine_rownum\", * FROM ($query) AS inner_tbl";
+
+ $start = $offset + 1;
+ $end = $offset + $count;
+
+ $query = "WITH outer_tbl AS ($query) SELECT * FROM outer_tbl WHERE \"doctrine_rownum\" BETWEEN $start AND $end";
+ }
+ }
+
+ return $query;
+ }
+
+ /**
+ * @override
+ */
+ public function convertBooleans($item)
+ {
+ if (is_array($item)) {
+ foreach ($item as $key => $value) {
+ if (is_bool($value) || is_numeric($item)) {
+ $item[$key] = ($value) ? 'TRUE' : 'FALSE';
+ }
+ }
+ } else {
+ if (is_bool($item) || is_numeric($item)) {
+ $item = ($item) ? 'TRUE' : 'FALSE';
+ }
+ }
+ return $item;
+ }
+
+ /**
+ * @override
+ */
+ public function getCreateTemporaryTableSnippetSQL()
+ {
+ return "CREATE TABLE";
+ }
+
+ /**
+ * @override
+ */
+ public function getTemporaryTableName($tableName)
+ {
+ return '#' . $tableName;
+ }
+
+ /**
+ * @override
+ */
+ public function getDateTimeFormatString()
+ {
+ return 'Y-m-d H:i:s.u';
+ }
+
+ /**
+ * @override
+ */
+ public function getDateTimeTzFormatString()
+ {
+ return $this->getDateTimeFormatString();
+ }
+
+ /**
+ * Get the platform name for this instance
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return 'mssql';
+ }
+
+ /**
+ * @override
+ */
+ protected function initializeDoctrineTypeMappings()
+ {
+ $this->doctrineTypeMapping = array(
+ 'bigint' => 'bigint',
+ 'numeric' => 'decimal',
+ 'bit' => 'boolean',
+ 'smallint' => 'smallint',
+ 'decimal' => 'decimal',
+ 'smallmoney' => 'integer',
+ 'int' => 'integer',
+ 'tinyint' => 'smallint',
+ 'money' => 'integer',
+ 'float' => 'float',
+ 'real' => 'float',
+ 'double' => 'float',
+ 'double precision' => 'float',
+ 'date' => 'date',
+ 'datetimeoffset' => 'datetimetz',
+ 'datetime2' => 'datetime',
+ 'smalldatetime' => 'datetime',
+ 'datetime' => 'datetime',
+ 'time' => 'time',
+ 'char' => 'string',
+ 'varchar' => 'string',
+ 'text' => 'text',
+ 'nchar' => 'string',
+ 'nvarchar' => 'string',
+ 'ntext' => 'text',
+ 'binary' => 'text',
+ 'varbinary' => 'text',
+ 'image' => 'text',
+ );
+ }
+
+ /**
+ * Generate SQL to create a new savepoint
+ *
+ * @param string $savepoint
+ * @return string
+ */
+ public function createSavePoint($savepoint)
+ {
+ return 'SAVE TRANSACTION ' . $savepoint;
+ }
+
+ /**
+ * Generate SQL to release a savepoint
+ *
+ * @param string $savepoint
+ * @return string
+ */
+ public function releaseSavePoint($savepoint)
+ {
+ return '';
+ }
+
+ /**
+ * Generate SQL to rollback a savepoint
+ *
+ * @param string $savepoint
+ * @return string
+ */
+ public function rollbackSavePoint($savepoint)
+ {
+ return 'ROLLBACK TRANSACTION ' . $savepoint;
+ }
+
+ /**
+ * @override
+ */
+ public function appendLockHint($fromClause, $lockMode)
+ {
+ // @todo coorect
+ if ($lockMode == \Doctrine\DBAL\LockMode::PESSIMISTIC_READ) {
+ return $fromClause . ' WITH (tablockx)';
+ } else if ($lockMode == \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE) {
+ return $fromClause . ' WITH (tablockx)';
+ }
+ else {
+ return $fromClause;
+ }
+ }
+
+ /**
+ * @override
+ */
+ public function getForUpdateSQL()
+ {
+ return ' ';
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Platforms;
+
+use Doctrine\DBAL\DBALException,
+ Doctrine\DBAL\Schema\TableDiff;
+
+/**
+ * The MySqlPlatform provides the behavior, features and SQL dialect of the
+ * MySQL database platform. This platform represents a MySQL 5.0 or greater platform that
+ * uses the InnoDB storage engine.
+ *
+ * @since 2.0
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @todo Rename: MySQLPlatform
+ */
+class MySqlPlatform extends AbstractPlatform
+{
+ /**
+ * Gets the character used for identifier quoting.
+ *
+ * @return string
+ * @override
+ */
+ public function getIdentifierQuoteCharacter()
+ {
+ return '`';
+ }
+
+ /**
+ * Returns the regular expression operator.
+ *
+ * @return string
+ * @override
+ */
+ public function getRegexpExpression()
+ {
+ return 'RLIKE';
+ }
+
+ /**
+ * Returns global unique identifier
+ *
+ * @return string to get global unique identifier
+ * @override
+ */
+ public function getGuidExpression()
+ {
+ return 'UUID()';
+ }
+
+ /**
+ * returns the position of the first occurrence of substring $substr in string $str
+ *
+ * @param string $substr literal string to find
+ * @param string $str literal string
+ * @param int $pos position to start at, beginning of string by default
+ * @return integer
+ */
+ public function getLocateExpression($str, $substr, $startPos = false)
+ {
+ if ($startPos == false) {
+ return 'LOCATE(' . $substr . ', ' . $str . ')';
+ } else {
+ return 'LOCATE(' . $substr . ', ' . $str . ', '.$startPos.')';
+ }
+ }
+
+ /**
+ * Returns a series of strings concatinated
+ *
+ * concat() accepts an arbitrary number of parameters. Each parameter
+ * must contain an expression or an array with expressions.
+ *
+ * @param string|array(string) strings that will be concatinated.
+ * @override
+ */
+ public function getConcatExpression()
+ {
+ $args = func_get_args();
+ return 'CONCAT(' . join(', ', (array) $args) . ')';
+ }
+
+ public function getListDatabasesSQL()
+ {
+ return 'SHOW DATABASES';
+ }
+
+ public function getListTableConstraintsSQL($table)
+ {
+ return 'SHOW INDEX FROM ' . $table;
+ }
+
+ public function getListTableIndexesSQL($table)
+ {
+ return 'SHOW INDEX FROM ' . $table;
+ }
+
+ public function getListViewsSQL($database)
+ {
+ return "SELECT * FROM information_schema.VIEWS WHERE TABLE_SCHEMA = '".$database."'";
+ }
+
+ public function getListTableForeignKeysSQL($table, $database = null)
+ {
+ $sql = "SELECT DISTINCT k.`CONSTRAINT_NAME`, k.`COLUMN_NAME`, k.`REFERENCED_TABLE_NAME`, ".
+ "k.`REFERENCED_COLUMN_NAME` /*!50116 , c.update_rule, c.delete_rule */ ".
+ "FROM information_schema.key_column_usage k /*!50116 ".
+ "INNER JOIN information_schema.referential_constraints c ON ".
+ " c.constraint_name = k.constraint_name AND ".
+ " c.table_name = '$table' */ WHERE k.table_name = '$table'";
+
+ if ($database) {
+ $sql .= " AND k.table_schema = '$database' /*!50116 AND c.constraint_schema = '$database' */";
+ }
+
+ $sql .= " AND k.`REFERENCED_COLUMN_NAME` is not NULL";
+
+ return $sql;
+ }
+
+ public function getCreateViewSQL($name, $sql)
+ {
+ return 'CREATE VIEW ' . $name . ' AS ' . $sql;
+ }
+
+ public function getDropViewSQL($name)
+ {
+ return 'DROP VIEW '. $name;
+ }
+
+ /**
+ * Gets the SQL snippet used to declare a VARCHAR column on the MySql platform.
+ *
+ * @params array $field
+ */
+ public function getVarcharTypeDeclarationSQL(array $field)
+ {
+ if ( ! isset($field['length'])) {
+ if (array_key_exists('default', $field)) {
+ $field['length'] = $this->getVarcharDefaultLength();
+ } else {
+ $field['length'] = false;
+ }
+ }
+
+ $length = ($field['length'] <= $this->getVarcharMaxLength()) ? $field['length'] : false;
+ $fixed = (isset($field['fixed'])) ? $field['fixed'] : false;
+
+ return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
+ : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)');
+ }
+
+ /** @override */
+ public function getClobTypeDeclarationSQL(array $field)
+ {
+ if ( ! empty($field['length']) && is_numeric($field['length'])) {
+ $length = $field['length'];
+ if ($length <= 255) {
+ return 'TINYTEXT';
+ } else if ($length <= 65532) {
+ return 'TEXT';
+ } else if ($length <= 16777215) {
+ return 'MEDIUMTEXT';
+ }
+ }
+ return 'LONGTEXT';
+ }
+
+ /**
+ * @override
+ */
+ public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] == true) {
+ return 'TIMESTAMP';
+ } else {
+ return 'DATETIME';
+ }
+ }
+
+ /**
+ * @override
+ */
+ public function getDateTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ return 'DATE';
+ }
+
+ /**
+ * @override
+ */
+ public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ return 'TIME';
+ }
+
+ /**
+ * @override
+ */
+ public function getBooleanTypeDeclarationSQL(array $field)
+ {
+ return 'TINYINT(1)';
+ }
+
+ /**
+ * Obtain DBMS specific SQL code portion needed to set the COLLATION
+ * of a field declaration to be used in statements like CREATE TABLE.
+ *
+ * @param string $collation name of the collation
+ * @return string DBMS specific SQL code portion needed to set the COLLATION
+ * of a field declaration.
+ */
+ public function getCollationFieldDeclaration($collation)
+ {
+ return 'COLLATE ' . $collation;
+ }
+
+ /**
+ * Whether the platform prefers identity columns for ID generation.
+ * MySql prefers "autoincrement" identity columns since sequences can only
+ * be emulated with a table.
+ *
+ * @return boolean
+ * @override
+ */
+ public function prefersIdentityColumns()
+ {
+ return true;
+ }
+
+ /**
+ * Whether the platform supports identity columns.
+ * MySql supports this through AUTO_INCREMENT columns.
+ *
+ * @return boolean
+ * @override
+ */
+ public function supportsIdentityColumns()
+ {
+ return true;
+ }
+
+ public function getShowDatabasesSQL()
+ {
+ return 'SHOW DATABASES';
+ }
+
+ public function getListTablesSQL()
+ {
+ return 'SHOW FULL TABLES WHERE Table_type = "BASE TABLE"';
+ }
+
+ public function getListTableColumnsSQL($table)
+ {
+ return 'DESCRIBE ' . $table;
+ }
+
+ /**
+ * create a new database
+ *
+ * @param string $name name of the database that should be created
+ * @return string
+ * @override
+ */
+ public function getCreateDatabaseSQL($name)
+ {
+ return 'CREATE DATABASE ' . $name;
+ }
+
+ /**
+ * drop an existing database
+ *
+ * @param string $name name of the database that should be dropped
+ * @return string
+ * @override
+ */
+ public function getDropDatabaseSQL($name)
+ {
+ return 'DROP DATABASE ' . $name;
+ }
+
+ /**
+ * create a new table
+ *
+ * @param string $tableName Name of the database that should be created
+ * @param array $columns Associative array that contains the definition of each field of the new table
+ * The indexes of the array entries are the names of the fields of the table an
+ * the array entry values are associative arrays like those that are meant to be
+ * passed with the field definitions to get[Type]Declaration() functions.
+ * array(
+ * 'id' => array(
+ * 'type' => 'integer',
+ * 'unsigned' => 1
+ * 'notnull' => 1
+ * 'default' => 0
+ * ),
+ * 'name' => array(
+ * 'type' => 'text',
+ * 'length' => 12
+ * ),
+ * 'password' => array(
+ * 'type' => 'text',
+ * 'length' => 12
+ * )
+ * );
+ * @param array $options An associative array of table options:
+ * array(
+ * 'comment' => 'Foo',
+ * 'charset' => 'utf8',
+ * 'collate' => 'utf8_unicode_ci',
+ * 'type' => 'innodb',
+ * );
+ *
+ * @return void
+ * @override
+ */
+ protected function _getCreateTableSQL($tableName, array $columns, array $options = array())
+ {
+ $queryFields = $this->getColumnDeclarationListSQL($columns);
+
+ if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
+ foreach ($options['uniqueConstraints'] as $index => $definition) {
+ $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($index, $definition);
+ }
+ }
+
+ // add all indexes
+ if (isset($options['indexes']) && ! empty($options['indexes'])) {
+ foreach($options['indexes'] as $index => $definition) {
+ $queryFields .= ', ' . $this->getIndexDeclarationSQL($index, $definition);
+ }
+ }
+
+ // attach all primary keys
+ if (isset($options['primary']) && ! empty($options['primary'])) {
+ $keyColumns = array_unique(array_values($options['primary']));
+ $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
+ }
+
+ $query = 'CREATE ';
+ if (!empty($options['temporary'])) {
+ $query .= 'TEMPORARY ';
+ }
+ $query.= 'TABLE ' . $tableName . ' (' . $queryFields . ')';
+
+ $optionStrings = array();
+
+ if (isset($options['comment'])) {
+ $optionStrings['comment'] = 'COMMENT = ' . $this->quote($options['comment'], 'text');
+ }
+ if (isset($options['charset'])) {
+ $optionStrings['charset'] = 'DEFAULT CHARACTER SET ' . $options['charset'];
+ if (isset($options['collate'])) {
+ $optionStrings['charset'] .= ' COLLATE ' . $options['collate'];
+ }
+ }
+
+ // get the type of the table
+ if (isset($options['engine'])) {
+ $optionStrings[] = 'ENGINE = ' . $options['engine'];
+ } else {
+ // default to innodb
+ $optionStrings[] = 'ENGINE = InnoDB';
+ }
+
+ if ( ! empty($optionStrings)) {
+ $query.= ' '.implode(' ', $optionStrings);
+ }
+ $sql[] = $query;
+
+ if (isset($options['foreignKeys'])) {
+ foreach ((array) $options['foreignKeys'] as $definition) {
+ $sql[] = $this->getCreateForeignKeySQL($definition, $tableName);
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Gets the SQL to alter an existing table.
+ *
+ * @param TableDiff $diff
+ * @return array
+ */
+ public function getAlterTableSQL(TableDiff $diff)
+ {
+ $queryParts = array();
+ if ($diff->newName !== false) {
+ $queryParts[] = 'RENAME TO ' . $diff->newName;
+ }
+
+ foreach ($diff->addedColumns AS $fieldName => $column) {
+ $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
+ }
+
+ foreach ($diff->removedColumns AS $column) {
+ $queryParts[] = 'DROP ' . $column->getQuotedName($this);
+ }
+
+ foreach ($diff->changedColumns AS $columnDiff) {
+ /* @var $columnDiff Doctrine\DBAL\Schema\ColumnDiff */
+ $column = $columnDiff->column;
+ $queryParts[] = 'CHANGE ' . ($columnDiff->oldColumnName) . ' '
+ . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
+ }
+
+ foreach ($diff->renamedColumns AS $oldColumnName => $column) {
+ $queryParts[] = 'CHANGE ' . $oldColumnName . ' '
+ . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
+ }
+
+ $sql = array();
+ if (count($queryParts) > 0) {
+ $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . implode(", ", $queryParts);
+ }
+ $sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySQL($diff));
+ return $sql;
+ }
+
+ /**
+ * Obtain DBMS specific SQL code portion needed to declare an integer type
+ * field to be used in statements like CREATE TABLE.
+ *
+ * @param string $name name the field to be declared.
+ * @param string $field associative array with the name of the properties
+ * of the field being declared as array indexes.
+ * Currently, the types of supported field
+ * properties are as follows:
+ *
+ * unsigned
+ * Boolean flag that indicates whether the field
+ * should be declared as unsigned integer if
+ * possible.
+ *
+ * default
+ * Integer value to be used as default for this
+ * field.
+ *
+ * notnull
+ * Boolean flag that indicates whether this field is
+ * constrained to not be set to null.
+ * @return string DBMS specific SQL code portion that should be used to
+ * declare the specified field.
+ * @override
+ */
+ public function getIntegerTypeDeclarationSQL(array $field)
+ {
+ return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
+ }
+
+ /** @override */
+ public function getBigIntTypeDeclarationSQL(array $field)
+ {
+ return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
+ }
+
+ /** @override */
+ public function getSmallIntTypeDeclarationSQL(array $field)
+ {
+ return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
+ }
+
+ /** @override */
+ protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
+ {
+ $autoinc = '';
+ if ( ! empty($columnDef['autoincrement'])) {
+ $autoinc = ' AUTO_INCREMENT';
+ }
+ $unsigned = (isset($columnDef['unsigned']) && $columnDef['unsigned']) ? ' UNSIGNED' : '';
+
+ return $unsigned . $autoinc;
+ }
+
+ /**
+ * Return the FOREIGN KEY query section dealing with non-standard options
+ * as MATCH, INITIALLY DEFERRED, ON UPDATE, ...
+ *
+ * @param ForeignKeyConstraint $foreignKey
+ * @return string
+ * @override
+ */
+ public function getAdvancedForeignKeyOptionsSQL(\Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey)
+ {
+ $query = '';
+ if ($foreignKey->hasOption('match')) {
+ $query .= ' MATCH ' . $foreignKey->getOption('match');
+ }
+ $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
+ return $query;
+ }
+
+ /**
+ * Gets the SQL to drop an index of a table.
+ *
+ * @param Index $index name of the index to be dropped
+ * @param string|Table $table name of table that should be used in method
+ * @override
+ */
+ public function getDropIndexSQL($index, $table=null)
+ {
+ if($index instanceof \Doctrine\DBAL\Schema\Index) {
+ $index = $index->getQuotedName($this);
+ } else if(!is_string($index)) {
+ throw new \InvalidArgumentException('MysqlPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.');
+ }
+
+ if($table instanceof \Doctrine\DBAL\Schema\Table) {
+ $table = $table->getQuotedName($this);
+ } else if(!is_string($table)) {
+ throw new \InvalidArgumentException('MysqlPlatform::getDropIndexSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.');
+ }
+
+ return 'DROP INDEX ' . $index . ' ON ' . $table;
+ }
+
+ /**
+ * Gets the SQL to drop a table.
+ *
+ * @param string $table The name of table to drop.
+ * @override
+ */
+ public function getDropTableSQL($table)
+ {
+ if ($table instanceof \Doctrine\DBAL\Schema\Table) {
+ $table = $table->getQuotedName($this);
+ } else if(!is_string($table)) {
+ throw new \InvalidArgumentException('MysqlPlatform::getDropTableSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.');
+ }
+
+ return 'DROP TABLE ' . $table;
+ }
+
+ public function getSetTransactionIsolationSQL($level)
+ {
+ return 'SET SESSION TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level);
+ }
+
+ /**
+ * Get the platform name for this instance.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return 'mysql';
+ }
+
+ public function getReadLockSQL()
+ {
+ return 'LOCK IN SHARE MODE';
+ }
+
+ protected function initializeDoctrineTypeMappings()
+ {
+ $this->doctrineTypeMapping = array(
+ 'tinyint' => 'boolean',
+ 'smallint' => 'smallint',
+ 'mediumint' => 'integer',
+ 'int' => 'integer',
+ 'integer' => 'integer',
+ 'bigint' => 'bigint',
+ 'tinytext' => 'text',
+ 'mediumtext' => 'text',
+ 'longtext' => 'text',
+ 'text' => 'text',
+ 'varchar' => 'string',
+ 'string' => 'string',
+ 'char' => 'string',
+ 'date' => 'date',
+ 'datetime' => 'datetime',
+ 'timestamp' => 'datetime',
+ 'time' => 'time',
+ 'float' => 'float',
+ 'double' => 'float',
+ 'real' => 'float',
+ 'decimal' => 'decimal',
+ 'numeric' => 'decimal',
+ 'year' => 'date',
+ );
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Platforms;
+
+use Doctrine\DBAL\Schema\TableDiff;
+
+/**
+ * OraclePlatform.
+ *
+ * @since 2.0
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class OraclePlatform extends AbstractPlatform
+{
+ /**
+ * return string to call a function to get a substring inside an SQL statement
+ *
+ * Note: Not SQL92, but common functionality.
+ *
+ * @param string $value an sql string literal or column name/alias
+ * @param integer $position where to start the substring portion
+ * @param integer $length the substring portion length
+ * @return string SQL substring function with given parameters
+ * @override
+ */
+ public function getSubstringExpression($value, $position, $length = null)
+ {
+ if ($length !== null) {
+ return "SUBSTR($value, $position, $length)";
+ }
+
+ return "SUBSTR($value, $position)";
+ }
+
+ /**
+ * Return string to call a variable with the current timestamp inside an SQL statement
+ * There are three special variables for current date and time:
+ * - CURRENT_TIMESTAMP (date and time, TIMESTAMP type)
+ * - CURRENT_DATE (date, DATE type)
+ * - CURRENT_TIME (time, TIME type)
+ *
+ * @return string to call a variable with the current timestamp
+ * @override
+ */
+ public function getNowExpression($type = 'timestamp')
+ {
+ switch ($type) {
+ case 'date':
+ case 'time':
+ case 'timestamp':
+ default:
+ return 'TO_CHAR(CURRENT_TIMESTAMP, \'YYYY-MM-DD HH24:MI:SS\')';
+ }
+ }
+
+ /**
+ * returns the position of the first occurrence of substring $substr in string $str
+ *
+ * @param string $substr literal string to find
+ * @param string $str literal string
+ * @param int $pos position to start at, beginning of string by default
+ * @return integer
+ */
+ public function getLocateExpression($str, $substr, $startPos = false)
+ {
+ if ($startPos == false) {
+ return 'INSTR('.$str.', '.$substr.')';
+ } else {
+ return 'INSTR('.$str.', '.$substr.', '.$startPos.')';
+ }
+ }
+
+ /**
+ * Returns global unique identifier
+ *
+ * @return string to get global unique identifier
+ * @override
+ */
+ public function getGuidExpression()
+ {
+ return 'SYS_GUID()';
+ }
+
+ /**
+ * Gets the SQL used to create a sequence that starts with a given value
+ * and increments by the given allocation size.
+ *
+ * Need to specifiy minvalue, since start with is hidden in the system and MINVALUE <= START WITH.
+ * Therefore we can use MINVALUE to be able to get a hint what START WITH was for later introspection
+ * in {@see listSequences()}
+ *
+ * @param \Doctrine\DBAL\Schema\Sequence $sequence
+ * @return string
+ */
+ public function getCreateSequenceSQL(\Doctrine\DBAL\Schema\Sequence $sequence)
+ {
+ return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) .
+ ' START WITH ' . $sequence->getInitialValue() .
+ ' MINVALUE ' . $sequence->getInitialValue() .
+ ' INCREMENT BY ' . $sequence->getAllocationSize();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $sequenceName
+ * @override
+ */
+ public function getSequenceNextValSQL($sequenceName)
+ {
+ return 'SELECT ' . $sequenceName . '.nextval FROM DUAL';
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param integer $level
+ * @override
+ */
+ public function getSetTransactionIsolationSQL($level)
+ {
+ return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level);
+ }
+
+ protected function _getTransactionIsolationLevelSQL($level)
+ {
+ switch ($level) {
+ case \Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED:
+ return 'READ UNCOMMITTED';
+ case \Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED:
+ return 'READ COMMITTED';
+ case \Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ:
+ case \Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE:
+ return 'SERIALIZABLE';
+ default:
+ return parent::_getTransactionIsolationLevelSQL($level);
+ }
+ }
+
+ /**
+ * @override
+ */
+ public function getBooleanTypeDeclarationSQL(array $field)
+ {
+ return 'NUMBER(1)';
+ }
+
+ /**
+ * @override
+ */
+ public function getIntegerTypeDeclarationSQL(array $field)
+ {
+ return 'NUMBER(10)';
+ }
+
+ /**
+ * @override
+ */
+ public function getBigIntTypeDeclarationSQL(array $field)
+ {
+ return 'NUMBER(20)';
+ }
+
+ /**
+ * @override
+ */
+ public function getSmallIntTypeDeclarationSQL(array $field)
+ {
+ return 'NUMBER(5)';
+ }
+
+ /**
+ * @override
+ */
+ public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ return 'TIMESTAMP(0)';
+ }
+
+ /**
+ * @override
+ */
+ public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ return 'TIMESTAMP(0) WITH TIME ZONE';
+ }
+
+ /**
+ * @override
+ */
+ public function getDateTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ return 'DATE';
+ }
+
+ /**
+ * @override
+ */
+ public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ return 'DATE';
+ }
+
+ /**
+ * @override
+ */
+ protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
+ {
+ return '';
+ }
+
+ /**
+ * Gets the SQL snippet used to declare a VARCHAR column on the Oracle platform.
+ *
+ * @params array $field
+ * @override
+ */
+ public function getVarcharTypeDeclarationSQL(array $field)
+ {
+ if ( ! isset($field['length'])) {
+ if (array_key_exists('default', $field)) {
+ $field['length'] = $this->getVarcharDefaultLength();
+ } else {
+ $field['length'] = false;
+ }
+ }
+
+ $length = ($field['length'] <= $this->getVarcharMaxLength()) ? $field['length'] : false;
+ $fixed = (isset($field['fixed'])) ? $field['fixed'] : false;
+
+ return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(2000)')
+ : ($length ? 'VARCHAR2(' . $length . ')' : 'VARCHAR2(4000)');
+ }
+
+ /** @override */
+ public function getClobTypeDeclarationSQL(array $field)
+ {
+ return 'CLOB';
+ }
+
+ public function getListDatabasesSQL()
+ {
+ return 'SELECT username FROM all_users';
+ }
+
+ public function getListSequencesSQL($database)
+ {
+ return "SELECT sequence_name, min_value, increment_by FROM sys.all_sequences ".
+ "WHERE SEQUENCE_OWNER = '".strtoupper($database)."'";
+ }
+
+ /**
+ *
+ * @param string $table
+ * @param array $columns
+ * @param array $options
+ * @return array
+ */
+ protected function _getCreateTableSQL($table, array $columns, array $options = array())
+ {
+ $indexes = isset($options['indexes']) ? $options['indexes'] : array();
+ $options['indexes'] = array();
+ $sql = parent::_getCreateTableSQL($table, $columns, $options);
+
+ foreach ($columns as $name => $column) {
+ if (isset($column['sequence'])) {
+ $sql[] = $this->getCreateSequenceSQL($column['sequence'], 1);
+ }
+
+ if (isset($column['autoincrement']) && $column['autoincrement'] ||
+ (isset($column['autoinc']) && $column['autoinc'])) {
+ $sql = array_merge($sql, $this->getCreateAutoincrementSql($name, $table));
+ }
+ }
+
+ if (isset($indexes) && ! empty($indexes)) {
+ foreach ($indexes as $indexName => $index) {
+ $sql[] = $this->getCreateIndexSQL($index, $table);
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * @license New BSD License
+ * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaOracleReader.html
+ * @param string $table
+ * @return string
+ */
+ public function getListTableIndexesSQL($table)
+ {
+ $table = strtoupper($table);
+
+ return "SELECT uind.index_name AS name, " .
+ " uind.index_type AS type, " .
+ " decode( uind.uniqueness, 'NONUNIQUE', 0, 'UNIQUE', 1 ) AS is_unique, " .
+ " uind_col.column_name AS column_name, " .
+ " uind_col.column_position AS column_pos, " .
+ " (SELECT ucon.constraint_type FROM user_constraints ucon WHERE ucon.constraint_name = uind.index_name) AS is_primary ".
+ "FROM user_indexes uind, user_ind_columns uind_col " .
+ "WHERE uind.index_name = uind_col.index_name AND uind_col.table_name = '$table' ORDER BY uind_col.column_position ASC";
+ }
+
+ public function getListTablesSQL()
+ {
+ return 'SELECT * FROM sys.user_tables';
+ }
+
+ public function getListViewsSQL($database)
+ {
+ return 'SELECT view_name, text FROM sys.user_views';
+ }
+
+ public function getCreateViewSQL($name, $sql)
+ {
+ return 'CREATE VIEW ' . $name . ' AS ' . $sql;
+ }
+
+ public function getDropViewSQL($name)
+ {
+ return 'DROP VIEW '. $name;
+ }
+
+ public function getCreateAutoincrementSql($name, $table, $start = 1)
+ {
+ $table = strtoupper($table);
+ $sql = array();
+
+ $indexName = $table . '_AI_PK';
+ $definition = array(
+ 'primary' => true,
+ 'columns' => array($name => true),
+ );
+
+ $idx = new \Doctrine\DBAL\Schema\Index($indexName, array($name), true, true);
+
+ $sql[] = 'DECLARE
+ constraints_Count NUMBER;
+BEGIN
+ SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count FROM USER_CONSTRAINTS WHERE TABLE_NAME = \''.$table.'\' AND CONSTRAINT_TYPE = \'P\';
+ IF constraints_Count = 0 OR constraints_Count = \'\' THEN
+ EXECUTE IMMEDIATE \''.$this->getCreateConstraintSQL($idx, $table).'\';
+ END IF;
+END;';
+
+ $sequenceName = $table . '_SEQ';
+ $sequence = new \Doctrine\DBAL\Schema\Sequence($sequenceName, $start);
+ $sql[] = $this->getCreateSequenceSQL($sequence);
+
+ $triggerName = $table . '_AI_PK';
+ $sql[] = 'CREATE TRIGGER ' . $triggerName . '
+ BEFORE INSERT
+ ON ' . $table . '
+ FOR EACH ROW
+DECLARE
+ last_Sequence NUMBER;
+ last_InsertID NUMBER;
+BEGIN
+ SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $name . ' FROM DUAL;
+ IF (:NEW.' . $name . ' IS NULL OR :NEW.'.$name.' = 0) THEN
+ SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $name . ' FROM DUAL;
+ ELSE
+ SELECT NVL(Last_Number, 0) INTO last_Sequence
+ FROM User_Sequences
+ WHERE Sequence_Name = \'' . $sequenceName . '\';
+ SELECT :NEW.' . $name . ' INTO last_InsertID FROM DUAL;
+ WHILE (last_InsertID > last_Sequence) LOOP
+ SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL;
+ END LOOP;
+ END IF;
+END;';
+ return $sql;
+ }
+
+ public function getDropAutoincrementSql($table)
+ {
+ $table = strtoupper($table);
+ $trigger = $table . '_AI_PK';
+
+ if ($trigger) {
+ $sql[] = 'DROP TRIGGER ' . $trigger;
+ $sql[] = $this->getDropSequenceSQL($table.'_SEQ');
+
+ $indexName = $table . '_AI_PK';
+ $sql[] = $this->getDropConstraintSQL($indexName, $table);
+ }
+
+ return $sql;
+ }
+
+ public function getListTableForeignKeysSQL($table)
+ {
+ $table = strtoupper($table);
+
+ return "SELECT alc.constraint_name,
+ alc.DELETE_RULE,
+ alc.search_condition,
+ cols.column_name \"local_column\",
+ cols.position,
+ r_alc.table_name \"references_table\",
+ r_cols.column_name \"foreign_column\"
+ FROM all_cons_columns cols
+LEFT JOIN all_constraints alc
+ ON alc.constraint_name = cols.constraint_name
+ AND alc.owner = cols.owner
+LEFT JOIN all_constraints r_alc
+ ON alc.r_constraint_name = r_alc.constraint_name
+ AND alc.r_owner = r_alc.owner
+LEFT JOIN all_cons_columns r_cols
+ ON r_alc.constraint_name = r_cols.constraint_name
+ AND r_alc.owner = r_cols.owner
+ AND cols.position = r_cols.position
+ WHERE alc.constraint_name = cols.constraint_name
+ AND alc.constraint_type = 'R'
+ AND alc.table_name = '".$table."'";
+ }
+
+ public function getListTableConstraintsSQL($table)
+ {
+ $table = strtoupper($table);
+ return 'SELECT * FROM user_constraints WHERE table_name = \'' . $table . '\'';
+ }
+
+ public function getListTableColumnsSQL($table)
+ {
+ $table = strtoupper($table);
+ return "SELECT * FROM all_tab_columns WHERE table_name = '" . $table . "' ORDER BY column_name";
+ }
+
+ /**
+ *
+ * @param \Doctrine\DBAL\Schema\Sequence $sequence
+ * @return string
+ */
+ public function getDropSequenceSQL($sequence)
+ {
+ if ($sequence instanceof \Doctrine\DBAL\Schema\Sequence) {
+ $sequence = $sequence->getQuotedName($this);
+ }
+
+ return 'DROP SEQUENCE ' . $sequence;
+ }
+
+ /**
+ * @param ForeignKeyConstraint|string $foreignKey
+ * @param Table|string $table
+ * @return string
+ */
+ public function getDropForeignKeySQL($foreignKey, $table)
+ {
+ if ($foreignKey instanceof \Doctrine\DBAL\Schema\ForeignKeyConstraint) {
+ $foreignKey = $foreignKey->getQuotedName($this);
+ }
+
+ if ($table instanceof \Doctrine\DBAL\Schema\Table) {
+ $table = $table->getQuotedName($this);
+ }
+
+ return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey;
+ }
+
+ public function getDropDatabaseSQL($database)
+ {
+ return 'DROP USER ' . $database . ' CASCADE';
+ }
+
+ /**
+ * Gets the sql statements for altering an existing table.
+ *
+ * The method returns an array of sql statements, since some platforms need several statements.
+ *
+ * @param string $diff->name name of the table that is intended to be changed.
+ * @param array $changes associative array that contains the details of each type *
+ * @param boolean $check indicates whether the function should just check if the DBMS driver
+ * can perform the requested table alterations if the value is true or
+ * actually perform them otherwise.
+ * @return array
+ */
+ public function getAlterTableSQL(TableDiff $diff)
+ {
+ $sql = array();
+
+ $fields = array();
+ foreach ($diff->addedColumns AS $column) {
+ $fields[] = $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
+ }
+ if (count($fields)) {
+ $sql[] = 'ALTER TABLE ' . $diff->name . ' ADD (' . implode(', ', $fields) . ')';
+ }
+
+ $fields = array();
+ foreach ($diff->changedColumns AS $columnDiff) {
+ $column = $columnDiff->column;
+ $fields[] = $column->getQuotedName($this). ' ' . $this->getColumnDeclarationSQL('', $column->toArray());
+ }
+ if (count($fields)) {
+ $sql[] = 'ALTER TABLE ' . $diff->name . ' MODIFY (' . implode(', ', $fields) . ')';
+ }
+
+ foreach ($diff->renamedColumns AS $oldColumnName => $column) {
+ $sql[] = 'ALTER TABLE ' . $diff->name . ' RENAME COLUMN ' . $oldColumnName .' TO ' . $column->getQuotedName($this);
+ }
+
+ $fields = array();
+ foreach ($diff->removedColumns AS $column) {
+ $fields[] = $column->getQuotedName($this);
+ }
+ if (count($fields)) {
+ $sql[] = 'ALTER TABLE ' . $diff->name . ' DROP COLUMN ' . implode(', ', $fields);
+ }
+
+ if ($diff->newName !== false) {
+ $sql[] = 'ALTER TABLE ' . $diff->name . ' RENAME TO ' . $diff->newName;
+ }
+
+ $sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySQL($diff));
+
+ return $sql;
+ }
+
+ /**
+ * Whether the platform prefers sequences for ID generation.
+ *
+ * @return boolean
+ */
+ public function prefersSequences()
+ {
+ return true;
+ }
+
+ /**
+ * Get the platform name for this instance
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return 'oracle';
+ }
+
+ /**
+ * Adds an driver-specific LIMIT clause to the query
+ *
+ * @param string $query query to modify
+ * @param integer $limit limit the number of rows
+ * @param integer $offset start reading from given offset
+ * @return string the modified query
+ */
+ public function modifyLimitQuery($query, $limit, $offset = null)
+ {
+ $limit = (int) $limit;
+ $offset = (int) $offset;
+ if (preg_match('/^\s*SELECT/i', $query)) {
+ if ( ! preg_match('/\sFROM\s/i', $query)) {
+ $query .= " FROM dual";
+ }
+ if ($limit > 0) {
+ $max = $offset + $limit;
+ $column = '*';
+ if ($offset > 0) {
+ $min = $offset + 1;
+ $query = 'SELECT b.'.$column.' FROM ('.
+ 'SELECT a.*, ROWNUM AS doctrine_rownum FROM ('
+ . $query . ') a '.
+ ') b '.
+ 'WHERE doctrine_rownum BETWEEN ' . $min . ' AND ' . $max;
+ } else {
+ $query = 'SELECT a.'.$column.' FROM (' . $query .') a WHERE ROWNUM <= ' . $max;
+ }
+ }
+ }
+ return $query;
+ }
+
+ /**
+ * Gets the character casing of a column in an SQL result set of this platform.
+ *
+ * Oracle returns all column names in SQL result sets in uppercase.
+ *
+ * @param string $column The column name for which to get the correct character casing.
+ * @return string The column name in the character casing used in SQL result sets.
+ */
+ public function getSQLResultCasing($column)
+ {
+ return strtoupper($column);
+ }
+
+ public function getCreateTemporaryTableSnippetSQL()
+ {
+ return "CREATE GLOBAL TEMPORARY TABLE";
+ }
+
+ public function getDateTimeTzFormatString()
+ {
+ return 'Y-m-d H:i:sP';
+ }
+
+ public function getDateFormatString()
+ {
+ return 'Y-m-d 00:00:00';
+ }
+
+ public function getTimeFormatString()
+ {
+ return '1900-01-01 H:i:s';
+ }
+
+ public function fixSchemaElementName($schemaElementName)
+ {
+ if (strlen($schemaElementName) > 30) {
+ // Trim it
+ return substr($schemaElementName, 0, 30);
+ }
+ return $schemaElementName;
+ }
+
+ /**
+ * Maximum length of any given databse identifier, like tables or column names.
+ *
+ * @return int
+ */
+ public function getMaxIdentifierLength()
+ {
+ return 30;
+ }
+
+ /**
+ * Whether the platform supports sequences.
+ *
+ * @return boolean
+ */
+ public function supportsSequences()
+ {
+ return true;
+ }
+
+ public function supportsForeignKeyOnUpdate()
+ {
+ return false;
+ }
+
+ /**
+ * Whether the platform supports releasing savepoints.
+ *
+ * @return boolean
+ */
+ public function supportsReleaseSavepoints()
+ {
+ return false;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getTruncateTableSQL($tableName, $cascade = false)
+ {
+ return 'TRUNCATE TABLE '.$tableName;
+ }
+
+ /**
+ * This is for test reasons, many vendors have special requirements for dummy statements.
+ *
+ * @return string
+ */
+ public function getDummySelectSQL()
+ {
+ return 'SELECT 1 FROM DUAL';
+ }
+
+ protected function initializeDoctrineTypeMappings()
+ {
+ $this->doctrineTypeMapping = array(
+ 'integer' => 'integer',
+ 'number' => 'integer',
+ 'pls_integer' => 'boolean',
+ 'binary_integer' => 'boolean',
+ 'varchar' => 'string',
+ 'varchar2' => 'string',
+ 'nvarchar2' => 'string',
+ 'char' => 'string',
+ 'nchar' => 'string',
+ 'date' => 'datetime',
+ 'timestamp' => 'datetime',
+ 'timestamptz' => 'datetimetz',
+ 'float' => 'float',
+ 'long' => 'string',
+ 'clob' => 'text',
+ 'nclob' => 'text',
+ 'rowid' => 'string',
+ 'urowid' => 'string'
+ );
+ }
+
+ /**
+ * Generate SQL to release a savepoint
+ *
+ * @param string $savepoint
+ * @return string
+ */
+ public function releaseSavePoint($savepoint)
+ {
+ return '';
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Platforms;
+
+use Doctrine\DBAL\Schema\TableDiff,
+ Doctrine\DBAL\Schema\Table;
+
+/**
+ * PostgreSqlPlatform.
+ *
+ * @since 2.0
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @todo Rename: PostgreSQLPlatform
+ */
+class PostgreSqlPlatform extends AbstractPlatform
+{
+ /**
+ * Returns part of a string.
+ *
+ * Note: Not SQL92, but common functionality.
+ *
+ * @param string $value the target $value the string or the string column.
+ * @param int $from extract from this characeter.
+ * @param int $len extract this amount of characters.
+ * @return string sql that extracts part of a string.
+ * @override
+ */
+ public function getSubstringExpression($value, $from, $len = null)
+ {
+ if ($len === null) {
+ return 'SUBSTR(' . $value . ', ' . $from . ')';
+ } else {
+ return 'SUBSTR(' . $value . ', ' . $from . ', ' . $len . ')';
+ }
+ }
+
+ /**
+ * Returns the SQL string to return the current system date and time.
+ *
+ * @return string
+ */
+ public function getNowExpression()
+ {
+ return 'LOCALTIMESTAMP(0)';
+ }
+
+ /**
+ * regexp
+ *
+ * @return string the regular expression operator
+ * @override
+ */
+ public function getRegexpExpression()
+ {
+ return 'SIMILAR TO';
+ }
+
+ /**
+ * returns the position of the first occurrence of substring $substr in string $str
+ *
+ * @param string $substr literal string to find
+ * @param string $str literal string
+ * @param int $pos position to start at, beginning of string by default
+ * @return integer
+ */
+ public function getLocateExpression($str, $substr, $startPos = false)
+ {
+ if ($startPos !== false) {
+ $str = $this->getSubstringExpression($str, $startPos);
+ return 'CASE WHEN (POSITION('.$substr.' IN '.$str.') = 0) THEN 0 ELSE (POSITION('.$substr.' IN '.$str.') + '.($startPos-1).') END';
+ } else {
+ return 'POSITION('.$substr.' IN '.$str.')';
+ }
+ }
+
+ /**
+ * parses a literal boolean value and returns
+ * proper sql equivalent
+ *
+ * @param string $value boolean value to be parsed
+ * @return string parsed boolean value
+ */
+ /*public function parseBoolean($value)
+ {
+ return $value;
+ }*/
+
+ /**
+ * Whether the platform supports sequences.
+ * Postgres has native support for sequences.
+ *
+ * @return boolean
+ */
+ public function supportsSequences()
+ {
+ return true;
+ }
+
+ /**
+ * Whether the platform supports database schemas.
+ *
+ * @return boolean
+ */
+ public function supportsSchemas()
+ {
+ return true;
+ }
+
+ /**
+ * Whether the platform supports identity columns.
+ * Postgres supports these through the SERIAL keyword.
+ *
+ * @return boolean
+ */
+ public function supportsIdentityColumns()
+ {
+ return true;
+ }
+
+ /**
+ * Whether the platform prefers sequences for ID generation.
+ *
+ * @return boolean
+ */
+ public function prefersSequences()
+ {
+ return true;
+ }
+
+ public function getListDatabasesSQL()
+ {
+ return 'SELECT datname FROM pg_database';
+ }
+
+ public function getListSequencesSQL($database)
+ {
+ return "SELECT
+ relname
+ FROM
+ pg_class
+ WHERE relkind = 'S' AND relnamespace IN
+ (SELECT oid FROM pg_namespace
+ WHERE nspname NOT LIKE 'pg_%' AND nspname != 'information_schema')";
+ }
+
+ public function getListTablesSQL()
+ {
+ return "SELECT
+ c.relname AS table_name
+ FROM pg_class c, pg_user u
+ WHERE c.relowner = u.usesysid
+ AND c.relkind = 'r'
+ AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname)
+ AND c.relname !~ '^(pg_|sql_)'
+ UNION
+ SELECT c.relname AS table_name
+ FROM pg_class c
+ WHERE c.relkind = 'r'
+ AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname)
+ AND NOT EXISTS (SELECT 1 FROM pg_user WHERE usesysid = c.relowner)
+ AND c.relname !~ '^pg_'";
+ }
+
+ public function getListViewsSQL($database)
+ {
+ return 'SELECT viewname, definition FROM pg_views';
+ }
+
+ public function getListTableForeignKeysSQL($table, $database = null)
+ {
+ return "SELECT r.conname, pg_catalog.pg_get_constraintdef(r.oid, true) as condef
+ FROM pg_catalog.pg_constraint r
+ WHERE r.conrelid =
+ (
+ SELECT c.oid
+ FROM pg_catalog.pg_class c
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+ WHERE c.relname = '" . $table . "' AND pg_catalog.pg_table_is_visible(c.oid)
+ )
+ AND r.contype = 'f'";
+ }
+
+ public function getCreateViewSQL($name, $sql)
+ {
+ return 'CREATE VIEW ' . $name . ' AS ' . $sql;
+ }
+
+ public function getDropViewSQL($name)
+ {
+ return 'DROP VIEW '. $name;
+ }
+
+ public function getListTableConstraintsSQL($table)
+ {
+ return "SELECT
+ relname
+ FROM
+ pg_class
+ WHERE oid IN (
+ SELECT indexrelid
+ FROM pg_index, pg_class
+ WHERE pg_class.relname = '$table'
+ AND pg_class.oid = pg_index.indrelid
+ AND (indisunique = 't' OR indisprimary = 't')
+ )";
+ }
+
+ /**
+ * @license New BSD License
+ * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
+ * @param string $table
+ * @return string
+ */
+ public function getListTableIndexesSQL($table)
+ {
+ return "SELECT relname, pg_index.indisunique, pg_index.indisprimary,
+ pg_index.indkey, pg_index.indrelid
+ FROM pg_class, pg_index
+ WHERE oid IN (
+ SELECT indexrelid
+ FROM pg_index, pg_class
+ WHERE pg_class.relname='$table' AND pg_class.oid=pg_index.indrelid
+ ) AND pg_index.indexrelid = oid";
+ }
+
+ public function getListTableColumnsSQL($table)
+ {
+ return "SELECT
+ a.attnum,
+ a.attname AS field,
+ t.typname AS type,
+ format_type(a.atttypid, a.atttypmod) AS complete_type,
+ (SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type,
+ (SELECT format_type(t2.typbasetype, t2.typtypmod) FROM pg_catalog.pg_type t2
+ WHERE t2.typtype = 'd' AND t2.typname = format_type(a.atttypid, a.atttypmod)) AS domain_complete_type,
+ a.attnotnull AS isnotnull,
+ (SELECT 't'
+ FROM pg_index
+ WHERE c.oid = pg_index.indrelid
+ AND pg_index.indkey[0] = a.attnum
+ AND pg_index.indisprimary = 't'
+ ) AS pri,
+ (SELECT pg_attrdef.adsrc
+ FROM pg_attrdef
+ WHERE c.oid = pg_attrdef.adrelid
+ AND pg_attrdef.adnum=a.attnum
+ ) AS default
+ FROM pg_attribute a, pg_class c, pg_type t
+ WHERE c.relname = '$table'
+ AND a.attnum > 0
+ AND a.attrelid = c.oid
+ AND a.atttypid = t.oid
+ ORDER BY a.attnum";
+ }
+
+ /**
+ * create a new database
+ *
+ * @param string $name name of the database that should be created
+ * @throws PDOException
+ * @return void
+ * @override
+ */
+ public function getCreateDatabaseSQL($name)
+ {
+ return 'CREATE DATABASE ' . $name;
+ }
+
+ /**
+ * drop an existing database
+ *
+ * @param string $name name of the database that should be dropped
+ * @throws PDOException
+ * @access public
+ */
+ public function getDropDatabaseSQL($name)
+ {
+ return 'DROP DATABASE ' . $name;
+ }
+
+ /**
+ * Return the FOREIGN KEY query section dealing with non-standard options
+ * as MATCH, INITIALLY DEFERRED, ON UPDATE, ...
+ *
+ * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey foreign key definition
+ * @return string
+ * @override
+ */
+ public function getAdvancedForeignKeyOptionsSQL(\Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey)
+ {
+ $query = '';
+ if ($foreignKey->hasOption('match')) {
+ $query .= ' MATCH ' . $foreignKey->getOption('match');
+ }
+ $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
+ if ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) {
+ $query .= ' DEFERRABLE';
+ } else {
+ $query .= ' NOT DEFERRABLE';
+ }
+ if ($foreignKey->hasOption('feferred') && $foreignKey->getOption('feferred') !== false) {
+ $query .= ' INITIALLY DEFERRED';
+ } else {
+ $query .= ' INITIALLY IMMEDIATE';
+ }
+ return $query;
+ }
+
+ /**
+ * generates the sql for altering an existing table on postgresql
+ *
+ * @param string $name name of the table that is intended to be changed.
+ * @param array $changes associative array that contains the details of each type *
+ * @param boolean $check indicates whether the function should just check if the DBMS driver
+ * can perform the requested table alterations if the value is true or
+ * actually perform them otherwise.
+ * @see Doctrine_Export::alterTable()
+ * @return array
+ * @override
+ */
+ public function getAlterTableSQL(TableDiff $diff)
+ {
+ $sql = array();
+
+ foreach ($diff->addedColumns as $column) {
+ $query = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
+ $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query;
+ }
+
+ foreach ($diff->removedColumns as $column) {
+ $query = 'DROP ' . $column->getQuotedName($this);
+ $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query;
+ }
+
+ foreach ($diff->changedColumns AS $columnDiff) {
+ $oldColumnName = $columnDiff->oldColumnName;
+ $column = $columnDiff->column;
+
+ if ($columnDiff->hasChanged('type')) {
+ $type = $column->getType();
+
+ // here was a server version check before, but DBAL API does not support this anymore.
+ $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $type->getSqlDeclaration($column->toArray(), $this);
+ $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query;
+ }
+ if ($columnDiff->hasChanged('default')) {
+ $query = 'ALTER ' . $oldColumnName . ' SET ' . $this->getDefaultValueDeclarationSQL($column->toArray());
+ $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query;
+ }
+ if ($columnDiff->hasChanged('notnull')) {
+ $query = 'ALTER ' . $oldColumnName . ' ' . ($column->getNotNull() ? 'SET' : 'DROP') . ' NOT NULL';
+ $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query;
+ }
+ if ($columnDiff->hasChanged('autoincrement')) {
+ if ($column->getAutoincrement()) {
+ // add autoincrement
+ $seqName = $diff->name . '_' . $oldColumnName . '_seq';
+
+ $sql[] = "CREATE SEQUENCE " . $seqName;
+ $sql[] = "SELECT setval('" . $seqName . "', (SELECT MAX(" . $oldColumnName . ") FROM " . $diff->name . "))";
+ $query = "ALTER " . $oldColumnName . " SET DEFAULT nextval('" . $seqName . "')";
+ $sql[] = "ALTER TABLE " . $diff->name . " " . $query;
+ } else {
+ // Drop autoincrement, but do NOT drop the sequence. It might be re-used by other tables or have
+ $query = "ALTER " . $oldColumnName . " " . "DROP DEFAULT";
+ $sql[] = "ALTER TABLE " . $diff->name . " " . $query;
+ }
+ }
+ }
+
+ foreach ($diff->renamedColumns as $oldColumnName => $column) {
+ $sql[] = 'ALTER TABLE ' . $diff->name . ' RENAME COLUMN ' . $oldColumnName . ' TO ' . $column->getQuotedName($this);
+ }
+
+ if ($diff->newName !== false) {
+ $sql[] = 'ALTER TABLE ' . $diff->name . ' RENAME TO ' . $diff->newName;
+ }
+
+ $sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySQL($diff));
+
+ return $sql;
+ }
+
+ /**
+ * Gets the SQL to create a sequence on this platform.
+ *
+ * @param \Doctrine\DBAL\Schema\Sequence $sequence
+ * @return string
+ */
+ public function getCreateSequenceSQL(\Doctrine\DBAL\Schema\Sequence $sequence)
+ {
+ return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) .
+ ' INCREMENT BY ' . $sequence->getAllocationSize() .
+ ' MINVALUE ' . $sequence->getInitialValue() .
+ ' START ' . $sequence->getInitialValue();
+ }
+
+ /**
+ * Drop existing sequence
+ * @param \Doctrine\DBAL\Schema\Sequence $sequence
+ * @return string
+ */
+ public function getDropSequenceSQL($sequence)
+ {
+ if ($sequence instanceof \Doctrine\DBAL\Schema\Sequence) {
+ $sequence = $sequence->getQuotedName($this);
+ }
+ return 'DROP SEQUENCE ' . $sequence;
+ }
+
+ /**
+ * @param ForeignKeyConstraint|string $foreignKey
+ * @param Table|string $table
+ * @return string
+ */
+ public function getDropForeignKeySQL($foreignKey, $table)
+ {
+ return $this->getDropConstraintSQL($foreignKey, $table);
+ }
+
+ /**
+ * Gets the SQL used to create a table.
+ *
+ * @param unknown_type $tableName
+ * @param array $columns
+ * @param array $options
+ * @return unknown
+ */
+ protected function _getCreateTableSQL($tableName, array $columns, array $options = array())
+ {
+ $queryFields = $this->getColumnDeclarationListSQL($columns);
+
+ if (isset($options['primary']) && ! empty($options['primary'])) {
+ $keyColumns = array_unique(array_values($options['primary']));
+ $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
+ }
+
+ $query = 'CREATE TABLE ' . $tableName . ' (' . $queryFields . ')';
+
+ $sql[] = $query;
+
+ if (isset($options['indexes']) && ! empty($options['indexes'])) {
+ foreach ($options['indexes'] AS $index) {
+ $sql[] = $this->getCreateIndexSQL($index, $tableName);
+ }
+ }
+
+ if (isset($options['foreignKeys'])) {
+ foreach ((array) $options['foreignKeys'] as $definition) {
+ $sql[] = $this->getCreateForeignKeySQL($definition, $tableName);
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Postgres wants boolean values converted to the strings 'true'/'false'.
+ *
+ * @param array $item
+ * @override
+ */
+ public function convertBooleans($item)
+ {
+ if (is_array($item)) {
+ foreach ($item as $key => $value) {
+ if (is_bool($value) || is_numeric($item)) {
+ $item[$key] = ($value) ? 'true' : 'false';
+ }
+ }
+ } else {
+ if (is_bool($item) || is_numeric($item)) {
+ $item = ($item) ? 'true' : 'false';
+ }
+ }
+ return $item;
+ }
+
+ public function getSequenceNextValSQL($sequenceName)
+ {
+ return "SELECT NEXTVAL('" . $sequenceName . "')";
+ }
+
+ public function getSetTransactionIsolationSQL($level)
+ {
+ return 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL '
+ . $this->_getTransactionIsolationLevelSQL($level);
+ }
+
+ /**
+ * @override
+ */
+ public function getBooleanTypeDeclarationSQL(array $field)
+ {
+ return 'BOOLEAN';
+ }
+
+ /**
+ * @override
+ */
+ public function getIntegerTypeDeclarationSQL(array $field)
+ {
+ if ( ! empty($field['autoincrement'])) {
+ return 'SERIAL';
+ }
+
+ return 'INT';
+ }
+
+ /**
+ * @override
+ */
+ public function getBigIntTypeDeclarationSQL(array $field)
+ {
+ if ( ! empty($field['autoincrement'])) {
+ return 'BIGSERIAL';
+ }
+ return 'BIGINT';
+ }
+
+ /**
+ * @override
+ */
+ public function getSmallIntTypeDeclarationSQL(array $field)
+ {
+ return 'SMALLINT';
+ }
+
+ /**
+ * @override
+ */
+ public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ return 'TIMESTAMP(0) WITHOUT TIME ZONE';
+ }
+
+ /**
+ * @override
+ */
+ public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ return 'TIMESTAMP(0) WITH TIME ZONE';
+ }
+
+ /**
+ * @override
+ */
+ public function getDateTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ return 'DATE';
+ }
+
+ /**
+ * @override
+ */
+ public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ return 'TIME(0) WITHOUT TIME ZONE';
+ }
+
+ /**
+ * @override
+ */
+ protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
+ {
+ return '';
+ }
+
+ /**
+ * Gets the SQL snippet used to declare a VARCHAR column on the MySql platform.
+ *
+ * @params array $field
+ * @override
+ */
+ public function getVarcharTypeDeclarationSQL(array $field)
+ {
+ if ( ! isset($field['length'])) {
+ if (array_key_exists('default', $field)) {
+ $field['length'] = $this->getVarcharDefaultLength();
+ } else {
+ $field['length'] = false;
+ }
+ }
+
+ $length = ($field['length'] <= $this->getVarcharMaxLength()) ? $field['length'] : false;
+ $fixed = (isset($field['fixed'])) ? $field['fixed'] : false;
+
+ return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
+ : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT');
+ }
+
+ /** @override */
+ public function getClobTypeDeclarationSQL(array $field)
+ {
+ return 'TEXT';
+ }
+
+ /**
+ * Get the platform name for this instance
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return 'postgresql';
+ }
+
+ /**
+ * Gets the character casing of a column in an SQL result set.
+ *
+ * PostgreSQL returns all column names in SQL result sets in lowercase.
+ *
+ * @param string $column The column name for which to get the correct character casing.
+ * @return string The column name in the character casing used in SQL result sets.
+ */
+ public function getSQLResultCasing($column)
+ {
+ return strtolower($column);
+ }
+
+ public function getDateTimeTzFormatString()
+ {
+ return 'Y-m-d H:i:sO';
+ }
+
+ /**
+ * Get the insert sql for an empty insert statement
+ *
+ * @param string $tableName
+ * @param string $identifierColumnName
+ * @return string $sql
+ */
+ public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName)
+ {
+ return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)';
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getTruncateTableSQL($tableName, $cascade = false)
+ {
+ return 'TRUNCATE '.$tableName.' '.($cascade)?'CASCADE':'';
+ }
+
+ public function getReadLockSQL()
+ {
+ return 'FOR SHARE';
+ }
+
+ protected function initializeDoctrineTypeMappings()
+ {
+ $this->doctrineTypeMapping = array(
+ 'smallint' => 'smallint',
+ 'int2' => 'smallint',
+ 'serial' => 'integer',
+ 'serial4' => 'integer',
+ 'int' => 'integer',
+ 'int4' => 'integer',
+ 'integer' => 'integer',
+ 'bigserial' => 'bigint',
+ 'serial8' => 'bigint',
+ 'bigint' => 'bigint',
+ 'int8' => 'bigint',
+ 'bool' => 'boolean',
+ 'boolean' => 'boolean',
+ 'text' => 'text',
+ 'varchar' => 'string',
+ 'interval' => 'string',
+ '_varchar' => 'string',
+ 'char' => 'string',
+ 'bpchar' => 'string',
+ 'date' => 'date',
+ 'datetime' => 'datetime',
+ 'timestamp' => 'datetime',
+ 'timestamptz' => 'datetimetz',
+ 'time' => 'time',
+ 'timetz' => 'time',
+ 'float' => 'float',
+ 'float4' => 'float',
+ 'float8' => 'float',
+ 'double' => 'float',
+ 'double precision' => 'float',
+ 'real' => 'float',
+ 'decimal' => 'decimal',
+ 'money' => 'decimal',
+ 'numeric' => 'decimal',
+ 'year' => 'date',
+ );
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Platforms;
+
+use Doctrine\DBAL\DBALException;
+
+/**
+ * The SqlitePlatform class describes the specifics and dialects of the SQLite
+ * database platform.
+ *
+ * @since 2.0
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @todo Rename: SQLitePlatform
+ */
+class SqlitePlatform extends AbstractPlatform
+{
+ /**
+ * returns the regular expression operator
+ *
+ * @return string
+ * @override
+ */
+ public function getRegexpExpression()
+ {
+ return 'RLIKE';
+ }
+
+ /**
+ * Return string to call a variable with the current timestamp inside an SQL statement
+ * There are three special variables for current date and time.
+ *
+ * @return string sqlite function as string
+ * @override
+ */
+ public function getNowExpression($type = 'timestamp')
+ {
+ switch ($type) {
+ case 'time':
+ return 'time(\'now\')';
+ case 'date':
+ return 'date(\'now\')';
+ case 'timestamp':
+ default:
+ return 'datetime(\'now\')';
+ }
+ }
+
+ /**
+ * Trim a string, leading/trailing/both and with a given char which defaults to space.
+ *
+ * @param string $str
+ * @param int $pos
+ * @param string $char
+ * @return string
+ */
+ public function getTrimExpression($str, $pos = self::TRIM_UNSPECIFIED, $char = false)
+ {
+ $trimFn = '';
+ $trimChar = ($char != false) ? (', ' . $char) : '';
+
+ if ($pos == self::TRIM_LEADING) {
+ $trimFn = 'LTRIM';
+ } else if($pos == self::TRIM_TRAILING) {
+ $trimFn = 'RTRIM';
+ } else {
+ $trimFn = 'TRIM';
+ }
+
+ return $trimFn . '(' . $str . $trimChar . ')';
+ }
+
+ /**
+ * return string to call a function to get a substring inside an SQL statement
+ *
+ * Note: Not SQL92, but common functionality.
+ *
+ * SQLite only supports the 2 parameter variant of this function
+ *
+ * @param string $value an sql string literal or column name/alias
+ * @param integer $position where to start the substring portion
+ * @param integer $length the substring portion length
+ * @return string SQL substring function with given parameters
+ * @override
+ */
+ public function getSubstringExpression($value, $position, $length = null)
+ {
+ if ($length !== null) {
+ return 'SUBSTR(' . $value . ', ' . $position . ', ' . $length . ')';
+ }
+ return 'SUBSTR(' . $value . ', ' . $position . ', LENGTH(' . $value . '))';
+ }
+
+ /**
+ * returns the position of the first occurrence of substring $substr in string $str
+ *
+ * @param string $substr literal string to find
+ * @param string $str literal string
+ * @param int $pos position to start at, beginning of string by default
+ * @return integer
+ */
+ public function getLocateExpression($str, $substr, $startPos = false)
+ {
+ if ($startPos == false) {
+ return 'LOCATE('.$str.', '.$substr.')';
+ } else {
+ return 'LOCATE('.$str.', '.$substr.', '.$startPos.')';
+ }
+ }
+
+ protected function _getTransactionIsolationLevelSQL($level)
+ {
+ switch ($level) {
+ case \Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED:
+ return 0;
+ case \Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED:
+ case \Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ:
+ case \Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE:
+ return 1;
+ default:
+ return parent::_getTransactionIsolationLevelSQL($level);
+ }
+ }
+
+ public function getSetTransactionIsolationSQL($level)
+ {
+ return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level);
+ }
+
+ /**
+ * @override
+ */
+ public function prefersIdentityColumns()
+ {
+ return true;
+ }
+
+ /**
+ * @override
+ */
+ public function getBooleanTypeDeclarationSQL(array $field)
+ {
+ return 'BOOLEAN';
+ }
+
+ /**
+ * @override
+ */
+ public function getIntegerTypeDeclarationSQL(array $field)
+ {
+ return $this->_getCommonIntegerTypeDeclarationSQL($field);
+ }
+
+ /**
+ * @override
+ */
+ public function getBigIntTypeDeclarationSQL(array $field)
+ {
+ return $this->_getCommonIntegerTypeDeclarationSQL($field);
+ }
+
+ /**
+ * @override
+ */
+ public function getTinyIntTypeDeclarationSql(array $field)
+ {
+ return $this->_getCommonIntegerTypeDeclarationSQL($field);
+ }
+
+ /**
+ * @override
+ */
+ public function getSmallIntTypeDeclarationSQL(array $field)
+ {
+ return $this->_getCommonIntegerTypeDeclarationSQL($field);
+ }
+
+ /**
+ * @override
+ */
+ public function getMediumIntTypeDeclarationSql(array $field)
+ {
+ return $this->_getCommonIntegerTypeDeclarationSQL($field);
+ }
+
+ /**
+ * @override
+ */
+ public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ return 'DATETIME';
+ }
+
+ /**
+ * @override
+ */
+ public function getDateTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ return 'DATE';
+ }
+
+ /**
+ * @override
+ */
+ public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
+ {
+ return 'TIME';
+ }
+
+ /**
+ * @override
+ */
+ protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
+ {
+ $autoinc = ! empty($columnDef['autoincrement']) ? ' AUTOINCREMENT' : '';
+ $pk = ! empty($columnDef['primary']) && ! empty($autoinc) ? ' PRIMARY KEY' : '';
+
+ return 'INTEGER' . $pk . $autoinc;
+ }
+
+ /**
+ * create a new table
+ *
+ * @param string $name Name of the database that should be created
+ * @param array $fields Associative array that contains the definition of each field of the new table
+ * The indexes of the array entries are the names of the fields of the table an
+ * the array entry values are associative arrays like those that are meant to be
+ * passed with the field definitions to get[Type]Declaration() functions.
+ * array(
+ * 'id' => array(
+ * 'type' => 'integer',
+ * 'unsigned' => 1
+ * 'notnull' => 1
+ * 'default' => 0
+ * ),
+ * 'name' => array(
+ * 'type' => 'text',
+ * 'length' => 12
+ * ),
+ * 'password' => array(
+ * 'type' => 'text',
+ * 'length' => 12
+ * )
+ * );
+ * @param array $options An associative array of table options:
+ *
+ * @return void
+ * @override
+ */
+ protected function _getCreateTableSQL($name, array $columns, array $options = array())
+ {
+ $queryFields = $this->getColumnDeclarationListSQL($columns);
+
+ $autoinc = false;
+ foreach($columns as $field) {
+ if (isset($field['autoincrement']) && $field['autoincrement']) {
+ $autoinc = true;
+ break;
+ }
+ }
+
+ if ( ! $autoinc && isset($options['primary']) && ! empty($options['primary'])) {
+ $keyColumns = array_unique(array_values($options['primary']));
+ $keyColumns = array_map(array($this, 'quoteIdentifier'), $keyColumns);
+ $queryFields.= ', PRIMARY KEY('.implode(', ', $keyColumns).')';
+ }
+
+ $query[] = 'CREATE TABLE ' . $name . ' (' . $queryFields . ')';
+
+ if (isset($options['indexes']) && ! empty($options['indexes'])) {
+ foreach ($options['indexes'] as $index => $indexDef) {
+ $query[] = $this->getCreateIndexSQL($indexDef, $name);
+ }
+ }
+ if (isset($options['unique']) && ! empty($options['unique'])) {
+ foreach ($options['unique'] as $index => $indexDef) {
+ $query[] = $this->getCreateIndexSQL($indexDef, $name);
+ }
+ }
+ return $query;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getVarcharTypeDeclarationSQL(array $field)
+ {
+ if ( ! isset($field['length'])) {
+ if (array_key_exists('default', $field)) {
+ $field['length'] = $this->getVarcharDefaultLength();
+ } else {
+ $field['length'] = false;
+ }
+ }
+ $length = ($field['length'] <= $this->getVarcharMaxLength()) ? $field['length'] : false;
+ $fixed = (isset($field['fixed'])) ? $field['fixed'] : false;
+
+ return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
+ : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT');
+ }
+
+ public function getClobTypeDeclarationSQL(array $field)
+ {
+ return 'CLOB';
+ }
+
+ public function getListTableConstraintsSQL($table)
+ {
+ return "SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name = '$table' AND sql NOT NULL ORDER BY name";
+ }
+
+ public function getListTableColumnsSQL($table)
+ {
+ return "PRAGMA table_info($table)";
+ }
+
+ public function getListTableIndexesSQL($table)
+ {
+ return "PRAGMA index_list($table)";
+ }
+
+ public function getListTablesSQL()
+ {
+ return "SELECT name FROM sqlite_master WHERE type = 'table' AND name != 'sqlite_sequence' "
+ . "UNION ALL SELECT name FROM sqlite_temp_master "
+ . "WHERE type = 'table' ORDER BY name";
+ }
+
+ public function getListViewsSQL($database)
+ {
+ return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL";
+ }
+
+ public function getCreateViewSQL($name, $sql)
+ {
+ return 'CREATE VIEW ' . $name . ' AS ' . $sql;
+ }
+
+ public function getDropViewSQL($name)
+ {
+ return 'DROP VIEW '. $name;
+ }
+
+ /**
+ * SQLite does support foreign key constraints, but only in CREATE TABLE statements...
+ * This really limits their usefulness and requires SQLite specific handling, so
+ * we simply say that SQLite does NOT support foreign keys for now...
+ *
+ * @return boolean FALSE
+ * @override
+ */
+ public function supportsForeignKeyConstraints()
+ {
+ return false;
+ }
+
+ public function supportsAlterTable()
+ {
+ return false;
+ }
+
+ public function supportsIdentityColumns()
+ {
+ return true;
+ }
+
+ /**
+ * Get the platform name for this instance
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return 'sqlite';
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getTruncateTableSQL($tableName, $cascade = false)
+ {
+ return 'DELETE FROM '.$tableName;
+ }
+
+ /**
+ * User-defined function for Sqlite that is used with PDO::sqliteCreateFunction()
+ *
+ * @param int|float $value
+ * @return float
+ */
+ static public function udfSqrt($value)
+ {
+ return sqrt($value);
+ }
+
+ /**
+ * User-defined function for Sqlite that implements MOD(a, b)
+ */
+ static public function udfMod($a, $b)
+ {
+ return ($a % $b);
+ }
+
+ /**
+ * @param string $str
+ * @param string $substr
+ * @param int $offset
+ */
+ static public function udfLocate($str, $substr, $offset = 0)
+ {
+ $pos = strpos($str, $substr, $offset);
+ if ($pos !== false) {
+ return $pos+1;
+ }
+ return 0;
+ }
+
+ public function getForUpdateSql()
+ {
+ return '';
+ }
+
+ protected function initializeDoctrineTypeMappings()
+ {
+ $this->doctrineTypeMapping = array(
+ 'boolean' => 'boolean',
+ 'tinyint' => 'boolean',
+ 'smallint' => 'smallint',
+ 'mediumint' => 'integer',
+ 'int' => 'integer',
+ 'integer' => 'integer',
+ 'serial' => 'integer',
+ 'bigint' => 'bigint',
+ 'bigserial' => 'bigint',
+ 'clob' => 'text',
+ 'tinytext' => 'text',
+ 'mediumtext' => 'text',
+ 'longtext' => 'text',
+ 'text' => 'text',
+ 'varchar' => 'string',
+ 'varchar2' => 'string',
+ 'nvarchar' => 'string',
+ 'image' => 'string',
+ 'ntext' => 'string',
+ 'char' => 'string',
+ 'date' => 'date',
+ 'datetime' => 'datetime',
+ 'timestamp' => 'datetime',
+ 'time' => 'time',
+ 'float' => 'float',
+ 'double' => 'float',
+ 'real' => 'float',
+ 'decimal' => 'decimal',
+ 'numeric' => 'decimal',
+ );
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Schema;
+
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+
+/**
+ * The abstract asset allows to reset the name of all assets without publishing this to the public userland.
+ *
+ * This encapsulation hack is necessary to keep a consistent state of the database schema. Say we have a list of tables
+ * array($tableName => Table($tableName)); if you want to rename the table, you have to make sure
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+abstract class AbstractAsset
+{
+ /**
+ * @var string
+ */
+ protected $_name;
+
+ protected $_quoted = false;
+
+ /**
+ * Set name of this asset
+ *
+ * @param string $name
+ */
+ protected function _setName($name)
+ {
+ if (strlen($name)) {
+ // TODO: find more elegant way to solve this issue.
+ if ($name[0] == '`') {
+ $this->_quoted = true;
+ $name = trim($name, '`');
+ } else if ($name[0] == '"') {
+ $this->_quoted = true;
+ $name = trim($name, '"');
+ }
+ }
+ $this->_name = $name;
+ }
+
+ /**
+ * Return name of this schema asset.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->_name;
+ }
+
+ /**
+ * Get the quoted representation of this asset but only if it was defined with one. Otherwise
+ * return the plain unquoted value as inserted.
+ *
+ * @param AbstractPlatform $platform
+ * @return string
+ */
+ public function getQuotedName(AbstractPlatform $platform)
+ {
+ return ($this->_quoted) ? $platform->quoteIdentifier($this->_name) : $this->_name;
+ }
+
+ /**
+ * Generate an identifier from a list of column names obeying a certain string length.
+ *
+ * This is especially important for Oracle, since it does not allow identifiers larger than 30 chars,
+ * however building idents automatically for foreign keys, composite keys or such can easily create
+ * very long names.
+ *
+ * @param array $columnNames
+ * @param string $postfix
+ * @param int $maxSize
+ * @return string
+ */
+ protected function _generateIdentifierName($columnNames, $postfix='', $maxSize=30)
+ {
+ $columnCount = count($columnNames);
+ $postfixLen = strlen($postfix);
+ $parts = array_map(function($columnName) use($columnCount, $postfixLen, $maxSize) {
+ return substr($columnName, -floor(($maxSize-$postfixLen)/$columnCount - 1));
+ }, $columnNames);
+ $parts[] = $postfix;
+
+ $identifier = trim(implode("_", $parts), '_');
+ // using implicit schema support of DB2 and Postgres there might be dots in the auto-generated
+ // identifier names which can easily be replaced by underscores.
+ $identifier = str_replace(".", "_", $identifier);
+
+ if (is_numeric(substr($identifier, 0, 1))) {
+ $identifier = "i" . substr($identifier, 0, strlen($identifier)-1);
+ }
+
+ return $identifier;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Schema;
+
+use Doctrine\DBAL\Types;
+use Doctrine\DBAL\DBALException;
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+
+/**
+ * Base class for schema managers. Schema managers are used to inspect and/or
+ * modify the database schema/structure.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
+ * @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @version $Revision$
+ * @since 2.0
+ */
+abstract class AbstractSchemaManager
+{
+ /**
+ * Holds instance of the Doctrine connection for this schema manager
+ *
+ * @var \Doctrine\DBAL\Connection
+ */
+ protected $_conn;
+
+ /**
+ * Holds instance of the database platform used for this schema manager
+ *
+ * @var \Doctrine\DBAL\Platforms\AbstractPlatform
+ */
+ protected $_platform;
+
+ /**
+ * Constructor. Accepts the Connection instance to manage the schema for
+ *
+ * @param \Doctrine\DBAL\Connection $conn
+ */
+ public function __construct(\Doctrine\DBAL\Connection $conn)
+ {
+ $this->_conn = $conn;
+ $this->_platform = $this->_conn->getDatabasePlatform();
+ }
+
+ /**
+ * Return associated platform.
+ *
+ * @return \Doctrine\DBAL\Platform\AbstractPlatform
+ */
+ public function getDatabasePlatform()
+ {
+ return $this->_platform;
+ }
+
+ /**
+ * Try any method on the schema manager. Normally a method throws an
+ * exception when your DBMS doesn't support it or if an error occurs.
+ * This method allows you to try and method on your SchemaManager
+ * instance and will return false if it does not work or is not supported.
+ *
+ * <code>
+ * $result = $sm->tryMethod('dropView', 'view_name');
+ * </code>
+ *
+ * @return mixed
+ */
+ public function tryMethod()
+ {
+ $args = func_get_args();
+ $method = $args[0];
+ unset($args[0]);
+ $args = array_values($args);
+
+ try {
+ return call_user_func_array(array($this, $method), $args);
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * List the available databases for this connection
+ *
+ * @return array $databases
+ */
+ public function listDatabases()
+ {
+ $sql = $this->_platform->getListDatabasesSQL();
+
+ $databases = $this->_conn->fetchAll($sql);
+
+ return $this->_getPortableDatabasesList($databases);
+ }
+
+ /**
+ * List the available sequences for this connection
+ *
+ * @return Sequence[]
+ */
+ public function listSequences($database = null)
+ {
+ if (is_null($database)) {
+ $database = $this->_conn->getDatabase();
+ }
+ $sql = $this->_platform->getListSequencesSQL($database);
+
+ $sequences = $this->_conn->fetchAll($sql);
+
+ return $this->_getPortableSequencesList($sequences);
+ }
+
+ /**
+ * List the columns for a given table.
+ *
+ * In contrast to other libraries and to the old version of Doctrine,
+ * this column definition does try to contain the 'primary' field for
+ * the reason that it is not portable accross different RDBMS. Use
+ * {@see listTableIndexes($tableName)} to retrieve the primary key
+ * of a table. We're a RDBMS specifies more details these are held
+ * in the platformDetails array.
+ *
+ * @param string $table The name of the table.
+ * @return Column[]
+ */
+ public function listTableColumns($table)
+ {
+ $sql = $this->_platform->getListTableColumnsSQL($table);
+
+ $tableColumns = $this->_conn->fetchAll($sql);
+
+ return $this->_getPortableTableColumnList($tableColumns);
+ }
+
+ /**
+ * List the indexes for a given table returning an array of Index instances.
+ *
+ * Keys of the portable indexes list are all lower-cased.
+ *
+ * @param string $table The name of the table
+ * @return Index[] $tableIndexes
+ */
+ public function listTableIndexes($table)
+ {
+ $sql = $this->_platform->getListTableIndexesSQL($table);
+
+ $tableIndexes = $this->_conn->fetchAll($sql);
+
+ return $this->_getPortableTableIndexesList($tableIndexes, $table);
+ }
+
+ /**
+ * Return true if all the given tables exist.
+ *
+ * @param array $tableNames
+ * @return bool
+ */
+ public function tablesExist($tableNames)
+ {
+ $tableNames = array_map('strtolower', (array)$tableNames);
+ return count($tableNames) == count(\array_intersect($tableNames, array_map('strtolower', $this->listTableNames())));
+ }
+
+
+ /**
+ * Return a list of all tables in the current database
+ *
+ * @return array
+ */
+ public function listTableNames()
+ {
+ $sql = $this->_platform->getListTablesSQL();
+
+ $tables = $this->_conn->fetchAll($sql);
+
+ return $this->_getPortableTablesList($tables);
+ }
+
+ /**
+ * List the tables for this connection
+ *
+ * @return Table[]
+ */
+ public function listTables()
+ {
+ $tableNames = $this->listTableNames();
+
+ $tables = array();
+ foreach ($tableNames AS $tableName) {
+ $tables[] = $this->listTableDetails($tableName);
+ }
+
+ return $tables;
+ }
+
+ /**
+ * @param string $tableName
+ * @return Table
+ */
+ public function listTableDetails($tableName)
+ {
+ $columns = $this->listTableColumns($tableName);
+ $foreignKeys = array();
+ if ($this->_platform->supportsForeignKeyConstraints()) {
+ $foreignKeys = $this->listTableForeignKeys($tableName);
+ }
+ $indexes = $this->listTableIndexes($tableName);
+
+ return new Table($tableName, $columns, $indexes, $foreignKeys, false, array());
+ }
+
+ /**
+ * List the views this connection has
+ *
+ * @return View[]
+ */
+ public function listViews()
+ {
+ $database = $this->_conn->getDatabase();
+ $sql = $this->_platform->getListViewsSQL($database);
+ $views = $this->_conn->fetchAll($sql);
+
+ return $this->_getPortableViewsList($views);
+ }
+
+ /**
+ * List the foreign keys for the given table
+ *
+ * @param string $table The name of the table
+ * @return ForeignKeyConstraint[]
+ */
+ public function listTableForeignKeys($table, $database = null)
+ {
+ if (is_null($database)) {
+ $database = $this->_conn->getDatabase();
+ }
+ $sql = $this->_platform->getListTableForeignKeysSQL($table, $database);
+ $tableForeignKeys = $this->_conn->fetchAll($sql);
+
+ return $this->_getPortableTableForeignKeysList($tableForeignKeys);
+ }
+
+ /* drop*() Methods */
+
+ /**
+ * Drops a database.
+ *
+ * NOTE: You can not drop the database this SchemaManager is currently connected to.
+ *
+ * @param string $database The name of the database to drop
+ */
+ public function dropDatabase($database)
+ {
+ $this->_execSql($this->_platform->getDropDatabaseSQL($database));
+ }
+
+ /**
+ * Drop the given table
+ *
+ * @param string $table The name of the table to drop
+ */
+ public function dropTable($table)
+ {
+ $this->_execSql($this->_platform->getDropTableSQL($table));
+ }
+
+ /**
+ * Drop the index from the given table
+ *
+ * @param Index|string $index The name of the index
+ * @param string|Table $table The name of the table
+ */
+ public function dropIndex($index, $table)
+ {
+ if($index instanceof Index) {
+ $index = $index->getQuotedName($this->_platform);
+ }
+
+ $this->_execSql($this->_platform->getDropIndexSQL($index, $table));
+ }
+
+ /**
+ * Drop the constraint from the given table
+ *
+ * @param Constraint $constraint
+ * @param string $table The name of the table
+ */
+ public function dropConstraint(Constraint $constraint, $table)
+ {
+ $this->_execSql($this->_platform->getDropConstraintSQL($constraint, $table));
+ }
+
+ /**
+ * Drops a foreign key from a table.
+ *
+ * @param ForeignKeyConstraint|string $table The name of the table with the foreign key.
+ * @param Table|string $name The name of the foreign key.
+ * @return boolean $result
+ */
+ public function dropForeignKey($foreignKey, $table)
+ {
+ $this->_execSql($this->_platform->getDropForeignKeySQL($foreignKey, $table));
+ }
+
+ /**
+ * Drops a sequence with a given name.
+ *
+ * @param string $name The name of the sequence to drop.
+ */
+ public function dropSequence($name)
+ {
+ $this->_execSql($this->_platform->getDropSequenceSQL($name));
+ }
+
+ /**
+ * Drop a view
+ *
+ * @param string $name The name of the view
+ * @return boolean $result
+ */
+ public function dropView($name)
+ {
+ $this->_execSql($this->_platform->getDropViewSQL($name));
+ }
+
+ /* create*() Methods */
+
+ /**
+ * Creates a new database.
+ *
+ * @param string $database The name of the database to create.
+ */
+ public function createDatabase($database)
+ {
+ $this->_execSql($this->_platform->getCreateDatabaseSQL($database));
+ }
+
+ /**
+ * Create a new table.
+ *
+ * @param Table $table
+ * @param int $createFlags
+ */
+ public function createTable(Table $table)
+ {
+ $createFlags = AbstractPlatform::CREATE_INDEXES|AbstractPlatform::CREATE_FOREIGNKEYS;
+ $this->_execSql($this->_platform->getCreateTableSQL($table, $createFlags));
+ }
+
+ /**
+ * Create a new sequence
+ *
+ * @param Sequence $sequence
+ * @throws Doctrine\DBAL\ConnectionException if something fails at database level
+ */
+ public function createSequence($sequence)
+ {
+ $this->_execSql($this->_platform->getCreateSequenceSQL($sequence));
+ }
+
+ /**
+ * Create a constraint on a table
+ *
+ * @param Constraint $constraint
+ * @param string|Table $table
+ */
+ public function createConstraint(Constraint $constraint, $table)
+ {
+ $this->_execSql($this->_platform->getCreateConstraintSQL($constraint, $table));
+ }
+
+ /**
+ * Create a new index on a table
+ *
+ * @param Index $index
+ * @param string $table name of the table on which the index is to be created
+ */
+ public function createIndex(Index $index, $table)
+ {
+ $this->_execSql($this->_platform->getCreateIndexSQL($index, $table));
+ }
+
+ /**
+ * Create a new foreign key
+ *
+ * @param ForeignKeyConstraint $foreignKey ForeignKey instance
+ * @param string|Table $table name of the table on which the foreign key is to be created
+ */
+ public function createForeignKey(ForeignKeyConstraint $foreignKey, $table)
+ {
+ $this->_execSql($this->_platform->getCreateForeignKeySQL($foreignKey, $table));
+ }
+
+ /**
+ * Create a new view
+ *
+ * @param View $view
+ */
+ public function createView(View $view)
+ {
+ $this->_execSql($this->_platform->getCreateViewSQL($view->getQuotedName($this->_platform), $view->getSql()));
+ }
+
+ /* dropAndCreate*() Methods */
+
+ /**
+ * Drop and create a constraint
+ *
+ * @param Constraint $constraint
+ * @param string $table
+ * @see dropConstraint()
+ * @see createConstraint()
+ */
+ public function dropAndCreateConstraint(Constraint $constraint, $table)
+ {
+ $this->tryMethod('dropConstraint', $constraint, $table);
+ $this->createConstraint($constraint, $table);
+ }
+
+ /**
+ * Drop and create a new index on a table
+ *
+ * @param string|Table $table name of the table on which the index is to be created
+ * @param Index $index
+ */
+ public function dropAndCreateIndex(Index $index, $table)
+ {
+ $this->tryMethod('dropIndex', $index->getQuotedName($this->_platform), $table);
+ $this->createIndex($index, $table);
+ }
+
+ /**
+ * Drop and create a new foreign key
+ *
+ * @param ForeignKeyConstraint $foreignKey associative array that defines properties of the foreign key to be created.
+ * @param string|Table $table name of the table on which the foreign key is to be created
+ */
+ public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table)
+ {
+ $this->tryMethod('dropForeignKey', $foreignKey, $table);
+ $this->createForeignKey($foreignKey, $table);
+ }
+
+ /**
+ * Drop and create a new sequence
+ *
+ * @param Sequence $sequence
+ * @throws Doctrine\DBAL\ConnectionException if something fails at database level
+ */
+ public function dropAndCreateSequence(Sequence $sequence)
+ {
+ $this->tryMethod('createSequence', $seqName, $start, $allocationSize);
+ $this->createSequence($seqName, $start, $allocationSize);
+ }
+
+ /**
+ * Drop and create a new table.
+ *
+ * @param Table $table
+ */
+ public function dropAndCreateTable(Table $table)
+ {
+ $this->tryMethod('dropTable', $table->getQuotedName($this->_platform));
+ $this->createTable($table);
+ }
+
+ /**
+ * Drop and creates a new database.
+ *
+ * @param string $database The name of the database to create.
+ */
+ public function dropAndCreateDatabase($database)
+ {
+ $this->tryMethod('dropDatabase', $database);
+ $this->createDatabase($database);
+ }
+
+ /**
+ * Drop and create a new view
+ *
+ * @param View $view
+ */
+ public function dropAndCreateView(View $view)
+ {
+ $this->tryMethod('dropView', $view->getQuotedName($this->_platform));
+ $this->createView($view);
+ }
+
+ /* alterTable() Methods */
+
+ /**
+ * Alter an existing tables schema
+ *
+ * @param TableDiff $tableDiff
+ */
+ public function alterTable(TableDiff $tableDiff)
+ {
+ $queries = $this->_platform->getAlterTableSQL($tableDiff);
+ if (is_array($queries) && count($queries)) {
+ foreach ($queries AS $ddlQuery) {
+ $this->_execSql($ddlQuery);
+ }
+ }
+ }
+
+ /**
+ * Rename a given table to another name
+ *
+ * @param string $name The current name of the table
+ * @param string $newName The new name of the table
+ */
+ public function renameTable($name, $newName)
+ {
+ $tableDiff = new TableDiff($name);
+ $tableDiff->newName = $newName;
+ $this->alterTable($tableDiff);
+ }
+
+ /**
+ * Methods for filtering return values of list*() methods to convert
+ * the native DBMS data definition to a portable Doctrine definition
+ */
+
+ protected function _getPortableDatabasesList($databases)
+ {
+ $list = array();
+ foreach ($databases as $key => $value) {
+ if ($value = $this->_getPortableDatabaseDefinition($value)) {
+ $list[] = $value;
+ }
+ }
+ return $list;
+ }
+
+ protected function _getPortableDatabaseDefinition($database)
+ {
+ return $database;
+ }
+
+ protected function _getPortableFunctionsList($functions)
+ {
+ $list = array();
+ foreach ($functions as $key => $value) {
+ if ($value = $this->_getPortableFunctionDefinition($value)) {
+ $list[] = $value;
+ }
+ }
+ return $list;
+ }
+
+ protected function _getPortableFunctionDefinition($function)
+ {
+ return $function;
+ }
+
+ protected function _getPortableTriggersList($triggers)
+ {
+ $list = array();
+ foreach ($triggers as $key => $value) {
+ if ($value = $this->_getPortableTriggerDefinition($value)) {
+ $list[] = $value;
+ }
+ }
+ return $list;
+ }
+
+ protected function _getPortableTriggerDefinition($trigger)
+ {
+ return $trigger;
+ }
+
+ protected function _getPortableSequencesList($sequences)
+ {
+ $list = array();
+ foreach ($sequences as $key => $value) {
+ if ($value = $this->_getPortableSequenceDefinition($value)) {
+ $list[] = $value;
+ }
+ }
+ return $list;
+ }
+
+ /**
+ * @param array $sequence
+ * @return Sequence
+ */
+ protected function _getPortableSequenceDefinition($sequence)
+ {
+ throw DBALException::notSupported('Sequences');
+ }
+
+ /**
+ * Independent of the database the keys of the column list result are lowercased.
+ *
+ * The name of the created column instance however is kept in its case.
+ *
+ * @param array $tableColumns
+ * @return array
+ */
+ protected function _getPortableTableColumnList($tableColumns)
+ {
+ $list = array();
+ foreach ($tableColumns as $key => $column) {
+ if ($column = $this->_getPortableTableColumnDefinition($column)) {
+ $name = strtolower($column->getQuotedName($this->_platform));
+ $list[$name] = $column;
+ }
+ }
+ return $list;
+ }
+
+ /**
+ * Get Table Column Definition
+ *
+ * @param array $tableColumn
+ * @return Column
+ */
+ abstract protected function _getPortableTableColumnDefinition($tableColumn);
+
+ /**
+ * Aggregate and group the index results according to the required data result.
+ *
+ * @param array $tableIndexRows
+ * @param string $tableName
+ * @return array
+ */
+ protected function _getPortableTableIndexesList($tableIndexRows, $tableName=null)
+ {
+ $result = array();
+ foreach($tableIndexRows AS $tableIndex) {
+ $indexName = $keyName = $tableIndex['key_name'];
+ if($tableIndex['primary']) {
+ $keyName = 'primary';
+ }
+ $keyName = strtolower($keyName);
+
+ if(!isset($result[$keyName])) {
+ $result[$keyName] = array(
+ 'name' => $indexName,
+ 'columns' => array($tableIndex['column_name']),
+ 'unique' => $tableIndex['non_unique'] ? false : true,
+ 'primary' => $tableIndex['primary'],
+ );
+ } else {
+ $result[$keyName]['columns'][] = $tableIndex['column_name'];
+ }
+ }
+
+ $indexes = array();
+ foreach($result AS $indexKey => $data) {
+ $indexes[$indexKey] = new Index($data['name'], $data['columns'], $data['unique'], $data['primary']);
+ }
+
+ return $indexes;
+ }
+
+ protected function _getPortableTablesList($tables)
+ {
+ $list = array();
+ foreach ($tables as $key => $value) {
+ if ($value = $this->_getPortableTableDefinition($value)) {
+ $list[] = $value;
+ }
+ }
+ return $list;
+ }
+
+ protected function _getPortableTableDefinition($table)
+ {
+ return $table;
+ }
+
+ protected function _getPortableUsersList($users)
+ {
+ $list = array();
+ foreach ($users as $key => $value) {
+ if ($value = $this->_getPortableUserDefinition($value)) {
+ $list[] = $value;
+ }
+ }
+ return $list;
+ }
+
+ protected function _getPortableUserDefinition($user)
+ {
+ return $user;
+ }
+
+ protected function _getPortableViewsList($views)
+ {
+ $list = array();
+ foreach ($views as $key => $value) {
+ if ($view = $this->_getPortableViewDefinition($value)) {
+ $viewName = strtolower($view->getQuotedName($this->_platform));
+ $list[$viewName] = $view;
+ }
+ }
+ return $list;
+ }
+
+ protected function _getPortableViewDefinition($view)
+ {
+ return false;
+ }
+
+ protected function _getPortableTableForeignKeysList($tableForeignKeys)
+ {
+ $list = array();
+ foreach ($tableForeignKeys as $key => $value) {
+ if ($value = $this->_getPortableTableForeignKeyDefinition($value)) {
+ $list[] = $value;
+ }
+ }
+ return $list;
+ }
+
+ protected function _getPortableTableForeignKeyDefinition($tableForeignKey)
+ {
+ return $tableForeignKey;
+ }
+
+ protected function _execSql($sql)
+ {
+ foreach ((array) $sql as $query) {
+ $this->_conn->executeUpdate($query);
+ }
+ }
+
+ /**
+ * Create a schema instance for the current database.
+ *
+ * @return Schema
+ */
+ public function createSchema()
+ {
+ $sequences = array();
+ if($this->_platform->supportsSequences()) {
+ $sequences = $this->listSequences();
+ }
+ $tables = $this->listTables();
+
+ return new Schema($tables, $sequences, $this->createSchemaConfig());
+ }
+
+ /**
+ * Create the configuration for this schema.
+ *
+ * @return SchemaConfig
+ */
+ public function createSchemaConfig()
+ {
+ $schemaConfig = new SchemaConfig();
+ $schemaConfig->setMaxIdentifierLength($this->_platform->getMaxIdentifierLength());
+
+ return $schemaConfig;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Schema;
+
+use \Doctrine\DBAL\Types\Type;
+use Doctrine\DBAL\Schema\Visitor\Visitor;
+
+/**
+ * Object representation of a database column
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class Column extends AbstractAsset
+{
+ /**
+ * @var \Doctrine\DBAL\Types\Type
+ */
+ protected $_type;
+
+ /**
+ * @var int
+ */
+ protected $_length = null;
+
+ /**
+ * @var int
+ */
+ protected $_precision = 10;
+
+ /**
+ * @var int
+ */
+ protected $_scale = 0;
+
+ /**
+ * @var bool
+ */
+ protected $_unsigned = false;
+
+ /**
+ * @var bool
+ */
+ protected $_fixed = false;
+
+ /**
+ * @var bool
+ */
+ protected $_notnull = true;
+
+ /**
+ * @var string
+ */
+ protected $_default = null;
+
+ /**
+ * @var bool
+ */
+ protected $_autoincrement = false;
+
+ /**
+ * @var array
+ */
+ protected $_platformOptions = array();
+
+ /**
+ * @var string
+ */
+ protected $_columnDefinition = null;
+
+ /**
+ * Create a new Column
+ *
+ * @param string $columnName
+ * @param Doctrine\DBAL\Types\Type $type
+ * @param int $length
+ * @param bool $notNull
+ * @param mixed $default
+ * @param bool $unsigned
+ * @param bool $fixed
+ * @param int $precision
+ * @param int $scale
+ * @param array $platformOptions
+ */
+ public function __construct($columnName, Type $type, array $options=array())
+ {
+ $this->_setName($columnName);
+ $this->setType($type);
+ $this->setOptions($options);
+ }
+
+ /**
+ * @param array $options
+ * @return Column
+ */
+ public function setOptions(array $options)
+ {
+ foreach ($options AS $name => $value) {
+ $method = "set".$name;
+ if (method_exists($this, $method)) {
+ $this->$method($value);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * @param Type $type
+ * @return Column
+ */
+ public function setType(Type $type)
+ {
+ $this->_type = $type;
+ return $this;
+ }
+
+ /**
+ * @param int $length
+ * @return Column
+ */
+ public function setLength($length)
+ {
+ if($length !== null) {
+ $this->_length = (int)$length;
+ } else {
+ $this->_length = null;
+ }
+ return $this;
+ }
+
+ /**
+ * @param int $precision
+ * @return Column
+ */
+ public function setPrecision($precision)
+ {
+ $this->_precision = (int)$precision;
+ return $this;
+ }
+
+ /**
+ * @param int $scale
+ * @return Column
+ */
+ public function setScale($scale)
+ {
+ $this->_scale = $scale;
+ return $this;
+ }
+
+ /**
+ *
+ * @param bool $unsigned
+ * @return Column
+ */
+ public function setUnsigned($unsigned)
+ {
+ $this->_unsigned = (bool)$unsigned;
+ return $this;
+ }
+
+ /**
+ *
+ * @param bool $fixed
+ * @return Column
+ */
+ public function setFixed($fixed)
+ {
+ $this->_fixed = (bool)$fixed;
+ return $this;
+ }
+
+ /**
+ * @param bool $notnull
+ * @return Column
+ */
+ public function setNotnull($notnull)
+ {
+ $this->_notnull = (bool)$notnull;
+ return $this;
+ }
+
+ /**
+ *
+ * @param mixed $default
+ * @return Column
+ */
+ public function setDefault($default)
+ {
+ $this->_default = $default;
+ return $this;
+ }
+
+ /**
+ *
+ * @param array $platformOptions
+ * @return Column
+ */
+ public function setPlatformOptions(array $platformOptions)
+ {
+ $this->_platformOptions = $platformOptions;
+ return $this;
+ }
+
+ /**
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return Column
+ */
+ public function setPlatformOption($name, $value)
+ {
+ $this->_platformOptions[$name] = $value;
+ return $this;
+ }
+
+ /**
+ *
+ * @param string
+ * @return Column
+ */
+ public function setColumnDefinition($value)
+ {
+ $this->_columnDefinition = $value;
+ return $this;
+ }
+
+ public function getType()
+ {
+ return $this->_type;
+ }
+
+ public function getLength()
+ {
+ return $this->_length;
+ }
+
+ public function getPrecision()
+ {
+ return $this->_precision;
+ }
+
+ public function getScale()
+ {
+ return $this->_scale;
+ }
+
+ public function getUnsigned()
+ {
+ return $this->_unsigned;
+ }
+
+ public function getFixed()
+ {
+ return $this->_fixed;
+ }
+
+ public function getNotnull()
+ {
+ return $this->_notnull;
+ }
+
+ public function getDefault()
+ {
+ return $this->_default;
+ }
+
+ public function getPlatformOptions()
+ {
+ return $this->_platformOptions;
+ }
+
+ public function hasPlatformOption($name)
+ {
+ return isset($this->_platformOptions[$name]);
+ }
+
+ public function getPlatformOption($name)
+ {
+ return $this->_platformOptions[$name];
+ }
+
+ public function getColumnDefinition()
+ {
+ return $this->_columnDefinition;
+ }
+
+ public function getAutoincrement()
+ {
+ return $this->_autoincrement;
+ }
+
+ public function setAutoincrement($flag)
+ {
+ $this->_autoincrement = $flag;
+ return $this;
+ }
+
+ /**
+ * @param Visitor $visitor
+ */
+ public function visit(\Doctrine\DBAL\Schema\Visitor $visitor)
+ {
+ $visitor->accept($this);
+ }
+
+ /**
+ * @return array
+ */
+ public function toArray()
+ {
+ return array_merge(array(
+ 'name' => $this->_name,
+ 'type' => $this->_type,
+ 'default' => $this->_default,
+ 'notnull' => $this->_notnull,
+ 'length' => $this->_length,
+ 'precision' => $this->_precision,
+ 'scale' => $this->_scale,
+ 'fixed' => $this->_fixed,
+ 'unsigned' => $this->_unsigned,
+ 'autoincrement' => $this->_autoincrement,
+ 'columnDefinition' => $this->_columnDefinition,
+ ), $this->_platformOptions);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Schema;
+
+/**
+ * Represent the change of a column
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class ColumnDiff
+{
+ public $oldColumnName;
+
+ /**
+ * @var Column
+ */
+ public $column;
+
+ /**
+ * @var array
+ */
+ public $changedProperties = array();
+
+ public function __construct($oldColumnName, Column $column, array $changedProperties = array())
+ {
+ $this->oldColumnName = $oldColumnName;
+ $this->column = $column;
+ $this->changedProperties = $changedProperties;
+ }
+
+ public function hasChanged($propertyName)
+ {
+ return in_array($propertyName, $this->changedProperties);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Schema;
+
+/**
+ * Compare to Schemas and return an instance of SchemaDiff
+ *
+ * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved.
+ * @license http://ez.no/licenses/new_bsd New BSD License
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class Comparator
+{
+ /**
+ * @param Schema $fromSchema
+ * @param Schema $toSchema
+ * @return SchemaDiff
+ */
+ static public function compareSchemas( Schema $fromSchema, Schema $toSchema )
+ {
+ $c = new self();
+ return $c->compare($fromSchema, $toSchema);
+ }
+
+ /**
+ * Returns a SchemaDiff object containing the differences between the schemas $fromSchema and $toSchema.
+ *
+ * The returned diferences are returned in such a way that they contain the
+ * operations to change the schema stored in $fromSchema to the schema that is
+ * stored in $toSchema.
+ *
+ * @param Schema $fromSchema
+ * @param Schema $toSchema
+ *
+ * @return SchemaDiff
+ */
+ public function compare(Schema $fromSchema, Schema $toSchema)
+ {
+ $diff = new SchemaDiff();
+
+ $foreignKeysToTable = array();
+
+ foreach ( $toSchema->getTables() AS $tableName => $table ) {
+ if ( !$fromSchema->hasTable($tableName) ) {
+ $diff->newTables[$tableName] = $table;
+ } else {
+ $tableDifferences = $this->diffTable( $fromSchema->getTable($tableName), $table );
+ if ( $tableDifferences !== false ) {
+ $diff->changedTables[$tableName] = $tableDifferences;
+ }
+ }
+ }
+
+ /* Check if there are tables removed */
+ foreach ( $fromSchema->getTables() AS $tableName => $table ) {
+ if ( !$toSchema->hasTable($tableName) ) {
+ $diff->removedTables[$tableName] = $table;
+ }
+
+ // also remember all foreign keys that point to a specific table
+ foreach ($table->getForeignKeys() AS $foreignKey) {
+ $foreignTable = strtolower($foreignKey->getForeignTableName());
+ if (!isset($foreignKeysToTable[$foreignTable])) {
+ $foreignKeysToTable[$foreignTable] = array();
+ }
+ $foreignKeysToTable[$foreignTable][] = $foreignKey;
+ }
+ }
+
+ foreach ($diff->removedTables AS $tableName => $table) {
+ if (isset($foreignKeysToTable[$tableName])) {
+ $diff->orphanedForeignKeys = array_merge($diff->orphanedForeignKeys, $foreignKeysToTable[$tableName]);
+ }
+ }
+
+ foreach ( $toSchema->getSequences() AS $sequenceName => $sequence) {
+ if (!$fromSchema->hasSequence($sequenceName)) {
+ $diff->newSequences[] = $sequence;
+ } else {
+ if ($this->diffSequence($sequence, $fromSchema->getSequence($sequenceName))) {
+ $diff->changedSequences[] = $fromSchema->getSequence($sequenceName);
+ }
+ }
+ }
+
+ foreach ($fromSchema->getSequences() AS $sequenceName => $sequence) {
+ if (!$toSchema->hasSequence($sequenceName)) {
+ $diff->removedSequences[] = $sequence;
+ }
+ }
+
+ return $diff;
+ }
+
+ /**
+ *
+ * @param Sequence $sequence1
+ * @param Sequence $sequence2
+ */
+ public function diffSequence(Sequence $sequence1, Sequence $sequence2)
+ {
+ if($sequence1->getAllocationSize() != $sequence2->getAllocationSize()) {
+ return true;
+ }
+
+ if($sequence1->getInitialValue() != $sequence2->getInitialValue()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the difference between the tables $table1 and $table2.
+ *
+ * If there are no differences this method returns the boolean false.
+ *
+ * @param Table $table1
+ * @param Table $table2
+ *
+ * @return bool|TableDiff
+ */
+ public function diffTable(Table $table1, Table $table2)
+ {
+ $changes = 0;
+ $tableDifferences = new TableDiff($table1->getName());
+
+ $table1Columns = $table1->getColumns();
+ $table2Columns = $table2->getColumns();
+
+ /* See if all the fields in table 1 exist in table 2 */
+ foreach ( $table2Columns as $columnName => $column ) {
+ if ( !$table1->hasColumn($columnName) ) {
+ $tableDifferences->addedColumns[$columnName] = $column;
+ $changes++;
+ }
+ }
+ /* See if there are any removed fields in table 2 */
+ foreach ( $table1Columns as $columnName => $column ) {
+ if ( !$table2->hasColumn($columnName) ) {
+ $tableDifferences->removedColumns[$columnName] = $column;
+ $changes++;
+ }
+ }
+ foreach ( $table1Columns as $columnName => $column ) {
+ if ( $table2->hasColumn($columnName) ) {
+ $changedProperties = $this->diffColumn( $column, $table2->getColumn($columnName) );
+ if (count($changedProperties) ) {
+ $columnDiff = new ColumnDiff($column->getName(), $table2->getColumn($columnName), $changedProperties);
+ $tableDifferences->changedColumns[$column->getName()] = $columnDiff;
+ $changes++;
+ }
+ }
+ }
+
+ $this->detectColumnRenamings($tableDifferences);
+
+ $table1Indexes = $table1->getIndexes();
+ $table2Indexes = $table2->getIndexes();
+
+ foreach ($table2Indexes AS $index2Name => $index2Definition) {
+ foreach ($table1Indexes AS $index1Name => $index1Definition) {
+ if ($this->diffIndex($index1Definition, $index2Definition) === false) {
+ unset($table1Indexes[$index1Name]);
+ unset($table2Indexes[$index2Name]);
+ } else {
+ if ($index1Name == $index2Name) {
+ $tableDifferences->changedIndexes[$index2Name] = $table2Indexes[$index2Name];
+ unset($table1Indexes[$index1Name]);
+ unset($table2Indexes[$index2Name]);
+ $changes++;
+ }
+ }
+ }
+ }
+
+ foreach ($table1Indexes AS $index1Name => $index1Definition) {
+ $tableDifferences->removedIndexes[$index1Name] = $index1Definition;
+ $changes++;
+ }
+
+ foreach ($table2Indexes AS $index2Name => $index2Definition) {
+ $tableDifferences->addedIndexes[$index2Name] = $index2Definition;
+ $changes++;
+ }
+
+ $fromFkeys = $table1->getForeignKeys();
+ $toFkeys = $table2->getForeignKeys();
+
+ foreach ($fromFkeys AS $key1 => $constraint1) {
+ foreach ($toFkeys AS $key2 => $constraint2) {
+ if($this->diffForeignKey($constraint1, $constraint2) === false) {
+ unset($fromFkeys[$key1]);
+ unset($toFkeys[$key2]);
+ } else {
+ if (strtolower($constraint1->getName()) == strtolower($constraint2->getName())) {
+ $tableDifferences->changedForeignKeys[] = $constraint2;
+ $changes++;
+ unset($fromFkeys[$key1]);
+ unset($toFkeys[$key2]);
+ }
+ }
+ }
+ }
+
+ foreach ($fromFkeys AS $key1 => $constraint1) {
+ $tableDifferences->removedForeignKeys[] = $constraint1;
+ $changes++;
+ }
+
+ foreach ($toFkeys AS $key2 => $constraint2) {
+ $tableDifferences->addedForeignKeys[] = $constraint2;
+ $changes++;
+ }
+
+ return $changes ? $tableDifferences : false;
+ }
+
+ /**
+ * Try to find columns that only changed their name, rename operations maybe cheaper than add/drop
+ * however ambiguouties between different possibilites should not lead to renaming at all.
+ *
+ * @param TableDiff $tableDifferences
+ */
+ private function detectColumnRenamings(TableDiff $tableDifferences)
+ {
+ $renameCandidates = array();
+ foreach ($tableDifferences->addedColumns AS $addedColumnName => $addedColumn) {
+ foreach ($tableDifferences->removedColumns AS $removedColumnName => $removedColumn) {
+ if (count($this->diffColumn($addedColumn, $removedColumn)) == 0) {
+ $renameCandidates[$addedColumn->getName()][] = array($removedColumn, $addedColumn);
+ }
+ }
+ }
+
+ foreach ($renameCandidates AS $candidate => $candidateColumns) {
+ if (count($candidateColumns) == 1) {
+ list($removedColumn, $addedColumn) = $candidateColumns[0];
+
+ $tableDifferences->renamedColumns[$removedColumn->getName()] = $addedColumn;
+ unset($tableDifferences->addedColumns[$addedColumnName]);
+ unset($tableDifferences->removedColumns[$removedColumnName]);
+ }
+ }
+ }
+
+ /**
+ * @param ForeignKeyConstraint $key1
+ * @param ForeignKeyConstraint $key2
+ * @return bool
+ */
+ public function diffForeignKey(ForeignKeyConstraint $key1, ForeignKeyConstraint $key2)
+ {
+ if (array_map('strtolower', $key1->getLocalColumns()) != array_map('strtolower', $key2->getLocalColumns())) {
+ return true;
+ }
+
+ if (array_map('strtolower', $key1->getForeignColumns()) != array_map('strtolower', $key2->getForeignColumns())) {
+ return true;
+ }
+
+ if ($key1->onUpdate() != $key2->onUpdate()) {
+ return true;
+ }
+
+ if ($key1->onDelete() != $key2->onDelete()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the difference between the fields $field1 and $field2.
+ *
+ * If there are differences this method returns $field2, otherwise the
+ * boolean false.
+ *
+ * @param Column $column1
+ * @param Column $column2
+ *
+ * @return array
+ */
+ public function diffColumn(Column $column1, Column $column2)
+ {
+ $changedProperties = array();
+ if ( $column1->getType() != $column2->getType() ) {
+ $changedProperties[] = 'type';
+ }
+
+ if ($column1->getNotnull() != $column2->getNotnull()) {
+ $changedProperties[] = 'notnull';
+ }
+
+ if ($column1->getDefault() != $column2->getDefault()) {
+ $changedProperties[] = 'default';
+ }
+
+ if ($column1->getUnsigned() != $column2->getUnsigned()) {
+ $changedProperties[] = 'unsigned';
+ }
+
+ if ($column1->getType() instanceof \Doctrine\DBAL\Types\StringType) {
+ if ($column1->getLength() != $column2->getLength()) {
+ $changedProperties[] = 'length';
+ }
+
+ if ($column1->getFixed() != $column2->getFixed()) {
+ $changedProperties[] = 'fixed';
+ }
+ }
+
+ if ($column1->getType() instanceof \Doctrine\DBAL\Types\DecimalType) {
+ if ($column1->getPrecision() != $column2->getPrecision()) {
+ $changedProperties[] = 'precision';
+ }
+ if ($column1->getScale() != $column2->getScale()) {
+ $changedProperties[] = 'scale';
+ }
+ }
+
+ if ($column1->getAutoincrement() != $column2->getAutoincrement()) {
+ $changedProperties[] = 'autoincrement';
+ }
+
+ return $changedProperties;
+ }
+
+ /**
+ * Finds the difference between the indexes $index1 and $index2.
+ *
+ * Compares $index1 with $index2 and returns $index2 if there are any
+ * differences or false in case there are no differences.
+ *
+ * @param Index $index1
+ * @param Index $index2
+ * @return bool
+ */
+ public function diffIndex(Index $index1, Index $index2)
+ {
+ if ($index1->isFullfilledBy($index2) && $index2->isFullfilledBy($index1)) {
+ return false;
+ }
+ return true;
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Schema;
+
+/**
+ * Marker interface for contraints
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+interface Constraint
+{
+ public function getName();
+
+ public function getColumns();
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\DBAL\Schema;
+
+/**
+ * IBM Db2 Schema Manager
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 1.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class DB2SchemaManager extends AbstractSchemaManager
+{
+ /**
+ * Return a list of all tables in the current database
+ *
+ * Apparently creator is the schema not the user who created it:
+ * {@link http://publib.boulder.ibm.com/infocenter/dzichelp/v2r2/index.jsp?topic=/com.ibm.db29.doc.sqlref/db2z_sysibmsystablestable.htm}
+ *
+ * @return array
+ */
+ public function listTableNames()
+ {
+ $sql = $this->_platform->getListTablesSQL();
+ $sql .= " AND CREATOR = UPPER('".$this->_conn->getUsername()."')";
+
+ $tables = $this->_conn->fetchAll($sql);
+
+ return $this->_getPortableTablesList($tables);
+ }
+
+
+ /**
+ * Get Table Column Definition
+ *
+ * @param array $tableColumn
+ * @return Column
+ */
+ protected function _getPortableTableColumnDefinition($tableColumn)
+ {
+ $tableColumn = array_change_key_case($tableColumn, \CASE_LOWER);
+
+ $length = null;
+ $fixed = null;
+ $unsigned = false;
+ $scale = false;
+ $precision = false;
+
+ $type = $this->_platform->getDoctrineTypeMapping($tableColumn['typename']);
+
+ switch (strtolower($tableColumn['typename'])) {
+ case 'varchar':
+ $length = $tableColumn['length'];
+ $fixed = false;
+ break;
+ case 'character':
+ $length = $tableColumn['length'];
+ $fixed = true;
+ break;
+ case 'clob':
+ $length = $tableColumn['length'];
+ break;
+ case 'decimal':
+ case 'double':
+ case 'real':
+ $scale = $tableColumn['scale'];
+ $precision = $tableColumn['length'];
+ break;
+ }
+
+ $options = array(
+ 'length' => $length,
+ 'unsigned' => (bool)$unsigned,
+ 'fixed' => (bool)$fixed,
+ 'default' => ($tableColumn['default'] == "NULL") ? null : $tableColumn['default'],
+ 'notnull' => (bool) ($tableColumn['nulls'] == 'N'),
+ 'scale' => null,
+ 'precision' => null,
+ 'platformOptions' => array(),
+ );
+
+ if ($scale !== null && $precision !== null) {
+ $options['scale'] = $scale;
+ $options['precision'] = $precision;
+ }
+
+ return new Column($tableColumn['colname'], \Doctrine\DBAL\Types\Type::getType($type), $options);
+ }
+
+ protected function _getPortableTablesList($tables)
+ {
+ $tableNames = array();
+ foreach ($tables AS $tableRow) {
+ $tableRow = array_change_key_case($tableRow, \CASE_LOWER);
+ $tableNames[] = $tableRow['name'];
+ }
+ return $tableNames;
+ }
+
+ protected function _getPortableTableIndexesList($tableIndexes, $tableName=null)
+ {
+ $tableIndexRows = array();
+ $indexes = array();
+ foreach($tableIndexes AS $indexKey => $data) {
+ $data = array_change_key_case($data, \CASE_LOWER);
+ $unique = ($data['uniquerule'] == "D") ? false : true;
+ $primary = ($data['uniquerule'] == "P");
+
+ $indexName = strtolower($data['name']);
+ if ($primary) {
+ $keyName = 'primary';
+ } else {
+ $keyName = $indexName;
+ }
+
+ $indexes[$keyName] = new Index($indexName, explode("+", ltrim($data['colnames'], '+')), $unique, $primary);
+ }
+
+ return $indexes;
+ }
+
+ protected function _getPortableTableForeignKeyDefinition($tableForeignKey)
+ {
+ $tableForeignKey = array_change_key_case($tableForeignKey, CASE_LOWER);
+
+ $tableForeignKey['deleterule'] = $this->_getPortableForeignKeyRuleDef($tableForeignKey['deleterule']);
+ $tableForeignKey['updaterule'] = $this->_getPortableForeignKeyRuleDef($tableForeignKey['updaterule']);
+
+ return new ForeignKeyConstraint(
+ array_map('trim', (array)$tableForeignKey['fkcolnames']),
+ $tableForeignKey['reftbname'],
+ array_map('trim', (array)$tableForeignKey['pkcolnames']),
+ $tableForeignKey['relname'],
+ array(
+ 'onUpdate' => $tableForeignKey['updaterule'],
+ 'onDelete' => $tableForeignKey['deleterule'],
+ )
+ );
+ }
+
+ protected function _getPortableForeignKeyRuleDef($def)
+ {
+ if ($def == "C") {
+ return "CASCADE";
+ } else if ($def == "N") {
+ return "SET NULL";
+ }
+ return null;
+ }
+
+ protected function _getPortableViewDefinition($view)
+ {
+ $view = array_change_key_case($view, \CASE_LOWER);
+ // sadly this still segfaults on PDO_IBM, see http://pecl.php.net/bugs/bug.php?id=17199
+ //$view['text'] = (is_resource($view['text']) ? stream_get_contents($view['text']) : $view['text']);
+ if (!is_resource($view['text'])) {
+ $pos = strpos($view['text'], ' AS ');
+ $sql = substr($view['text'], $pos+4);
+ } else {
+ $sql = '';
+ }
+
+ return new View($view['name'], $sql);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Schema;
+
+use Doctrine\DBAL\Schema\Visitor\Visitor;
+
+class ForeignKeyConstraint extends AbstractAsset implements Constraint
+{
+ /**
+ * @var Table
+ */
+ protected $_localTable;
+
+ /**
+ * @var array
+ */
+ protected $_localColumnNames;
+
+ /**
+ * @var string
+ */
+ protected $_foreignTableName;
+
+ /**
+ * @var array
+ */
+ protected $_foreignColumnNames;
+
+ /**
+ * @var string
+ */
+ protected $_cascade = '';
+
+ /**
+ * @var array
+ */
+ protected $_options;
+
+ /**
+ *
+ * @param array $localColumnNames
+ * @param string $foreignTableName
+ * @param array $foreignColumnNames
+ * @param string $cascade
+ * @param string|null $name
+ */
+ public function __construct(array $localColumnNames, $foreignTableName, array $foreignColumnNames, $name=null, array $options=array())
+ {
+ $this->_setName($name);
+ $this->_localColumnNames = $localColumnNames;
+ $this->_foreignTableName = $foreignTableName;
+ $this->_foreignColumnNames = $foreignColumnNames;
+ $this->_options = $options;
+ }
+
+ /**
+ * @return string
+ */
+ public function getLocalTableName()
+ {
+ return $this->_localTable->getName();
+ }
+
+ /**
+ * @param Table $table
+ */
+ public function setLocalTable(Table $table)
+ {
+ $this->_localTable = $table;
+ }
+
+ /**
+ * @return array
+ */
+ public function getLocalColumns()
+ {
+ return $this->_localColumnNames;
+ }
+
+ public function getColumns()
+ {
+ return $this->_localColumnNames;
+ }
+
+ /**
+ * @return string
+ */
+ public function getForeignTableName()
+ {
+ return $this->_foreignTableName;
+ }
+
+ /**
+ * @return array
+ */
+ public function getForeignColumns()
+ {
+ return $this->_foreignColumnNames;
+ }
+
+ public function hasOption($name)
+ {
+ return isset($this->_options[$name]);
+ }
+
+ public function getOption($name)
+ {
+ return $this->_options[$name];
+ }
+
+ /**
+ * Foreign Key onUpdate status
+ *
+ * @return string|null
+ */
+ public function onUpdate()
+ {
+ return $this->_onEvent('onUpdate');
+ }
+
+ /**
+ * Foreign Key onDelete status
+ *
+ * @return string|null
+ */
+ public function onDelete()
+ {
+ return $this->_onEvent('onDelete');
+ }
+
+ /**
+ * @param string $event
+ * @return string|null
+ */
+ private function _onEvent($event)
+ {
+ if (isset($this->_options[$event])) {
+ $onEvent = strtoupper($this->_options[$event]);
+ if (!in_array($onEvent, array('NO ACTION', 'RESTRICT'))) {
+ return $onEvent;
+ }
+ }
+ return false;
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Schema;
+
+use Doctrine\DBAL\Schema\Visitor\Visitor;
+
+class Index extends AbstractAsset implements Constraint
+{
+ /**
+ * @var array
+ */
+ protected $_columns;
+
+ /**
+ * @var bool
+ */
+ protected $_isUnique = false;
+
+ /**
+ * @var bool
+ */
+ protected $_isPrimary = false;
+
+ /**
+ * @param string $indexName
+ * @param array $column
+ * @param bool $isUnique
+ * @param bool $isPrimary
+ */
+ public function __construct($indexName, array $columns, $isUnique=false, $isPrimary=false)
+ {
+ $isUnique = ($isPrimary)?true:$isUnique;
+
+ $this->_setName($indexName);
+ $this->_isUnique = $isUnique;
+ $this->_isPrimary = $isPrimary;
+
+ foreach($columns AS $column) {
+ $this->_addColumn($column);
+ }
+ }
+
+ /**
+ * @param string $column
+ */
+ protected function _addColumn($column)
+ {
+ if(is_string($column)) {
+ $this->_columns[] = $column;
+ } else {
+ throw new \InvalidArgumentException("Expecting a string as Index Column");
+ }
+ }
+
+ /**
+ * @return array
+ */
+ public function getColumns()
+ {
+ return $this->_columns;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isUnique()
+ {
+ return $this->_isUnique;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isPrimary()
+ {
+ return $this->_isPrimary;
+ }
+
+ /**
+ * @param string $columnName
+ * @param int $pos
+ * @return bool
+ */
+ public function hasColumnAtPosition($columnName, $pos=0)
+ {
+ $columnName = strtolower($columnName);
+ $indexColumns = \array_map('strtolower', $this->getColumns());
+ return \array_search($columnName, $indexColumns) === $pos;
+ }
+
+ /**
+ * Check if this index exactly spans the given column names in the correct order.
+ *
+ * @param array $columnNames
+ * @return boolean
+ */
+ public function spansColumns(array $columnNames)
+ {
+ $sameColumns = true;
+ for ($i = 0; $i < count($this->_columns); $i++) {
+ if (!isset($columnNames[$i]) || strtolower($this->_columns[$i]) != strtolower($columnNames[$i])) {
+ $sameColumns = false;
+ }
+ }
+ return $sameColumns;
+ }
+
+ /**
+ * Check if the other index already fullfills all the indexing and constraint needs of the current one.
+ *
+ * @param Index $other
+ * @return bool
+ */
+ public function isFullfilledBy(Index $other)
+ {
+ // allow the other index to be equally large only. It being larger is an option
+ // but it creates a problem with scenarios of the kind PRIMARY KEY(foo,bar) UNIQUE(foo)
+ if (count($other->getColumns()) != count($this->getColumns())) {
+ return false;
+ }
+
+ // Check if columns are the same, and even in the same order
+ $sameColumns = $this->spansColumns($other->getColumns());
+
+ if ($sameColumns) {
+ if (!$this->isUnique() && !$this->isPrimary()) {
+ // this is a special case: If the current key is neither primary or unique, any uniqe or
+ // primary key will always have the same effect for the index and there cannot be any constraint
+ // overlaps. This means a primary or unique index can always fullfill the requirements of just an
+ // index that has no constraints.
+ return true;
+ } else if ($other->isPrimary() != $this->isPrimary()) {
+ return false;
+ } else if ($other->isUnique() != $this->isUnique()) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Detect if the other index is a non-unique, non primary index that can be overwritten by this one.
+ *
+ * @param Index $other
+ * @return bool
+ */
+ public function overrules(Index $other)
+ {
+ if ($other->isPrimary() || $other->isUnique()) {
+ return false;
+ }
+
+ if ($this->spansColumns($other->getColumns()) && ($this->isPrimary() || $this->isUnique())) {
+ return true;
+ }
+ return false;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.phpdoctrine.org>.
+ */
+
+namespace Doctrine\DBAL\Schema;
+
+/**
+ * xxx
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
+ * @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
+ * @author Juozas Kaziukenas <juozas@juokaz.com>
+ * @version $Revision$
+ * @since 2.0
+ */
+class MsSqlSchemaManager extends AbstractSchemaManager
+{
+
+ /**
+ * @override
+ */
+ protected function _getPortableTableColumnDefinition($tableColumn)
+ {
+ $dbType = strtolower($tableColumn['TYPE_NAME']);
+
+ $autoincrement = false;
+ if (stripos($dbType, 'identity')) {
+ $dbType = trim(str_ireplace('identity', '', $dbType));
+ $autoincrement = true;
+ }
+
+ $type = array();
+ $unsigned = $fixed = null;
+
+ if (!isset($tableColumn['name'])) {
+ $tableColumn['name'] = '';
+ }
+
+ $default = $tableColumn['COLUMN_DEF'];
+
+ while ($default != ($default2 = preg_replace("/^\((.*)\)$/", '$1', $default))) {
+ $default = $default2;
+ }
+
+ $length = (int) $tableColumn['LENGTH'];
+
+ $type = $this->_platform->getDoctrineTypeMapping($dbType);
+ switch ($type) {
+ case 'char':
+ if ($tableColumn['LENGTH'] == '1') {
+ $type = 'boolean';
+ if (preg_match('/^(is|has)/', $tableColumn['name'])) {
+ $type = array_reverse($type);
+ }
+ }
+ $fixed = true;
+ break;
+ case 'text':
+ $fixed = false;
+ break;
+ }
+ switch ($dbType) {
+ case 'nchar':
+ case 'nvarchar':
+ case 'ntext':
+ // Unicode data requires 2 bytes per character
+ $length = $length / 2;
+ break;
+ }
+
+ $options = array(
+ 'length' => ($length == 0 || !in_array($type, array('text', 'string'))) ? null : $length,
+ 'unsigned' => (bool) $unsigned,
+ 'fixed' => (bool) $fixed,
+ 'default' => $default !== 'NULL' ? $default : null,
+ 'notnull' => (bool) ($tableColumn['IS_NULLABLE'] != 'YES'),
+ 'scale' => $tableColumn['SCALE'],
+ 'precision' => $tableColumn['PRECISION'],
+ 'autoincrement' => $autoincrement,
+ );
+
+ return new Column($tableColumn['COLUMN_NAME'], \Doctrine\DBAL\Types\Type::getType($type), $options);
+ }
+
+ /**
+ * @override
+ */
+ protected function _getPortableTableIndexesList($tableIndexRows, $tableName=null)
+ {
+ $result = array();
+ foreach ($tableIndexRows AS $tableIndex) {
+ $indexName = $keyName = $tableIndex['index_name'];
+ if (strpos($tableIndex['index_description'], 'primary key') !== false) {
+ $keyName = 'primary';
+ }
+ $keyName = strtolower($keyName);
+
+ $result[$keyName] = array(
+ 'name' => $indexName,
+ 'columns' => explode(', ', $tableIndex['index_keys']),
+ 'unique' => strpos($tableIndex['index_description'], 'unique') !== false,
+ 'primary' => strpos($tableIndex['index_description'], 'primary key') !== false,
+ );
+ }
+
+ $indexes = array();
+ foreach ($result AS $indexKey => $data) {
+ $indexes[$indexKey] = new Index($data['name'], $data['columns'], $data['unique'], $data['primary']);
+ }
+
+ return $indexes;
+ }
+
+ /**
+ * @override
+ */
+ public function _getPortableTableForeignKeyDefinition($tableForeignKey)
+ {
+ return new ForeignKeyConstraint(
+ (array) $tableForeignKey['ColumnName'],
+ $tableForeignKey['ReferenceTableName'],
+ (array) $tableForeignKey['ReferenceColumnName'],
+ $tableForeignKey['ForeignKey'],
+ array(
+ 'onUpdate' => str_replace('_', ' ', $tableForeignKey['update_referential_action_desc']),
+ 'onDelete' => str_replace('_', ' ', $tableForeignKey['delete_referential_action_desc']),
+ )
+ );
+ }
+
+ /**
+ * @override
+ */
+ protected function _getPortableTableDefinition($table)
+ {
+ return $table['name'];
+ }
+
+ /**
+ * @override
+ */
+ protected function _getPortableDatabaseDefinition($database)
+ {
+ return $database['name'];
+ }
+
+ /**
+ * @override
+ */
+ protected function _getPortableViewDefinition($view)
+ {
+ // @todo
+ return new View($view['name'], null);
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Schema;
+
+/**
+ * Schema manager for the MySql RDBMS.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
+ * @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @version $Revision$
+ * @since 2.0
+ */
+class MySqlSchemaManager extends AbstractSchemaManager
+{
+ protected function _getPortableViewDefinition($view)
+ {
+ return new View($view['TABLE_NAME'], $view['VIEW_DEFINITION']);
+ }
+
+ protected function _getPortableTableDefinition($table)
+ {
+ return array_shift($table);
+ }
+
+ protected function _getPortableUserDefinition($user)
+ {
+ return array(
+ 'user' => $user['User'],
+ 'password' => $user['Password'],
+ );
+ }
+
+ protected function _getPortableTableIndexesList($tableIndexes, $tableName=null)
+ {
+ foreach($tableIndexes AS $k => $v) {
+ $v = array_change_key_case($v, CASE_LOWER);
+ if($v['key_name'] == 'PRIMARY') {
+ $v['primary'] = true;
+ } else {
+ $v['primary'] = false;
+ }
+ $tableIndexes[$k] = $v;
+ }
+
+ return parent::_getPortableTableIndexesList($tableIndexes, $tableName);
+ }
+
+ protected function _getPortableSequenceDefinition($sequence)
+ {
+ return end($sequence);
+ }
+
+ protected function _getPortableDatabaseDefinition($database)
+ {
+ return $database['Database'];
+ }
+
+ /**
+ * Gets a portable column definition.
+ *
+ * The database type is mapped to a corresponding Doctrine mapping type.
+ *
+ * @param $tableColumn
+ * @return array
+ */
+ protected function _getPortableTableColumnDefinition($tableColumn)
+ {
+ $tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
+
+ $dbType = strtolower($tableColumn['type']);
+ $dbType = strtok($dbType, '(), ');
+ if (isset($tableColumn['length'])) {
+ $length = $tableColumn['length'];
+ $decimal = '';
+ } else {
+ $length = strtok('(), ');
+ $decimal = strtok('(), ') ? strtok('(), '):null;
+ }
+ $type = array();
+ $unsigned = $fixed = null;
+
+ if ( ! isset($tableColumn['name'])) {
+ $tableColumn['name'] = '';
+ }
+
+ $scale = null;
+ $precision = null;
+
+ $type = $this->_platform->getDoctrineTypeMapping($dbType);
+ switch ($dbType) {
+ case 'char':
+ $fixed = true;
+ break;
+ case 'float':
+ case 'double':
+ case 'real':
+ case 'numeric':
+ case 'decimal':
+ if(preg_match('([A-Za-z]+\(([0-9]+)\,([0-9]+)\))', $tableColumn['type'], $match)) {
+ $precision = $match[1];
+ $scale = $match[2];
+ $length = null;
+ }
+ break;
+ case 'tinyint':
+ case 'smallint':
+ case 'mediumint':
+ case 'int':
+ case 'integer':
+ case 'bigint':
+ case 'tinyblob':
+ case 'mediumblob':
+ case 'longblob':
+ case 'blob':
+ case 'binary':
+ case 'varbinary':
+ case 'year':
+ $length = null;
+ break;
+ }
+
+ $length = ((int) $length == 0) ? null : (int) $length;
+ $def = array(
+ 'type' => $type,
+ 'length' => $length,
+ 'unsigned' => (bool) $unsigned,
+ 'fixed' => (bool) $fixed
+ );
+
+ $options = array(
+ 'length' => $length,
+ 'unsigned' => (bool)$unsigned,
+ 'fixed' => (bool)$fixed,
+ 'default' => $tableColumn['default'],
+ 'notnull' => (bool) ($tableColumn['null'] != 'YES'),
+ 'scale' => null,
+ 'precision' => null,
+ 'autoincrement' => (bool) (strpos($tableColumn['extra'], 'auto_increment') !== false),
+ );
+
+ if ($scale !== null && $precision !== null) {
+ $options['scale'] = $scale;
+ $options['precision'] = $precision;
+ }
+
+ return new Column($tableColumn['field'], \Doctrine\DBAL\Types\Type::getType($type), $options);
+ }
+
+ public function _getPortableTableForeignKeyDefinition($tableForeignKey)
+ {
+ $tableForeignKey = array_change_key_case($tableForeignKey, CASE_LOWER);
+
+ if (!isset($tableForeignKey['delete_rule']) || $tableForeignKey['delete_rule'] == "RESTRICT") {
+ $tableForeignKey['delete_rule'] = null;
+ }
+ if (!isset($tableForeignKey['update_rule']) || $tableForeignKey['update_rule'] == "RESTRICT") {
+ $tableForeignKey['update_rule'] = null;
+ }
+
+ return new ForeignKeyConstraint(
+ (array)$tableForeignKey['column_name'],
+ $tableForeignKey['referenced_table_name'],
+ (array)$tableForeignKey['referenced_column_name'],
+ $tableForeignKey['constraint_name'],
+ array(
+ 'onUpdate' => $tableForeignKey['update_rule'],
+ 'onDelete' => $tableForeignKey['delete_rule'],
+ )
+ );
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.phpdoctrine.org>.
+ */
+
+namespace Doctrine\DBAL\Schema;
+
+/**
+ * Oracle Schema Manager
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
+ * @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @version $Revision$
+ * @since 2.0
+ */
+class OracleSchemaManager extends AbstractSchemaManager
+{
+ protected function _getPortableViewDefinition($view)
+ {
+ $view = \array_change_key_case($view, CASE_LOWER);
+
+ return new View($view['view_name'], $view['text']);
+ }
+
+ protected function _getPortableUserDefinition($user)
+ {
+ $user = \array_change_key_case($user, CASE_LOWER);
+
+ return array(
+ 'user' => $user['username'],
+ );
+ }
+
+ protected function _getPortableTableDefinition($table)
+ {
+ $table = \array_change_key_case($table, CASE_LOWER);
+
+ return $table['table_name'];
+ }
+
+ /**
+ * @license New BSD License
+ * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
+ * @param array $tableIndexes
+ * @param string $tableName
+ * @return array
+ */
+ protected function _getPortableTableIndexesList($tableIndexes, $tableName=null)
+ {
+ $indexBuffer = array();
+ foreach ( $tableIndexes as $tableIndex ) {
+ $tableIndex = \array_change_key_case($tableIndex, CASE_LOWER);
+
+ $keyName = strtolower($tableIndex['name']);
+
+ if ( strtolower($tableIndex['is_primary']) == "p" ) {
+ $keyName = 'primary';
+ $buffer['primary'] = true;
+ $buffer['non_unique'] = false;
+ } else {
+ $buffer['primary'] = false;
+ $buffer['non_unique'] = ( $tableIndex['is_unique'] == 0 ) ? true : false;
+ }
+ $buffer['key_name'] = $keyName;
+ $buffer['column_name'] = $tableIndex['column_name'];
+ $indexBuffer[] = $buffer;
+ }
+ return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
+ }
+
+ protected function _getPortableTableColumnDefinition($tableColumn)
+ {
+ $tableColumn = \array_change_key_case($tableColumn, CASE_LOWER);
+
+ $dbType = strtolower($tableColumn['data_type']);
+ if(strpos($dbType, "timestamp(") === 0) {
+ if (strpos($dbType, "WITH TIME ZONE")) {
+ $dbType = "timestamptz";
+ } else {
+ $dbType = "timestamp";
+ }
+ }
+
+ $type = array();
+ $length = $unsigned = $fixed = null;
+ if ( ! empty($tableColumn['data_length'])) {
+ $length = $tableColumn['data_length'];
+ }
+
+ if ( ! isset($tableColumn['column_name'])) {
+ $tableColumn['column_name'] = '';
+ }
+
+ if (stripos($tableColumn['data_default'], 'NULL') !== null) {
+ $tableColumn['data_default'] = null;
+ }
+
+ $precision = null;
+ $scale = null;
+
+ $type = $this->_platform->getDoctrineTypeMapping($dbType);
+ switch ($dbType) {
+ case 'number':
+ if ($tableColumn['data_precision'] == 20 && $tableColumn['data_scale'] == 0) {
+ $precision = 20;
+ $scale = 0;
+ $type = 'bigint';
+ } elseif ($tableColumn['data_precision'] == 5 && $tableColumn['data_scale'] == 0) {
+ $type = 'smallint';
+ $precision = 5;
+ $scale = 0;
+ } elseif ($tableColumn['data_precision'] == 1 && $tableColumn['data_scale'] == 0) {
+ $precision = 1;
+ $scale = 0;
+ $type = 'boolean';
+ } elseif ($tableColumn['data_scale'] > 0) {
+ $precision = $tableColumn['data_precision'];
+ $scale = $tableColumn['data_scale'];
+ $type = 'decimal';
+ }
+ $length = null;
+ break;
+ case 'pls_integer':
+ case 'binary_integer':
+ $length = null;
+ break;
+ case 'varchar':
+ case 'varchar2':
+ case 'nvarchar2':
+ $fixed = false;
+ break;
+ case 'char':
+ case 'nchar':
+ $fixed = true;
+ break;
+ case 'date':
+ case 'timestamp':
+ $length = null;
+ break;
+ case 'float':
+ $precision = $tableColumn['data_precision'];
+ $scale = $tableColumn['data_scale'];
+ $length = null;
+ break;
+ case 'clob':
+ case 'nclob':
+ $length = null;
+ break;
+ case 'blob':
+ case 'raw':
+ case 'long raw':
+ case 'bfile':
+ $length = null;
+ break;
+ case 'rowid':
+ case 'urowid':
+ default:
+ $length = null;
+ }
+
+ $options = array(
+ 'notnull' => (bool) ($tableColumn['nullable'] === 'N'),
+ 'fixed' => (bool) $fixed,
+ 'unsigned' => (bool) $unsigned,
+ 'default' => $tableColumn['data_default'],
+ 'length' => $length,
+ 'precision' => $precision,
+ 'scale' => $scale,
+ 'platformDetails' => array(),
+ );
+
+ return new Column($tableColumn['column_name'], \Doctrine\DBAL\Types\Type::getType($type), $options);
+ }
+
+ protected function _getPortableTableForeignKeysList($tableForeignKeys)
+ {
+ $list = array();
+ foreach ($tableForeignKeys as $key => $value) {
+ $value = \array_change_key_case($value, CASE_LOWER);
+ if (!isset($list[$value['constraint_name']])) {
+ if ($value['delete_rule'] == "NO ACTION") {
+ $value['delete_rule'] = null;
+ }
+
+ $list[$value['constraint_name']] = array(
+ 'name' => $value['constraint_name'],
+ 'local' => array(),
+ 'foreign' => array(),
+ 'foreignTable' => $value['references_table'],
+ 'onDelete' => $value['delete_rule'],
+ );
+ }
+ $list[$value['constraint_name']]['local'][$value['position']] = $value['local_column'];
+ $list[$value['constraint_name']]['foreign'][$value['position']] = $value['foreign_column'];
+ }
+
+ $result = array();
+ foreach($list AS $constraint) {
+ $result[] = new ForeignKeyConstraint(
+ array_values($constraint['local']), $constraint['foreignTable'],
+ array_values($constraint['foreign']), $constraint['name'],
+ array('onDelete' => $constraint['onDelete'])
+ );
+ }
+
+ return $result;
+ }
+
+ protected function _getPortableSequenceDefinition($sequence)
+ {
+ $sequence = \array_change_key_case($sequence, CASE_LOWER);
+ return new Sequence($sequence['sequence_name'], $sequence['increment_by'], $sequence['min_value']);
+ }
+
+ protected function _getPortableFunctionDefinition($function)
+ {
+ $function = \array_change_key_case($function, CASE_LOWER);
+ return $function['name'];
+ }
+
+ protected function _getPortableDatabaseDefinition($database)
+ {
+ $database = \array_change_key_case($database, CASE_LOWER);
+ return $database['username'];
+ }
+
+ public function createDatabase($database = null)
+ {
+ if (is_null($database)) {
+ $database = $this->_conn->getDatabase();
+ }
+
+ $params = $this->_conn->getParams();
+ $username = $database;
+ $password = $params['password'];
+
+ $query = 'CREATE USER ' . $username . ' IDENTIFIED BY ' . $password;
+ $result = $this->_conn->executeUpdate($query);
+
+ $query = 'GRANT CREATE SESSION, CREATE TABLE, UNLIMITED TABLESPACE, CREATE SEQUENCE, CREATE TRIGGER TO ' . $username;
+ $result = $this->_conn->executeUpdate($query);
+
+ return true;
+ }
+
+ public function dropAutoincrement($table)
+ {
+ $sql = $this->_platform->getDropAutoincrementSql($table);
+ foreach ($sql as $query) {
+ $this->_conn->executeUpdate($query);
+ }
+
+ return true;
+ }
+
+ public function dropTable($name)
+ {
+ $this->dropAutoincrement($name);
+
+ return parent::dropTable($name);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Schema;
+
+/**
+ * xxx
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
+ * @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @version $Revision$
+ * @since 2.0
+ */
+class PostgreSqlSchemaManager extends AbstractSchemaManager
+{
+
+ protected function _getPortableTableForeignKeyDefinition($tableForeignKey)
+ {
+ $onUpdate = null;
+ $onDelete = null;
+
+ if (preg_match('(ON UPDATE ([a-zA-Z0-9]+))', $tableForeignKey['condef'], $match)) {
+ $onUpdate = $match[1];
+ }
+ if (preg_match('(ON DELETE ([a-zA-Z0-9]+))', $tableForeignKey['condef'], $match)) {
+ $onDelete = $match[1];
+ }
+
+ if (preg_match('/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)/', $tableForeignKey['condef'], $values)) {
+ $localColumns = array_map('trim', explode(",", $values[1]));
+ $foreignColumns = array_map('trim', explode(",", $values[3]));
+ $foreignTable = $values[2];
+ }
+
+ return new ForeignKeyConstraint(
+ $localColumns, $foreignTable, $foreignColumns, $tableForeignKey['conname'],
+ array('onUpdate' => $onUpdate, 'onDelete' => $onDelete)
+ );
+ }
+
+ public function dropDatabase($database)
+ {
+ $params = $this->_conn->getParams();
+ $params["dbname"] = "postgres";
+ $tmpPlatform = $this->_platform;
+ $tmpConn = $this->_conn;
+
+ $this->_conn = \Doctrine\DBAL\DriverManager::getConnection($params);
+ $this->_platform = $this->_conn->getDatabasePlatform();
+
+ parent::dropDatabase($database);
+
+ $this->_platform = $tmpPlatform;
+ $this->_conn = $tmpConn;
+ }
+
+ public function createDatabase($database)
+ {
+ $params = $this->_conn->getParams();
+ $params["dbname"] = "postgres";
+ $tmpPlatform = $this->_platform;
+ $tmpConn = $this->_conn;
+
+ $this->_conn = \Doctrine\DBAL\DriverManager::getConnection($params);
+ $this->_platform = $this->_conn->getDatabasePlatform();
+
+ parent::createDatabase($database);
+
+ $this->_platform = $tmpPlatform;
+ $this->_conn = $tmpConn;
+ }
+
+ protected function _getPortableTriggerDefinition($trigger)
+ {
+ return $trigger['trigger_name'];
+ }
+
+ protected function _getPortableViewDefinition($view)
+ {
+ return new View($view['viewname'], $view['definition']);
+ }
+
+ protected function _getPortableUserDefinition($user)
+ {
+ return array(
+ 'user' => $user['usename'],
+ 'password' => $user['passwd']
+ );
+ }
+
+ protected function _getPortableTableDefinition($table)
+ {
+ return $table['table_name'];
+ }
+
+ /**
+ * @license New BSD License
+ * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
+ * @param array $tableIndexes
+ * @param string $tableName
+ * @return array
+ */
+ protected function _getPortableTableIndexesList($tableIndexes, $tableName=null)
+ {
+ $buffer = array();
+ foreach ($tableIndexes AS $row) {
+ $colNumbers = explode(' ', $row['indkey']);
+ $colNumbersSql = 'IN (' . join(' ,', $colNumbers) . ' )';
+ $columnNameSql = "SELECT attnum, attname FROM pg_attribute
+ WHERE attrelid={$row['indrelid']} AND attnum $colNumbersSql ORDER BY attnum ASC;";
+
+ $stmt = $this->_conn->executeQuery($columnNameSql);
+ $indexColumns = $stmt->fetchAll();
+
+ // required for getting the order of the columns right.
+ foreach ($colNumbers AS $colNum) {
+ foreach ($indexColumns as $colRow) {
+ if ($colNum == $colRow['attnum']) {
+ $buffer[] = array(
+ 'key_name' => $row['relname'],
+ 'column_name' => trim($colRow['attname']),
+ 'non_unique' => !$row['indisunique'],
+ 'primary' => $row['indisprimary']
+ );
+ }
+ }
+ }
+ }
+ return parent::_getPortableTableIndexesList($buffer);
+ }
+
+ protected function _getPortableDatabaseDefinition($database)
+ {
+ return $database['datname'];
+ }
+
+ protected function _getPortableSequenceDefinition($sequence)
+ {
+ $data = $this->_conn->fetchAll('SELECT min_value, increment_by FROM ' . $sequence['relname']);
+ return new Sequence($sequence['relname'], $data[0]['increment_by'], $data[0]['min_value']);
+ }
+
+ protected function _getPortableTableColumnDefinition($tableColumn)
+ {
+ $tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
+
+ if (strtolower($tableColumn['type']) === 'varchar') {
+ // get length from varchar definition
+ $length = preg_replace('~.*\(([0-9]*)\).*~', '$1', $tableColumn['complete_type']);
+ $tableColumn['length'] = $length;
+ }
+
+ $matches = array();
+
+ $autoincrement = false;
+ if (preg_match("/^nextval\('(.*)'(::.*)?\)$/", $tableColumn['default'], $matches)) {
+ $tableColumn['sequence'] = $matches[1];
+ $tableColumn['default'] = null;
+ $autoincrement = true;
+ }
+
+ if (stripos($tableColumn['default'], 'NULL') === 0) {
+ $tableColumn['default'] = null;
+ }
+
+ $length = (isset($tableColumn['length'])) ? $tableColumn['length'] : null;
+ if ($length == '-1' && isset($tableColumn['atttypmod'])) {
+ $length = $tableColumn['atttypmod'] - 4;
+ }
+ if ((int) $length <= 0) {
+ $length = null;
+ }
+ $type = array();
+ $fixed = null;
+
+ if (!isset($tableColumn['name'])) {
+ $tableColumn['name'] = '';
+ }
+
+ $precision = null;
+ $scale = null;
+
+ if ($this->_platform->hasDoctrineTypeMappingFor($tableColumn['type'])) {
+ $dbType = strtolower($tableColumn['type']);
+ } else {
+ $dbType = strtolower($tableColumn['domain_type']);
+ $tableColumn['complete_type'] = $tableColumn['domain_complete_type'];
+ }
+
+ $type = $this->_platform->getDoctrineTypeMapping($dbType);
+ switch ($dbType) {
+ case 'smallint':
+ case 'int2':
+ $length = null;
+ break;
+ case 'int':
+ case 'int4':
+ case 'integer':
+ $length = null;
+ break;
+ case 'bigint':
+ case 'int8':
+ $length = null;
+ break;
+ case 'bool':
+ case 'boolean':
+ $length = null;
+ break;
+ case 'text':
+ $fixed = false;
+ break;
+ case 'varchar':
+ case 'interval':
+ case '_varchar':
+ $fixed = false;
+ break;
+ case 'char':
+ case 'bpchar':
+ $fixed = true;
+ break;
+ case 'float':
+ case 'float4':
+ case 'float8':
+ case 'double':
+ case 'double precision':
+ case 'real':
+ case 'decimal':
+ case 'money':
+ case 'numeric':
+ if (preg_match('([A-Za-z]+\(([0-9]+)\,([0-9]+)\))', $tableColumn['complete_type'], $match)) {
+ $precision = $match[1];
+ $scale = $match[2];
+ $length = null;
+ }
+ break;
+ case 'year':
+ $length = null;
+ break;
+ }
+
+ $options = array(
+ 'length' => $length,
+ 'notnull' => (bool) $tableColumn['isnotnull'],
+ 'default' => $tableColumn['default'],
+ 'primary' => (bool) ($tableColumn['pri'] == 't'),
+ 'precision' => $precision,
+ 'scale' => $scale,
+ 'fixed' => $fixed,
+ 'unsigned' => false,
+ 'autoincrement' => $autoincrement,
+ );
+
+ return new Column($tableColumn['field'], \Doctrine\DBAL\Types\Type::getType($type), $options);
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Schema;
+
+use Doctrine\DBAL\Schema\Visitor\CreateSchemaSqlCollector;
+use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector;
+use Doctrine\DBAL\Schema\Visitor\Visitor;
+
+/**
+ * Object representation of a database schema
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class Schema extends AbstractAsset
+{
+ /**
+ * @var array
+ */
+ protected $_tables = array();
+
+ /**
+ * @var array
+ */
+ protected $_sequences = array();
+
+ /**
+ * @var SchemaConfig
+ */
+ protected $_schemaConfig = false;
+
+ /**
+ * @param array $tables
+ * @param array $sequences
+ * @param array $views
+ * @param array $triggers
+ * @param SchemaConfig $schemaConfig
+ */
+ public function __construct(array $tables=array(), array $sequences=array(), SchemaConfig $schemaConfig=null)
+ {
+ if ($schemaConfig == null) {
+ $schemaConfig = new SchemaConfig();
+ }
+ $this->_schemaConfig = $schemaConfig;
+
+ foreach ($tables AS $table) {
+ $this->_addTable($table);
+ }
+ foreach ($sequences AS $sequence) {
+ $this->_addSequence($sequence);
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasExplicitForeignKeyIndexes()
+ {
+ return $this->_schemaConfig->hasExplicitForeignKeyIndexes();
+ }
+
+ /**
+ * @param Table $table
+ */
+ protected function _addTable(Table $table)
+ {
+ $tableName = strtolower($table->getName());
+ if(isset($this->_tables[$tableName])) {
+ throw SchemaException::tableAlreadyExists($tableName);
+ }
+
+ $this->_tables[$tableName] = $table;
+ $table->setSchemaConfig($this->_schemaConfig);
+ }
+
+ /**
+ * @param Sequence $sequence
+ */
+ protected function _addSequence(Sequence $sequence)
+ {
+ $seqName = strtolower($sequence->getName());
+ if (isset($this->_sequences[$seqName])) {
+ throw SchemaException::sequenceAlreadyExists($seqName);
+ }
+ $this->_sequences[$seqName] = $sequence;
+ }
+
+ /**
+ * Get all tables of this schema.
+ *
+ * @return array
+ */
+ public function getTables()
+ {
+ return $this->_tables;
+ }
+
+ /**
+ * @param string $tableName
+ * @return Table
+ */
+ public function getTable($tableName)
+ {
+ $tableName = strtolower($tableName);
+ if (!isset($this->_tables[$tableName])) {
+ throw SchemaException::tableDoesNotExist($tableName);
+ }
+
+ return $this->_tables[$tableName];
+ }
+
+ /**
+ * Does this schema have a table with the given name?
+ *
+ * @param string $tableName
+ * @return Schema
+ */
+ public function hasTable($tableName)
+ {
+ $tableName = strtolower($tableName);
+ return isset($this->_tables[$tableName]);
+ }
+
+ /**
+ * @param string $sequenceName
+ * @return bool
+ */
+ public function hasSequence($sequenceName)
+ {
+ $sequenceName = strtolower($sequenceName);
+ return isset($this->_sequences[$sequenceName]);
+ }
+
+ /**
+ * @throws SchemaException
+ * @param string $sequenceName
+ * @return Doctrine\DBAL\Schema\Sequence
+ */
+ public function getSequence($sequenceName)
+ {
+ $sequenceName = strtolower($sequenceName);
+ if(!$this->hasSequence($sequenceName)) {
+ throw SchemaException::sequenceDoesNotExist($sequenceName);
+ }
+ return $this->_sequences[$sequenceName];
+ }
+
+ /**
+ * @return Doctrine\DBAL\Schema\Sequence[]
+ */
+ public function getSequences()
+ {
+ return $this->_sequences;
+ }
+
+ /**
+ * Create a new table
+ *
+ * @param string $tableName
+ * @return Table
+ */
+ public function createTable($tableName)
+ {
+ $table = new Table($tableName);
+ $this->_addTable($table);
+ return $table;
+ }
+
+ /**
+ * Rename a table
+ *
+ * @param string $oldTableName
+ * @param string $newTableName
+ * @return Schema
+ */
+ public function renameTable($oldTableName, $newTableName)
+ {
+ $table = $this->getTable($oldTableName);
+ $table->_setName($newTableName);
+
+ $this->dropTable($oldTableName);
+ $this->_addTable($table);
+ return $this;
+ }
+
+ /**
+ * Drop a table from the schema.
+ *
+ * @param string $tableName
+ * @return Schema
+ */
+ public function dropTable($tableName)
+ {
+ $tableName = strtolower($tableName);
+ $table = $this->getTable($tableName);
+ unset($this->_tables[$tableName]);
+ return $this;
+ }
+
+ /**
+ * Create a new sequence
+ *
+ * @param string $sequenceName
+ * @param int $allocationSize
+ * @param int $initialValue
+ * @return Sequence
+ */
+ public function createSequence($sequenceName, $allocationSize=1, $initialValue=1)
+ {
+ $seq = new Sequence($sequenceName, $allocationSize, $initialValue);
+ $this->_addSequence($seq);
+ return $seq;
+ }
+
+ /**
+ * @param string $sequenceName
+ * @return Schema
+ */
+ public function dropSequence($sequenceName)
+ {
+ $sequenceName = strtolower($sequenceName);
+ unset($this->_sequences[$sequenceName]);
+ return $this;
+ }
+
+ /**
+ * Return an array of necessary sql queries to create the schema on the given platform.
+ *
+ * @param AbstractPlatform $platform
+ * @return array
+ */
+ public function toSql(\Doctrine\DBAL\Platforms\AbstractPlatform $platform)
+ {
+ $sqlCollector = new CreateSchemaSqlCollector($platform);
+ $this->visit($sqlCollector);
+
+ return $sqlCollector->getQueries();
+ }
+
+ /**
+ * Return an array of necessary sql queries to drop the schema on the given platform.
+ *
+ * @param AbstractPlatform $platform
+ * @return array
+ */
+ public function toDropSql(\Doctrine\DBAL\Platforms\AbstractPlatform $platform)
+ {
+ $dropSqlCollector = new DropSchemaSqlCollector($platform);
+ $this->visit($dropSqlCollector);
+
+ return $dropSqlCollector->getQueries();
+ }
+
+ /**
+ * @param Schema $toSchema
+ * @param AbstractPlatform $platform
+ */
+ public function getMigrateToSql(Schema $toSchema, \Doctrine\DBAL\Platforms\AbstractPlatform $platform)
+ {
+ $comparator = new Comparator();
+ $schemaDiff = $comparator->compare($this, $toSchema);
+ return $schemaDiff->toSql($platform);
+ }
+
+ /**
+ * @param Schema $fromSchema
+ * @param AbstractPlatform $platform
+ */
+ public function getMigrateFromSql(Schema $fromSchema, \Doctrine\DBAL\Platforms\AbstractPlatform $platform)
+ {
+ $comparator = new Comparator();
+ $schemaDiff = $comparator->compare($fromSchema, $this);
+ return $schemaDiff->toSql($platform);
+ }
+
+ /**
+ * @param Visitor $visitor
+ */
+ public function visit(Visitor $visitor)
+ {
+ $visitor->acceptSchema($this);
+
+ foreach ($this->_tables AS $table) {
+ $table->visit($visitor);
+ }
+ foreach ($this->_sequences AS $sequence) {
+ $sequence->visit($visitor);
+ }
+ }
+
+ /**
+ * Cloning a Schema triggers a deep clone of all related assets.
+ *
+ * @return void
+ */
+ public function __clone()
+ {
+ foreach ($this->_tables AS $k => $table) {
+ $this->_tables[$k] = clone $table;
+ }
+ foreach ($this->_sequences AS $k => $sequence) {
+ $this->_sequences[$k] = clone $sequence;
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id: Schema.php 6876 2009-12-06 23:11:35Z beberlei $
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Schema;
+
+/**
+ * Configuration for a Schema
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class SchemaConfig
+{
+ /**
+ * @var bool
+ */
+ protected $_hasExplicitForeignKeyIndexes = false;
+
+ /**
+ * @var int
+ */
+ protected $_maxIdentifierLength = 63;
+
+ /**
+ * @return bool
+ */
+ public function hasExplicitForeignKeyIndexes()
+ {
+ return $this->_hasExplicitForeignKeyIndexes;
+ }
+
+ /**
+ * @param bool $flag
+ */
+ public function setExplicitForeignKeyIndexes($flag)
+ {
+ $this->_hasExplicitForeignKeyIndexes = (bool)$flag;
+ }
+
+ /**
+ * @param int $length
+ */
+ public function setMaxIdentifierLength($length)
+ {
+ $this->_maxIdentifierLength = (int)$length;
+ }
+
+ /**
+ * @return int
+ */
+ public function getMaxIdentifierLength()
+ {
+ return $this->_maxIdentifierLength;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Schema;
+
+use \Doctrine\DBAL\Platforms\AbstractPlatform;
+
+/**
+ * Schema Diff
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved.
+ * @license http://ez.no/licenses/new_bsd New BSD License
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class SchemaDiff
+{
+ /**
+ * All added tables
+ *
+ * @var array(string=>ezcDbSchemaTable)
+ */
+ public $newTables = array();
+
+ /**
+ * All changed tables
+ *
+ * @var array(string=>ezcDbSchemaTableDiff)
+ */
+ public $changedTables = array();
+
+ /**
+ * All removed tables
+ *
+ * @var array(string=>Table)
+ */
+ public $removedTables = array();
+
+ /**
+ * @var array
+ */
+ public $newSequences = array();
+
+ /**
+ * @var array
+ */
+ public $changedSequences = array();
+
+ /**
+ * @var array
+ */
+ public $removedSequences = array();
+
+ /**
+ * @var array
+ */
+ public $orphanedForeignKeys = array();
+
+ /**
+ * Constructs an SchemaDiff object.
+ *
+ * @param array(string=>Table) $newTables
+ * @param array(string=>TableDiff) $changedTables
+ * @param array(string=>bool) $removedTables
+ */
+ public function __construct($newTables = array(), $changedTables = array(), $removedTables = array())
+ {
+ $this->newTables = $newTables;
+ $this->changedTables = $changedTables;
+ $this->removedTables = $removedTables;
+ }
+
+ /**
+ * The to save sql mode ensures that the following things don't happen:
+ *
+ * 1. Tables are deleted
+ * 2. Sequences are deleted
+ * 3. Foreign Keys which reference tables that would otherwise be deleted.
+ *
+ * This way it is ensured that assets are deleted which might not be relevant to the metadata schema at all.
+ *
+ * @param AbstractPlatform $platform
+ * @return array
+ */
+ public function toSaveSql(AbstractPlatform $platform)
+ {
+ return $this->_toSql($platform, true);
+ }
+
+ /**
+ * @param AbstractPlatform $platform
+ * @return array
+ */
+ public function toSql(AbstractPlatform $platform)
+ {
+ return $this->_toSql($platform, false);
+ }
+
+ /**
+ * @param AbstractPlatform $platform
+ * @param bool $saveMode
+ * @return array
+ */
+ protected function _toSql(AbstractPlatform $platform, $saveMode = false)
+ {
+ $sql = array();
+
+ if ($platform->supportsForeignKeyConstraints() && $saveMode == false) {
+ foreach ($this->orphanedForeignKeys AS $orphanedForeignKey) {
+ $sql[] = $platform->getDropForeignKeySQL($orphanedForeignKey, $orphanedForeignKey->getLocalTableName());
+ }
+ }
+
+ if ($platform->supportsSequences() == true) {
+ foreach ($this->changedSequences AS $sequence) {
+ $sql[] = $platform->getDropSequenceSQL($sequence);
+ $sql[] = $platform->getCreateSequenceSQL($sequence);
+ }
+
+ if ($saveMode === false) {
+ foreach ($this->removedSequences AS $sequence) {
+ $sql[] = $platform->getDropSequenceSQL($sequence);
+ }
+ }
+
+ foreach ($this->newSequences AS $sequence) {
+ $sql[] = $platform->getCreateSequenceSQL($sequence);
+ }
+ }
+
+ $foreignKeySql = array();
+ foreach ($this->newTables AS $table) {
+ $sql = array_merge(
+ $sql,
+ $platform->getCreateTableSQL($table, AbstractPlatform::CREATE_INDEXES)
+ );
+
+ if ($platform->supportsForeignKeyConstraints()) {
+ foreach ($table->getForeignKeys() AS $foreignKey) {
+ $foreignKeySql[] = $platform->getCreateForeignKeySQL($foreignKey, $table);
+ }
+ }
+ }
+ $sql = array_merge($sql, $foreignKeySql);
+
+ if ($saveMode === false) {
+ foreach ($this->removedTables AS $table) {
+ $sql[] = $platform->getDropTableSQL($table);
+ }
+ }
+
+ foreach ($this->changedTables AS $tableDiff) {
+ $sql = array_merge($sql, $platform->getAlterTableSQL($tableDiff));
+ }
+
+ return $sql;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace Doctrine\DBAL\Schema;
+
+class SchemaException extends \Doctrine\DBAL\DBALException
+{
+ const TABLE_DOESNT_EXIST = 10;
+ const TABLE_ALREADY_EXISTS = 20;
+ const COLUMN_DOESNT_EXIST = 30;
+ const COLUMN_ALREADY_EXISTS = 40;
+ const INDEX_DOESNT_EXIST = 50;
+ const INDEX_ALREADY_EXISTS = 60;
+ const SEQUENCE_DOENST_EXIST = 70;
+ const SEQUENCE_ALREADY_EXISTS = 80;
+ const INDEX_INVALID_NAME = 90;
+ const FOREIGNKEY_DOESNT_EXIST = 100;
+
+ /**
+ * @param string $tableName
+ * @return SchemaException
+ */
+ static public function tableDoesNotExist($tableName)
+ {
+ return new self("There is no table with name '".$tableName."' in the schema.", self::TABLE_DOESNT_EXIST);
+ }
+
+ /**
+ * @param string $indexName
+ * @return SchemaException
+ */
+ static public function indexNameInvalid($indexName)
+ {
+ return new self("Invalid index-name $indexName given, has to be [a-zA-Z0-9_]", self::INDEX_INVALID_NAME);
+ }
+
+ /**
+ * @param string $indexName
+ * @return SchemaException
+ */
+ static public function indexDoesNotExist($indexName, $table)
+ {
+ return new self("Index '$indexName' does not exist on table '$table'.", self::INDEX_DOESNT_EXIST);
+ }
+
+ /**
+ * @param string $indexName
+ * @return SchemaException
+ */
+ static public function indexAlreadyExists($indexName, $table)
+ {
+ return new self("An index with name '$indexName' was already defined on table '$table'.", self::INDEX_ALREADY_EXISTS);
+ }
+
+ /**
+ * @param string $columnName
+ * @return SchemaException
+ */
+ static public function columnDoesNotExist($columnName, $table)
+ {
+ return new self("There is no column with name '$columnName' on table '$table'.", self::COLUMN_DOESNT_EXIST);
+ }
+
+ /**
+ *
+ * @param string $tableName
+ * @return SchemaException
+ */
+ static public function tableAlreadyExists($tableName)
+ {
+ return new self("The table with name '".$tableName."' already exists.", self::TABLE_ALREADY_EXISTS);
+ }
+
+ /**
+ *
+ * @param string $tableName
+ * @param string $columnName
+ * @return SchemaException
+ */
+ static public function columnAlreadyExists($tableName, $columnName)
+ {
+ return new self(
+ "The column '".$columnName."' on table '".$tableName."' already exists.", self::COLUMN_ALREADY_EXISTS
+ );
+ }
+
+ /**
+ * @param string $sequenceName
+ * @return SchemaException
+ */
+ static public function sequenceAlreadyExists($sequenceName)
+ {
+ return new self("The sequence '".$sequenceName."' already exists.", self::SEQUENCE_ALREADY_EXISTS);
+ }
+
+ /**
+ * @param string $sequenceName
+ * @return SchemaException
+ */
+ static public function sequenceDoesNotExist($sequenceName)
+ {
+ return new self("There exists no sequence with the name '".$sequenceName."'.", self::SEQUENCE_DOENST_EXIST);
+ }
+
+ /**
+ * @param string $fkName
+ * @return SchemaException
+ */
+ static public function foreignKeyDoesNotExist($fkName, $table)
+ {
+ return new self("There exists no foreign key with the name '$fkName' on table '$table'.", self::FOREIGNKEY_DOESNT_EXIST);
+ }
+
+ static public function namedForeignKeyRequired(Table $localTable, ForeignKeyConstraint $foreignKey)
+ {
+ return new self(
+ "The performed schema operation on ".$localTable->getName()." requires a named foreign key, ".
+ "but the given foreign key from (".implode(", ", $foreignKey->getColumns()).") onto foreign table ".
+ "'".$foreignKey->getForeignTableName()."' (".implode(", ", $foreignKey->getForeignColumns()).") is currently ".
+ "unnamed."
+ );
+ }
+
+ static public function alterTableChangeNotSupported($changeName) {
+ return new self ("Alter table change not supported, given '$changeName'");
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Schema;
+
+use Doctrine\DBAL\Schema\Visitor\Visitor;
+
+/**
+ * Sequence Structure
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class Sequence extends AbstractAsset
+{
+ /**
+ * @var int
+ */
+ protected $_allocationSize = 1;
+
+ /**
+ * @var int
+ */
+ protected $_initialValue = 1;
+
+ /**
+ *
+ * @param string $name
+ * @param int $allocationSize
+ * @param int $initialValue
+ */
+ public function __construct($name, $allocationSize=1, $initialValue=1)
+ {
+ $this->_setName($name);
+ $this->_allocationSize = (is_numeric($allocationSize))?$allocationSize:1;
+ $this->_initialValue = (is_numeric($initialValue))?$initialValue:1;
+ }
+
+ public function getAllocationSize()
+ {
+ return $this->_allocationSize;
+ }
+
+ public function getInitialValue()
+ {
+ return $this->_initialValue;
+ }
+
+ /**
+ * @param Visitor $visitor
+ */
+ public function visit(Visitor $visitor)
+ {
+ $visitor->acceptSequence($this);
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Schema;
+
+/**
+ * SqliteSchemaManager
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
+ * @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ * @version $Revision$
+ * @since 2.0
+ */
+class SqliteSchemaManager extends AbstractSchemaManager
+{
+ /**
+ * {@inheritdoc}
+ *
+ * @override
+ */
+ public function dropDatabase($database)
+ {
+ if (file_exists($database)) {
+ unlink($database);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @override
+ */
+ public function createDatabase($database)
+ {
+ $params = $this->_conn->getParams();
+ $driver = $params['driver'];
+ $options = array(
+ 'driver' => $driver,
+ 'path' => $database
+ );
+ $conn = \Doctrine\DBAL\DriverManager::getConnection($options);
+ $conn->connect();
+ $conn->close();
+ }
+
+ protected function _getPortableTableDefinition($table)
+ {
+ return $table['name'];
+ }
+
+ /**
+ * @license New BSD License
+ * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
+ * @param array $tableIndexes
+ * @param string $tableName
+ * @return array
+ */
+ protected function _getPortableTableIndexesList($tableIndexes, $tableName=null)
+ {
+ $indexBuffer = array();
+
+ // fetch primary
+ $stmt = $this->_conn->executeQuery( "PRAGMA TABLE_INFO ('$tableName')" );
+ $indexArray = $stmt->fetchAll(\PDO::FETCH_ASSOC);
+ foreach($indexArray AS $indexColumnRow) {
+ if($indexColumnRow['pk'] == "1") {
+ $indexBuffer[] = array(
+ 'key_name' => 'primary',
+ 'primary' => true,
+ 'non_unique' => false,
+ 'column_name' => $indexColumnRow['name']
+ );
+ }
+ }
+
+ // fetch regular indexes
+ foreach($tableIndexes AS $tableIndex) {
+ $keyName = $tableIndex['name'];
+ $idx = array();
+ $idx['key_name'] = $keyName;
+ $idx['primary'] = false;
+ $idx['non_unique'] = $tableIndex['unique']?false:true;
+
+ $stmt = $this->_conn->executeQuery( "PRAGMA INDEX_INFO ( '{$keyName}' )" );
+ $indexArray = $stmt->fetchAll(\PDO::FETCH_ASSOC);
+
+ foreach ( $indexArray as $indexColumnRow ) {
+ $idx['column_name'] = $indexColumnRow['name'];
+ $indexBuffer[] = $idx;
+ }
+ }
+
+ return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
+ }
+
+ protected function _getPortableTableIndexDefinition($tableIndex)
+ {
+ return array(
+ 'name' => $tableIndex['name'],
+ 'unique' => (bool) $tableIndex['unique']
+ );
+ }
+
+ protected function _getPortableTableColumnDefinition($tableColumn)
+ {
+ $e = explode('(', $tableColumn['type']);
+ $tableColumn['type'] = $e[0];
+ if (isset($e[1])) {
+ $length = trim($e[1], ')');
+ $tableColumn['length'] = $length;
+ }
+
+ $dbType = strtolower($tableColumn['type']);
+ $length = isset($tableColumn['length']) ? $tableColumn['length'] : null;
+ $unsigned = (boolean) isset($tableColumn['unsigned']) ? $tableColumn['unsigned'] : false;
+ $fixed = false;
+ $type = $this->_platform->getDoctrineTypeMapping($dbType);
+ $default = $tableColumn['dflt_value'];
+ if ($default == 'NULL') {
+ $default = null;
+ }
+ $notnull = (bool) $tableColumn['notnull'];
+
+ if ( ! isset($tableColumn['name'])) {
+ $tableColumn['name'] = '';
+ }
+
+ $precision = null;
+ $scale = null;
+
+ switch ($dbType) {
+ case 'char':
+ $fixed = true;
+ break;
+ case 'float':
+ case 'double':
+ case 'real':
+ case 'decimal':
+ case 'numeric':
+ list($precision, $scale) = array_map('trim', explode(', ', $tableColumn['length']));
+ $length = null;
+ break;
+ }
+
+ $options = array(
+ 'length' => $length,
+ 'unsigned' => (bool) $unsigned,
+ 'fixed' => $fixed,
+ 'notnull' => $notnull,
+ 'default' => $default,
+ 'precision' => $precision,
+ 'scale' => $scale,
+ 'autoincrement' => (bool) $tableColumn['pk'],
+ );
+
+ return new Column($tableColumn['name'], \Doctrine\DBAL\Types\Type::getType($type), $options);
+ }
+
+ protected function _getPortableViewDefinition($view)
+ {
+ return new View($view['name'], $view['sql']);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Schema;
+
+use Doctrine\DBAL\Types\Type;
+use Doctrine\DBAL\Schema\Visitor\Visitor;
+use Doctrine\DBAL\DBALException;
+
+/**
+ * Object Representation of a table
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class Table extends AbstractAsset
+{
+ /**
+ * @var string
+ */
+ protected $_name = null;
+
+ /**
+ * @var array
+ */
+ protected $_columns = array();
+
+ /**
+ * @var array
+ */
+ protected $_indexes = array();
+
+ /**
+ * @var string
+ */
+ protected $_primaryKeyName = false;
+
+ /**
+ * @var array
+ */
+ protected $_fkConstraints = array();
+
+ /**
+ * @var array
+ */
+ protected $_options = array();
+
+ /**
+ * @var SchemaConfig
+ */
+ protected $_schemaConfig = null;
+
+ /**
+ *
+ * @param string $tableName
+ * @param array $columns
+ * @param array $indexes
+ * @param array $fkConstraints
+ * @param int $idGeneratorType
+ * @param array $options
+ */
+ public function __construct($tableName, array $columns=array(), array $indexes=array(), array $fkConstraints=array(), $idGeneratorType = 0, array $options=array())
+ {
+ if (strlen($tableName) == 0) {
+ throw DBALException::invalidTableName($tableName);
+ }
+
+ $this->_setName($tableName);
+ $this->_idGeneratorType = $idGeneratorType;
+
+ foreach ($columns AS $column) {
+ $this->_addColumn($column);
+ }
+
+ foreach ($indexes AS $idx) {
+ $this->_addIndex($idx);
+ }
+
+ foreach ($fkConstraints AS $constraint) {
+ $this->_addForeignKeyConstraint($constraint);
+ }
+
+ $this->_options = $options;
+ }
+
+ /**
+ * @param SchemaConfig $schemaConfig
+ */
+ public function setSchemaConfig(SchemaConfig $schemaConfig)
+ {
+ $this->_schemaConfig = $schemaConfig;
+ }
+
+ /**
+ * @return int
+ */
+ protected function _getMaxIdentifierLength()
+ {
+ if ($this->_schemaConfig instanceof SchemaConfig) {
+ return $this->_schemaConfig->getMaxIdentifierLength();
+ } else {
+ return 63;
+ }
+ }
+
+ /**
+ * Set Primary Key
+ *
+ * @param array $columns
+ * @param string $indexName
+ * @return Table
+ */
+ public function setPrimaryKey(array $columns, $indexName = false)
+ {
+ $primaryKey = $this->_createIndex($columns, $indexName ?: "primary", true, true);
+
+ foreach ($columns AS $columnName) {
+ $column = $this->getColumn($columnName);
+ $column->setNotnull(true);
+ }
+
+ return $primaryKey;
+ }
+
+ /**
+ * @param array $columnNames
+ * @param string $indexName
+ * @return Table
+ */
+ public function addIndex(array $columnNames, $indexName = null)
+ {
+ if($indexName == null) {
+ $indexName = $this->_generateIdentifierName(
+ array_merge(array($this->getName()), $columnNames), "idx", $this->_getMaxIdentifierLength()
+ );
+ }
+
+ return $this->_createIndex($columnNames, $indexName, false, false);
+ }
+
+ /**
+ *
+ * @param array $columnNames
+ * @param string $indexName
+ * @return Table
+ */
+ public function addUniqueIndex(array $columnNames, $indexName = null)
+ {
+ if ($indexName == null) {
+ $indexName = $this->_generateIdentifierName(
+ array_merge(array($this->getName()), $columnNames), "uniq", $this->_getMaxIdentifierLength()
+ );
+ }
+
+ return $this->_createIndex($columnNames, $indexName, true, false);
+ }
+
+ /**
+ * Check if an index begins in the order of the given columns.
+ *
+ * @param array $columnsNames
+ * @return bool
+ */
+ public function columnsAreIndexed(array $columnsNames)
+ {
+ foreach ($this->getIndexes() AS $index) {
+ /* @var $index Index */
+ if ($index->spansColumns($columnsNames)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ *
+ * @param array $columnNames
+ * @param string $indexName
+ * @param bool $isUnique
+ * @param bool $isPrimary
+ * @return Table
+ */
+ private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary)
+ {
+ if (preg_match('(([^a-zA-Z0-9_]+))', $indexName)) {
+ throw SchemaException::indexNameInvalid($indexName);
+ }
+
+ foreach ($columnNames AS $columnName => $indexColOptions) {
+ if (is_numeric($columnName) && is_string($indexColOptions)) {
+ $columnName = $indexColOptions;
+ }
+
+ if ( ! $this->hasColumn($columnName)) {
+ throw SchemaException::columnDoesNotExist($columnName, $this->_name);
+ }
+ }
+ $this->_addIndex(new Index($indexName, $columnNames, $isUnique, $isPrimary));
+ return $this;
+ }
+
+ /**
+ * @param string $columnName
+ * @param string $columnType
+ * @param array $options
+ * @return Column
+ */
+ public function addColumn($columnName, $typeName, array $options=array())
+ {
+ $column = new Column($columnName, Type::getType($typeName), $options);
+
+ $this->_addColumn($column);
+ return $column;
+ }
+
+ /**
+ * Rename Column
+ *
+ * @param string $oldColumnName
+ * @param string $newColumnName
+ * @return Table
+ */
+ public function renameColumn($oldColumnName, $newColumnName)
+ {
+ $column = $this->getColumn($oldColumnName);
+ $this->dropColumn($oldColumnName);
+
+ $column->_setName($newColumnName);
+ return $this;
+ }
+
+ /**
+ * Change Column Details
+ *
+ * @param string $columnName
+ * @param array $options
+ * @return Table
+ */
+ public function changeColumn($columnName, array $options)
+ {
+ $column = $this->getColumn($columnName);
+ $column->setOptions($options);
+ return $this;
+ }
+
+ /**
+ * Drop Column from Table
+ *
+ * @param string $columnName
+ * @return Table
+ */
+ public function dropColumn($columnName)
+ {
+ $columnName = strtolower($columnName);
+ $column = $this->getColumn($columnName);
+ unset($this->_columns[$columnName]);
+ return $this;
+ }
+
+
+ /**
+ * Add a foreign key constraint
+ *
+ * Name is inferred from the local columns
+ *
+ * @param Table $foreignTable
+ * @param array $localColumns
+ * @param array $foreignColumns
+ * @param array $options
+ * @return Table
+ */
+ public function addForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array())
+ {
+ $name = $this->_generateIdentifierName(array_merge((array)$this->getName(), $localColumnNames), "fk", $this->_getMaxIdentifierLength());
+ return $this->addNamedForeignKeyConstraint($name, $foreignTable, $localColumnNames, $foreignColumnNames, $options);
+ }
+
+ /**
+ * Add a foreign key constraint
+ *
+ * Name is to be generated by the database itsself.
+ *
+ * @param Table $foreignTable
+ * @param array $localColumns
+ * @param array $foreignColumns
+ * @param array $options
+ * @return Table
+ */
+ public function addUnnamedForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array())
+ {
+ return $this->addNamedForeignKeyConstraint(null, $foreignTable, $localColumnNames, $foreignColumnNames, $options);
+ }
+
+ /**
+ * Add a foreign key constraint with a given name
+ *
+ * @param string $name
+ * @param Table $foreignTable
+ * @param array $localColumns
+ * @param array $foreignColumns
+ * @param array $options
+ * @return Table
+ */
+ public function addNamedForeignKeyConstraint($name, $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array())
+ {
+ if ($foreignTable instanceof Table) {
+ $foreignTableName = $foreignTable->getName();
+
+ foreach ($foreignColumnNames AS $columnName) {
+ if ( ! $foreignTable->hasColumn($columnName)) {
+ throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName());
+ }
+ }
+ } else {
+ $foreignTableName = $foreignTable;
+ }
+
+ foreach ($localColumnNames AS $columnName) {
+ if ( ! $this->hasColumn($columnName)) {
+ throw SchemaException::columnDoesNotExist($columnName, $this->_name);
+ }
+ }
+
+ $constraint = new ForeignKeyConstraint(
+ $localColumnNames, $foreignTableName, $foreignColumnNames, $name, $options
+ );
+ $this->_addForeignKeyConstraint($constraint);
+
+ return $this;
+ }
+
+ /**
+ * @param string $name
+ * @param string $value
+ * @return Table
+ */
+ public function addOption($name, $value)
+ {
+ $this->_options[$name] = $value;
+ return $this;
+ }
+
+ /**
+ * @param Column $column
+ */
+ protected function _addColumn(Column $column)
+ {
+ $columnName = $column->getName();
+ $columnName = strtolower($columnName);
+
+ if (isset($this->_columns[$columnName])) {
+ throw SchemaException::columnAlreadyExists($this->getName(), $columnName);
+ }
+
+ $this->_columns[$columnName] = $column;
+ }
+
+ /**
+ * Add index to table
+ *
+ * @param Index $indexCandidate
+ * @return Table
+ */
+ protected function _addIndex(Index $indexCandidate)
+ {
+ // check for duplicates
+ foreach ($this->_indexes AS $existingIndex) {
+ if ($indexCandidate->isFullfilledBy($existingIndex)) {
+ return $this;
+ }
+ }
+
+ $indexName = $indexCandidate->getName();
+ $indexName = strtolower($indexName);
+
+ if (isset($this->_indexes[$indexName]) || ($this->_primaryKeyName != false && $indexCandidate->isPrimary())) {
+ throw SchemaException::indexAlreadyExists($indexName, $this->_name);
+ }
+
+ // remove overruled indexes
+ foreach ($this->_indexes AS $idxKey => $existingIndex) {
+ if ($indexCandidate->overrules($existingIndex)) {
+ unset($this->_indexes[$idxKey]);
+ }
+ }
+
+ if ($indexCandidate->isPrimary()) {
+ $this->_primaryKeyName = $indexName;
+ }
+
+ $this->_indexes[$indexName] = $indexCandidate;
+ return $this;
+ }
+
+ /**
+ * @param ForeignKeyConstraint $constraint
+ */
+ protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint)
+ {
+ $constraint->setLocalTable($this);
+
+ if(strlen($constraint->getName())) {
+ $name = $constraint->getName();
+ } else {
+ $name = $this->_generateIdentifierName(
+ array_merge((array)$this->getName(), $constraint->getLocalColumns()), "fk", $this->_getMaxIdentifierLength()
+ );
+ }
+ $name = strtolower($name);
+
+ $this->_fkConstraints[$name] = $constraint;
+ // add an explicit index on the foreign key columns. If there is already an index that fullfils this requirements drop the request.
+ // In the case of __construct calling this method during hydration from schema-details all the explicitly added indexes
+ // lead to duplicates. This creates compuation overhead in this case, however no duplicate indexes are ever added (based on columns).
+ $this->addIndex($constraint->getColumns());
+ }
+
+ /**
+ * Does Table have a foreign key constraint with the given name?
+ * *
+ * @param string $constraintName
+ * @return bool
+ */
+ public function hasForeignKey($constraintName)
+ {
+ $constraintName = strtolower($constraintName);
+ return isset($this->_fkConstraints[$constraintName]);
+ }
+
+ /**
+ * @param string $constraintName
+ * @return ForeignKeyConstraint
+ */
+ public function getForeignKey($constraintName)
+ {
+ $constraintName = strtolower($constraintName);
+ if(!$this->hasForeignKey($constraintName)) {
+ throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
+ }
+
+ return $this->_fkConstraints[$constraintName];
+ }
+
+ /**
+ * @return Column[]
+ */
+ public function getColumns()
+ {
+ $columns = $this->_columns;
+
+ $pkCols = array();
+ $fkCols = array();
+
+ if ($this->hasIndex($this->_primaryKeyName)) {
+ $pkCols = $this->getPrimaryKey()->getColumns();
+ }
+ foreach ($this->getForeignKeys() AS $fk) {
+ /* @var $fk ForeignKeyConstraint */
+ $fkCols = array_merge($fkCols, $fk->getColumns());
+ }
+ $colNames = array_unique(array_merge($pkCols, $fkCols, array_keys($columns)));
+
+ uksort($columns, function($a, $b) use($colNames) {
+ return (array_search($a, $colNames) >= array_search($b, $colNames));
+ });
+ return $columns;
+ }
+
+
+ /**
+ * Does this table have a column with the given name?
+ *
+ * @param string $columnName
+ * @return bool
+ */
+ public function hasColumn($columnName)
+ {
+ $columnName = strtolower($columnName);
+ return isset($this->_columns[$columnName]);
+ }
+
+ /**
+ * Get a column instance
+ *
+ * @param string $columnName
+ * @return Column
+ */
+ public function getColumn($columnName)
+ {
+ $columnName = strtolower($columnName);
+ if (!$this->hasColumn($columnName)) {
+ throw SchemaException::columnDoesNotExist($columnName, $this->_name);
+ }
+
+ return $this->_columns[$columnName];
+ }
+
+ /**
+ * @return Index
+ */
+ public function getPrimaryKey()
+ {
+ return $this->getIndex($this->_primaryKeyName);
+ }
+
+ /**
+ * @param string $indexName
+ * @return bool
+ */
+ public function hasIndex($indexName)
+ {
+ $indexName = strtolower($indexName);
+ return (isset($this->_indexes[$indexName]));
+ }
+
+ /**
+ * @param string $indexName
+ * @return Index
+ */
+ public function getIndex($indexName)
+ {
+ $indexName = strtolower($indexName);
+ if (!$this->hasIndex($indexName)) {
+ throw SchemaException::indexDoesNotExist($indexName, $this->_name);
+ }
+ return $this->_indexes[$indexName];
+ }
+
+ /**
+ * @return array
+ */
+ public function getIndexes()
+ {
+ return $this->_indexes;
+ }
+
+ /**
+ * Get Constraints
+ *
+ * @return array
+ */
+ public function getForeignKeys()
+ {
+ return $this->_fkConstraints;
+ }
+
+ public function hasOption($name)
+ {
+ return isset($this->_options[$name]);
+ }
+
+ public function getOption($name)
+ {
+ return $this->_options[$name];
+ }
+
+ public function getOptions()
+ {
+ return $this->_options;
+ }
+
+ /**
+ * @param Visitor $visitor
+ */
+ public function visit(Visitor $visitor)
+ {
+ $visitor->acceptTable($this);
+
+ foreach ($this->getColumns() AS $column) {
+ $visitor->acceptColumn($this, $column);
+ }
+
+ foreach ($this->getIndexes() AS $index) {
+ $visitor->acceptIndex($this, $index);
+ }
+
+ foreach ($this->getForeignKeys() AS $constraint) {
+ $visitor->acceptForeignKey($this, $constraint);
+ }
+ }
+
+ /**
+ * Clone of a Table triggers a deep clone of all affected assets
+ */
+ public function __clone()
+ {
+ foreach ($this->_columns AS $k => $column) {
+ $this->_columns[$k] = clone $column;
+ }
+ foreach ($this->_indexes AS $k => $index) {
+ $this->_indexes[$k] = clone $index;
+ }
+ foreach ($this->_fkConstraints AS $k => $fk) {
+ $this->_fkConstraints[$k] = clone $fk;
+ $this->_fkConstraints[$k]->setLocalTable($this);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Schema;
+
+/**
+ * Table Diff
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved.
+ * @license http://ez.no/licenses/new_bsd New BSD License
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class TableDiff
+{
+ /**
+ * @var string
+ */
+ public $name = null;
+
+ /**
+ * @var string
+ */
+ public $newName = false;
+
+ /**
+ * All added fields
+ *
+ * @var array(string=>Column)
+ */
+ public $addedColumns;
+
+ /**
+ * All changed fields
+ *
+ * @var array(string=>Column)
+ */
+ public $changedColumns = array();
+
+ /**
+ * All removed fields
+ *
+ * @var array(string=>bool)
+ */
+ public $removedColumns = array();
+
+ /**
+ * Columns that are only renamed from key to column instance name.
+ *
+ * @var array(string=>Column)
+ */
+ public $renamedColumns = array();
+
+ /**
+ * All added indexes
+ *
+ * @var array(string=>Index)
+ */
+ public $addedIndexes = array();
+
+ /**
+ * All changed indexes
+ *
+ * @var array(string=>Index)
+ */
+ public $changedIndexes = array();
+
+ /**
+ * All removed indexes
+ *
+ * @var array(string=>bool)
+ */
+ public $removedIndexes = array();
+
+ /**
+ * All added foreign key definitions
+ *
+ * @var array
+ */
+ public $addedForeignKeys = array();
+
+ /**
+ * All changed foreign keys
+ *
+ * @var array
+ */
+ public $changedForeignKeys = array();
+
+ /**
+ * All removed foreign keys
+ *
+ * @var array
+ */
+ public $removedForeignKeys = array();
+
+ /**
+ * Constructs an TableDiff object.
+ *
+ * @param array(string=>Column) $addedColumns
+ * @param array(string=>Column) $changedColumns
+ * @param array(string=>bool) $removedColumns
+ * @param array(string=>Index) $addedIndexes
+ * @param array(string=>Index) $changedIndexes
+ * @param array(string=>bool) $removedIndexes
+ */
+ public function __construct($tableName, $addedColumns = array(),
+ $changedColumns = array(), $removedColumns = array(), $addedIndexes = array(),
+ $changedIndexes = array(), $removedIndexes = array())
+ {
+ $this->name = $tableName;
+ $this->addedColumns = $addedColumns;
+ $this->changedColumns = $changedColumns;
+ $this->removedColumns = $removedColumns;
+ $this->addedIndexes = $addedIndexes;
+ $this->changedIndexes = $changedIndexes;
+ $this->removedIndexes = $removedIndexes;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\DBAL\Schema;
+
+/**
+ * Representation of a Database View
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 1.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class View extends AbstractAsset
+{
+ /**
+ * @var string
+ */
+ private $_sql;
+
+ public function __construct($name, $sql)
+ {
+ $this->_setName($name);
+ $this->_sql = $sql;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSql()
+ {
+ return $this->_sql;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Schema\Visitor;
+
+use Doctrine\DBAL\Platforms\AbstractPlatform,
+ Doctrine\DBAL\Schema\Table,
+ Doctrine\DBAL\Schema\Schema,
+ Doctrine\DBAL\Schema\Column,
+ Doctrine\DBAL\Schema\ForeignKeyConstraint,
+ Doctrine\DBAL\Schema\Constraint,
+ Doctrine\DBAL\Schema\Sequence,
+ Doctrine\DBAL\Schema\Index;
+
+class CreateSchemaSqlCollector implements Visitor
+{
+ /**
+ * @var array
+ */
+ private $_createTableQueries = array();
+
+ /**
+ * @var array
+ */
+ private $_createSequenceQueries = array();
+
+ /**
+ * @var array
+ */
+ private $_createFkConstraintQueries = array();
+
+ /**
+ *
+ * @var \Doctrine\DBAL\Platforms\AbstractPlatform
+ */
+ private $_platform = null;
+
+ /**
+ * @param AbstractPlatform $platform
+ */
+ public function __construct(AbstractPlatform $platform)
+ {
+ $this->_platform = $platform;
+ }
+
+ /**
+ * @param Schema $schema
+ */
+ public function acceptSchema(Schema $schema)
+ {
+
+ }
+
+ /**
+ * Generate DDL Statements to create the accepted table with all its dependencies.
+ *
+ * @param Table $table
+ */
+ public function acceptTable(Table $table)
+ {
+ $this->_createTableQueries = array_merge($this->_createTableQueries,
+ $this->_platform->getCreateTableSQL($table)
+ );
+ }
+
+ public function acceptColumn(Table $table, Column $column)
+ {
+
+ }
+
+ /**
+ * @param Table $localTable
+ * @param ForeignKeyConstraint $fkConstraint
+ */
+ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint)
+ {
+ // Append the foreign key constraints SQL
+ if ($this->_platform->supportsForeignKeyConstraints()) {
+ $this->_createFkConstraintQueries = array_merge($this->_createFkConstraintQueries,
+ (array) $this->_platform->getCreateForeignKeySQL(
+ $fkConstraint, $localTable->getQuotedName($this->_platform)
+ )
+ );
+ }
+ }
+
+ /**
+ * @param Table $table
+ * @param Index $index
+ */
+ public function acceptIndex(Table $table, Index $index)
+ {
+
+ }
+
+ /**
+ * @param Sequence $sequence
+ */
+ public function acceptSequence(Sequence $sequence)
+ {
+ $this->_createSequenceQueries = array_merge(
+ $this->_createSequenceQueries, (array)$this->_platform->getCreateSequenceSQL($sequence)
+ );
+ }
+
+ /**
+ * @return array
+ */
+ public function resetQueries()
+ {
+ $this->_createTableQueries = array();
+ $this->_createSequenceQueries = array();
+ $this->_createFkConstraintQueries = array();
+ }
+
+ /**
+ * Get all queries collected so far.
+ *
+ * @return array
+ */
+ public function getQueries()
+ {
+ return array_merge(
+ $this->_createTableQueries,
+ $this->_createSequenceQueries,
+ $this->_createFkConstraintQueries
+ );
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Schema\Visitor;
+
+use Doctrine\DBAL\Platforms\AbstractPlatform,
+ Doctrine\DBAL\Schema\Table,
+ Doctrine\DBAL\Schema\Schema,
+ Doctrine\DBAL\Schema\Column,
+ Doctrine\DBAL\Schema\ForeignKeyConstraint,
+ Doctrine\DBAL\Schema\Constraint,
+ Doctrine\DBAL\Schema\Sequence,
+ Doctrine\DBAL\Schema\Index;
+
+/**
+ * Gather SQL statements that allow to completly drop the current schema.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class DropSchemaSqlCollector implements Visitor
+{
+ /**
+ * @var array
+ */
+ private $_constraints = array();
+
+ /**
+ * @var array
+ */
+ private $_sequences = array();
+
+ /**
+ * @var array
+ */
+ private $_tables = array();
+
+ /**
+ *
+ * @var \Doctrine\DBAL\Platforms\AbstractPlatform
+ */
+ private $_platform = null;
+
+ /**
+ * @param AbstractPlatform $platform
+ */
+ public function __construct(AbstractPlatform $platform)
+ {
+ $this->_platform = $platform;
+ }
+
+ /**
+ * @param Schema $schema
+ */
+ public function acceptSchema(Schema $schema)
+ {
+
+ }
+
+ /**
+ * @param Table $table
+ */
+ public function acceptTable(Table $table)
+ {
+ $this->_tables[] = $this->_platform->getDropTableSQL($table->getQuotedName($this->_platform));
+ }
+
+ /**
+ * @param Column $column
+ */
+ public function acceptColumn(Table $table, Column $column)
+ {
+
+ }
+
+ /**
+ * @param Table $localTable
+ * @param ForeignKeyConstraint $fkConstraint
+ */
+ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint)
+ {
+ if (strlen($fkConstraint->getName()) == 0) {
+ throw SchemaException::namedForeignKeyRequired($localTable, $fkConstraint);
+ }
+
+ $this->_constraints[] = $this->_platform->getDropForeignKeySQL($fkConstraint->getQuotedName($this->_platform), $localTable->getQuotedName($this->_platform));
+ }
+
+ /**
+ * @param Table $table
+ * @param Index $index
+ */
+ public function acceptIndex(Table $table, Index $index)
+ {
+
+ }
+
+ /**
+ * @param Sequence $sequence
+ */
+ public function acceptSequence(Sequence $sequence)
+ {
+ $this->_sequences[] = $this->_platform->getDropSequenceSQL($sequence->getQuotedName($this->_platform));
+ }
+
+ /**
+ * @return array
+ */
+ public function clearQueries()
+ {
+ $this->_constraints = $this->_sequences = $this->_tables = array();
+ }
+
+ /**
+ * @return array
+ */
+ public function getQueries()
+ {
+ return array_merge($this->_constraints, $this->_tables, $this->_sequences);
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Schema\Visitor;
+
+use Doctrine\DBAL\Platforms\AbstractPlatform,
+ Doctrine\DBAL\Schema\Table,
+ Doctrine\DBAL\Schema\Schema,
+ Doctrine\DBAL\Schema\Column,
+ Doctrine\DBAL\Schema\ForeignKeyConstraint,
+ Doctrine\DBAL\Schema\Constraint,
+ Doctrine\DBAL\Schema\Sequence,
+ Doctrine\DBAL\Schema\Index;
+
+/**
+ * Schema Visitor used for Validation or Generation purposes.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+interface Visitor
+{
+ /**
+ * @param Schema $schema
+ */
+ public function acceptSchema(Schema $schema);
+
+ /**
+ * @param Table $table
+ */
+ public function acceptTable(Table $table);
+
+ /**
+ * @param Column $column
+ */
+ public function acceptColumn(Table $table, Column $column);
+
+ /**
+ * @param Table $localTable
+ * @param ForeignKeyConstraint $fkConstraint
+ */
+ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint);
+
+ /**
+ * @param Table $table
+ * @param Index $index
+ */
+ public function acceptIndex(Table $table, Index $index);
+
+ /**
+ * @param Sequence $sequence
+ */
+ public function acceptSequence(Sequence $sequence);
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL;
+
+use PDO,
+ Doctrine\DBAL\Types\Type,
+ Doctrine\DBAL\Driver\Statement as DriverStatement;
+
+/**
+ * A thin wrapper around a Doctrine\DBAL\Driver\Statement that adds support
+ * for logging, DBAL mapping types, etc.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @since 2.0
+ */
+class Statement implements DriverStatement
+{
+ /**
+ * @var string The SQL statement.
+ */
+ private $_sql;
+ /**
+ * @var array The bound parameters.
+ */
+ private $_params = array();
+ /**
+ * @var Doctrine\DBAL\Driver\Statement The underlying driver statement.
+ */
+ private $_stmt;
+ /**
+ * @var Doctrine\DBAL\Platforms\AbstractPlatform The underlying database platform.
+ */
+ private $_platform;
+ /**
+ * @var Doctrine\DBAL\Connection The connection this statement is bound to and executed on.
+ */
+ private $_conn;
+
+ /**
+ * Creates a new <tt>Statement</tt> for the given SQL and <tt>Connection</tt>.
+ *
+ * @param string $sql The SQL of the statement.
+ * @param Doctrine\DBAL\Connection The connection on which the statement should be executed.
+ */
+ public function __construct($sql, Connection $conn)
+ {
+ $this->_sql = $sql;
+ $this->_stmt = $conn->getWrappedConnection()->prepare($sql);
+ $this->_conn = $conn;
+ $this->_platform = $conn->getDatabasePlatform();
+ }
+
+ /**
+ * Binds a parameter value to the statement.
+ *
+ * The value can optionally be bound with a PDO binding type or a DBAL mapping type.
+ * If bound with a DBAL mapping type, the binding type is derived from the mapping
+ * type and the value undergoes the conversion routines of the mapping type before
+ * being bound.
+ *
+ * @param $name The name or position of the parameter.
+ * @param $value The value of the parameter.
+ * @param mixed $type Either a PDO binding type or a DBAL mapping type name or instance.
+ * @return boolean TRUE on success, FALSE on failure.
+ */
+ public function bindValue($name, $value, $type = null)
+ {
+ $this->_params[$name] = $value;
+ if ($type !== null) {
+ if (is_string($type)) {
+ $type = Type::getType($type);
+ }
+ if ($type instanceof Type) {
+ $value = $type->convertToDatabaseValue($value, $this->_platform);
+ $bindingType = $type->getBindingType();
+ } else {
+ $bindingType = $type; // PDO::PARAM_* constants
+ }
+ return $this->_stmt->bindValue($name, $value, $bindingType);
+ } else {
+ return $this->_stmt->bindValue($name, $value);
+ }
+ }
+
+ /**
+ * Binds a parameter to a value by reference.
+ *
+ * Binding a parameter by reference does not support DBAL mapping types.
+ *
+ * @param string $name The name or position of the parameter.
+ * @param mixed $value The reference to the variable to bind
+ * @param integer $type The PDO binding type.
+ * @return boolean TRUE on success, FALSE on failure.
+ */
+ public function bindParam($name, &$var, $type = PDO::PARAM_STR)
+ {
+ return $this->_stmt->bindParam($name, $var, $type);
+ }
+
+ /**
+ * Executes the statement with the currently bound parameters.
+ *
+ * @return boolean TRUE on success, FALSE on failure.
+ */
+ public function execute($params = null)
+ {
+ $hasLogger = $this->_conn->getConfiguration()->getSQLLogger();
+ if ($hasLogger) {
+ $this->_conn->getConfiguration()->getSQLLogger()->startQuery($this->_sql, $this->_params);
+ }
+
+ $stmt = $this->_stmt->execute($params);
+
+ if ($hasLogger) {
+ $this->_conn->getConfiguration()->getSQLLogger()->stopQuery();
+ }
+ $this->_params = array();
+ return $stmt;
+ }
+
+ /**
+ * Closes the cursor, freeing the database resources used by this statement.
+ *
+ * @return boolean TRUE on success, FALSE on failure.
+ */
+ public function closeCursor()
+ {
+ return $this->_stmt->closeCursor();
+ }
+
+ /**
+ * Returns the number of columns in the result set.
+ *
+ * @return integer
+ */
+ public function columnCount()
+ {
+ return $this->_stmt->columnCount();
+ }
+
+ /**
+ * Fetches the SQLSTATE associated with the last operation on the statement.
+ *
+ * @return string
+ */
+ public function errorCode()
+ {
+ return $this->_stmt->errorCode();
+ }
+
+ /**
+ * Fetches extended error information associated with the last operation on the statement.
+ *
+ * @return array
+ */
+ public function errorInfo()
+ {
+ return $this->_stmt->errorInfo();
+ }
+
+ /**
+ * Fetches the next row from a result set.
+ *
+ * @param integer $fetchStyle
+ * @return mixed The return value of this function on success depends on the fetch type.
+ * In all cases, FALSE is returned on failure.
+ */
+ public function fetch($fetchStyle = PDO::FETCH_BOTH)
+ {
+ return $this->_stmt->fetch($fetchStyle);
+ }
+
+ /**
+ * Returns an array containing all of the result set rows.
+ *
+ * @param integer $fetchStyle
+ * @param integer $columnIndex
+ * @return array An array containing all of the remaining rows in the result set.
+ */
+ public function fetchAll($fetchStyle = PDO::FETCH_BOTH, $columnIndex = 0)
+ {
+ if ($columnIndex != 0) {
+ return $this->_stmt->fetchAll($fetchStyle, $columnIndex);
+ }
+ return $this->_stmt->fetchAll($fetchStyle);
+ }
+
+ /**
+ * Returns a single column from the next row of a result set.
+ *
+ * @param integer $columnIndex
+ * @return mixed A single column from the next row of a result set or FALSE if there are no more rows.
+ */
+ public function fetchColumn($columnIndex = 0)
+ {
+ return $this->_stmt->fetchColumn($columnIndex);
+ }
+
+ /**
+ * Returns the number of rows affected by the last execution of this statement.
+ *
+ * @return integer The number of affected rows.
+ */
+ public function rowCount()
+ {
+ return $this->_stmt->rowCount();
+ }
+
+ /**
+ * Gets the wrapped driver statement.
+ *
+ * @return Doctrine\DBAL\Driver\Statement
+ */
+ public function getWrappedStatement()
+ {
+ return $this->_stmt;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Tools\Console\Command;
+
+use Symfony\Component\Console\Input\InputArgument,
+ Symfony\Component\Console;
+
+/**
+ * Task for executing arbitrary SQL that can come from a file or directly from
+ * the command line.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class ImportCommand extends Console\Command\Command
+{
+ /**
+ * @see Console\Command\Command
+ */
+ protected function configure()
+ {
+ $this
+ ->setName('dbal:import')
+ ->setDescription('Import SQL file(s) directly to Database.')
+ ->setDefinition(array(
+ new InputArgument(
+ 'file', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'File path(s) of SQL to be executed.'
+ )
+ ))
+ ->setHelp(<<<EOT
+Import SQL file(s) directly to Database.
+EOT
+ );
+ }
+
+ /**
+ * @see Console\Command\Command
+ */
+ protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output)
+ {
+ $conn = $this->getHelper('db')->getConnection();
+
+ if (($fileNames = $input->getArgument('file')) !== null) {
+ foreach ((array) $fileNames as $fileName) {
+ $fileName = realpath($fileName);
+
+ if ( ! file_exists($fileName)) {
+ throw new \InvalidArgumentException(
+ sprintf("SQL file '<info>%s</info>' does not exist.", $fileName)
+ );
+ } else if ( ! is_readable($fileName)) {
+ throw new \InvalidArgumentException(
+ sprintf("SQL file '<info>%s</info>' does not have read permissions.", $fileName)
+ );
+ }
+
+ $output->write(sprintf("Processing file '<info>%s</info>'... ", $fileName));
+ $sql = file_get_contents($fileName);
+
+ if ($conn instanceof \Doctrine\DBAL\Driver\PDOConnection) {
+ // PDO Drivers
+ try {
+ $lines = 0;
+
+ $stmt = $conn->prepare($sql);
+ $stmt->execute();
+
+ do {
+ // Required due to "MySQL has gone away!" issue
+ $stmt->fetch();
+ $stmt->closeCursor();
+
+ $lines++;
+ } while ($stmt->nextRowset());
+
+ $output->write(sprintf('%d statements executed!', $lines) . PHP_EOL);
+ } catch (\PDOException $e) {
+ $output->write('error!' . PHP_EOL);
+
+ throw new \RuntimeException($e->getMessage(), $e->getCode(), $e);
+ }
+ } else {
+ // Non-PDO Drivers (ie. OCI8 driver)
+ $stmt = $conn->prepare($sql);
+ $rs = $stmt->execute();
+
+ if ($rs) {
+ $printer->writeln('OK!');
+ } else {
+ $error = $stmt->errorInfo();
+
+ $output->write('error!' . PHP_EOL);
+
+ throw new \RuntimeException($error[2], $error[0]);
+ }
+
+ $stmt->closeCursor();
+ }
+ }
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Tools\Console\Command;
+
+use Symfony\Component\Console\Input\InputArgument,
+ Symfony\Component\Console\Input\InputOption,
+ Symfony\Component\Console;
+
+/**
+ * Task for executing arbitrary SQL that can come from a file or directly from
+ * the command line.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class RunSqlCommand extends Console\Command\Command
+{
+ /**
+ * @see Console\Command\Command
+ */
+ protected function configure()
+ {
+ $this
+ ->setName('dbal:run-sql')
+ ->setDescription('Executes arbitrary SQL directly from the command line.')
+ ->setDefinition(array(
+ new InputArgument('sql', InputArgument::REQUIRED, 'The SQL statement to execute.'),
+ new InputOption('depth', null, InputOption::VALUE_REQUIRED, 'Dumping depth of result set.', 7)
+ ))
+ ->setHelp(<<<EOT
+Executes arbitrary SQL directly from the command line.
+EOT
+ );
+ }
+
+ /**
+ * @see Console\Command\Command
+ */
+ protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output)
+ {
+ $conn = $this->getHelper('db')->getConnection();
+
+ if (($sql = $input->getArgument('sql')) === null) {
+ throw new \RuntimeException("Argument 'SQL' 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");
+ }
+
+ if (preg_match('/^select/i', $sql)) {
+ $resultSet = $conn->fetchAll($sql);
+ } else {
+ $resultSet = $conn->executeUpdate($sql);
+ }
+
+ \Doctrine\Common\Util\Debug::dump($resultSet, (int) $depth);
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Tools\Console\Helper;
+
+use Symfony\Component\Console\Helper\Helper,
+ Doctrine\DBAL\Connection;
+
+/**
+ * Doctrine CLI Connection Helper.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class ConnectionHelper extends Helper
+{
+ /**
+ * Doctrine Database Connection
+ * @var Connection
+ */
+ protected $_connection;
+
+ /**
+ * Constructor
+ *
+ * @param Connection $connection Doctrine Database Connection
+ */
+ public function __construct(Connection $connection)
+ {
+ $this->_connection = $connection;
+ }
+
+ /**
+ * Retrieves Doctrine Database Connection
+ *
+ * @return Connection
+ */
+ public function getConnection()
+ {
+ return $this->_connection;
+ }
+
+ /**
+ * @see Helper
+ */
+ public function getName()
+ {
+ return 'connection';
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Types;
+
+/**
+ * Type that maps a PHP array to a clob SQL type.
+ *
+ * @since 2.0
+ */
+class ArrayType extends Type
+{
+ public function getSQLDeclaration(array $fieldDeclaration, \Doctrine\DBAL\Platforms\AbstractPlatform $platform)
+ {
+ return $platform->getClobTypeDeclarationSQL($fieldDeclaration);
+ }
+
+ public function convertToDatabaseValue($value, \Doctrine\DBAL\Platforms\AbstractPlatform $platform)
+ {
+ return serialize($value);
+ }
+
+ public function convertToPHPValue($value, \Doctrine\DBAL\Platforms\AbstractPlatform $platform)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ $value = (is_resource($value)) ? stream_get_contents($value) : $value;
+ $val = unserialize($value);
+ if ($val === false) {
+ throw ConversionException::conversionFailed($value, $this->getName());
+ }
+ return $val;
+ }
+
+ public function getName()
+ {
+ return Type::TARRAY;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Types;
+
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+
+/**
+ * Type that maps a database BIGINT to a PHP string.
+ *
+ * @author robo
+ * @since 2.0
+ */
+class BigIntType extends Type
+{
+ public function getName()
+ {
+ return Type::BIGINT;
+ }
+
+ public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
+ {
+ return $platform->getBigIntTypeDeclarationSQL($fieldDeclaration);
+ }
+
+ public function getBindingType()
+ {
+ return \PDO::PARAM_INT;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Types;
+
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+
+/**
+ * Type that maps an SQL boolean to a PHP boolean.
+ *
+ * @since 2.0
+ */
+class BooleanType extends Type
+{
+ public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
+ {
+ return $platform->getBooleanTypeDeclarationSQL($fieldDeclaration);
+ }
+
+ public function convertToDatabaseValue($value, AbstractPlatform $platform)
+ {
+ return $platform->convertBooleans($value);
+ }
+
+ public function convertToPHPValue($value, AbstractPlatform $platform)
+ {
+ return (null === $value) ? null : (bool) $value;
+ }
+
+ public function getName()
+ {
+ return Type::BOOLEAN;
+ }
+
+ public function getBindingType()
+ {
+ return \PDO::PARAM_BOOL;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+
+/**
+ * Conversion Exception is thrown when the database to PHP conversion fails
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 2.0
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+namespace Doctrine\DBAL\Types;
+
+class ConversionException extends \Doctrine\DBAL\DBALException
+{
+ /**
+ * Thrown when a Database to Doctrine Type Conversion fails.
+ *
+ * @param string $value
+ * @param string $toType
+ * @return ConversionException
+ */
+ static public function conversionFailed($value, $toType)
+ {
+ $value = (strlen($value) > 32) ? substr($value, 0, 20) . "..." : $value;
+ return new self('Could not convert database value "' . $value . '" to Doctrine Type ' . $toType);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Types;
+
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+
+/**
+ * Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTime object.
+ *
+ * @since 2.0
+ */
+class DateTimeType extends Type
+{
+ public function getName()
+ {
+ return Type::DATETIME;
+ }
+
+ public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
+ {
+ return $platform->getDateTimeTypeDeclarationSQL($fieldDeclaration);
+ }
+
+ public function convertToDatabaseValue($value, AbstractPlatform $platform)
+ {
+ return ($value !== null)
+ ? $value->format($platform->getDateTimeFormatString()) : null;
+ }
+
+ public function convertToPHPValue($value, AbstractPlatform $platform)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ $val = \DateTime::createFromFormat($platform->getDateTimeFormatString(), $value);
+ if (!$val) {
+ throw ConversionException::conversionFailed($value, $this->getName());
+ }
+ return $val;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+
+namespace Doctrine\DBAL\Types;
+
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+
+/**
+ * DateTime type saving additional timezone information.
+ *
+ * Caution: Databases are not necessarily experts at storing timezone related
+ * data of dates. First, of all the supported vendors only PostgreSQL and Oracle
+ * support storing Timezone data. But those two don't save the actual timezone
+ * attached to a DateTime instance (for example "Europe/Berlin" or "America/Montreal")
+ * but the current offset of them related to UTC. That means depending on daylight saving times
+ * or not you may get different offsets.
+ *
+ * This datatype makes only sense to use, if your application works with an offset, not
+ * with an actual timezone that uses transitions. Otherwise your DateTime instance
+ * attached with a timezone such as Europe/Berlin gets saved into the database with
+ * the offset and re-created from persistence with only the offset, not the original timezone
+ * attached.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 1.0
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class DateTimeTzType extends Type
+{
+ public function getName()
+ {
+ return Type::DATETIMETZ;
+ }
+
+ public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
+ {
+ return $platform->getDateTimeTzTypeDeclarationSQL($fieldDeclaration);
+ }
+
+ public function convertToDatabaseValue($value, AbstractPlatform $platform)
+ {
+ return ($value !== null)
+ ? $value->format($platform->getDateTimeTzFormatString()) : null;
+ }
+
+ public function convertToPHPValue($value, AbstractPlatform $platform)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ $val = \DateTime::createFromFormat($platform->getDateTimeTzFormatString(), $value);
+ if (!$val) {
+ throw ConversionException::conversionFailed($value, $this->getName());
+ }
+ return $val;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Types;
+
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+
+/**
+ * Type that maps an SQL DATE to a PHP Date object.
+ *
+ * @since 2.0
+ */
+class DateType extends Type
+{
+ public function getName()
+ {
+ return Type::DATE;
+ }
+
+ public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
+ {
+ return $platform->getDateTypeDeclarationSQL($fieldDeclaration);
+ }
+
+ public function convertToDatabaseValue($value, AbstractPlatform $platform)
+ {
+ return ($value !== null)
+ ? $value->format($platform->getDateFormatString()) : null;
+ }
+
+ public function convertToPHPValue($value, AbstractPlatform $platform)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ $val = \DateTime::createFromFormat('!'.$platform->getDateFormatString(), $value);
+ if (!$val) {
+ throw ConversionException::conversionFailed($value, $this->getName());
+ }
+ return $val;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Types;
+
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+
+/**
+ * Type that maps an SQL DECIMAL to a PHP double.
+ *
+ * @since 2.0
+ */
+class DecimalType extends Type
+{
+ public function getName()
+ {
+ return Type::DECIMAL;
+ }
+
+ public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
+ {
+ return $platform->getDecimalTypeDeclarationSQL($fieldDeclaration);
+ }
+
+ public function convertToPHPValue($value, AbstractPlatform $platform)
+ {
+ return (null === $value) ? null : (double) $value;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+
+namespace Doctrine\DBAL\Types;
+
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+
+class FloatType extends Type
+{
+ public function getName()
+ {
+ return Type::FLOAT;
+ }
+
+ /**
+ * @param array $fieldDeclaration
+ * @param AbstractPlatform $platform
+ * @return string
+ */
+ public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
+ {
+ return $platform->getFloatDeclarationSQL($fieldDeclaration);
+ }
+
+ /**
+ * Converts a value from its database representation to its PHP representation
+ * of this type.
+ *
+ * @param mixed $value The value to convert.
+ * @param AbstractPlatform $platform The currently used database platform.
+ * @return mixed The PHP representation of the value.
+ */
+ public function convertToPHPValue($value, AbstractPlatform $platform)
+ {
+ return (null === $value) ? null : (double) $value;
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Types;
+
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+
+/**
+ * Type that maps an SQL INT to a PHP integer.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @since 2.0
+ */
+class IntegerType extends Type
+{
+ public function getName()
+ {
+ return Type::INTEGER;
+ }
+
+ public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
+ {
+ return $platform->getIntegerTypeDeclarationSQL($fieldDeclaration);
+ }
+
+ public function convertToPHPValue($value, AbstractPlatform $platform)
+ {
+ return (null === $value) ? null : (int) $value;
+ }
+
+ public function getBindingType()
+ {
+ return \PDO::PARAM_INT;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace Doctrine\DBAL\Types;
+
+/**
+ * Type that maps a PHP object to a clob SQL type.
+ *
+ * @since 2.0
+ */
+class ObjectType extends Type
+{
+ public function getSQLDeclaration(array $fieldDeclaration, \Doctrine\DBAL\Platforms\AbstractPlatform $platform)
+ {
+ return $platform->getClobTypeDeclarationSQL($fieldDeclaration);
+ }
+
+ public function convertToDatabaseValue($value, \Doctrine\DBAL\Platforms\AbstractPlatform $platform)
+ {
+ return serialize($value);
+ }
+
+ public function convertToPHPValue($value, \Doctrine\DBAL\Platforms\AbstractPlatform $platform)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ $value = (is_resource($value)) ? stream_get_contents($value) : $value;
+ $val = unserialize($value);
+ if ($val === false) {
+ throw ConversionException::conversionFailed($value, $this->getName());
+ }
+ return $val;
+ }
+
+ public function getName()
+ {
+ return Type::OBJECT;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Types;
+
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+
+/**
+ * Type that maps a database SMALLINT to a PHP integer.
+ *
+ * @author robo
+ */
+class SmallIntType extends Type
+{
+ public function getName()
+ {
+ return Type::SMALLINT;
+ }
+
+ public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
+ {
+ return $platform->getSmallIntTypeDeclarationSQL($fieldDeclaration);
+ }
+
+ public function convertToPHPValue($value, AbstractPlatform $platform)
+ {
+ return (null === $value) ? null : (int) $value;
+ }
+
+ public function getBindingType()
+ {
+ return \PDO::PARAM_INT;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Types;
+
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+
+/**
+ * Type that maps an SQL VARCHAR to a PHP string.
+ *
+ * @since 2.0
+ */
+class StringType extends Type
+{
+ /** @override */
+ public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
+ {
+ return $platform->getVarcharTypeDeclarationSQL($fieldDeclaration);
+ }
+
+ /** @override */
+ public function getDefaultLength(AbstractPlatform $platform)
+ {
+ return $platform->getVarcharDefaultLength();
+ }
+
+ /** @override */
+ public function getName()
+ {
+ return Type::STRING;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Types;
+
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+
+/**
+ * Type that maps an SQL CLOB to a PHP string.
+ *
+ * @since 2.0
+ */
+class TextType extends Type
+{
+ /** @override */
+ public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
+ {
+ return $platform->getClobTypeDeclarationSQL($fieldDeclaration);
+ }
+
+ /**
+ * Converts a value from its database representation to its PHP representation
+ * of this type.
+ *
+ * @param mixed $value The value to convert.
+ * @param AbstractPlatform $platform The currently used database platform.
+ * @return mixed The PHP representation of the value.
+ */
+ public function convertToPHPValue($value, AbstractPlatform $platform)
+ {
+ return (is_resource($value)) ? stream_get_contents($value) : $value;
+ }
+
+ public function getName()
+ {
+ return Type::TEXT;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Types;
+
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+
+/**
+ * Type that maps an SQL TIME to a PHP DateTime object.
+ *
+ * @since 2.0
+ */
+class TimeType extends Type
+{
+ public function getName()
+ {
+ return Type::TIME;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
+ {
+ return $platform->getTimeTypeDeclarationSQL($fieldDeclaration);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function convertToDatabaseValue($value, AbstractPlatform $platform)
+ {
+ return ($value !== null)
+ ? $value->format($platform->getTimeFormatString()) : null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function convertToPHPValue($value, AbstractPlatform $platform)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ $val = \DateTime::createFromFormat($platform->getTimeFormatString(), $value);
+ if (!$val) {
+ throw ConversionException::conversionFailed($value, $this->getName());
+ }
+ return $val;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL\Types;
+
+use Doctrine\DBAL\Platforms\AbstractPlatform,
+ Doctrine\DBAL\DBALException;
+
+/**
+ * The base class for so-called Doctrine mapping types.
+ *
+ * A Type object is obtained by calling the static {@link getType()} method.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @since 2.0
+ */
+abstract class Type
+{
+ const TARRAY = 'array';
+ const BIGINT = 'bigint';
+ const BOOLEAN = 'boolean';
+ const DATETIME = 'datetime';
+ const DATETIMETZ = 'datetimetz';
+ const DATE = 'date';
+ const TIME = 'time';
+ const DECIMAL = 'decimal';
+ const INTEGER = 'integer';
+ const OBJECT = 'object';
+ const SMALLINT = 'smallint';
+ const STRING = 'string';
+ const TEXT = 'text';
+ const FLOAT = 'float';
+
+ /** Map of already instantiated type objects. One instance per type (flyweight). */
+ private static $_typeObjects = array();
+
+ /** The map of supported doctrine mapping types. */
+ private static $_typesMap = array(
+ self::TARRAY => 'Doctrine\DBAL\Types\ArrayType',
+ self::OBJECT => 'Doctrine\DBAL\Types\ObjectType',
+ self::BOOLEAN => 'Doctrine\DBAL\Types\BooleanType',
+ self::INTEGER => 'Doctrine\DBAL\Types\IntegerType',
+ self::SMALLINT => 'Doctrine\DBAL\Types\SmallIntType',
+ self::BIGINT => 'Doctrine\DBAL\Types\BigIntType',
+ self::STRING => 'Doctrine\DBAL\Types\StringType',
+ self::TEXT => 'Doctrine\DBAL\Types\TextType',
+ self::DATETIME => 'Doctrine\DBAL\Types\DateTimeType',
+ self::DATETIMETZ => 'Doctrine\DBAL\Types\DateTimeTzType',
+ self::DATE => 'Doctrine\DBAL\Types\DateType',
+ self::TIME => 'Doctrine\DBAL\Types\TimeType',
+ self::DECIMAL => 'Doctrine\DBAL\Types\DecimalType',
+ self::FLOAT => 'Doctrine\DBAL\Types\FloatType',
+ );
+
+ /* Prevent instantiation and force use of the factory method. */
+ final private function __construct() {}
+
+ /**
+ * Converts a value from its PHP representation to its database representation
+ * of this type.
+ *
+ * @param mixed $value The value to convert.
+ * @param AbstractPlatform $platform The currently used database platform.
+ * @return mixed The database representation of the value.
+ */
+ public function convertToDatabaseValue($value, AbstractPlatform $platform)
+ {
+ return $value;
+ }
+
+ /**
+ * Converts a value from its database representation to its PHP representation
+ * of this type.
+ *
+ * @param mixed $value The value to convert.
+ * @param AbstractPlatform $platform The currently used database platform.
+ * @return mixed The PHP representation of the value.
+ */
+ public function convertToPHPValue($value, AbstractPlatform $platform)
+ {
+ return $value;
+ }
+
+ /**
+ * Gets the default length of this type.
+ *
+ * @todo Needed?
+ */
+ public function getDefaultLength(AbstractPlatform $platform)
+ {
+ return null;
+ }
+
+ /**
+ * Gets the SQL declaration snippet for a field of this type.
+ *
+ * @param array $fieldDeclaration The field declaration.
+ * @param AbstractPlatform $platform The currently used database platform.
+ */
+ abstract public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform);
+
+ /**
+ * Gets the name of this type.
+ *
+ * @return string
+ * @todo Needed?
+ */
+ abstract public function getName();
+
+ /**
+ * Factory method to create type instances.
+ * Type instances are implemented as flyweights.
+ *
+ * @static
+ * @throws DBALException
+ * @param string $name The name of the type (as returned by getName()).
+ * @return Doctrine\DBAL\Types\Type
+ */
+ public static function getType($name)
+ {
+ if ( ! isset(self::$_typeObjects[$name])) {
+ if ( ! isset(self::$_typesMap[$name])) {
+ throw DBALException::unknownColumnType($name);
+ }
+ self::$_typeObjects[$name] = new self::$_typesMap[$name]();
+ }
+
+ return self::$_typeObjects[$name];
+ }
+
+ /**
+ * Adds a custom type to the type map.
+ *
+ * @static
+ * @param string $name Name of the type. This should correspond to what getName() returns.
+ * @param string $className The class name of the custom type.
+ * @throws DBALException
+ */
+ public static function addType($name, $className)
+ {
+ if (isset(self::$_typesMap[$name])) {
+ throw DBALException::typeExists($name);
+ }
+
+ self::$_typesMap[$name] = $className;
+ }
+
+ /**
+ * Checks if exists support for a type.
+ *
+ * @static
+ * @param string $name Name of the type
+ * @return boolean TRUE if type is supported; FALSE otherwise
+ */
+ public static function hasType($name)
+ {
+ return isset(self::$_typesMap[$name]);
+ }
+
+ /**
+ * Overrides an already defined type to use a different implementation.
+ *
+ * @static
+ * @param string $name
+ * @param string $className
+ * @throws DBALException
+ */
+ public static function overrideType($name, $className)
+ {
+ if ( ! isset(self::$_typesMap[$name])) {
+ throw DBALException::typeNotFound($name);
+ }
+
+ self::$_typesMap[$name] = $className;
+ }
+
+ /**
+ * Gets the (preferred) binding type for values of this type that
+ * can be used when binding parameters to prepared statements.
+ *
+ * This method should return one of the PDO::PARAM_* constants, that is, one of:
+ *
+ * PDO::PARAM_BOOL
+ * PDO::PARAM_NULL
+ * PDO::PARAM_INT
+ * PDO::PARAM_STR
+ * PDO::PARAM_LOB
+ *
+ * @return integer
+ */
+ public function getBindingType()
+ {
+ return \PDO::PARAM_STR;
+ }
+
+ /**
+ * Get the types array map which holds all registered types and the corresponding
+ * type class
+ *
+ * @return array $typesMap
+ */
+ public static function getTypesMap()
+ {
+ return self::$_typesMap;
+ }
+
+ public function __toString()
+ {
+ $e = explode('\\', get_class($this));
+ return str_replace('Type', '', end($e));
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+
+namespace Doctrine\DBAL\Types;
+
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+
+/**
+ * Variable DateTime Type using date_create() instead of DateTime::createFromFormat()
+ *
+ * This type has performance implications as it runs twice as long as the regular
+ * {@see DateTimeType}, however in certain PostgreSQL configurations with
+ * TIMESTAMP(n) columns where n > 0 it is necessary to use this type.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 2.0
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class VarDateTimeType extends DateTimeType
+{
+ /**
+ * @throws ConversionException
+ * @param string $value
+ * @param AbstractPlatform $platform
+ * @return DateTime
+ */
+ public function convertToPHPValue($value, AbstractPlatform $platform)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ $val = date_create($value);
+ if (!$val) {
+ throw ConversionException::conversionFailed($value, $this->getName());
+ }
+ return $val;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\DBAL;
+
+/**
+ * Class to store and retrieve the version of Doctrine
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class Version
+{
+ /**
+ * Current Doctrine Version
+ */
+ const VERSION = '2.0.0RC4-DEV';
+
+ /**
+ * 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);
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id: Abstract.php 1393 2008-03-06 17:49:16Z guilhermeblanco $
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM;
+
+use Doctrine\DBAL\Types\Type,
+ Doctrine\ORM\Query\QueryException;
+
+/**
+ * Base contract for ORM queries. Base class for Query and NativeQuery.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
+ */
+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;
+
+ /**
+ * @var array The parameter map of this query.
+ */
+ protected $_params = array();
+
+ /**
+ * @var array The parameter type map of this query.
+ */
+ protected $_paramTypes = array();
+
+ /**
+ * @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;
+
+ /**
+ * The locally set cache driver used for caching result sets of this query.
+ *
+ * @var CacheDriver
+ */
+ protected $_resultCacheDriver;
+
+ /**
+ * Boolean flag for whether or not to cache the results of this query.
+ *
+ * @var boolean
+ */
+ protected $_useResultCache;
+
+ /**
+ * @var string The id to store the result cache entry under.
+ */
+ protected $_resultCacheId;
+
+ /**
+ * @var boolean Boolean value that indicates whether or not expire the result cache.
+ */
+ protected $_expireResultCache = false;
+
+ /**
+ * @var int Result Cache lifetime.
+ */
+ protected $_resultCacheTTL;
+
+ /**
+ * Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
+ *
+ * @param Doctrine\ORM\EntityManager $entityManager
+ */
+ public function __construct(EntityManager $em)
+ {
+ $this->_em = $em;
+ }
+
+ /**
+ * 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->_params = array();
+ $this->_paramTypes = array();
+ $this->_hints = array();
+ }
+
+ /**
+ * Get all defined parameters.
+ *
+ * @return array The defined query parameters.
+ */
+ public function getParameters()
+ {
+ return $this->_params;
+ }
+
+ /**
+ * 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)
+ {
+ return isset($this->_params[$key]) ? $this->_params[$key] : null;
+ }
+
+ /**
+ * 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();
+
+ /**
+ * 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)
+ {
+ if ($type !== null) {
+ $this->_paramTypes[$key] = $type;
+ }
+ $this->_params[$key] = $value;
+ return $this;
+ }
+
+ /**
+ * Sets a collection of query parameters.
+ *
+ * @param array $params
+ * @param array $types
+ * @return Doctrine\ORM\AbstractQuery This query instance.
+ */
+ public function setParameters(array $params, array $types = array())
+ {
+ foreach ($params as $key => $value) {
+ if (isset($types[$key])) {
+ $this->setParameter($key, $value, $types[$key]);
+ } else {
+ $this->setParameter($key, $value);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Defines a cache driver to be used for caching result sets.
+ *
+ * @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->_resultCacheDriver = $resultCacheDriver;
+ if ($resultCacheDriver) {
+ $this->_useResultCache = true;
+ }
+ return $this;
+ }
+
+ /**
+ * Returns the cache driver used for caching result sets.
+ *
+ * @return Doctrine\Common\Cache\Cache Cache driver
+ */
+ public function getResultCacheDriver()
+ {
+ if ($this->_resultCacheDriver) {
+ return $this->_resultCacheDriver;
+ } else {
+ 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 $timeToLive
+ * @param string $resultCacheId
+ * @return This query instance.
+ */
+ public function useResultCache($bool, $timeToLive = null, $resultCacheId = null)
+ {
+ $this->_useResultCache = $bool;
+ if ($timeToLive) {
+ $this->setResultCacheLifetime($timeToLive);
+ }
+ if ($resultCacheId) {
+ $this->_resultCacheId = $resultCacheId;
+ }
+ return $this;
+ }
+
+ /**
+ * Defines how long the result cache will be active before expire.
+ *
+ * @param integer $timeToLive How long the cache entry is valid.
+ * @return Doctrine\ORM\AbstractQuery This query instance.
+ */
+ public function setResultCacheLifetime($timeToLive)
+ {
+ if ($timeToLive !== null) {
+ $timeToLive = (int) $timeToLive;
+ }
+
+ $this->_resultCacheTTL = $timeToLive;
+ return $this;
+ }
+
+ /**
+ * Retrieves the lifetime of resultset cache.
+ *
+ * @return integer
+ */
+ public function getResultCacheLifetime()
+ {
+ return $this->_resultCacheTTL;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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(array(), $hydrationMode = HYDRATE_OBJECT).
+ *
+ * @return array
+ */
+ public function getResult($hydrationMode = self::HYDRATE_OBJECT)
+ {
+ return $this->execute(array(), $hydrationMode);
+ }
+
+ /**
+ * Gets the array of results for the query.
+ *
+ * Alias for execute(array(), HYDRATE_ARRAY).
+ *
+ * @return array
+ */
+ public function getArrayResult()
+ {
+ return $this->execute(array(), self::HYDRATE_ARRAY);
+ }
+
+ /**
+ * Gets the scalar results for the query.
+ *
+ * Alias for execute(array(), HYDRATE_SCALAR).
+ *
+ * @return array
+ */
+ public function getScalarResult()
+ {
+ return $this->execute(array(), self::HYDRATE_SCALAR);
+ }
+
+ /**
+ * 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(array(), $hydrationMode);
+
+ if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
+ throw new NoResultException;
+ }
+
+ if (is_array($result)) {
+ if (count($result) > 1) {
+ throw new NonUniqueResultException;
+ }
+ return array_shift($result);
+ }
+
+ return $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;
+ }
+
+ /**
+ * Executes the query and returns an IterableResult that can be used to incrementally
+ * iterate over the result.
+ *
+ * @param array $params The query parameters.
+ * @param integer $hydrationMode The hydration mode to use.
+ * @return IterableResult
+ */
+ public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT)
+ {
+ return $this->_em->newHydrator($this->_hydrationMode)->iterate(
+ $this->_doExecute($params, $hydrationMode), $this->_resultSetMapping, $this->_hints
+ );
+ }
+
+ /**
+ * Executes the query.
+ *
+ * @param string $params Any additional query parameters.
+ * @param integer $hydrationMode Processing mode to be used during the hydration process.
+ * @return mixed
+ */
+ public function execute($params = array(), $hydrationMode = null)
+ {
+ // If there are still pending insertions in the UnitOfWork we need to flush
+ // in order to guarantee a correct result.
+ //TODO: Think this over. Its tricky. Not doing this can lead to strange results
+ // potentially, but doing it could result in endless loops when querying during
+ // a flush, i.e. inside an event listener.
+ if ($this->_em->getUnitOfWork()->hasPendingInsertions()) {
+ $this->_em->flush();
+ }
+
+ if ($hydrationMode !== null) {
+ $this->setHydrationMode($hydrationMode);
+ }
+
+ if ($params) {
+ $this->setParameters($params);
+ }
+
+ if (isset($this->_params[0])) {
+ throw QueryException::invalidParameterPosition(0);
+ }
+
+ // Check result cache
+ if ($this->_useResultCache && $cacheDriver = $this->getResultCacheDriver()) {
+ $id = $this->_getResultCacheId();
+ $cached = $this->_expireResultCache ? false : $cacheDriver->fetch($id);
+
+ if ($cached === false) {
+ // Cache miss.
+ $stmt = $this->_doExecute();
+
+ $result = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
+ $stmt, $this->_resultSetMapping, $this->_hints
+ );
+
+ $cacheDriver->save($id, $result, $this->_resultCacheTTL);
+
+ return $result;
+ } else {
+ // Cache hit.
+ return $cached;
+ }
+ }
+
+ $stmt = $this->_doExecute();
+
+ if (is_numeric($stmt)) {
+ return $stmt;
+ }
+
+ return $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
+ $stmt, $this->_resultSetMapping, $this->_hints
+ );
+ }
+
+ /**
+ * Set the result cache id to use to store the result set cache entry.
+ * If this is not explicitely 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->_resultCacheId = $id;
+ return $this;
+ }
+
+ /**
+ * 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 string $id
+ */
+ protected function _getResultCacheId()
+ {
+ if ($this->_resultCacheId) {
+ return $this->_resultCacheId;
+ } else {
+ $params = $this->_params;
+ foreach ($params AS $key => $value) {
+ if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($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);
+ }
+ $params[$key] = $idValues;
+ }
+ }
+
+ $sql = $this->getSql();
+ ksort($this->_hints);
+ return md5(implode(";", (array)$sql) . var_export($params, true) .
+ var_export($this->_hints, true)."&hydrationMode=".$this->_hydrationMode);
+ }
+ }
+
+ /**
+ * 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->_params = array();
+ $this->_paramTypes = array();
+ $this->_hints = array();
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM;
+
+use Doctrine\Common\Cache\Cache,
+ Doctrine\ORM\Mapping\Driver\Driver;
+
+/**
+ * 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 <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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 Driver $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(Driver $driverImpl)
+ {
+ $this->_attributes['metadataDriverImpl'] = $driverImpl;
+ }
+
+ /**
+ * Add a new default annotation driver with a correctly configured annotation reader.
+ *
+ * @param array $paths
+ * @return Mapping\Driver\AnnotationDriver
+ */
+ public function newDefaultAnnotationDriver($paths = array())
+ {
+ $reader = new \Doctrine\Common\Annotations\AnnotationReader();
+ $reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
+
+ return new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader, (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
+ * @return string
+ * @throws MappingException
+ */
+ 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 $entityAliasMap
+ * @return void
+ */
+ public function setEntityNamespaces(array $entityNamespaces)
+ {
+ $this->_attributes['entityNamespaces'] = $entityNamespaces;
+ }
+
+ /**
+ * Gets the cache driver implementation that is used for the mapping metadata.
+ *
+ * @throws ORMException
+ * @return Mapping\Driver\Driver
+ */
+ public function getMetadataDriverImpl()
+ {
+ return isset($this->_attributes['metadataDriverImpl']) ?
+ $this->_attributes['metadataDriverImpl'] : null;
+ }
+
+ /**
+ * Gets the cache driver implementation that is used for query result caching.
+ *
+ * @return \Doctrine\Common\Cache\Cache
+ */
+ public function getResultCacheImpl()
+ {
+ return isset($this->_attributes['resultCacheImpl']) ?
+ $this->_attributes['resultCacheImpl'] : null;
+ }
+
+ /**
+ * Sets the cache driver implementation that is used for query result caching.
+ *
+ * @param \Doctrine\Common\Cache\Cache $cacheImpl
+ */
+ public function setResultCacheImpl(Cache $cacheImpl)
+ {
+ $this->_attributes['resultCacheImpl'] = $cacheImpl;
+ }
+
+ /**
+ * 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 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.
+ * @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 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.
+ * @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
+ */
+ public function addCustomStringFunction($name, $className)
+ {
+ $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)
+ {
+ $this->_attributes['customStringFunctions'] = array_change_key_case($functions);
+ }
+
+ /**
+ * 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
+ */
+ public function addCustomNumericFunction($name, $className)
+ {
+ $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)
+ {
+ $this->_attributes['customNumericFunctions'] = array_change_key_case($functions);
+ }
+
+ /**
+ * 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
+ */
+ public function addCustomDatetimeFunction($name, $className)
+ {
+ $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)
+ {
+ $this->_attributes['customDatetimeFunctions'] = array_change_key_case($functions);
+ }
+
+ /**
+ * 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 $cmf
+ */
+ 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'];
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM;
+
+use Closure, Exception,
+ Doctrine\Common\EventManager,
+ Doctrine\DBAL\Connection,
+ Doctrine\DBAL\LockMode,
+ Doctrine\ORM\Mapping\ClassMetadata,
+ Doctrine\ORM\Mapping\ClassMetadataFactory,
+ Doctrine\ORM\Query\ResultSetMapping,
+ Doctrine\ORM\Proxy\ProxyFactory;
+
+/**
+ * The EntityManager is the central access point to ORM functionality.
+ *
+ * @since 2.0
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class EntityManager
+{
+ /**
+ * 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;
+
+ /**
+ * @var ExpressionBuilder The expression builder instance used to generate query expressions.
+ */
+ private $expressionBuilder;
+
+ /**
+ * Whether the EntityManager is closed or not.
+ */
+ private $closed = false;
+
+ /**
+ * 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:
+ *
+ * <code>
+ * $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)));
+ * </code>
+ *
+ * @return ExpressionBuilder
+ */
+ public function getExpressionBuilder()
+ {
+ if ($this->expressionBuilder === null) {
+ $this->expressionBuilder = new Query\Expr;
+ }
+ return $this->expressionBuilder;
+ }
+
+ /**
+ * Starts a transaction on the underlying database connection.
+ *
+ * @deprecated Use {@link getConnection}.beginTransaction().
+ */
+ 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 Closure $func The function to execute transactionally.
+ */
+ public function transactional(Closure $func)
+ {
+ $this->conn->beginTransaction();
+ try {
+ $func($this);
+ $this->flush();
+ $this->conn->commit();
+ } catch (Exception $e) {
+ $this->close();
+ $this->conn->rollback();
+ throw $e;
+ }
+ }
+
+ /**
+ * Commits a transaction on the underlying database connection.
+ *
+ * @deprecated Use {@link getConnection}.commit().
+ */
+ public function commit()
+ {
+ $this->conn->commit();
+ }
+
+ /**
+ * Performs a rollback on the underlying database connection.
+ *
+ * @deprecated Use {@link getConnection}.rollback().
+ */
+ 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 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.
+ *
+ * @throws Doctrine\ORM\OptimisticLockException If a version check on an entity that
+ * makes use of optimistic locking fails.
+ */
+ public function flush()
+ {
+ $this->errorIfClosed();
+ $this->unitOfWork->commit();
+ }
+
+ /**
+ * Finds an Entity by its identifier.
+ *
+ * This is just a convenient shortcut for getRepository($entityName)->find($id).
+ *
+ * @param string $entityName
+ * @param mixed $identifier
+ * @param int $lockMode
+ * @param int $lockVersion
+ * @return object
+ */
+ public function find($entityName, $identifier, $lockMode = LockMode::NONE, $lockVersion = null)
+ {
+ return $this->getRepository($entityName)->find($identifier, $lockMode, $lockVersion);
+ }
+
+ /**
+ * 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 $identifier The entity identifier.
+ * @return object The entity reference.
+ */
+ public function getReference($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)) {
+ return $entity;
+ }
+ if ($class->subClasses) {
+ $entity = $this->find($entityName, $identifier);
+ } else {
+ if ( ! is_array($identifier)) {
+ $identifier = array($class->identifier[0] => $identifier);
+ }
+ $entity = $this->proxyFactory->getProxy($class->name, $identifier);
+ $this->unitOfWork->registerManaged($entity, $identifier, 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)) {
+ return $entity;
+ }
+ if ( ! is_array($identifier)) {
+ $identifier = array($class->identifier[0] => $identifier);
+ }
+
+ $entity = $class->newInstance();
+ $class->setIdentifierValues($entity, $identifier);
+ $this->unitOfWork->registerManaged($entity, $identifier, array());
+
+ return $entity;
+ }
+
+ /**
+ * Clears the EntityManager. All entities that are currently managed
+ * by this EntityManager become detached.
+ *
+ * @param string $entityName
+ */
+ public function clear($entityName = null)
+ {
+ if ($entityName === null) {
+ $this->unitOfWork->clear();
+ } else {
+ //TODO
+ throw new ORMException("EntityManager#clear(\$entityName) not yet implemented.");
+ }
+ }
+
+ /**
+ * 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 new \InvalidArgumentException(gettype($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 new \InvalidArgumentException(gettype($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 new \InvalidArgumentException(gettype($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 new \InvalidArgumentException(gettype($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 new \InvalidArgumentException(gettype($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);
+ $customRepositoryClassName = $metadata->customRepositoryClassName;
+
+ if ($customRepositoryClassName !== null) {
+ $repository = new $customRepositoryClassName($this, $metadata);
+ } else {
+ $repository = new EntityRepository($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:
+ $hydrator = new Internal\Hydration\ObjectHydrator($this);
+ break;
+ case Query::HYDRATE_ARRAY:
+ $hydrator = new Internal\Hydration\ArrayHydrator($this);
+ break;
+ case Query::HYDRATE_SCALAR:
+ $hydrator = new Internal\Hydration\ScalarHydrator($this);
+ break;
+ case Query::HYDRATE_SINGLE_SCALAR:
+ $hydrator = new Internal\Hydration\SingleScalarHydrator($this);
+ break;
+ default:
+ if ($class = $this->config->getCustomHydrationMode($hydrationMode)) {
+ $hydrator = new $class($this);
+ break;
+ }
+ throw ORMException::invalidHydrationMode($hydrationMode);
+ }
+
+ return $hydrator;
+ }
+
+ /**
+ * Gets the proxy factory used by the EntityManager to create entity proxies.
+ *
+ * @return ProxyFactory
+ */
+ public function getProxyFactory()
+ {
+ return $this->proxyFactory;
+ }
+
+ /**
+ * 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();
+ }
+
+ if (is_array($conn)) {
+ $conn = \Doctrine\DBAL\DriverManager::getConnection($conn, $config, ($eventManager ?: new EventManager()));
+ } else if ($conn instanceof Connection) {
+ if ($eventManager !== null && $conn->getEventManager() !== $eventManager) {
+ throw ORMException::mismatchedEventManager();
+ }
+ } else {
+ throw new \InvalidArgumentException("Invalid argument: " . $conn);
+ }
+
+ return new EntityManager($conn, $config, $conn->getEventManager());
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM;
+
+/**
+ * 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.');
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM;
+
+use Doctrine\DBAL\LockMode;
+
+/**
+ * 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 <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class EntityRepository
+{
+ /**
+ * @var string
+ */
+ protected $_entityName;
+
+ /**
+ * @var EntityManager
+ */
+ protected $_em;
+
+ /**
+ * @var Doctrine\ORM\Mapping\ClassMetadata
+ */
+ protected $_class;
+
+ /**
+ * Initializes a new <tt>EntityRepository</tt>.
+ *
+ * @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);
+ }
+
+ /**
+ * 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 $id The identifier.
+ * @param int $lockMode
+ * @param int $lockVersion
+ * @return object The entity.
+ */
+ public function find($id, $lockMode = LockMode::NONE, $lockVersion = null)
+ {
+ // Check identity map first
+ if ($entity = $this->_em->getUnitOfWork()->tryGetById($id, $this->_class->rootEntityName)) {
+ if ($lockMode != LockMode::NONE) {
+ $this->_em->lock($entity, $lockMode, $lockVersion);
+ }
+
+ return $entity; // Hit!
+ }
+
+ if ( ! is_array($id) || count($id) <= 1) {
+ // @todo FIXME: Not correct. Relies on specific order.
+ $value = is_array($id) ? array_values($id) : array($id);
+ $id = array_combine($this->_class->identifier, $value);
+ }
+
+ if ($lockMode == LockMode::NONE) {
+ return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id);
+ } else if ($lockMode == LockMode::OPTIMISTIC) {
+ if (!$this->_class->isVersioned) {
+ throw OptimisticLockException::notVersioned($this->_entityName);
+ }
+ $entity = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id);
+
+ $this->_em->getUnitOfWork()->lock($entity, $lockMode, $lockVersion);
+
+ return $entity;
+ } else {
+ if (!$this->_em->getConnection()->isTransactionActive()) {
+ throw TransactionRequiredException::transactionRequired();
+ }
+
+ return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id, null, null, array(), $lockMode);
+ }
+ }
+
+ /**
+ * 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
+ * @return array
+ */
+ public function findBy(array $criteria)
+ {
+ return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->loadAll($criteria);
+ }
+
+ /**
+ * Finds a single entity by a set of criteria.
+ *
+ * @param array $criteria
+ * @return object
+ */
+ public function findOneBy(array $criteria)
+ {
+ return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($criteria);
+ }
+
+ /**
+ * 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)
+ {
+ if (substr($method, 0, 6) == 'findBy') {
+ $by = substr($method, 6, strlen($method));
+ $method = 'findBy';
+ } else if (substr($method, 0, 9) == 'findOneBy') {
+ $by = substr($method, 9, strlen($method));
+ $method = 'findOneBy';
+ } else {
+ throw new \BadMethodCallException(
+ "Undefined method '$method'. The method name must start with ".
+ "either findBy or findOneBy!"
+ );
+ }
+
+ if ( !isset($arguments[0])) {
+ // we dont even want to allow null at this point, because we cannot (yet) transform it into IS NULL.
+ throw ORMException::findByRequiresParameter($method.$by);
+ }
+
+ $fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by));
+
+ if ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName)) {
+ return $this->$method(array($fieldName => $arguments[0]));
+ } else {
+ throw ORMException::invalidFindByCall($this->_entityName, $fieldName, $method.$by);
+ }
+ }
+
+ /**
+ * @return string
+ */
+ protected function getEntityName()
+ {
+ return $this->_entityName;
+ }
+
+ /**
+ * @return EntityManager
+ */
+ protected function getEntityManager()
+ {
+ return $this->_em;
+ }
+
+ /**
+ * @return Mapping\ClassMetadata
+ */
+ protected function getClassMetadata()
+ {
+ return $this->_class;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\ORM\Event;
+
+/**
+ * Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions
+ * of entities.
+ *
+ * @since 2.0
+ * @author Roman Borschel <roman@code-factory.de>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class LifecycleEventArgs extends \Doctrine\Common\EventArgs
+{
+ /**
+ * @var EntityManager
+ */
+ private $_em;
+
+ /**
+ * @var object
+ */
+ private $_entity;
+
+ public function __construct($entity, $em)
+ {
+ $this->_entity = $entity;
+ $this->_em = $em;
+ }
+
+ public function getEntity()
+ {
+ return $this->_entity;
+ }
+
+ /**
+ * @return EntityManager
+ */
+ public function getEntityManager()
+ {
+ return $this->_em;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+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 <jonwage@gmail.com>
+ * @since 2.0
+ */
+class LoadClassMetadataEventArgs extends EventArgs
+{
+ /**
+ * @var ClassMetadata
+ */
+ private $classMetadata;
+
+ /**
+ * @var EntityManager
+ */
+ private $em;
+
+ /**
+ * @param ClassMetadataInfo $classMetadata
+ * @param EntityManager $em
+ */
+ public function __construct(ClassMetadataInfo $classMetadata, EntityManager $em)
+ {
+ $this->classMetadata = $classMetadata;
+ $this->em = $em;
+ }
+
+ /**
+ * @return ClassMetadataInfo
+ */
+ public function getClassMetadata()
+ {
+ return $this->classMetadata;
+ }
+
+ /**
+ * @return EntityManager
+ */
+ public function getEntityManager()
+ {
+ return $this->em;
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\ORM\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
+ * @version $Revision$
+ * @author Roman Borschel <roman@code-factory.de>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class OnFlushEventArgs extends \Doctrine\Common\EventArgs
+{
+ /**
+ * @var EntityManager
+ */
+ private $_em;
+
+ //private $_entitiesToPersist = array();
+ //private $_entitiesToRemove = array();
+
+ public function __construct($em)
+ {
+ $this->_em = $em;
+ }
+
+ /**
+ * @return 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;
+ }
+ */
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace Doctrine\ORM\Event;
+
+use Doctrine\Common\EventArgs,
+ Doctrine\ORM\EntityManager;
+
+/**
+ * Class that holds event arguments for a preInsert/preUpdate event.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @since 2.0
+ */
+class PreUpdateEventArgs extends LifecycleEventArgs
+{
+ /**
+ * @var array
+ */
+ private $_entityChangeSet;
+
+ /**
+ *
+ * @param object $entity
+ * @param EntityManager $em
+ * @param array $changeSet
+ */
+ public function __construct($entity, $em, array &$changeSet)
+ {
+ parent::__construct($entity, $em);
+ $this->_entityChangeSet = &$changeSet;
+ }
+
+ public function getEntityChangeSet()
+ {
+ return $this->_entityChangeSet;
+ }
+
+ /**
+ * Field has a changeset?
+ *
+ * @return bool
+ */
+ 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;
+ }
+
+ private function _assertValidField($field)
+ {
+ if (!isset($this->_entityChangeSet[$field])) {
+ throw new \InvalidArgumentException(
+ "Field '".$field."' is not a valid field of the entity ".
+ "'".get_class($this->getEntity())."' in PreInsertUpdateEventArgs."
+ );
+ }
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM;
+
+/**
+ * Container for all ORM events.
+ *
+ * This class cannot be instantiated.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @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 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';
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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 <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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));
+ $identifier = array();
+ if ($class->isIdentifierComposite) {
+ $idFields = $class->getIdentifierFieldNames();
+ foreach ($idFields as $idField) {
+ $value = $class->getReflectionProperty($idField)->getValue($entity);
+ if (isset($value)) {
+ $identifier[$idField] = $value;
+ } else {
+ throw ORMException::entityMissingAssignedId($entity);
+ }
+ }
+ } else {
+ $idField = $class->identifier[0];
+ $value = $class->reflFields[$idField]->getValue($entity);
+ if (isset($value)) {
+ $identifier[$idField] = $value;
+ } else {
+ throw ORMException::entityMissingAssignedId($entity);
+ }
+ }
+
+ return $identifier;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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
+{
+ /** @var string The name of the sequence to pass to lastInsertId(), if any. */
+ private $_seqName;
+
+ /**
+ * @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($seqName = null)
+ {
+ $this->_seqName = $seqName;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function generate(EntityManager $em, $entity)
+ {
+ return $em->getConnection()->lastInsertId($this->_seqName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isPostInsertGenerator()
+ {
+ return true;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Id;
+
+use Serializable, Doctrine\ORM\EntityManager;
+
+/**
+ * Represents an ID generator that uses a database sequence.
+ *
+ * @since 2.0
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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 = $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'];
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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 <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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++;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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 <roman@code-factory.org>
+ */
+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 == 0) {
+ return array();
+ } else if ($nodeCount == 1) {
+ return array_values($this->_classes);
+ }
+
+ // 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;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Internal\Hydration;
+
+use PDO,
+ Doctrine\DBAL\Connection,
+ Doctrine\DBAL\Types\Type,
+ Doctrine\ORM\EntityManager;
+
+/**
+ * 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 <kvesteri@cc.hut.fi>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+abstract class AbstractHydrator
+{
+ /** @var ResultSetMapping The ResultSetMapping. */
+ protected $_rsm;
+
+ /** @var EntityManager The EntityManager instance. */
+ protected $_em;
+
+ /** @var AbstractPlatform The dbms Platform instance */
+ protected $_platform;
+
+ /** @var UnitOfWork The UnitOfWork of the associated EntityManager. */
+ protected $_uow;
+
+ /** @var array The cache used during row-by-row hydration. */
+ protected $_cache = array();
+
+ /** @var 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 <tt>AbstractHydrator</tt>.
+ *
+ * @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;
+ $this->_prepare();
+ return new IterableResult($this);
+ }
+
+ /**
+ * Hydrates all rows returned by the passed statement instance at once.
+ *
+ * @param object $stmt
+ * @param object $resultSetMapping
+ * @return mixed
+ */
+ public function hydrateAll($stmt, $resultSetMapping, array $hints = array())
+ {
+ $this->_stmt = $stmt;
+ $this->_rsm = $resultSetMapping;
+ $this->_hints = $hints;
+ $this->_prepare();
+ $result = $this->_hydrateAll();
+ $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->_hydrateRow($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 _hydrateRow(array $data, array &$cache, array &$result)
+ {
+ throw new HydrationException("_hydrateRow() not implemented by this hydrator.");
+ }
+
+ /**
+ * Hydrates all rows from the current statement instance at once.
+ */
+ abstract protected function _hydrateAll();
+
+ /**
+ * 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 class
+ * 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.
+ *
+ * @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])) {
+ if (isset($this->_rsm->scalarMappings[$key])) {
+ $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key];
+ $cache[$key]['isScalar'] = true;
+ } else if (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];
+ } else if (!isset($this->_rsm->metaMappings[$key])) {
+ // 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;
+ } else {
+ // 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];
+ }
+ }
+
+ if (isset($cache[$key]['isScalar'])) {
+ $rowData['scalars'][$cache[$key]['fieldName']] = $value;
+ continue;
+ }
+
+ $dqlAlias = $cache[$key]['dqlAlias'];
+
+ if (isset($cache[$key]['isMetaColumn'])) {
+ $rowData[$dqlAlias][$cache[$key]['fieldName']] = $value;
+ continue;
+ }
+
+ if ($cache[$key]['isIdentifier']) {
+ $id[$dqlAlias] .= '|' . $value;
+ }
+
+ $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])) {
+ if (isset($this->_rsm->scalarMappings[$key])) {
+ $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key];
+ $cache[$key]['isScalar'] = true;
+ } else if (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];
+ } else if (!isset($this->_rsm->metaMappings[$key])) {
+ // 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;
+ } else {
+ // 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];
+ }
+ }
+
+ $fieldName = $cache[$key]['fieldName'];
+
+ if (isset($cache[$key]['isScalar'])) {
+ $rowData[$fieldName] = $value;
+ } else if (isset($cache[$key]['isMetaColumn'])) {
+ $rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value;
+ } else {
+ $rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $cache[$key]['type']
+ ->convertToPHPValue($value, $this->_platform);
+ }
+ }
+
+ return $rowData;
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @since 1.0
+ */
+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;
+
+ /** @override */
+ 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] = '';
+ }
+ }
+
+ /** @override */
+ protected function _hydrateAll()
+ {
+ $result = array();
+ $cache = array();
+ while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
+ $this->_hydrateRow($data, $cache, $result);
+ }
+
+ return $result;
+ }
+
+ /** @override */
+ protected function _hydrateRow(array $data, array &$cache, array &$result)
+ {
+ // 1) Initialize
+ $id = $this->_idTemplate; // initialize the id-memory
+ $nonemptyComponents = array();
+ $rowData = $this->_gatherRowData($data, $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;
+
+ // 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])) {
+ $field = $this->_rsm->indexByMap[$dqlAlias];
+ $baseElement[$relationAlias][$element[$field]] = $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
+
+ // Check for an existing element
+ if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
+ $element = $rowData[$dqlAlias];
+ if (isset($this->_rsm->indexByMap[$dqlAlias])) {
+ $field = $this->_rsm->indexByMap[$dqlAlias];
+ if ($this->_rsm->isMixed) {
+ $result[] = array($element[$field] => $element);
+ ++$this->_resultCounter;
+ } else {
+ $result[$element[$field]] = $element;
+ }
+ } else {
+ if ($this->_rsm->isMixed) {
+ $result[] = array($element);
+ ++$this->_resultCounter;
+ } else {
+ $result[] = $element;
+ }
+ }
+ end($result);
+ $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = key($result);
+ } else {
+ $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
+ /*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)) {
+ foreach ($scalars as $name => $value) {
+ $result[$this->_resultCounter - 1][$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;
+ } else {
+ if ($coll) {
+ if ($oneToOne) {
+ $this->_resultPointers[$dqlAlias] =& $coll;
+ } else {
+ end($coll);
+ $this->_resultPointers[$dqlAlias] =& $coll[key($coll)];
+ }
+ }
+ }
+ }
+
+ private function _getClassMetadata($className)
+ {
+ if ( ! isset($this->_ce[$className])) {
+ $this->_ce[$className] = $this->_em->getClassMetadata($className);
+ }
+ return $this->_ce[$className];
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace Doctrine\ORM\Internal\Hydration;
+
+class HydrationException extends \Doctrine\ORM\ORMException
+{
+ public static function nonUniqueResult()
+ {
+ return new self("The result returned by the query was not unique.");
+ }
+
+ public static function parentObjectOfRelationNotFound($alias, $parentAlias)
+ {
+ return new self("The parent object of entity result with alias '$alias' was not found."
+ . " The parent alias is '$parentAlias'.");
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Internal\Hydration;
+
+use PDO,
+ Doctrine\ORM\Mapping\ClassMetadata,
+ Doctrine\ORM\PersistentCollection,
+ Doctrine\ORM\Query,
+ Doctrine\Common\Collections\ArrayCollection,
+ Doctrine\Common\Collections\Collection;
+
+/**
+ * The ObjectHydrator constructs an object graph out of an SQL result set.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @since 2.0
+ * @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();
+ //private $_createdEntities;
+
+
+ /** @override */
+ protected function _prepare()
+ {
+ $this->_identifierMap =
+ $this->_resultPointers =
+ $this->_idTemplate = array();
+ $this->_resultCounter = 0;
+
+ foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
+ $this->_identifierMap[$dqlAlias] = array();
+ $this->_idTemplate[$dqlAlias] = '';
+ $class = $this->_em->getClassMetadata($className);
+
+ if ( ! isset($this->_ce[$className])) {
+ $this->_ce[$className] = $class;
+ }
+
+ // 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])) {
+ $sourceClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]];
+ $sourceClass = $this->_getClassMetadata($sourceClassName);
+ $assoc = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]];
+ $this->_hints['fetched'][$sourceClassName][$assoc['fieldName']] = true;
+ if ($sourceClass->subClasses) {
+ foreach ($sourceClass->subClasses as $sourceSubclassName) {
+ $this->_hints['fetched'][$sourceSubclassName][$assoc['fieldName']] = true;
+ }
+ }
+ if ($assoc['type'] != ClassMetadata::MANY_TO_MANY) {
+ // Mark any non-collection opposite sides as fetched, too.
+ if ($assoc['mappedBy']) {
+ $this->_hints['fetched'][$className][$assoc['mappedBy']] = true;
+ } else {
+ if ($assoc['inversedBy']) {
+ $inverseAssoc = $class->associationMappings[$assoc['inversedBy']];
+ if ($inverseAssoc['type'] & ClassMetadata::TO_ONE) {
+ $this->_hints['fetched'][$className][$inverseAssoc['fieldName']] = true;
+ if ($class->subClasses) {
+ foreach ($class->subClasses as $targetSubclassName) {
+ $this->_hints['fetched'][$targetSubclassName][$inverseAssoc['fieldName']] = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _cleanup()
+ {
+ parent::_cleanup();
+ $this->_identifierMap =
+ $this->_initializedCollections =
+ $this->_existingCollections =
+ $this->_resultPointers = array();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _hydrateAll()
+ {
+ $result = array();
+ $cache = array();
+
+ while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
+ $this->_hydrateRow($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 string $name The name of the field on the entity that holds the collection.
+ */
+ private function _initRelatedCollection($entity, $class, $fieldName)
+ {
+ $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'][$class->name][$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 $data The instance data.
+ * @param $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])) {
+ $discrColumn = $this->_rsm->metaMappings[$this->_rsm->discriminatorColumns[$dqlAlias]];
+ $className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]];
+ unset($data[$discrColumn]);
+ }
+ return $this->_uow->createEntity($className, $data, $this->_hints);
+ }
+
+ private function _getEntityFromIdentityMap($className, array $data)
+ {
+ $class = $this->_ce[$className];
+ if ($class->isIdentifierComposite) {
+ $idHash = '';
+ foreach ($class->identifier as $fieldName) {
+ $idHash .= $data[$fieldName] . ' ';
+ }
+ return $this->_uow->tryGetByIdHash(rtrim($idHash), $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 $data The data of the row to process.
+ * @param array $cache The cache to use.
+ * @param array $result The result array to fill.
+ */
+ protected function _hydrateRow(array $data, 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($data, $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;
+
+ // 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 = $this->_resultPointers[$parentAlias][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)) {
+ // 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);
+ }
+
+ $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])) {
+ $field = $this->_rsm->indexByMap[$dqlAlias];
+ $indexValue = $this->_ce[$entityName]->reflFields[$field]->getValue($element);
+ $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 ( ! $reflField->getValue($parentObject)) {
+ $coll = new PersistentCollection($this->_em, $this->_ce[$entityName], new ArrayCollection);
+ $coll->setOwner($parentObject, $relation);
+ $reflField->setValue($parentObject, $coll);
+ $this->_uow->setOriginalEntityProperty($oid, $relationField, $coll);
+ }
+ } else {
+ // PATH B: Single-valued association
+ $reflFieldValue = $reflField->getValue($parentObject);
+ if ( ! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH])) {
+ 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 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
+
+ if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
+ $element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias);
+ if (isset($this->_rsm->indexByMap[$dqlAlias])) {
+ $field = $this->_rsm->indexByMap[$dqlAlias];
+ $key = $this->_ce[$entityName]->reflFields[$field]->getValue($element);
+ if ($this->_rsm->isMixed) {
+ $element = array($key => $element);
+ $result[] = $element;
+ $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $this->_resultCounter;
+ ++$this->_resultCounter;
+ } else {
+ $result[$key] = $element;
+ $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $key;
+ }
+ } else {
+ if ($this->_rsm->isMixed) {
+ $element = array(0 => $element);
+ }
+ $result[] = $element;
+ $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $this->_resultCounter;
+ ++$this->_resultCounter;
+ }
+
+ // Update result pointer
+ $this->_resultPointers[$dqlAlias] = $element;
+
+ } else {
+ // Update result pointer
+ $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
+ $this->_resultPointers[$dqlAlias] = $result[$index];
+ /*if ($this->_rsm->isMixed) {
+ $result[] = $result[$index];
+ ++$this->_resultCounter;
+ }*/
+ }
+ }
+ }
+
+ // Append scalar values to mixed result sets
+ if (isset($scalars)) {
+ foreach ($scalars as $name => $value) {
+ $result[$this->_resultCounter - 1][$name] = $value;
+ }
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Internal\Hydration;
+
+use Doctrine\DBAL\Connection;
+
+/**
+ * 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.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @since 2.0
+ */
+class ScalarHydrator extends AbstractHydrator
+{
+ /** @override */
+ protected function _hydrateAll()
+ {
+ $result = array();
+ $cache = array();
+ while ($data = $this->_stmt->fetch(\PDO::FETCH_ASSOC)) {
+ $result[] = $this->_gatherScalarRowData($data, $cache);
+ }
+ return $result;
+ }
+
+ /** @override */
+ protected function _hydrateRow(array $data, array &$cache, array &$result)
+ {
+ $result[] = $this->_gatherScalarRowData($data, $cache);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Internal\Hydration;
+
+use Doctrine\DBAL\Connection;
+
+/**
+ * Hydrator that hydrates a single scalar value from the result set.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @since 2.0
+ */
+class SingleScalarHydrator extends AbstractHydrator
+{
+ /** @override */
+ protected function _hydrateAll()
+ {
+ $cache = array();
+ $result = $this->_stmt->fetchAll(\PDO::FETCH_ASSOC);
+ $num = count($result);
+
+ if ($num == 0) {
+ throw new \Doctrine\ORM\NoResultException;
+ } else if ($num > 1 || count($result[key($result)]) > 1) {
+ throw new \Doctrine\ORM\NonUniqueResultException;
+ }
+
+ $result = $this->_gatherScalarRowData($result[key($result)], $cache);
+
+ return array_shift($result);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Mapping;
+
+use ReflectionClass, ReflectionProperty;
+
+/**
+ * A <tt>ClassMetadata</tt> 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.
+ *
+ * <b>IMPORTANT NOTE:</b>
+ *
+ * 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 <roman@code-factory.org>
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ * @since 2.0
+ */
+class ClassMetadata extends ClassMetadataInfo
+{
+ /**
+ * 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.
+ */
+ public function __construct($entityName)
+ {
+ parent::__construct($entityName);
+ $this->reflClass = new ReflectionClass($entityName);
+ $this->namespace = $this->reflClass->getNamespaceName();
+ $this->table['name'] = $this->reflClass->getShortName();
+ }
+
+ /**
+ * 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]];
+ }
+
+ /**
+ * Validates & completes the given field mapping.
+ *
+ * @param array $mapping The field mapping to validated & complete.
+ * @return array The validated and completed field mapping.
+ *
+ * @throws MappingException
+ */
+ protected function _validateAndCompleteFieldMapping(array &$mapping)
+ {
+ parent::_validateAndCompleteFieldMapping($mapping);
+
+ // Store ReflectionProperty of mapped field
+ $refProp = $this->reflClass->getProperty($mapping['fieldName']);
+ $refProp->setAccessible(true);
+ $this->reflFields[$mapping['fieldName']] = $refProp;
+ }
+
+ /**
+ * 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;
+ } else {
+ $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);
+ }
+
+ /**
+ * Stores the association mapping.
+ *
+ * @param AssociationMapping $assocMapping
+ */
+ protected function _storeAssociationMapping(array $assocMapping)
+ {
+ parent::_storeAssociationMapping($assocMapping);
+
+ // Store ReflectionProperty of mapped field
+ $sourceFieldName = $assocMapping['fieldName'];
+
+ $refProp = $this->reflClass->getProperty($sourceFieldName);
+ $refProp->setAccessible(true);
+ $this->reflFields[$sourceFieldName] = $refProp;
+ }
+
+ /**
+ * Gets the (possibly quoted) column name of a mapped field for safe use
+ * in an SQL statement.
+ *
+ * @param string $field
+ * @param 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.
+ *
+ * @param 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.
+ *
+ * @param AbstractPlatform $platform
+ * @return string
+ */
+ public function getQuotedJoinTableName(array $assoc, $platform)
+ {
+ return isset($assoc['joinTable']['quoted'])
+ ? $platform->quoteIdentifier($assoc['joinTable']['name'])
+ : $assoc['joinTable']['name'];
+ }
+
+ /**
+ * 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->isVersioned) {
+ $serialized[] = 'isVersioned';
+ $serialized[] = 'versionField';
+ }
+
+ if ($this->lifecycleCallbacks) {
+ $serialized[] = 'lifecycleCallbacks';
+ }
+
+ return $serialized;
+ }
+
+ /**
+ * Restores some state that can not be serialized/unserialized.
+ *
+ * @return void
+ */
+ public function __wakeup()
+ {
+ // Restore ReflectionClass and properties
+ $this->reflClass = new ReflectionClass($this->name);
+
+ foreach ($this->fieldMappings as $field => $mapping) {
+ if (isset($mapping['declared'])) {
+ $reflField = new ReflectionProperty($mapping['declared'], $field);
+ } else {
+ $reflField = $this->reflClass->getProperty($field);
+ }
+ $reflField->setAccessible(true);
+ $this->reflFields[$field] = $reflField;
+ }
+
+ foreach ($this->associationMappings as $field => $mapping) {
+ if (isset($mapping['declared'])) {
+ $reflField = new ReflectionProperty($mapping['declared'], $field);
+ } else {
+ $reflField = $this->reflClass->getProperty($field);
+ }
+
+ $reflField->setAccessible(true);
+ $this->reflFields[$field] = $reflField;
+ }
+ }
+
+ /**
+ * 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;
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Mapping;
+
+use ReflectionException,
+ Doctrine\ORM\ORMException,
+ Doctrine\ORM\EntityManager,
+ Doctrine\DBAL\Platforms,
+ Doctrine\ORM\Events;
+
+/**
+ * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
+ * metadata mapping informations of a class which describes how a class should be mapped
+ * to a relational database.
+ *
+ * @since 2.0
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class ClassMetadataFactory
+{
+ /**
+ * @var EntityManager
+ */
+ private $em;
+
+ /**
+ * @var AbstractPlatform
+ */
+ private $targetPlatform;
+
+ /**
+ * @var Driver\Driver
+ */
+ private $driver;
+
+ /**
+ * @var \Doctrine\Common\EventManager
+ */
+ private $evm;
+
+ /**
+ * @var \Doctrine\Common\Cache\Cache
+ */
+ private $cacheDriver;
+
+ /**
+ * @var array
+ */
+ private $loadedMetadata = array();
+
+ /**
+ * @var bool
+ */
+ private $initialized = false;
+
+ /**
+ * @param EntityManager $$em
+ */
+ public function setEntityManager(EntityManager $em)
+ {
+ $this->em = $em;
+ }
+
+ /**
+ * Sets the cache driver used by the factory to cache ClassMetadata instances.
+ *
+ * @param Doctrine\Common\Cache\Cache $cacheDriver
+ */
+ public function setCacheDriver($cacheDriver)
+ {
+ $this->cacheDriver = $cacheDriver;
+ }
+
+ /**
+ * Gets the cache driver used by the factory to cache ClassMetadata instances.
+ *
+ * @return Doctrine\Common\Cache\Cache
+ */
+ public function getCacheDriver()
+ {
+ return $this->cacheDriver;
+ }
+
+ public function getLoadedMetadata()
+ {
+ return $this->loadedMetadata;
+ }
+
+ /**
+ * Forces the factory to load the metadata of all classes known to the underlying
+ * mapping driver.
+ *
+ * @return array The ClassMetadata instances of all mapped classes.
+ */
+ public function getAllMetadata()
+ {
+ if ( ! $this->initialized) {
+ $this->initialize();
+ }
+
+ $metadata = array();
+ foreach ($this->driver->getAllClassNames() as $className) {
+ $metadata[] = $this->getMetadataFor($className);
+ }
+
+ return $metadata;
+ }
+
+ /**
+ * Lazy initialization of this stuff, especially the metadata driver,
+ * since these are not needed at all when a metadata cache is active.
+ */
+ private function initialize()
+ {
+ $this->driver = $this->em->getConfiguration()->getMetadataDriverImpl();
+ $this->targetPlatform = $this->em->getConnection()->getDatabasePlatform();
+ $this->evm = $this->em->getEventManager();
+ $this->initialized = true;
+ }
+
+ /**
+ * Gets the class metadata descriptor for a class.
+ *
+ * @param string $className The name of the class.
+ * @return Doctrine\ORM\Mapping\ClassMetadata
+ */
+ public function getMetadataFor($className)
+ {
+ if ( ! isset($this->loadedMetadata[$className])) {
+ $realClassName = $className;
+
+ // Check for namespace alias
+ if (strpos($className, ':') !== false) {
+ list($namespaceAlias, $simpleClassName) = explode(':', $className);
+ $realClassName = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
+
+ if (isset($this->loadedMetadata[$realClassName])) {
+ // We do not have the alias name in the map, include it
+ $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
+
+ return $this->loadedMetadata[$realClassName];
+ }
+ }
+
+ if ($this->cacheDriver) {
+ if (($cached = $this->cacheDriver->fetch("$realClassName\$CLASSMETADATA")) !== false) {
+ $this->loadedMetadata[$realClassName] = $cached;
+ } else {
+ foreach ($this->loadMetadata($realClassName) as $loadedClassName) {
+ $this->cacheDriver->save(
+ "$loadedClassName\$CLASSMETADATA", $this->loadedMetadata[$loadedClassName], null
+ );
+ }
+ }
+ } else {
+ $this->loadMetadata($realClassName);
+ }
+
+ if ($className != $realClassName) {
+ // We do not have the alias name in the map, include it
+ $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
+ }
+ }
+
+ return $this->loadedMetadata[$className];
+ }
+
+ /**
+ * Checks whether the factory has the metadata for a class loaded already.
+ *
+ * @param string $className
+ * @return boolean TRUE if the metadata of the class in question is already loaded, FALSE otherwise.
+ */
+ public function hasMetadataFor($className)
+ {
+ return isset($this->loadedMetadata[$className]);
+ }
+
+ /**
+ * Sets the metadata descriptor for a specific class.
+ *
+ * NOTE: This is only useful in very special cases, like when generating proxy classes.
+ *
+ * @param string $className
+ * @param ClassMetadata $class
+ */
+ public function setMetadataFor($className, $class)
+ {
+ $this->loadedMetadata[$className] = $class;
+ }
+
+ /**
+ * Get array of parent classes for the given entity class
+ *
+ * @param string $name
+ * @return array $parentClasses
+ */
+ protected function getParentClasses($name)
+ {
+ // Collect parent classes, ignoring transient (not-mapped) classes.
+ $parentClasses = array();
+ foreach (array_reverse(class_parents($name)) as $parentClass) {
+ if ( ! $this->driver->isTransient($parentClass)) {
+ $parentClasses[] = $parentClass;
+ }
+ }
+ return $parentClasses;
+ }
+
+ /**
+ * Loads the metadata of the class in question and all it's ancestors whose metadata
+ * is still not loaded.
+ *
+ * @param string $name The name of the class for which the metadata should get loaded.
+ * @param array $tables The metadata collection to which the loaded metadata is added.
+ */
+ protected function loadMetadata($name)
+ {
+ if ( ! $this->initialized) {
+ $this->initialize();
+ }
+
+ $loaded = array();
+
+ $parentClasses = $this->getParentClasses($name);
+ $parentClasses[] = $name;
+
+ // Move down the hierarchy of parent classes, starting from the topmost class
+ $parent = null;
+ $visited = array();
+ foreach ($parentClasses as $className) {
+ if (isset($this->loadedMetadata[$className])) {
+ $parent = $this->loadedMetadata[$className];
+ if ( ! $parent->isMappedSuperclass) {
+ array_unshift($visited, $className);
+ }
+ continue;
+ }
+
+ $class = $this->newClassMetadataInstance($className);
+
+ if ($parent) {
+ if (!$parent->isMappedSuperclass) {
+ $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);
+ if (!$parent->isMappedSuperclass) {
+ $class->setDiscriminatorMap($parent->discriminatorMap);
+ }
+ $class->setLifecycleCallbacks($parent->lifecycleCallbacks);
+ $class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
+ }
+
+ // Invoke driver
+ try {
+ $this->driver->loadMetadataForClass($className, $class);
+ } catch (ReflectionException $e) {
+ throw MappingException::reflectionFailure($className, $e);
+ }
+
+ // Verify & complete identifier mapping
+ if ( ! $class->identifier && ! $class->isMappedSuperclass) {
+ throw MappingException::identifierRequired($className);
+ }
+ if ($parent && ! $parent->isMappedSuperclass) {
+ if ($parent->isIdGeneratorSequence()) {
+ $class->setSequenceGeneratorDefinition($parent->sequenceGeneratorDefinition);
+ } else if ($parent->isIdGeneratorTable()) {
+ $class->getTableGeneratorDefinition($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);
+ }
+
+ $class->setParentClasses($visited);
+
+ if ($this->evm->hasListeners(Events::loadClassMetadata)) {
+ $eventArgs = new \Doctrine\ORM\Event\LoadClassMetadataEventArgs($class, $this->em);
+ $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
+ }
+
+ // verify inheritance
+ if (!$parent && !$class->isMappedSuperclass && !$class->isInheritanceTypeNone()) {
+ if (count($class->discriminatorMap) == 0) {
+ throw MappingException::missingDiscriminatorMap($class->name);
+ }
+ if (!$class->discriminatorColumn) {
+ throw MappingException::missingDiscriminatorColumn($class->name);
+ }
+ }
+
+ $this->loadedMetadata[$className] = $class;
+
+ $parent = $class;
+
+ if ( ! $class->isMappedSuperclass) {
+ array_unshift($visited, $className);
+ }
+
+ $loaded[] = $className;
+ }
+
+ return $loaded;
+ }
+
+ /**
+ * Creates a new ClassMetadata instance for the given class name.
+ *
+ * @param string $className
+ * @return Doctrine\ORM\Mapping\ClassMetadata
+ */
+ protected function newClassMetadataInstance($className)
+ {
+ return new ClassMetadata($className);
+ }
+
+ /**
+ * 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 $fieldName => $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
+ */
+ private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass)
+ {
+ foreach ($parentClass->associationMappings as $field => $mapping) {
+ if ($parentClass->isMappedSuperclass) {
+ $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);
+ }
+ }
+
+ /**
+ * Completes the ID generator mapping. If "auto" is specified we choose the generator
+ * most appropriate for the targeted database platform.
+ *
+ * @param Doctrine\ORM\Mapping\ClassMetadata $class
+ */
+ 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
+ // <table>_<column>_seq in PostgreSQL for SERIAL columns.
+ // Not pretty but necessary and the simplest solution that currently works.
+ $seqName = $this->targetPlatform instanceof Platforms\PostgreSQLPlatform ?
+ $class->table['name'] . '_' . $class->columnNames[$class->identifier[0]] . '_seq' :
+ null;
+ $class->setIdGenerator(new \Doctrine\ORM\Id\IdentityGenerator($seqName));
+ break;
+ case ClassMetadata::GENERATOR_TYPE_SEQUENCE:
+ // If there is no sequence definition yet, create a default definition
+ $definition = $class->sequenceGeneratorDefinition;
+ if ( ! $definition) {
+ $sequenceName = $class->getTableName() . '_' . $class->getSingleIdentifierColumnName() . '_seq';
+ $definition['sequenceName'] = $this->targetPlatform->fixSchemaElementName($sequenceName);
+ $definition['allocationSize'] = 1;
+ $definition['initialValue'] = 1;
+ $class->setSequenceGeneratorDefinition($definition);
+ }
+ $sequenceGenerator = new \Doctrine\ORM\Id\SequenceGenerator(
+ $definition['sequenceName'],
+ $definition['allocationSize']
+ );
+ $class->setIdGenerator($sequenceGenerator);
+ break;
+ case ClassMetadata::GENERATOR_TYPE_NONE:
+ $class->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator());
+ break;
+ case ClassMetadata::GENERATOR_TYPE_TABLE:
+ throw new ORMException("TableGenerator not yet implemented.");
+ break;
+ default:
+ throw new ORMException("Unknown generator type: " . $class->generatorType);
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Mapping;
+
+use ReflectionClass;
+
+/**
+ * A <tt>ClassMetadata</tt> 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.
+ *
+ * <b>IMPORTANT NOTE:</b>
+ *
+ * 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 <roman@code-factory.org>
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ * @since 2.0
+ */
+class ClassMetadataInfo
+{
+ /* 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
+ * <tt>Class Table Inheritance</tt>.
+ */
+ const INHERITANCE_TYPE_JOINED = 2;
+ /**
+ * SINGLE_TABLE means the class will be persisted according to the rules of
+ * <tt>Single Table Inheritance</tt>.
+ */
+ const INHERITANCE_TYPE_SINGLE_TABLE = 3;
+ /**
+ * TABLE_PER_CLASS means the class will be persisted according to the rules
+ * of <tt>Concrete Table Inheritance</tt>.
+ */
+ 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;
+ /**
+ * 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 <tt>NotifyPropertyChanged</tt> 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;
+ /**
+ * Identifies a one-to-one association.
+ */
+ const ONE_TO_ONE = 1;
+ /**
+ * Identifies a many-to-one association.
+ */
+ const MANY_TO_ONE = 2;
+ /**
+ * Combined bitmask for to-one (single-valued) associations.
+ */
+ const TO_ONE = 3;
+ /**
+ * 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-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;
+
+ /**
+ * 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 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:
+ *
+ * - <b>fieldName</b> (string)
+ * The name of the field in the Entity.
+ *
+ * - <b>type</b> (string)
+ * The type name of the mapped field. Can be one of Doctrine's mapping types
+ * or a custom mapping type.
+ *
+ * - <b>columnName</b> (string, optional)
+ * The column name. Optional. Defaults to the field name.
+ *
+ * - <b>length</b> (integer, optional)
+ * The database length of the column. Optional. Default value taken from
+ * the type.
+ *
+ * - <b>id</b> (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.
+ *
+ * - <b>nullable</b> (boolean, optional)
+ * Whether the column is nullable. Defaults to FALSE.
+ *
+ * - <b>columnDefinition</b> (string, optional, schema-only)
+ * The SQL fragment that is used when generating the DDL for the column.
+ *
+ * - <b>precision</b> (integer, optional, schema-only)
+ * The precision of a decimal column. Only valid if the column type is decimal.
+ *
+ * - <b>scale</b> (integer, optional, schema-only)
+ * The scale of a decimal column. Only valid if the column type is decimal.
+ *
+ * - <b>unique (string, optional, schema-only)</b>
+ * 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.
+ *
+ * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies
+ * where a discriminator column is used.</b>
+ *
+ * @var mixed
+ * @see discriminatorColumn
+ */
+ public $discriminatorValue;
+
+ /**
+ * READ-ONLY: The discriminator map of all mapped classes in the hierarchy.
+ *
+ * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies
+ * where a discriminator column is used.</b>
+ *
+ * @var mixed
+ * @see discriminatorColumn
+ */
+ public $discriminatorMap = array();
+
+ /**
+ * READ-ONLY: The definition of the descriminator 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 => <tableName>
+ * schema => <schemaName>
+ * indexes => array
+ * uniqueConstraints => array
+ *
+ * @var array
+ * @todo Rename to just $table
+ */
+ 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:
+ *
+ * - <b>fieldName</b> (string)
+ * The name of the field in the entity the association is mapped to.
+ *
+ * - <b>targetEntity</b> (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.
+ *
+ * - <b>mappedBy</b> (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.
+ *
+ * - <b>inversedBy</b> (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.
+ *
+ * - <b>cascade</b> (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).
+ *
+ * - <b>orderBy</b> (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')
+ *
+ * - <b>fetch</b> (integer, optional)
+ * The fetching strategy to use for the association, usually defaults to FETCH_LAZY.
+ * Possible values are: ClassMetadata::FETCH_EAGER, ClassMetadata::FETCH_LAZY.
+ *
+ * - <b>joinTable</b> (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.
+ *
+ * A join table definition has the following structure:
+ * <pre>
+ * array(
+ * 'name' => <join table name>,
+ * 'joinColumns' => array(<join column mapping from join table to source table>),
+ * 'inverseJoinColumns' => array(<join column mapping from join table to target table>)
+ * )
+ * </pre>
+ *
+ *
+ * @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: The ID generator used for generating IDs for this class.
+ *
+ * @var 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:
+ * <code>
+ * array(
+ * 'sequenceName' => 'name',
+ * 'allocationSize' => 20,
+ * 'initialValue' => 1
+ * )
+ * </code>
+ *
+ * @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;
+
+ /**
+ * 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.
+ */
+ public function __construct($entityName)
+ {
+ $this->name = $entityName;
+ $this->rootEntityName = $entityName;
+ }
+
+ /**
+ * Gets the ReflectionClass instance of the mapped class.
+ *
+ * @return ReflectionClass
+ */
+ public function getReflectionClass()
+ {
+ if ( ! $this->reflClass) {
+ $this->reflClass = new ReflectionClass($this->name);
+ }
+ 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.
+ * @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.
+ * @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;
+ }
+
+ /**
+ * Validates & completes the given field mapping.
+ *
+ * @param array $mapping The field mapping to validated & complete.
+ * @return array The validated and completed field mapping.
+ */
+ protected function _validateAndCompleteFieldMapping(array &$mapping)
+ {
+ // Check mandatory fields
+ if ( ! isset($mapping['fieldName'])) {
+ throw MappingException::missingFieldName($this->name, $mapping);
+ }
+ if ( ! isset($mapping['type'])) {
+ // Default to string
+ $mapping['type'] = 'string';
+ }
+
+ // Complete fieldName and columnName mapping
+ if ( ! isset($mapping['columnName'])) {
+ $mapping['columnName'] = $mapping['fieldName'];
+ } else {
+ 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;
+ }
+ }
+ }
+
+ /**
+ * 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
+
+ // If targetEntity is unqualified, assume it is in the same namespace as
+ // the sourceEntity.
+ $mapping['sourceEntity'] = $this->name;
+ if (isset($mapping['targetEntity']) && strpos($mapping['targetEntity'], '\\') === false
+ && strlen($this->namespace) > 0) {
+ $mapping['targetEntity'] = $this->namespace . '\\' . $mapping['targetEntity'];
+ }
+
+ // Mandatory: fieldName, targetEntity
+ if ( ! isset($mapping['fieldName'])) {
+ throw MappingException::missingFieldName();
+ }
+ 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;
+ }
+
+ // Fetch mode. Default fetch mode to LAZY, if not set.
+ if ( ! isset($mapping['fetch'])) {
+ $mapping['fetch'] = self::FETCH_LAZY;
+ }
+
+ // Cascades
+ $cascades = isset($mapping['cascade']) ? $mapping['cascade'] : array();
+ if (in_array('all', $cascades)) {
+ $cascades = array(
+ 'remove',
+ 'persist',
+ 'refresh',
+ 'merge',
+ 'detach'
+ );
+ }
+ $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.
+ * @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' => $mapping['fieldName'] . '_id',
+ 'referencedColumnName' => 'id'
+ ));
+ }
+ foreach ($mapping['joinColumns'] as $key => &$joinColumn) {
+ if ($mapping['type'] === self::ONE_TO_ONE) {
+ $joinColumn['unique'] = true;
+ }
+ if (empty($joinColumn['name'])) {
+ $joinColumn['name'] = $mapping['fieldName'] . '_id';
+ }
+ if (empty($joinColumn['referencedColumnName'])) {
+ $joinColumn['referencedColumnName'] = 'id';
+ }
+ $mapping['sourceToTargetKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
+ $mapping['joinColumnFieldNames'][$joinColumn['name']] = isset($joinColumn['fieldName'])
+ ? $joinColumn['fieldName'] : $joinColumn['name'];
+ }
+ $mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']);
+ }
+
+ //TODO: if orphanRemoval, cascade=remove is implicit!
+ $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ?
+ (bool) $mapping['orphanRemoval'] : false;
+
+ return $mapping;
+ }
+
+ /**
+ * Validates and completes the mapping.
+ *
+ * @param array $mapping The mapping to validate and complete.
+ * @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']);
+ }
+
+ //TODO: if orphanRemoval, cascade=remove is implicit!
+ $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;
+ }
+
+ protected function _validateAndCompleteManyToManyMapping(array $mapping)
+ {
+ $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
+ if ($mapping['isOwningSide']) {
+ $sourceShortName = strtolower(substr($mapping['sourceEntity'], strrpos($mapping['sourceEntity'], '\\') + 1));
+ $targetShortName = strtolower(substr($mapping['targetEntity'], strrpos($mapping['targetEntity'], '\\') + 1));
+ // owning side MUST have a join table
+ if ( ! isset($mapping['joinTable']['name'])) {
+ $mapping['joinTable']['name'] = $sourceShortName .'_' . $targetShortName;
+ }
+ if ( ! isset($mapping['joinTable']['joinColumns'])) {
+ $mapping['joinTable']['joinColumns'] = array(array(
+ 'name' => $sourceShortName . '_id',
+ 'referencedColumnName' => 'id',
+ 'onDelete' => 'CASCADE'));
+ }
+ if ( ! isset($mapping['joinTable']['inverseJoinColumns'])) {
+ $mapping['joinTable']['inverseJoinColumns'] = array(array(
+ 'name' => $targetShortName . '_id',
+ 'referencedColumnName' => 'id',
+ 'onDelete' => 'CASCADE'));
+ }
+
+ foreach ($mapping['joinTable']['joinColumns'] as &$joinColumn) {
+ if (empty($joinColumn['name'])) {
+ $joinColumn['name'] = $sourceShortName . '_id';
+ }
+ if (empty($joinColumn['referencedColumnName'])) {
+ $joinColumn['referencedColumnName'] = 'id';
+ }
+ 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'] = $targetShortName . '_id';
+ }
+ if (empty($inverseJoinColumn['referencedColumnName'])) {
+ $inverseJoinColumn['referencedColumnName'] = 'id';
+ }
+ if (isset($inverseJoinColumn['onDelete']) && strtolower($inverseJoinColumn['onDelete']) == 'cascade') {
+ $mapping['isOnDeleteCascade'] = true;
+ }
+ $mapping['relationToTargetKeyColumns'][$inverseJoinColumn['name']] = $inverseJoinColumn['referencedColumnName'];
+ $mapping['joinTableColumns'][] = $inverseJoinColumn['name'];
+ }
+ }
+
+ 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;
+ }
+
+ /**
+ * Gets the identifier (primary key) field names of the class.
+ *
+ * @return mixed
+ */
+ 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);
+ }
+
+ /**
+ * Checks whether the class has a (mapped) field with a certain name.
+ *
+ * @return boolean
+ */
+ public function hasField($fieldName)
+ {
+ return isset($this->fieldMappings[$fieldName]);
+ }
+
+ /**
+ * Gets an array containing all the column names.
+ *
+ * @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()
+ {
+ if ($this->isIdentifierComposite) {
+ $columnNames = array();
+ foreach ($this->identifier as $idField) {
+ $columnNames[] = $this->fieldMappings[$idField]['columnName'];
+ }
+ return $columnNames;
+ } else {
+ return array($this->fieldMappings[$this->identifier[0]]['columnName']);
+ }
+ }
+
+ /**
+ * 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()
+ {
+ $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;
+ }
+
+ /**
+ * Gets the type of a field.
+ *
+ * @param string $fieldName
+ * @return Doctrine\DBAL\Types\Type
+ */
+ public function getTypeOfField($fieldName)
+ {
+ return isset($this->fieldMappings[$fieldName]) ?
+ $this->fieldMappings[$fieldName]['type'] : null;
+ }
+
+ /**
+ * Gets the type of a column.
+ *
+ * @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()
+ {
+ return $this->table['name'] . '_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
+ */
+ public function setInheritanceType($type)
+ {
+ if ( ! $this->_isInheritanceType($type)) {
+ throw MappingException::invalidInheritanceType($this->name, $type);
+ }
+ $this->inheritanceType = $type;
+ }
+
+ /**
+ * Checks whether a mapped field is inherited from an entity superclass.
+ *
+ * @return boolean TRUE if the field is inherited, FALSE otherwise.
+ */
+ public function isInheritedField($fieldName)
+ {
+ return isset($this->fieldMappings[$fieldName]['inherited']);
+ }
+
+ /**
+ * 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 => <tableName> (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] == '`') {
+ $this->table['name'] = trim($table['name'], '`');
+ $this->table['quoted'] = true;
+ } else {
+ $this->table['name'] = $table['name'];
+ }
+ }
+ if (isset($table['indexes'])) {
+ $this->table['indexes'] = $table['indexes'];
+ }
+ if (isset($table['uniqueConstraints'])) {
+ $this->table['uniqueConstraints'] = $table['uniqueConstraints'];
+ }
+ }
+
+ /**
+ * 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.
+ */
+ 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 AssociationMapping $mapping
+ * @param string $owningClassName The name of the class that defined this mapping.
+ */
+ 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 $mapping
+ * @todo Rename: addInheritedFieldMapping
+ */
+ public function addInheritedFieldMapping(array $fieldMapping)
+ {
+ $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
+ $this->columnNames[$fieldMapping['fieldName']] = $fieldMapping['columnName'];
+ $this->fieldNames[$fieldMapping['columnName']] = $fieldMapping['fieldName'];
+ }
+
+ /**
+ * 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 AssociationMapping $assocMapping
+ */
+ 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 $mapperClassName The class name of the custom mapper.
+ */
+ public function setCustomRepositoryClass($repositoryClassName)
+ {
+ $this->customRepositoryClassName = $repositoryClassName;
+ }
+
+ /**
+ * Dispatches the lifecycle event of the given entity to the registered
+ * lifecycle callbacks and lifecycle listeners.
+ *
+ * @param string $event The lifecycle event.
+ * @param Entity $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.
+ *
+ * Note: If the same callback is registered more than once, the old one
+ * will be overridden.
+ *
+ * @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
+ * @see getDiscriminatorColumn()
+ */
+ public function setDiscriminatorColumn($columnDef)
+ {
+ if ($columnDef !== null) {
+ if (isset($this->fieldNames[$columnDef['name']])) {
+ throw MappingException::duplicateColumnName($this->name, $columnDef['name']);
+ }
+
+ if ( ! isset($columnDef['name'])) {
+ throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name, $columnDef);
+ }
+ 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) {
+ if (strpos($className, '\\') === false && strlen($this->namespace)) {
+ $className = $this->namespace . '\\' . $className;
+ }
+ $this->discriminatorMap[$value] = $className;
+ if ($this->name == $className) {
+ $this->discriminatorValue = $value;
+ } else {
+ if ( ! class_exists($className)) {
+ throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
+ }
+ if (is_subclass_of($className, $this->name)) {
+ $this->subClasses[] = $className;
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks whether the class has a mapped association with the given field name.
+ *
+ * @param string $fieldName
+ * @return boolean
+ */
+ public function hasAssociation($fieldName)
+ {
+ return isset($this->associationMappings[$fieldName]);
+ }
+
+ /**
+ * Checks whether the class has a mapped association for the specified field
+ * and if yes, checks whether it is a single-valued association (to-one).
+ *
+ * @param string $fieldName
+ * @return boolean TRUE if the association exists and is single-valued, FALSE otherwise.
+ */
+ public function isSingleValuedAssociation($fieldName)
+ {
+ return isset($this->associationMappings[$fieldName]) &&
+ ($this->associationMappings[$fieldName]['type'] & self::TO_ONE);
+ }
+
+ /**
+ * Checks whether the class has a mapped association for the specified field
+ * and if yes, checks whether it is a collection-valued association (to-many).
+ *
+ * @param string $fieldName
+ * @return boolean TRUE if the association exists and is collection-valued, FALSE otherwise.
+ */
+ public function isCollectionValuedAssociation($fieldName)
+ {
+ return isset($this->associationMappings[$fieldName]) &&
+ ! ($this->associationMappings[$fieldName]['type'] & self::TO_ONE);
+ }
+
+ /**
+ * Sets the ID generator used to generate IDs for instances of this class.
+ *
+ * @param AbstractIdGenerator $generator
+ */
+ public function setIdGenerator($generator)
+ {
+ $this->idGenerator = $generator;
+ }
+
+ /**
+ * Sets the definition of the sequence ID generator for this class.
+ *
+ * The definition must have the following structure:
+ * <code>
+ * array(
+ * 'sequenceName' => 'name',
+ * 'allocationSize' => 20,
+ * 'initialValue' => 1
+ * )
+ * </code>
+ *
+ * @param array $definition
+ */
+ public function setSequenceGeneratorDefinition(array $definition)
+ {
+ $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
+ */
+ public function setVersionMapping(array &$mapping)
+ {
+ $this->isVersioned = true;
+ $this->versionField = $mapping['fieldName'];
+
+ if ( ! isset($mapping['default'])) {
+ if ($mapping['type'] == 'integer') {
+ $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;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Mapping\Driver;
+
+use Doctrine\ORM\Mapping\MappingException;
+
+/**
+ * Base driver for file-based metadata drivers.
+ *
+ * A file driver operates in a mode where it loads the mapping files of individual
+ * classes on demand. This requires the user to adhere to the convention of 1 mapping
+ * file per class and the file names of the mapping files must correspond to the full
+ * class name, including namespace, with the namespace delimiters '\', replaced by dots '.'.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+abstract class AbstractFileDriver implements Driver
+{
+ /**
+ * The paths where to look for mapping files.
+ *
+ * @var array
+ */
+ protected $_paths = array();
+
+ /**
+ * The file extension of mapping documents.
+ *
+ * @var string
+ */
+ protected $_fileExtension;
+
+ /**
+ * Initializes a new FileDriver that looks in the given path(s) for mapping
+ * documents and operates in the specified operating mode.
+ *
+ * @param string|array $paths One or multiple paths where mapping documents can be found.
+ */
+ public function __construct($paths)
+ {
+ $this->addPaths((array) $paths);
+ }
+
+ /**
+ * Append lookup paths to metadata driver.
+ *
+ * @param array $paths
+ */
+ public function addPaths(array $paths)
+ {
+ $this->_paths = array_unique(array_merge($this->_paths, $paths));
+ }
+
+ /**
+ * Retrieve the defined metadata lookup paths.
+ *
+ * @return array
+ */
+ public function getPaths()
+ {
+ return $this->_paths;
+ }
+
+ /**
+ * Get the file extension used to look for mapping files under
+ *
+ * @return void
+ */
+ public function getFileExtension()
+ {
+ return $this->_fileExtension;
+ }
+
+ /**
+ * Set the file extension used to look for mapping files under
+ *
+ * @param string $fileExtension The file extension to set
+ * @return void
+ */
+ public function setFileExtension($fileExtension)
+ {
+ $this->_fileExtension = $fileExtension;
+ }
+
+ /**
+ * Get the element of schema meta data for the class from the mapping file.
+ * This will lazily load the mapping file if it is not loaded yet
+ *
+ * @return array $element The element of schema meta data
+ */
+ public function getElement($className)
+ {
+ $result = $this->_loadMappingFile($this->_findMappingFile($className));
+
+ return $result[$className];
+ }
+
+ /**
+ * Whether the class with the specified name should have its metadata loaded.
+ * This is only the case if it is either mapped as an Entity or a
+ * MappedSuperclass.
+ *
+ * @param string $className
+ * @return boolean
+ */
+ public function isTransient($className)
+ {
+ $fileName = str_replace('\\', '.', $className) . $this->_fileExtension;
+
+ // Check whether file exists
+ foreach ((array) $this->_paths as $path) {
+ if (file_exists($path . DIRECTORY_SEPARATOR . $fileName)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets the names of all mapped classes known to this driver.
+ *
+ * @return array The names of all mapped classes known to this driver.
+ */
+ public function getAllClassNames()
+ {
+ $classes = array();
+
+ if ($this->_paths) {
+ foreach ((array) $this->_paths as $path) {
+ if ( ! is_dir($path)) {
+ throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
+ }
+
+ $iterator = new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator($path),
+ \RecursiveIteratorIterator::LEAVES_ONLY
+ );
+
+ foreach ($iterator as $file) {
+ if (($fileName = $file->getBasename($this->_fileExtension)) == $file->getBasename()) {
+ continue;
+ }
+
+ // NOTE: All files found here means classes are not transient!
+ $classes[] = str_replace('.', '\\', $fileName);
+ }
+ }
+ }
+
+ return $classes;
+ }
+
+ /**
+ * Finds the mapping file for the class with the given name by searching
+ * through the configured paths.
+ *
+ * @param $className
+ * @return string The (absolute) file name.
+ * @throws MappingException
+ */
+ protected function _findMappingFile($className)
+ {
+ $fileName = str_replace('\\', '.', $className) . $this->_fileExtension;
+
+ // Check whether file exists
+ foreach ((array) $this->_paths as $path) {
+ if (file_exists($path . DIRECTORY_SEPARATOR . $fileName)) {
+ return $path . DIRECTORY_SEPARATOR . $fileName;
+ }
+ }
+
+ throw MappingException::mappingFileNotFound($className, $fileName);
+ }
+
+ /**
+ * Loads a mapping file with the given name and returns a map
+ * from class/entity names to their corresponding elements.
+ *
+ * @param string $file The mapping file to load.
+ * @return array
+ */
+ abstract protected function _loadMappingFile($file);
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Mapping\Driver;
+
+use Doctrine\Common\Cache\ArrayCache,
+ Doctrine\Common\Annotations\AnnotationReader,
+ Doctrine\ORM\Mapping\ClassMetadataInfo,
+ Doctrine\ORM\Mapping\MappingException;
+
+require __DIR__ . '/DoctrineAnnotations.php';
+
+/**
+ * The AnnotationDriver reads the mapping metadata from docblock annotations.
+ *
+ * @since 2.0
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class AnnotationDriver implements Driver
+{
+ /**
+ * The AnnotationReader.
+ *
+ * @var AnnotationReader
+ */
+ private $_reader;
+
+ /**
+ * The paths where to look for mapping files.
+ *
+ * @var array
+ */
+ protected $_paths = array();
+
+ /**
+ * The file extension of mapping documents.
+ *
+ * @var string
+ */
+ protected $_fileExtension = '.php';
+
+ /**
+ * @param array
+ */
+ protected $_classNames;
+
+ /**
+ * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
+ * docblock annotations.
+ *
+ * @param $reader The AnnotationReader to use.
+ * @param string|array $paths One or multiple paths where mapping classes can be found.
+ */
+ public function __construct(AnnotationReader $reader, $paths = null)
+ {
+ $this->_reader = $reader;
+ if ($paths) {
+ $this->addPaths((array) $paths);
+ }
+ }
+
+ /**
+ * Append lookup paths to metadata driver.
+ *
+ * @param array $paths
+ */
+ public function addPaths(array $paths)
+ {
+ $this->_paths = array_unique(array_merge($this->_paths, $paths));
+ }
+
+ /**
+ * Retrieve the defined metadata lookup paths.
+ *
+ * @return array
+ */
+ public function getPaths()
+ {
+ return $this->_paths;
+ }
+
+ /**
+ * Get the file extension used to look for mapping files under
+ *
+ * @return void
+ */
+ public function getFileExtension()
+ {
+ return $this->_fileExtension;
+ }
+
+ /**
+ * Set the file extension used to look for mapping files under
+ *
+ * @param string $fileExtension The file extension to set
+ * @return void
+ */
+ public function setFileExtension($fileExtension)
+ {
+ $this->_fileExtension = $fileExtension;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
+ {
+ $class = $metadata->getReflectionClass();
+
+ $classAnnotations = $this->_reader->getClassAnnotations($class);
+
+ // Evaluate Entity annotation
+ if (isset($classAnnotations['Doctrine\ORM\Mapping\Entity'])) {
+ $entityAnnot = $classAnnotations['Doctrine\ORM\Mapping\Entity'];
+ $metadata->setCustomRepositoryClass($entityAnnot->repositoryClass);
+ } else if (isset($classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass'])) {
+ $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) {
+ $primaryTable['indexes'][$indexAnnot->name] = array(
+ 'columns' => $indexAnnot->columns
+ );
+ }
+ }
+
+ if ($tableAnnot->uniqueConstraints !== null) {
+ foreach ($tableAnnot->uniqueConstraints as $uniqueConstraint) {
+ $primaryTable['uniqueConstraints'][$uniqueConstraint->name] = array(
+ 'columns' => $uniqueConstraint->columns
+ );
+ }
+ }
+
+ $metadata->setPrimaryTable($primaryTable);
+ }
+
+ // 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
+ ));
+ } 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
+ 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[] = array(
+ 'name' => $joinColumnAnnot->name,
+ 'referencedColumnName' => $joinColumnAnnot->referencedColumnName,
+ 'unique' => $joinColumnAnnot->unique,
+ 'nullable' => $joinColumnAnnot->nullable,
+ 'onDelete' => $joinColumnAnnot->onDelete,
+ 'onUpdate' => $joinColumnAnnot->onUpdate,
+ 'columnDefinition' => $joinColumnAnnot->columnDefinition,
+ );
+ } else if ($joinColumnsAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinColumns')) {
+ foreach ($joinColumnsAnnot->value as $joinColumn) {
+ $joinColumns[] = array(
+ 'name' => $joinColumn->name,
+ 'referencedColumnName' => $joinColumn->referencedColumnName,
+ 'unique' => $joinColumn->unique,
+ 'nullable' => $joinColumn->nullable,
+ 'onDelete' => $joinColumn->onDelete,
+ 'onUpdate' => $joinColumn->onUpdate,
+ 'columnDefinition' => $joinColumn->columnDefinition,
+ );
+ }
+ }
+
+ // 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['type'] = $columnAnnot->type;
+ $mapping['length'] = $columnAnnot->length;
+ $mapping['precision'] = $columnAnnot->precision;
+ $mapping['scale'] = $columnAnnot->scale;
+ $mapping['nullable'] = $columnAnnot->nullable;
+ $mapping['unique'] = $columnAnnot->unique;
+ if ($columnAnnot->options) {
+ $mapping['options'] = $columnAnnot->options;
+ }
+
+ if (isset($columnAnnot->name)) {
+ $mapping['columnName'] = $columnAnnot->name;
+ }
+
+ if (isset($columnAnnot->columnDefinition)) {
+ $mapping['columnDefinition'] = $columnAnnot->columnDefinition;
+ }
+
+ 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 ($versionAnnot = $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 ($tblGeneratorAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\TableGenerator')) {
+ throw MappingException::tableIdGeneratorNotImplemented($className);
+ }
+ } else if ($oneToOneAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToOne')) {
+ $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'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $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['orphanRemoval'] = $oneToManyAnnot->orphanRemoval;
+ $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $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')) {
+ $mapping['joinColumns'] = $joinColumns;
+ $mapping['cascade'] = $manyToOneAnnot->cascade;
+ $mapping['inversedBy'] = $manyToOneAnnot->inversedBy;
+ $mapping['targetEntity'] = $manyToOneAnnot->targetEntity;
+ $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $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'][] = array(
+ 'name' => $joinColumn->name,
+ 'referencedColumnName' => $joinColumn->referencedColumnName,
+ 'unique' => $joinColumn->unique,
+ 'nullable' => $joinColumn->nullable,
+ 'onDelete' => $joinColumn->onDelete,
+ 'onUpdate' => $joinColumn->onUpdate,
+ 'columnDefinition' => $joinColumn->columnDefinition,
+ );
+ }
+
+ foreach ($joinTableAnnot->inverseJoinColumns as $joinColumn) {
+ $joinTable['inverseJoinColumns'][] = array(
+ 'name' => $joinColumn->name,
+ 'referencedColumnName' => $joinColumn->referencedColumnName,
+ 'unique' => $joinColumn->unique,
+ 'nullable' => $joinColumn->nullable,
+ 'onDelete' => $joinColumn->onDelete,
+ 'onUpdate' => $joinColumn->onUpdate,
+ 'columnDefinition' => $joinColumn->columnDefinition,
+ );
+ }
+ }
+
+ $mapping['joinTable'] = $joinTable;
+ $mapping['targetEntity'] = $manyToManyAnnot->targetEntity;
+ $mapping['mappedBy'] = $manyToManyAnnot->mappedBy;
+ $mapping['inversedBy'] = $manyToManyAnnot->inversedBy;
+ $mapping['cascade'] = $manyToManyAnnot->cascade;
+ $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToManyAnnot->fetch);
+
+ if ($orderByAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) {
+ $mapping['orderBy'] = $orderByAnnot->value;
+ }
+
+ $metadata->mapManyToMany($mapping);
+ }
+ }
+
+ // Evaluate @HasLifecycleCallbacks annotation
+ if (isset($classAnnotations['Doctrine\ORM\Mapping\HasLifecycleCallbacks'])) {
+ foreach ($class->getMethods() as $method) {
+ if ($method->isPublic()) {
+ $annotations = $this->_reader->getMethodAnnotations($method);
+
+ 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);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Whether the class with the specified name is transient. Only non-transient
+ * classes, that is entities and mapped superclasses, should have their metadata loaded.
+ * A class is non-transient if it is annotated with either @Entity or
+ * @MappedSuperclass in the class doc block.
+ *
+ * @param string $className
+ * @return boolean
+ */
+ public function isTransient($className)
+ {
+ $classAnnotations = $this->_reader->getClassAnnotations(new \ReflectionClass($className));
+
+ return ! isset($classAnnotations['Doctrine\ORM\Mapping\Entity']) &&
+ ! isset($classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass']);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getAllClassNames()
+ {
+ if ($this->_classNames !== null) {
+ return $this->_classNames;
+ }
+
+ if (!$this->_paths) {
+ throw MappingException::pathRequired();
+ }
+
+ $classes = array();
+ $includedFiles = array();
+
+ foreach ($this->_paths as $path) {
+ if ( ! is_dir($path)) {
+ throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
+ }
+
+ $iterator = new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator($path),
+ \RecursiveIteratorIterator::LEAVES_ONLY
+ );
+
+ foreach ($iterator as $file) {
+ if (($fileName = $file->getBasename($this->_fileExtension)) == $file->getBasename()) {
+ continue;
+ }
+
+ $sourceFile = realpath($file->getPathName());
+ require_once $sourceFile;
+ $includedFiles[] = $sourceFile;
+ }
+ }
+
+ $declared = get_declared_classes();
+
+ foreach ($declared as $className) {
+ $rc = new \ReflectionClass($className);
+ $sourceFile = $rc->getFileName();
+ if (in_array($sourceFile, $includedFiles) && ! $this->isTransient($className)) {
+ $classes[] = $className;
+ }
+ }
+
+ $this->_classNames = $classes;
+
+ return $classes;
+ }
+
+ /**
+ * 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();
+ $reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
+ }
+ return new self($reader, $paths);
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Mapping\Driver;
+
+use Doctrine\Common\Cache\ArrayCache,
+ Doctrine\Common\Annotations\AnnotationReader,
+ Doctrine\DBAL\Schema\AbstractSchemaManager,
+ Doctrine\ORM\Mapping\ClassMetadataInfo,
+ Doctrine\ORM\Mapping\MappingException,
+ Doctrine\Common\Util\Inflector;
+
+/**
+ * The DatabaseDriver reverse engineers the mapping metadata from a database.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class DatabaseDriver implements Driver
+{
+ /**
+ * @var AbstractSchemaManager
+ */
+ private $_sm;
+
+ /**
+ * @var array
+ */
+ private $tables = null;
+
+ private $classToTableNames = array();
+
+ /**
+ * @var array
+ */
+ private $manyToManyTables = array();
+
+ /**
+ * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
+ * docblock annotations.
+ *
+ * @param AnnotationReader $reader The AnnotationReader to use.
+ */
+ public function __construct(AbstractSchemaManager $schemaManager)
+ {
+ $this->_sm = $schemaManager;
+ }
+
+ private function reverseEngineerMappingFromDatabase()
+ {
+ if ($this->tables !== null) {
+ return;
+ }
+
+ foreach ($this->_sm->listTableNames() as $tableName) {
+ $tables[$tableName] = $this->_sm->listTableDetails($tableName);
+ }
+
+ $this->tables = array();
+ foreach ($tables AS $tableName => $table) {
+ /* @var $table Table */
+ if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) {
+ $foreignKeys = $table->getForeignKeys();
+ } else {
+ $foreignKeys = array();
+ }
+
+ $allForeignKeyColumns = array();
+ foreach ($foreignKeys AS $foreignKey) {
+ $allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns());
+ }
+
+ $pkColumns = $table->getPrimaryKey()->getColumns();
+ sort($pkColumns);
+ sort($allForeignKeyColumns);
+
+ if ($pkColumns == $allForeignKeyColumns) {
+ if (count($table->getForeignKeys()) > 2) {
+ throw new \InvalidArgumentException("ManyToMany table '" . $tableName . "' with more or less than two foreign keys are not supported by the Database Reverese Engineering Driver.");
+ }
+
+ $this->manyToManyTables[$tableName] = $table;
+ } else {
+ // lower-casing is necessary because of Oracle Uppercase Tablenames,
+ // assumption is lower-case + underscore separated.
+ $className = Inflector::classify(strtolower($tableName));
+ $this->tables[$tableName] = $table;
+ $this->classToTableNames[$className] = $tableName;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function loadMetadataForClass($className, ClassMetadataInfo $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();
+
+ 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 (isset($indexes['primary']) && in_array($column->getName(), $indexes['primary']->getColumns())) {
+ $fieldMapping['id'] = true;
+ } else if (in_array($column->getName(), $allForeignKeyColumns)) {
+ continue;
+ }
+
+ $fieldMapping['fieldName'] = Inflector::camelize(strtolower($column->getName()));
+ $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) {
+ if (strtolower($tableName) == strtolower($foreignKey->getForeignTableName())) {
+ $myFk = $foreignKey;
+ foreach ($manyTable->getForeignKeys() AS $foreignKey) {
+ if ($foreignKey != $myFk) {
+ $otherFk = $foreignKey;
+ break;
+ }
+ }
+
+ $localColumn = current($myFk->getColumns());
+ $associationMapping = array();
+ $associationMapping['fieldName'] = Inflector::camelize(str_replace('_id', '', strtolower(current($otherFk->getColumns()))));
+ $associationMapping['targetEntity'] = Inflector::classify(strtolower($otherFk->getForeignTableName()));
+ if (current($manyTable->getColumns())->getName() == $localColumn) {
+ $associationMapping['inversedBy'] = Inflector::camelize(str_replace('_id', '', strtolower(current($myFk->getColumns()))));
+ $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'] = Inflector::camelize(str_replace('_id', '', strtolower(current($myFk->getColumns()))));
+ }
+ $metadata->mapManyToMany($associationMapping);
+ break;
+ }
+ }
+ }
+
+ foreach ($foreignKeys as $foreignKey) {
+ $foreignTable = $foreignKey->getForeignTableName();
+ $cols = $foreignKey->getColumns();
+ $fkCols = $foreignKey->getForeignColumns();
+
+ $localColumn = current($cols);
+ $associationMapping = array();
+ $associationMapping['fieldName'] = Inflector::camelize(str_replace('_id', '', strtolower($localColumn)));
+ $associationMapping['targetEntity'] = Inflector::classify($foreignTable);
+
+ for ($i = 0; $i < count($cols); $i++) {
+ $associationMapping['joinColumns'][] = array(
+ 'name' => $cols[$i],
+ 'referencedColumnName' => $fkCols[$i],
+ );
+ }
+ $metadata->mapManyToOne($associationMapping);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isTransient($className)
+ {
+ return true;
+ }
+
+ /**
+ * Return all the class names supported by this driver.
+ *
+ * IMPORTANT: This method must return an array of class not tables names.
+ *
+ * @return array
+ */
+ public function getAllClassNames()
+ {
+ $this->reverseEngineerMappingFromDatabase();
+
+ return array_keys($this->classToTableNames);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Mapping;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/* Annotations */
+
+final class Entity extends Annotation {
+ public $repositoryClass;
+}
+final class MappedSuperclass extends Annotation {}
+final class InheritanceType extends Annotation {}
+final class DiscriminatorColumn extends Annotation {
+ public $name;
+ public $fieldName; // field name used in non-object hydration (array/scalar)
+ public $type;
+ public $length;
+}
+final class DiscriminatorMap extends Annotation {}
+final class Id extends Annotation {}
+final class GeneratedValue extends Annotation {
+ public $strategy = 'AUTO';
+}
+final class Version extends Annotation {}
+final class JoinColumn extends Annotation {
+ public $name;
+ public $fieldName; // field name used in non-object hydration (array/scalar)
+ public $referencedColumnName = 'id';
+ public $unique = false;
+ public $nullable = true;
+ public $onDelete;
+ public $onUpdate;
+ public $columnDefinition;
+}
+final class JoinColumns extends Annotation {}
+final class Column extends Annotation {
+ public $type = 'string';
+ public $length;
+ // The precision for a decimal (exact numeric) column (Applies only for decimal column)
+ public $precision = 0;
+ // The scale for a decimal (exact numeric) column (Applies only for decimal column)
+ public $scale = 0;
+ public $unique = false;
+ public $nullable = false;
+ public $name;
+ public $options = array();
+ public $columnDefinition;
+}
+final class OneToOne extends Annotation {
+ public $targetEntity;
+ public $mappedBy;
+ public $inversedBy;
+ public $cascade;
+ public $fetch = 'LAZY';
+ public $orphanRemoval = false;
+}
+final class OneToMany extends Annotation {
+ public $mappedBy;
+ public $targetEntity;
+ public $cascade;
+ public $fetch = 'LAZY';
+ public $orphanRemoval = false;
+}
+final class ManyToOne extends Annotation {
+ public $targetEntity;
+ public $cascade;
+ public $fetch = 'LAZY';
+ public $inversedBy;
+}
+final class ManyToMany extends Annotation {
+ public $targetEntity;
+ public $mappedBy;
+ public $inversedBy;
+ public $cascade;
+ public $fetch = 'LAZY';
+}
+final class ElementCollection extends Annotation {
+ public $tableName;
+}
+final class Table extends Annotation {
+ public $name;
+ public $schema;
+ public $indexes;
+ public $uniqueConstraints;
+}
+final class UniqueConstraint extends Annotation {
+ public $name;
+ public $columns;
+}
+final class Index extends Annotation {
+ public $name;
+ public $columns;
+}
+final class JoinTable extends Annotation {
+ public $name;
+ public $schema;
+ public $joinColumns = array();
+ public $inverseJoinColumns = array();
+}
+final class SequenceGenerator extends Annotation {
+ public $sequenceName;
+ public $allocationSize = 1;
+ public $initialValue = 1;
+}
+final class ChangeTrackingPolicy extends Annotation {}
+final class OrderBy extends Annotation {}
+
+/* Annotations for lifecycle callbacks */
+final class HasLifecycleCallbacks extends Annotation {}
+final class PrePersist extends Annotation {}
+final class PostPersist extends Annotation {}
+final class PreUpdate extends Annotation {}
+final class PostUpdate extends Annotation {}
+final class PreRemove extends Annotation {}
+final class PostRemove extends Annotation {}
+final class PostLoad extends Annotation {}
+
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Mapping\Driver;
+
+use Doctrine\ORM\Mapping\ClassMetadataInfo;
+
+/**
+ * Contract for metadata drivers.
+ *
+ * @since 2.0
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ * @todo Rename: MetadataDriver or MappingDriver
+ */
+interface Driver
+{
+ /**
+ * Loads the metadata for the specified class into the provided container.
+ *
+ * @param string $className
+ * @param ClassMetadataInfo $metadata
+ */
+ function loadMetadataForClass($className, ClassMetadataInfo $metadata);
+
+ /**
+ * Gets the names of all mapped classes known to this driver.
+ *
+ * @return array The names of all mapped classes known to this driver.
+ */
+ function getAllClassNames();
+
+ /**
+ * Whether the class with the specified name should have its metadata loaded.
+ * This is only the case if it is either mapped as an Entity or a
+ * MappedSuperclass.
+ *
+ * @param string $className
+ * @return boolean
+ */
+ function isTransient($className);
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Mapping\Driver;
+
+use Doctrine\ORM\Mapping\Driver\Driver,
+ Doctrine\ORM\Mapping\ClassMetadataInfo,
+ Doctrine\ORM\Mapping\MappingException;
+
+/**
+ * The DriverChain allows you to add multiple other mapping drivers for
+ * certain namespaces
+ *
+ * @since 2.0
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @todo Rename: MappingDriverChain or MetadataDriverChain
+ */
+class DriverChain implements Driver
+{
+ /**
+ * @var array
+ */
+ private $_drivers = array();
+
+ /**
+ * Add a nested driver.
+ *
+ * @param Driver $nestedDriver
+ * @param string $namespace
+ */
+ public function addDriver(Driver $nestedDriver, $namespace)
+ {
+ $this->_drivers[$namespace] = $nestedDriver;
+ }
+
+ /**
+ * Get the array of nested drivers.
+ *
+ * @return array $drivers
+ */
+ public function getDrivers()
+ {
+ return $this->_drivers;
+ }
+
+ /**
+ * Loads the metadata for the specified class into the provided container.
+ *
+ * @param string $className
+ * @param ClassMetadataInfo $metadata
+ */
+ public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
+ {
+ foreach ($this->_drivers as $namespace => $driver) {
+ if (strpos($className, $namespace) === 0) {
+ $driver->loadMetadataForClass($className, $metadata);
+ return;
+ }
+ }
+
+ throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
+ }
+
+ /**
+ * Gets the names of all mapped classes known to this driver.
+ *
+ * @return array The names of all mapped classes known to this driver.
+ */
+ public function getAllClassNames()
+ {
+ $classNames = array();
+ foreach ($this->_drivers AS $driver) {
+ $classNames = array_merge($classNames, $driver->getAllClassNames());
+ }
+ return $classNames;
+ }
+
+ /**
+ * Whether the class with the specified name should have its metadata loaded.
+ *
+ * This is only the case for non-transient classes either mapped as an Entity or MappedSuperclass.
+ *
+ * @param string $className
+ * @return boolean
+ */
+ public function isTransient($className)
+ {
+ foreach ($this->_drivers AS $namespace => $driver) {
+ if (strpos($className, $namespace) === 0) {
+ return $driver->isTransient($className);
+ }
+ }
+
+ // class isTransient, i.e. not an entity or mapped superclass
+ return true;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Mapping\Driver;
+
+use Doctrine\Common\Cache\ArrayCache,
+ Doctrine\Common\Annotations\AnnotationReader,
+ Doctrine\DBAL\Schema\AbstractSchemaManager,
+ Doctrine\ORM\Mapping\ClassMetadataInfo,
+ Doctrine\ORM\Mapping\MappingException,
+ Doctrine\Common\Util\Inflector,
+ Doctrine\ORM\Mapping\Driver\AbstractFileDriver;
+
+/**
+ * The PHPDriver includes php files which just populate ClassMetadataInfo
+ * instances with plain php code
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @todo Rename: PHPDriver
+ */
+class PHPDriver extends AbstractFileDriver
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $_fileExtension = '.php';
+ protected $_metadata;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
+ {
+ $this->_metadata = $metadata;
+ $this->_loadMappingFile($this->_findMappingFile($className));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _loadMappingFile($file)
+ {
+ $metadata = $this->_metadata;
+ include $file;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Mapping\Driver;
+
+use Doctrine\ORM\Mapping\ClassMetadataInfo,
+ Doctrine\ORM\Mapping\MappingException;
+
+/**
+ * The StaticPHPDriver calls a static loadMetadata() method on your entity
+ * classes where you can manually populate the ClassMetadata instance.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class StaticPHPDriver implements Driver
+{
+ private $_paths = array();
+
+ public function __construct($paths)
+ {
+ $this->addPaths((array) $paths);
+ }
+
+ public function addPaths(array $paths)
+ {
+ $this->_paths = array_unique(array_merge($this->_paths, $paths));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
+ {
+ call_user_func_array(array($className, 'loadMetadata'), array($metadata));
+ }
+
+ /**
+ * {@inheritDoc}
+ * @todo Same code exists in AnnotationDriver, should we re-use it somehow or not worry about it?
+ */
+ public function getAllClassNames()
+ {
+ if ($this->_classNames !== null) {
+ return $this->_classNames;
+ }
+
+ if (!$this->_paths) {
+ throw MappingException::pathRequired();
+ }
+
+ $classes = array();
+ $includedFiles = array();
+
+ foreach ($this->_paths as $path) {
+ if ( ! is_dir($path)) {
+ throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
+ }
+
+ $iterator = new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator($path),
+ \RecursiveIteratorIterator::LEAVES_ONLY
+ );
+
+ foreach ($iterator as $file) {
+ if (($fileName = $file->getBasename($this->_fileExtension)) == $file->getBasename()) {
+ continue;
+ }
+
+ $sourceFile = realpath($file->getPathName());
+ require_once $sourceFile;
+ $includedFiles[] = $sourceFile;
+ }
+ }
+
+ $declared = get_declared_classes();
+
+ foreach ($declared as $className) {
+ $rc = new \ReflectionClass($className);
+ $sourceFile = $rc->getFileName();
+ if (in_array($sourceFile, $includedFiles) && ! $this->isTransient($className)) {
+ $classes[] = $className;
+ }
+ }
+
+ $this->_classNames = $classes;
+
+ return $classes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isTransient($className)
+ {
+ return method_exists($className, 'loadMetadata') ? false : true;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Mapping\Driver;
+
+use SimpleXMLElement,
+ Doctrine\ORM\Mapping\ClassMetadataInfo,
+ 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
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class XmlDriver extends AbstractFileDriver
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $_fileExtension = '.dcm.xml';
+
+ /**
+ * {@inheritdoc}
+ */
+ public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
+ {
+ $xmlRoot = $this->getElement($className);
+
+ if ($xmlRoot->getName() == 'entity') {
+ $metadata->setCustomRepositoryClass(
+ isset($xmlRoot['repository-class']) ? (string)$xmlRoot['repository-class'] : null
+ );
+ } else if ($xmlRoot->getName() == 'mapped-superclass') {
+ $metadata->isMappedSuperclass = true;
+ } else {
+ throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
+ }
+
+ // Evaluate <entity...> attributes
+ $table = array();
+ if (isset($xmlRoot['table'])) {
+ $table['name'] = (string)$xmlRoot['table'];
+ }
+
+ $metadata->setPrimaryTable($table);
+
+ /* 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 <discriminator-column...>
+ if (isset($xmlRoot->{'discriminator-column'})) {
+ $discrColumn = $xmlRoot->{'discriminator-column'};
+ $metadata->setDiscriminatorColumn(array(
+ 'name' => (string)$discrColumn['name'],
+ 'type' => (string)$discrColumn['type'],
+ 'length' => (string)$discrColumn['length']
+ ));
+ } else {
+ $metadata->setDiscriminatorColumn(array('name' => 'dtype', 'type' => 'string', 'length' => 255));
+ }
+
+ // Evaluate <discriminator-map...>
+ 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 <change-tracking-policy...>
+ if (isset($xmlRoot['change-tracking-policy'])) {
+ $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_'
+ . strtoupper((string)$xmlRoot['change-tracking-policy'])));
+ }
+
+ // Evaluate <indexes...>
+ 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 <unique-constraints..>
+ 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
+ );
+ }
+ }
+ }
+
+ // Evaluate <field ...> mappings
+ if (isset($xmlRoot->field)) {
+ foreach ($xmlRoot->field as $fieldMapping) {
+ $mapping = array(
+ 'fieldName' => (string)$fieldMapping['name'],
+ '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'] = ((string)$fieldMapping['unique'] == "false") ? false : true;
+ }
+
+ if (isset($fieldMapping['options'])) {
+ $mapping['options'] = (array)$fieldMapping['options'];
+ }
+
+ if (isset($fieldMapping['nullable'])) {
+ $mapping['nullable'] = ((string)$fieldMapping['nullable'] == "false") ? false : true;
+ }
+
+ if (isset($fieldMapping['version']) && $fieldMapping['version']) {
+ $metadata->setVersionMapping($mapping);
+ }
+
+ if (isset($fieldMapping['column-definition'])) {
+ $mapping['columnDefinition'] = (string)$fieldMapping['column-definition'];
+ }
+
+ $metadata->mapField($mapping);
+ }
+ }
+
+ // Evaluate <id ...> mappings
+ foreach ($xmlRoot->id as $idElement) {
+ $mapping = array(
+ 'id' => true,
+ 'fieldName' => (string)$idElement['name'],
+ 'type' => (string)$idElement['type']
+ );
+
+ if (isset($idElement['column'])) {
+ $mapping['columnName'] = (string)$idElement['column'];
+ }
+
+ $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->{'table-generator'})) {
+ throw MappingException::tableIdGeneratorNotImplemented($className);
+ }
+ }
+
+ // Evaluate <one-to-one ...> 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($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->_getJoinColumnMapping($oneToOneElement->{'join-column'});
+ } else if (isset($oneToOneElement->{'join-columns'})) {
+ foreach ($oneToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
+ $joinColumns[] = $this->_getJoinColumnMapping($joinColumnElement);
+ }
+ }
+
+ $mapping['joinColumns'] = $joinColumns;
+ }
+
+ if (isset($oneToOneElement->cascade)) {
+ $mapping['cascade'] = $this->_getCascadeMappings($oneToOneElement->cascade);
+ }
+
+ if (isset($oneToOneElement->{'orphan-removal'})) {
+ $mapping['orphanRemoval'] = (bool)$oneToOneElement->{'orphan-removal'};
+ }
+
+ $metadata->mapOneToOne($mapping);
+ }
+ }
+
+ // Evaluate <one-to-many ...> 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'] = (bool)$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;
+ }
+
+ $metadata->mapOneToMany($mapping);
+ }
+ }
+
+ // Evaluate <many-to-one ...> 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($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->_getJoinColumnMapping($manyToOneElement->{'join-column'});
+ } else if (isset($manyToOneElement->{'join-columns'})) {
+ foreach ($manyToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
+ if (!isset($joinColumnElement['name'])) {
+ $joinColumnElement['name'] = $name;
+ }
+ $joinColumns[] = $this->_getJoinColumnMapping($joinColumnElement);
+ }
+ }
+
+ $mapping['joinColumns'] = $joinColumns;
+
+ if (isset($manyToOneElement->cascade)) {
+ $mapping['cascade'] = $this->_getCascadeMappings($manyToOneElement->cascade);
+ }
+
+ if (isset($manyToOneElement->{'orphan-removal'})) {
+ $mapping['orphanRemoval'] = (bool)$manyToOneElement->{'orphan-removal'};
+ }
+
+ $metadata->mapManyToOne($mapping);
+ }
+ }
+
+ // Evaluate <many-to-many ...> 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['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->_getJoinColumnMapping($joinColumnElement);
+ }
+
+ foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} as $joinColumnElement) {
+ $joinTable['inverseJoinColumns'][] = $this->_getJoinColumnMapping($joinColumnElement);
+ }
+
+ $mapping['joinTable'] = $joinTable;
+ }
+
+ if (isset($manyToManyElement->cascade)) {
+ $mapping['cascade'] = $this->_getCascadeMappings($manyToManyElement->cascade);
+ }
+
+ if (isset($manyToManyElement->{'orphan-removal'})) {
+ $mapping['orphanRemoval'] = (bool)$manyToManyElement->{'orphan-removal'};
+ }
+
+ 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;
+ }
+
+ $metadata->mapManyToMany($mapping);
+ }
+ }
+
+ // Evaluate <lifecycle-callbacks...>
+ 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']));
+ }
+ }
+ }
+
+ /**
+ * Constructs a joinColumn mapping array based on the information
+ * found in the given SimpleXMLElement.
+ *
+ * @param $joinColumnElement The XML element.
+ * @return array The mapping array.
+ */
+ private function _getJoinColumnMapping(SimpleXMLElement $joinColumnElement)
+ {
+ $joinColumn = array(
+ 'name' => (string)$joinColumnElement['name'],
+ 'referencedColumnName' => (string)$joinColumnElement['referenced-column-name']
+ );
+
+ if (isset($joinColumnElement['unique'])) {
+ $joinColumn['unique'] = ((string)$joinColumnElement['unique'] == "false") ? false : true;
+ }
+
+ if (isset($joinColumnElement['nullable'])) {
+ $joinColumn['nullable'] = ((string)$joinColumnElement['nullable'] == "false") ? false : true;
+ }
+
+ if (isset($joinColumnElement['on-delete'])) {
+ $joinColumn['onDelete'] = (string)$joinColumnElement['on-delete'];
+ }
+
+ if (isset($joinColumnElement['on-update'])) {
+ $joinColumn['onUpdate'] = (string)$joinColumnElement['on-update'];
+ }
+
+ if (isset($joinColumnElement['column-definition'])) {
+ $joinColumn['columnDefinition'] = (string)$joinColumnElement['column-definition'];
+ }
+
+ return $joinColumn;
+ }
+
+ /**
+ * Gathers a list of cascade options found in the given cascade element.
+ *
+ * @param $cascadeElement The cascade element.
+ * @return array The list of cascade options.
+ */
+ private function _getCascadeMappings($cascadeElement)
+ {
+ $cascades = array();
+ 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;
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Mapping\Driver;
+
+use Doctrine\ORM\Mapping\ClassMetadataInfo,
+ Doctrine\ORM\Mapping\MappingException;
+
+/**
+ * The YamlDriver reads the mapping metadata from yaml schema files.
+ *
+ * @since 2.0
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class YamlDriver extends AbstractFileDriver
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $_fileExtension = '.dcm.yml';
+
+ /**
+ * {@inheritdoc}
+ */
+ public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
+ {
+ $element = $this->getElement($className);
+
+ if ($element['type'] == 'entity') {
+ $metadata->setCustomRepositoryClass(
+ isset($element['repositoryClass']) ? $element['repositoryClass'] : null
+ );
+ } else if ($element['type'] == 'mappedSuperclass') {
+ $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);
+
+ /* 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' => $discrColumn['name'],
+ 'type' => $discrColumn['type'],
+ 'length' => $discrColumn['length']
+ ));
+ } 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']);
+ } 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']);
+ } else {
+ $columns = $unique['columns'];
+ }
+
+ $metadata->table['uniqueConstraints'][$unique['name']] = array(
+ 'columns' => $columns
+ );
+ }
+ }
+
+ if (isset($element['id'])) {
+ // Evaluate identifier settings
+ foreach ($element['id'] as $name => $idElement) {
+ if (!isset($idElement['type'])) {
+ throw MappingException::propertyTypeIsRequired($className, $name);
+ }
+
+ $mapping = array(
+ 'id' => true,
+ 'fieldName' => $name,
+ 'type' => $idElement['type']
+ );
+
+ if (isset($idElement['column'])) {
+ $mapping['columnName'] = $idElement['column'];
+ }
+
+ if (isset($idElement['length'])) {
+ $mapping['length'] = $idElement['length'];
+ }
+
+ $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['tableGenerator'])) {
+ throw MappingException::tableIdGeneratorNotImplemented($className);
+ }
+ }
+ }
+
+ // Evaluate fields
+ if (isset($element['fields'])) {
+ foreach ($element['fields'] as $name => $fieldMapping) {
+ if (!isset($fieldMapping['type'])) {
+ throw MappingException::propertyTypeIsRequired($className, $name);
+ }
+
+ $e = explode('(', $fieldMapping['type']);
+ $fieldMapping['type'] = $e[0];
+ if (isset($e[1])) {
+ $fieldMapping['length'] = substr($e[1], 0, strlen($e[1]) - 1);
+ }
+ $mapping = array(
+ 'fieldName' => $name,
+ 'type' => $fieldMapping['type']
+ );
+ 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($fieldMapping['column'])) {
+ $mapping['columnName'] = $fieldMapping['column'];
+ }
+ if (isset($fieldMapping['length'])) {
+ $mapping['length'] = $fieldMapping['length'];
+ }
+ if (isset($fieldMapping['precision'])) {
+ $mapping['precision'] = $fieldMapping['precision'];
+ }
+ if (isset($fieldMapping['scale'])) {
+ $mapping['scale'] = $fieldMapping['scale'];
+ }
+ if (isset($fieldMapping['unique'])) {
+ $mapping['unique'] = (bool)$fieldMapping['unique'];
+ }
+ if (isset($fieldMapping['options'])) {
+ $mapping['options'] = $fieldMapping['options'];
+ }
+ if (isset($fieldMapping['nullable'])) {
+ $mapping['nullable'] = $fieldMapping['nullable'];
+ }
+ if (isset($fieldMapping['version']) && $fieldMapping['version']) {
+ $metadata->setVersionMapping($mapping);
+ }
+ if (isset($fieldMapping['columnDefinition'])) {
+ $mapping['columnDefinition'] = $fieldMapping['columnDefinition'];
+ }
+
+ $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($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->_getJoinColumnMapping($oneToOneElement['joinColumn']);
+ } else if (isset($oneToOneElement['joinColumns'])) {
+ foreach ($oneToOneElement['joinColumns'] as $name => $joinColumnElement) {
+ if (!isset($joinColumnElement['name'])) {
+ $joinColumnElement['name'] = $name;
+ }
+
+ $joinColumns[] = $this->_getJoinColumnMapping($joinColumnElement);
+ }
+ }
+
+ $mapping['joinColumns'] = $joinColumns;
+ }
+
+ if (isset($oneToOneElement['cascade'])) {
+ $mapping['cascade'] = $oneToOneElement['cascade'];
+ }
+
+ $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['orderBy'])) {
+ $mapping['orderBy'] = $oneToManyElement['orderBy'];
+ }
+
+ $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($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->_getJoinColumnMapping($manyToOneElement['joinColumn']);
+ } else if (isset($manyToOneElement['joinColumns'])) {
+ foreach ($manyToOneElement['joinColumns'] as $name => $joinColumnElement) {
+ if (!isset($joinColumnElement['name'])) {
+ $joinColumnElement['name'] = $name;
+ }
+
+ $joinColumns[] = $this->_getJoinColumnMapping($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'])) {
+ if (isset($manyToManyElement['inversedBy'])) {
+ $mapping['inversedBy'] = $manyToManyElement['inversedBy'];
+ }
+
+ $joinTableElement = $manyToManyElement['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->_getJoinColumnMapping($joinColumnElement);
+ }
+
+ foreach ($joinTableElement['inverseJoinColumns'] as $name => $joinColumnElement) {
+ if (!isset($joinColumnElement['name'])) {
+ $joinColumnElement['name'] = $name;
+ }
+
+ $joinTable['inverseJoinColumns'][] = $this->_getJoinColumnMapping($joinColumnElement);
+ }
+
+ $mapping['joinTable'] = $joinTable;
+ }
+
+ if (isset($manyToManyElement['cascade'])) {
+ $mapping['cascade'] = $manyToManyElement['cascade'];
+ }
+
+ if (isset($manyToManyElement['orderBy'])) {
+ $mapping['orderBy'] = $manyToManyElement['orderBy'];
+ }
+
+ $metadata->mapManyToMany($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 $joinColumnElement The array join column element
+ * @return array The mapping array.
+ */
+ private function _getJoinColumnMapping($joinColumnElement)
+ {
+ $joinColumn = array(
+ 'name' => $joinColumnElement['name'],
+ 'referencedColumnName' => $joinColumnElement['referencedColumnName']
+ );
+
+ 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['onUpdate'])) {
+ $joinColumn['onUpdate'] = $joinColumnElement['onUpdate'];
+ }
+
+ if (isset($joinColumnElement['columnDefinition'])) {
+ $joinColumn['columnDefinition'] = $joinColumnElement['columnDefinition'];
+ }
+
+ return $joinColumn;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _loadMappingFile($file)
+ {
+ return \Symfony\Component\Yaml\Yaml::load($file);
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.phpdoctrine.org>.
+ */
+
+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)
+ {
+ return new self("No identifier/primary key specified for Entity '$entityName'."
+ . " Every Entity must have an identifier/primary key.");
+ }
+
+ 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()
+ {
+ return new self("The association mapping misses the 'fieldName' attribute.");
+ }
+
+ 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'.");
+ }
+
+ public static function mappingNotFound($fieldName)
+ {
+ return new self("No mapping found for field '$fieldName'.");
+ }
+
+ 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)
+ {
+ return new self('Class '.$className.' is not a valid entity or mapped super class.');
+ }
+
+ 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 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 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 cannotVersionIdField($className, $fieldName)
+ {
+ return new self("Setting Id field '$fieldName' as versionale in entity class '$className' is not supported.");
+ }
+
+ /**
+ * @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.");
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM;
+
+/**
+ * Represents a native SQL query.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @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()
+ {
+ $stmt = $this->_em->getConnection()->prepare($this->_sql);
+ $params = $this->_params;
+ foreach ($params as $key => $value) {
+ if (isset($this->_paramTypes[$key])) {
+ $stmt->bindValue($key, $value, $this->_paramTypes[$key]);
+ } else {
+ $stmt->bindValue($key, $value);
+ }
+ }
+ $stmt->execute();
+
+ return $stmt;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM;
+
+/**
+ * Exception thrown when an ORM query unexpectedly does not return any results.
+ *
+ * @author robo
+ * @since 2.0
+ */
+class NoResultException extends ORMException
+{
+ public function __construct()
+ {
+ parent::__construct('No result was found for query although at least one row was expected.');
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM;
+
+/**
+ * Exception thrown when an ORM query unexpectedly returns more than one result.
+ *
+ * @author robo
+ * @since 2.0
+ */
+class NonUniqueResultException extends ORMException {}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM;
+
+use Exception;
+
+/**
+ * Base exception class for all ORM exceptions.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @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 entityMissingAssignedId($entity)
+ {
+ return new self("Entity of type " . get_class($entity) . " is missing an assigned ID.");
+ }
+
+ public static function unrecognizedField($field)
+ {
+ return new self("Unrecognized field: $field");
+ }
+
+ 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'."
+ );
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM;
+
+/**
+ * An OptimisticLockException is thrown when a version check on an object
+ * that uses optimistic locking through a version field fails.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @since 2.0
+ */
+class OptimisticLockException extends ORMException
+{
+ private $entity;
+
+ public function __construct($msg, $entity)
+ {
+ $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);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM;
+
+use Doctrine\ORM\Mapping\ClassMetadata,
+ Doctrine\Common\Collections\Collection,
+ 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 <kvesteri@cc.hut.fi>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
+ * @todo Design for inheritance to allow custom implementations?
+ */
+final class PersistentCollection implements Collection
+{
+ /**
+ * 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 Doctrine\ORM\Mapping\AssociationMapping
+ */
+ 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) {
+ if ($this->isDirty) {
+ // Has NEW objects added through add(). Remember them.
+ $newObjects = $this->coll->toArray();
+ }
+ $this->coll->clear();
+ $this->em->getUnitOfWork()->loadCollection($this);
+ $this->takeSnapshot();
+ // Reattach NEW objects added through add(), if any.
+ if (isset($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 Doctrine\ORM\Mapping\AssociationMapping
+ */
+ public function getMapping()
+ {
+ return $this->association;
+ }
+
+ /**
+ * Marks this collection as changed/dirty.
+ */
+ private function changed()
+ {
+ if ( ! $this->isDirty) {
+ $this->isDirty = true;
+ if ($this->association !== null && $this->association['isOwningSide'] && $this->association['type'] == ClassMetadata::MANY_TO_MANY &&
+ $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) {
+ $this->changed();
+ if ($this->association !== null && $this->association['type'] == ClassMetadata::ONE_TO_MANY &&
+ $this->association['orphanRemoval']) {
+ $this->em->getUnitOfWork()->scheduleOrphanRemoval($removed);
+ }
+ }
+
+ return $removed;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeElement($element)
+ {
+ // TODO: Assuming the identity of entities in a collection is always based
+ // on their primary key (there is no equals/hashCode in PHP),
+ // if the collection is not initialized, we could issue a straight
+ // SQL DELETE/UPDATE on the association (table) without initializing
+ // the collection.
+ /*if ( ! $this->initialized) {
+ $this->em->getUnitOfWork()->getCollectionPersister($this->association)
+ ->deleteRows($this, $element);
+ }*/
+
+ $this->initialize();
+ $removed = $this->coll->removeElement($element);
+ if ($removed) {
+ $this->changed();
+ if ($this->association !== null && $this->association['type'] == ClassMetadata::ONE_TO_MANY &&
+ $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)
+ {
+ /* DRAFT
+ if ($this->initialized) {
+ return $this->coll->contains($element);
+ } else {
+ if ($element is MANAGED) {
+ if ($this->coll->contains($element)) {
+ return true;
+ }
+ $exists = check db for existence;
+ if ($exists) {
+ $this->coll->add($element);
+ }
+ return $exists;
+ }
+ return false;
+ }*/
+
+ $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()
+ {
+ $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;
+ }
+ if ($this->association['type'] == ClassMetadata::ONE_TO_MANY && $this->association['orphanRemoval']) {
+ foreach ($this->coll as $element) {
+ $this->em->getUnitOfWork()->scheduleOrphanRemoval($element);
+ }
+ }
+ $this->coll->clear();
+ if ($this->association['isOwningSide']) {
+ $this->changed();
+ $this->em->getUnitOfWork()->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.
+ */
+ 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)
+ {
+ $this->initialize();
+ return $this->coll->slice($offset, $length);
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Persisters;
+
+use Doctrine\ORM\EntityManager,
+ Doctrine\ORM\PersistentCollection;
+
+/**
+ * Base class for all collection persisters.
+ *
+ * @since 2.0
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+abstract class AbstractCollectionPersister
+{
+ /**
+ * @var EntityManager
+ */
+ protected $_em;
+
+ /**
+ * @var Doctrine\DBAL\Connection
+ */
+ protected $_conn;
+
+ /**
+ * @var Doctrine\ORM\UnitOfWork
+ */
+ protected $_uow;
+
+ /**
+ * 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();
+ }
+
+ /**
+ * 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));
+ }
+ }
+
+ /**
+ * 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);
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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 <roman@code-factory.org>
+ * @since 2.0
+ */
+abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
+{
+ /**
+ * Map from column names to class names that declare the field the column is mapped to.
+ *
+ * @var array
+ */
+ private $_declaringClassMap = array();
+
+ /**
+ * {@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 _processSQLResult(array $sqlResult)
+ {
+ $data = array();
+ $discrColumnName = $this->_platform->getSQLResultCasing($this->_class->discriminatorColumn['name']);
+ $entityName = $this->_class->discriminatorMap[$sqlResult[$discrColumnName]];
+ unset($sqlResult[$discrColumnName]);
+ foreach ($sqlResult as $column => $value) {
+ $realColumnName = $this->_resultColumnNames[$column];
+ if (isset($this->_declaringClassMap[$column])) {
+ $class = $this->_declaringClassMap[$column];
+ if ($class->name == $entityName || is_subclass_of($entityName, $class->name)) {
+ $field = $class->fieldNames[$realColumnName];
+ if (isset($data[$field])) {
+ $data[$realColumnName] = $value;
+ } else {
+ $data[$field] = Type::getType($class->fieldMappings[$field]['type'])
+ ->convertToPHPValue($value, $this->_platform);
+ }
+ }
+ } else {
+ $data[$realColumnName] = $value;
+ }
+ }
+
+ return array($entityName, $data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _getSelectColumnSQL($field, ClassMetadata $class)
+ {
+ $columnName = $class->columnNames[$field];
+ $sql = $this->_getSQLTableAlias($class->name) . '.' . $class->getQuotedColumnName($field, $this->_platform);
+ $columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++);
+ if ( ! isset($this->_resultColumnNames[$columnAlias])) {
+ $this->_resultColumnNames[$columnAlias] = $columnName;
+ $this->_declaringClassMap[$columnAlias] = $class;
+ }
+
+ return "$sql AS $columnAlias";
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Persisters;
+
+use PDO,
+ Doctrine\DBAL\LockMode,
+ Doctrine\DBAL\Types\Type,
+ Doctrine\ORM\ORMException,
+ Doctrine\ORM\OptimisticLockException,
+ Doctrine\ORM\EntityManager,
+ Doctrine\ORM\Query,
+ Doctrine\ORM\PersistentCollection,
+ Doctrine\ORM\Mapping\MappingException,
+ Doctrine\ORM\Mapping\ClassMetadata;
+
+/**
+ * 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 <roman@code-factory.org>
+ * @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
+ * @since 2.0
+ */
+class BasicEntityPersister
+{
+ /**
+ * 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();
+
+ /**
+ * Case-sensitive mappings of column names as they appear in an SQL result set
+ * to column names as they are defined in the mapping. This is necessary because different
+ * RDBMS vendors return column names in result sets in different casings.
+ *
+ * @var array
+ */
+ protected $_resultColumnNames = array();
+
+ /**
+ * 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 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;
+
+ /**
+ * 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();
+
+ /**
+ * Initializes a new <tt>BasicEntityPersister</tt> 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();
+ }
+
+ /**
+ * 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->table['name'];
+
+ 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($this->_class, $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 Doctrine\ORM\Mapping\ClassMetadata $class
+ * @param object $entity
+ * @param mixed $id
+ */
+ protected function _assignDefaultVersionValue($class, $entity, $id)
+ {
+ $versionField = $this->_class->versionField;
+ $identifier = $this->_class->getIdentifierColumnNames();
+ $versionFieldColumnName = $this->_class->getColumnName($versionField);
+ //FIXME: Order with composite keys might not be correct
+ $sql = "SELECT " . $versionFieldColumnName . " FROM " . $class->getQuotedTableName($this->_platform)
+ . " WHERE " . implode(' = ? AND ', $identifier) . " = ?";
+ $value = $this->_conn->fetchColumn($sql, array_values((array)$id));
+
+ $value = Type::getType($class->fieldMappings[$versionField]['type'])->convertToPHPValue($value, $this->_platform);
+ $this->_class->setFieldValue($entity, $versionField, $value);
+ }
+
+ /**
+ * 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->table['name'];
+ if (isset($updateData[$tableName]) && $updateData[$tableName]) {
+ $this->_updateTable(
+ $entity, $this->_class->getQuotedTableName($this->_platform),
+ $updateData[$tableName], $this->_class->isVersioned
+ );
+
+ if ($this->_class->isVersioned) {
+ $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity);
+ $this->_assignDefaultVersionValue($this->_class, $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) {
+ if (isset($this->_class->fieldNames[$columnName])) {
+ $set[] = $this->_class->getQuotedColumnName($this->_class->fieldNames[$columnName], $this->_platform) . ' = ?';
+ } else {
+ $set[] = $columnName . ' = ?';
+ }
+ $params[] = $value;
+ $types[] = $this->_columnTypes[$columnName];
+ }
+
+ $where = array();
+ $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity);
+ foreach ($this->_class->identifier as $idField) {
+ $where[] = $this->_class->getQuotedColumnName($idField, $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->_class->getQuotedColumnName($versionField, $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 ($this->_class->isVersioned && ! $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']);
+
+ if ( ! $mapping['isOwningSide']) {
+ $relatedClass = $this->_em->getClassMetadata($mapping['targetEntity']);
+ $mapping = $relatedClass->associationMappings[$mapping['mappedBy']];
+ $keys = array_keys($mapping['relationToTargetKeyColumns']);
+ if ($selfReferential) {
+ $otherKeys = array_keys($mapping['relationToSourceKeyColumns']);
+ }
+ } else {
+ $keys = array_keys($mapping['relationToSourceKeyColumns']);
+ if ($selfReferential) {
+ $otherKeys = array_keys($mapping['relationToTargetKeyColumns']);
+ }
+ }
+
+ if ( ! isset($mapping['isOnDeleteCascade'])) {
+ $this->_conn->delete($mapping['joinTable']['name'], array_combine($keys, $identifier));
+
+ if ($selfReferential) {
+ $this->_conn->delete($mapping['joinTable']['name'], 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->_class->getIdentifierColumnNames(), $identifier);
+ $this->_conn->delete($this->_class->getQuotedTableName($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
+ * <columnName> => <value> pairs, grouped by table name.
+ *
+ * Example:
+ * <code>
+ * array(
+ * 'foo_table' => array('column1' => 'value1', 'column2' => 'value2', ...),
+ * 'bar_table' => array('columnX' => 'valueX', 'columnY' => 'valueY', ...),
+ * ...
+ * )
+ * </code>
+ *
+ * @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) {
+ $versionField = $this->_class->versionField;
+ }
+
+ foreach ($uow->getEntityChangeSet($entity) as $field => $change) {
+ if ($versioned && $versionField == $field) {
+ continue;
+ }
+
+ $oldVal = $change[0];
+ $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['sourceToTargetKeyColumns'] as $sourceColumn => $targetColumn) {
+ if ($newVal === null) {
+ $result[$owningTable][$sourceColumn] = null;
+ } 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->table['name'];
+ }
+
+ /**
+ * 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
+ * @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)
+ {
+ $sql = $this->_getSelectEntitiesSQL($criteria, $assoc, $lockMode);
+ $stmt = $this->_conn->executeQuery($sql, array_values($criteria));
+ $result = $stmt->fetch(PDO::FETCH_ASSOC);
+ $stmt->closeCursor();
+
+ return $this->_createEntity($result, $entity, $hints);
+ }
+
+ /**
+ * 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 object $targetEntity The existing ghost entity (proxy) to load, if any.
+ * @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, $targetEntity, array $identifier = array())
+ {
+ $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'][$targetClass->name][$assoc['inversedBy']] = true;
+ if ($targetClass->subClasses) {
+ foreach ($targetClass->subClasses as $targetSubclassName) {
+ $hints['fetched'][$targetSubclassName][$assoc['inversedBy']] = true;
+ }
+ }
+ }
+ /* cascade read-only status
+ if ($this->_em->getUnitOfWork()->isReadOnly($sourceEntity)) {
+ $hints[Query::HINT_READ_ONLY] = true;
+ }
+ */
+
+ $targetEntity = $this->load($identifier, $targetEntity, $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])) {
+ $identifier[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
+ } else {
+ throw MappingException::joinColumnMustPointToMappedField(
+ $sourceClass->name, $sourceKeyColumn
+ );
+ }
+ }
+
+ $targetEntity = $this->load($identifier, $targetEntity, $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)
+ {
+ $sql = $this->_getSelectEntitiesSQL($id);
+ $stmt = $this->_conn->executeQuery($sql, array_values($id));
+ $result = $stmt->fetch(PDO::FETCH_ASSOC);
+ $stmt->closeCursor();
+
+ $metaColumns = array();
+ $newData = array();
+
+ // Refresh simple state
+ foreach ($result as $column => $value) {
+ $column = $this->_resultColumnNames[$column];
+ if (isset($this->_class->fieldNames[$column])) {
+ $fieldName = $this->_class->fieldNames[$column];
+ $newValue = $this->_conn->convertToPHPValue($value, $this->_class->fieldMappings[$fieldName]['type']);
+ $this->_class->reflFields[$fieldName]->setValue($entity, $newValue);
+ $newData[$fieldName] = $newValue;
+ } else {
+ $metaColumns[$column] = $value;
+ }
+ }
+
+ // Refresh associations
+ foreach ($this->_class->associationMappings as $field => $assoc) {
+ $value = $this->_class->reflFields[$field]->getValue($entity);
+ if ($assoc['type'] & ClassMetadata::TO_ONE) {
+ if ($value instanceof Proxy && ! $value->__isInitialized__) {
+ continue; // skip uninitialized proxies
+ }
+
+ if ($assoc['isOwningSide']) {
+ $joinColumnValues = array();
+ foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) {
+ if ($metaColumns[$srcColumn] !== null) {
+ $joinColumnValues[$targetColumn] = $metaColumns[$srcColumn];
+ }
+ }
+ if ( ! $joinColumnValues && $value !== null) {
+ $this->_class->reflFields[$field]->setValue($entity, null);
+ $newData[$field] = null;
+ } else if ($value !== null) {
+ // Check identity map first, if the entity is not there,
+ // place a proxy in there instead.
+ $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']);
+ if ($found = $this->_em->getUnitOfWork()->tryGetById($joinColumnValues, $targetClass->rootEntityName)) {
+ $this->_class->reflFields[$field]->setValue($entity, $found);
+ // Complete inverse side, if necessary.
+ if ($assoc['inversedBy'] && $assoc['type'] & ClassMetadata::ONE_TO_ONE) {
+ $inverseAssoc = $targetClass->associationMappings[$assoc['inversedBy']];
+ $targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($found, $entity);
+ }
+ $newData[$field] = $found;
+ } else {
+ // FIXME: What is happening with subClassees here?
+ $proxy = $this->_em->getProxyFactory()->getProxy($assoc['targetEntity'], $joinColumnValues);
+ $this->_class->reflFields[$field]->setValue($entity, $proxy);
+ $newData[$field] = $proxy;
+ $this->_em->getUnitOfWork()->registerManaged($proxy, $joinColumnValues, array());
+ }
+ }
+ } else {
+ // Inverse side of 1-1/1-x can never be lazy.
+ //$newData[$field] = $assoc->load($entity, null, $this->_em);
+ $newData[$field] = $this->_em->getUnitOfWork()->getEntityPersister($assoc['targetEntity'])
+ ->loadOneToOneEntity($assoc, $entity, null);
+ }
+ } else if ($value instanceof PersistentCollection && $value->isInitialized()) {
+ $value->setInitialized(false);
+ // no matter if dirty or non-dirty entities are already loaded, smoke them out!
+ // the beauty of it being, they are still in the identity map
+ $value->unwrap()->clear();
+ $newData[$field] = $value;
+ }
+ }
+
+ $this->_em->getUnitOfWork()->setOriginalEntityData($entity, $newData);
+ }
+
+ /**
+ * Loads a list of entities by a list of field criteria.
+ *
+ * @param array $criteria
+ * @return array
+ */
+ public function loadAll(array $criteria = array())
+ {
+ $entities = array();
+ $sql = $this->_getSelectEntitiesSQL($criteria);
+ $stmt = $this->_conn->executeQuery($sql, array_values($criteria));
+ $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ $stmt->closeCursor();
+
+ foreach ($result as $row) {
+ $entities[] = $this->_createEntity($row);
+ }
+
+ return $entities;
+ }
+
+ /**
+ * 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.
+ */
+ public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll)
+ {
+ $criteria = array();
+ $sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']);
+ $joinTableConditions = array();
+ if ($assoc['isOwningSide']) {
+ foreach ($assoc['relationToSourceKeyColumns'] as $relationKeyColumn => $sourceKeyColumn) {
+ if (isset($sourceClass->fieldNames[$sourceKeyColumn])) {
+ $criteria[$relationKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
+ } else {
+ throw MappingException::joinColumnMustPointToMappedField(
+ $sourceClass->name, $sourceKeyColumn
+ );
+ }
+ }
+ } else {
+ $owningAssoc = $this->_em->getClassMetadata($assoc['targetEntity'])->associationMappings[$assoc['mappedBy']];
+ // TRICKY: since the association is inverted source and target are flipped
+ foreach ($owningAssoc['relationToTargetKeyColumns'] as $relationKeyColumn => $sourceKeyColumn) {
+ if (isset($sourceClass->fieldNames[$sourceKeyColumn])) {
+ $criteria[$relationKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
+ } else {
+ throw MappingException::joinColumnMustPointToMappedField(
+ $sourceClass->name, $sourceKeyColumn
+ );
+ }
+ }
+ }
+
+ $sql = $this->_getSelectEntitiesSQL($criteria, $assoc);
+ $stmt = $this->_conn->executeQuery($sql, array_values($criteria));
+ while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) {
+ $coll->hydrateAdd($this->_createEntity($result));
+ }
+ $stmt->closeCursor();
+ }
+
+ /**
+ * Creates or fills a single entity object from an SQL result.
+ *
+ * @param $result The SQL result.
+ * @param object $entity The entity object to fill, if any.
+ * @param array $hints Hints for entity creation.
+ * @return object The filled and managed entity object or NULL, if the SQL result is empty.
+ */
+ private function _createEntity($result, $entity = null, array $hints = array())
+ {
+ if ($result === false) {
+ return null;
+ }
+
+ list($entityName, $data) = $this->_processSQLResult($result);
+
+ if ($entity !== null) {
+ $hints[Query::HINT_REFRESH] = true;
+ $id = array();
+ if ($this->_class->isIdentifierComposite) {
+ foreach ($this->_class->identifier as $fieldName) {
+ $id[$fieldName] = $data[$fieldName];
+ }
+ } else {
+ $id = array($this->_class->identifier[0] => $data[$this->_class->identifier[0]]);
+ }
+ $this->_em->getUnitOfWork()->registerManaged($entity, $id, $data);
+ }
+
+ return $this->_em->getUnitOfWork()->createEntity($entityName, $data, $hints);
+ }
+
+ /**
+ * Processes an SQL result set row that contains data for an entity of the type
+ * this persister is responsible for.
+ *
+ * Subclasses are supposed to override this method if they need to change the
+ * hydration procedure for entities loaded through basic find operations or
+ * lazy-loading (not DQL).
+ *
+ * @param array $sqlResult The SQL result set row to process.
+ * @return array A tuple where the first value is the actual type of the entity and
+ * the second value the prepared data of the entity (a map from field
+ * names to values).
+ */
+ protected function _processSQLResult(array $sqlResult)
+ {
+ $data = array();
+ foreach ($sqlResult as $column => $value) {
+ $column = $this->_resultColumnNames[$column];
+ if (isset($this->_class->fieldNames[$column])) {
+ $field = $this->_class->fieldNames[$column];
+ if (isset($data[$field])) {
+ $data[$column] = $value;
+ } else {
+ $data[$field] = Type::getType($this->_class->fieldMappings[$field]['type'])
+ ->convertToPHPValue($value, $this->_platform);
+ }
+ } else {
+ $data[$column] = $value;
+ }
+ }
+
+ return array($this->_class->name, $data);
+ }
+
+ /**
+ * Gets the SELECT SQL to select one or more entities by a set of field criteria.
+ *
+ * @param array $criteria
+ * @param AssociationMapping $assoc
+ * @param string $orderBy
+ * @param int $lockMode
+ * @return string
+ * @todo Refactor: _getSelectSQL(...)
+ */
+ protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0)
+ {
+ $joinSql = $assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY ?
+ $this->_getSelectManyToManyJoinSQL($assoc) : '';
+
+ $conditionSql = $this->_getSelectConditionSQL($criteria, $assoc);
+
+ $orderBySql = $assoc !== null && isset($assoc['orderBy']) ?
+ $this->_getCollectionOrderBySQL($assoc['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();
+ }
+
+ return 'SELECT ' . $this->_getSelectColumnListSQL()
+ . $this->_platform->appendLockHint(' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' '
+ . $this->_getSQLTableAlias($this->_class->name), $lockMode)
+ . $joinSql
+ . ($conditionSql ? ' WHERE ' . $conditionSql : '')
+ . $orderBySql
+ . $lockSql;
+ }
+
+ /**
+ * Gets the ORDER BY SQL snippet for ordered collections.
+ *
+ * @param array $orderBy
+ * @param string $baseTableAlias
+ * @return string
+ * @todo Rename: _getOrderBySQL
+ */
+ protected final function _getCollectionOrderBySQL(array $orderBy, $baseTableAlias)
+ {
+ $orderBySql = '';
+ foreach ($orderBy as $fieldName => $orientation) {
+ if ( ! isset($this->_class->fieldMappings[$fieldName])) {
+ ORMException::unrecognizedField($fieldName);
+ }
+
+ $tableAlias = isset($this->_class->fieldMappings[$fieldName]['inherited']) ?
+ $this->_getSQLTableAlias($this->_class->fieldMappings[$fieldName]['inherited'])
+ : $baseTableAlias;
+
+ $columnName = $this->_class->getQuotedColumnName($fieldName, $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 = '';
+
+ // Add regular columns to select list
+ foreach ($this->_class->fieldNames as $field) {
+ if ($columnList) $columnList .= ', ';
+ $columnList .= $this->_getSelectColumnSQL($field, $this->_class);
+ }
+
+ $this->_selectColumnListSql = $columnList . $this->_getSelectJoinColumnsSQL($this->_class);
+
+ return $this->_selectColumnListSql;
+ }
+
+ /**
+ * 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)
+ {
+ if ($manyToMany['isOwningSide']) {
+ $owningAssoc = $manyToMany;
+ $joinClauses = $manyToMany['relationToTargetKeyColumns'];
+ } else {
+ $owningAssoc = $this->_em->getClassMetadata($manyToMany['targetEntity'])->associationMappings[$manyToMany['mappedBy']];
+ $joinClauses = $owningAssoc['relationToSourceKeyColumns'];
+ }
+
+ $joinTableName = $this->_class->getQuotedJoinTableName($owningAssoc, $this->_platform);
+
+ $joinSql = '';
+ foreach ($joinClauses as $joinTableColumn => $sourceColumn) {
+ if ($joinSql != '') $joinSql .= ' AND ';
+ $joinSql .= $this->_getSQLTableAlias($this->_class->name) .
+ '.' . $this->_class->getQuotedColumnName($this->_class->fieldNames[$sourceColumn], $this->_platform) . ' = '
+ . $joinTableName . '.' . $joinTableColumn;
+ }
+
+ return " INNER JOIN $joinTableName ON $joinSql";
+ }
+
+ /**
+ * 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->_class->getQuotedTableName($this->_platform),
+ $this->_class->getQuotedColumnName($this->_class->identifier[0], $this->_platform)
+ );
+ } else {
+ $columns = array_unique($columns);
+ $values = array_fill(0, count($columns), '?');
+
+ $insertSql = 'INSERT INTO ' . $this->_class->getQuotedTableName($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['targetToSourceKeyColumns'] as $sourceCol) {
+ $columns[] = $sourceCol;
+ }
+ }
+ } else if ($this->_class->generatorType != ClassMetadata::GENERATOR_TYPE_IDENTITY ||
+ $this->_class->identifier[0] != $name) {
+ $columns[] = $this->_class->getQuotedColumnName($name, $this->_platform);
+ }
+ }
+
+ 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.
+ */
+ protected function _getSelectColumnSQL($field, ClassMetadata $class)
+ {
+ $columnName = $class->columnNames[$field];
+ $sql = $this->_getSQLTableAlias($class->name) . '.' . $class->getQuotedColumnName($field, $this->_platform);
+ $columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++);
+ if ( ! isset($this->_resultColumnNames[$columnAlias])) {
+ $this->_resultColumnNames[$columnAlias] = $columnName;
+ }
+
+ return "$sql AS $columnAlias";
+ }
+
+ /**
+ * Gets the SQL snippet for all join columns of the given class that are to be
+ * placed in an SQL SELECT statement.
+ *
+ * @param $class
+ * @return string
+ * @todo Not reused... inline?
+ */
+ private function _getSelectJoinColumnsSQL(ClassMetadata $class)
+ {
+ $sql = '';
+ foreach ($class->associationMappings as $assoc) {
+ if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
+ foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
+ $columnAlias = $srcColumn . $this->_sqlAliasCounter++;
+ $sql .= ', ' . $this->_getSQLTableAlias($this->_class->name) . ".$srcColumn AS $columnAlias";
+ $resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
+ if ( ! isset($this->_resultColumnNames[$resultColumnName])) {
+ $this->_resultColumnNames[$resultColumnName] = $srcColumn;
+ }
+ }
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * 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)
+ {
+ 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;
+ $params = array_values($criteria);
+ $this->_conn->executeQuery($sql, $params);
+ }
+
+ /**
+ * Get the FROM and optionally JOIN conditions to lock the entity managed by this persister.
+ *
+ * @return string
+ */
+ protected function getLockTablesSql()
+ {
+ return 'FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' '
+ . $this->_getSQLTableAlias($this->_class->name);
+ }
+
+ /**
+ * 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 ' : '';
+
+ if (isset($this->_class->columnNames[$field])) {
+ if (isset($this->_class->fieldMappings[$field]['inherited'])) {
+ $conditionSql .= $this->_getSQLTableAlias($this->_class->fieldMappings[$field]['inherited']) . '.';
+ } else {
+ $conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.';
+ }
+ $conditionSql .= $this->_class->getQuotedColumnName($field, $this->_platform);
+ } else if (isset($this->_class->associationMappings[$field])) {
+ if (!$this->_class->associationMappings[$field]['isOwningSide']) {
+ throw ORMException::invalidFindByInverseAssociation($this->_class->name, $field);
+ }
+
+ if (isset($this->_class->associationMappings[$field]['inherited'])) {
+ $conditionSql .= $this->_getSQLTableAlias($this->_class->associationMappings[$field]['inherited']) . '.';
+ } else {
+ $conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.';
+ }
+
+
+ $conditionSql .= $this->_class->associationMappings[$field]['joinColumns'][0]['name'];
+ } else if ($assoc !== null) {
+ if ($assoc['type'] == ClassMetadata::MANY_TO_MANY) {
+ $owningAssoc = $assoc['isOwningSide'] ? $assoc : $this->_em->getClassMetadata($assoc['targetEntity'])
+ ->associationMappings[$assoc['mappedBy']];
+ $conditionSql .= $this->_class->getQuotedJoinTableName($owningAssoc, $this->_platform) . '.' . $field;
+ } else {
+ $conditionSql .= $field;
+ }
+ } else {
+ throw ORMException::unrecognizedField($field);
+ }
+ $conditionSql .= ' = ?';
+ }
+ return $conditionSql;
+ }
+
+ /**
+ * Loads a collection of entities in a one-to-many association.
+ *
+ * @param OneToManyMapping $assoc
+ * @param array $criteria The criteria by which to select the entities.
+ * @param PersistentCollection The collection to load/fill.
+ */
+ public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll)
+ {
+ $criteria = array();
+ $owningAssoc = $this->_class->associationMappings[$assoc['mappedBy']];
+ $sourceClass = $this->_em->getClassMetadata($assoc['sourceEntity']);
+ foreach ($owningAssoc['targetToSourceKeyColumns'] as $sourceKeyColumn => $targetKeyColumn) {
+ $criteria[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
+ }
+
+ $sql = $this->_getSelectEntitiesSQL($criteria, $assoc);
+ $params = array_values($criteria);
+ $stmt = $this->_conn->executeQuery($sql, $params);
+ while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) {
+ $coll->hydrateAdd($this->_createEntity($result));
+ }
+ $stmt->closeCursor();
+ }
+
+ /**
+ * 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)
+ {
+ $criteria = $this->_class->getIdentifierValues($entity);
+ $sql = 'SELECT 1 FROM ' . $this->_class->getQuotedTableName($this->_platform)
+ . ' ' . $this->_getSQLTableAlias($this->_class->name)
+ . ' WHERE ' . $this->_getSelectConditionSQL($criteria);
+
+ return (bool) $this->_conn->fetchColumn($sql, array_values($criteria));
+ }
+
+ //TODO
+ /*protected function _getOneToOneEagerFetchSQL()
+ {
+
+ }*/
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Persisters;
+
+use Doctrine\ORM\ORMException,
+ Doctrine\ORM\Mapping\ClassMetadata;
+
+/**
+ * The joined subclass persister maps a single entity instance to several tables in the
+ * database as it is defined by the <tt>Class Table Inheritance</tt> strategy.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @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()
+ {
+ if ($this->_class->name == $this->_class->rootEntityName) {
+ return $this->_class->table['name'];
+ } else {
+ return $this->_em->getClassMetadata($this->_class->rootEntityName)->table['name'];
+ }
+ }
+
+ /**
+ * 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])) {
+ 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;
+ }
+ $this->_owningTableMap[$fieldName] = $cm->table['name'];
+ $this->_quotedTableMap[$cm->table['name']] = $cm->getQuotedTableName($this->_platform);
+ }
+
+ return $this->_owningTableMap[$fieldName];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function executeInserts()
+ {
+ if ( ! $this->_queuedInserts) {
+ return;
+ }
+
+ if ($this->_class->isVersioned) {
+ $versionedClass = $this->_getVersionedClassMetadata();
+ }
+
+ $postInsertIds = array();
+ $idGen = $this->_class->idGenerator;
+ $isPostInsertId = $idGen->isPostInsertGenerator();
+
+ // Prepare statement for the root table
+ $rootClass = $this->_class->name == $this->_class->rootEntityName ?
+ $this->_class : $this->_em->getClassMetadata($this->_class->rootEntityName);
+ $rootPersister = $this->_em->getUnitOfWork()->getEntityPersister($rootClass->name);
+ $rootTableName = $rootClass->table['name'];
+ $rootTableStmt = $this->_conn->prepare($rootPersister->_getInsertSQL());
+
+ // Prepare statements for sub tables.
+ $subTableStmts = array();
+ if ($rootClass !== $this->_class) {
+ $subTableStmts[$this->_class->table['name']] = $this->_conn->prepare($this->_getInsertSQL());
+ }
+ foreach ($this->_class->parentClasses as $parentClassName) {
+ $parentClass = $this->_em->getClassMetadata($parentClassName);
+ $parentTableName = $parentClass->table['name'];
+ 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 $idVal) {
+ $stmt->bindValue($paramIndex++, $idVal);
+ }
+ foreach ($data as $columnName => $value) {
+ $stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]);
+ }
+ $stmt->execute();
+ }
+ }
+
+ $rootTableStmt->closeCursor();
+ foreach ($subTableStmts as $stmt) {
+ $stmt->closeCursor();
+ }
+
+ if (isset($versionedClass)) {
+ $this->_assignDefaultVersionValue($versionedClass, $entity, $id);
+ }
+
+ $this->_queuedInserts = array();
+
+ return $postInsertIds;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function update($entity)
+ {
+ $updateData = $this->_prepareUpdateData($entity);
+
+ if ($isVersioned = $this->_class->isVersioned) {
+ $versionedClass = $this->_getVersionedClassMetadata();
+ $versionedTable = $versionedClass->table['name'];
+ }
+
+ 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 && ! isset($updateData[$versionedTable])) {
+ $this->_updateTable($entity, $versionedClass->getQuotedTableName($this->_platform), array(), true);
+
+ $id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity);
+ $this->_assignDefaultVersionValue($this->_class, $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->_em->getClassMetadata($this->_class->rootEntityName)
+ ->getQuotedTableName($this->_platform), $id);
+ } else {
+ // Delete from all tables individually, starting from this class' table up to the root table.
+ $this->_conn->delete($this->_class->getQuotedTableName($this->_platform), $id);
+ foreach ($this->_class->parentClasses as $parentClass) {
+ $this->_conn->delete($this->_em->getClassMetadata($parentClass)->getQuotedTableName($this->_platform), $id);
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0)
+ {
+ $idColumns = $this->_class->getIdentifierColumnNames();
+ $baseTableAlias = $this->_getSQLTableAlias($this->_class->name);
+
+ // Create the column list fragment only once
+ if ($this->_selectColumnListSql === null) {
+ // 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) {
+ $columnAlias = $srcColumn . $this->_sqlAliasCounter++;
+ $columnList .= ", $tableAlias.$srcColumn AS $columnAlias";
+ $resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
+ if ( ! isset($this->_resultColumnNames[$resultColumnName])) {
+ $this->_resultColumnNames[$resultColumnName] = $srcColumn;
+ }
+ }
+ }
+ }
+
+ // Add discriminator column (DO NOT ALIAS, see AbstractEntityInheritancePersister#_processSQLResult).
+ $discrColumn = $this->_class->discriminatorColumn['name'];
+ if ($this->_class->rootEntityName == $this->_class->name) {
+ $columnList .= ", $baseTableAlias.$discrColumn";
+ } else {
+ $columnList .= ', ' . $this->_getSQLTableAlias($this->_class->rootEntityName)
+ . ".$discrColumn";
+ }
+
+ $resultColumnName = $this->_platform->getSQLResultCasing($discrColumn);
+ $this->_resultColumnNames[$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 ' . $parentClass->getQuotedTableName($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) {
+ $columnAlias = $srcColumn . $this->_sqlAliasCounter++;
+ $columnList .= ', ' . $tableAlias . ".$srcColumn AS $columnAlias";
+ $resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
+ if ( ! isset($this->_resultColumnNames[$resultColumnName])) {
+ $this->_resultColumnNames[$resultColumnName] = $srcColumn;
+ }
+ }
+ }
+ }
+ }
+
+ // Add LEFT JOIN
+ $joinSql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($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 = $this->_getSelectConditionSQL($criteria, $assoc);
+
+ $orderBySql = '';
+ if ($assoc != null && isset($assoc['orderBy'])) {
+ $orderBySql = $this->_getCollectionOrderBySQL($assoc['orderBy'], $baseTableAlias);
+ }
+
+ if ($this->_selectColumnListSql === null) {
+ $this->_selectColumnListSql = $columnList;
+ }
+
+ return 'SELECT ' . $this->_selectColumnListSql
+ . ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $baseTableAlias
+ . $joinSql
+ . ($conditionSql != '' ? ' WHERE ' . $conditionSql : '') . $orderBySql;
+ }
+
+ /**
+ * Get the FROM and optionally JOIN conditions to lock the entity managed by this persister.
+ *
+ * @return string
+ */
+ public function getLockTablesSql()
+ {
+ $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 ' . $parentClass->getQuotedTableName($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->_class->getQuotedTableName($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->_class->getQuotedColumnName($name, $this->_platform);
+ }
+ }
+
+ // Add discriminator column if it is the topmost class.
+ if ($this->_class->name == $this->_class->rootEntityName) {
+ $columns[] = $this->_class->discriminatorColumn['name'];
+ }
+
+ return $columns;
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Persisters;
+
+use Doctrine\ORM\PersistentCollection;
+
+/**
+ * Persister for many-to-many collections.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @since 2.0
+ */
+class ManyToManyPersister extends AbstractCollectionPersister
+{
+ /**
+ * {@inheritdoc}
+ *
+ * @override
+ */
+ protected function _getDeleteRowSQL(PersistentCollection $coll)
+ {
+ $mapping = $coll->getMapping();
+ $joinTable = $mapping['joinTable'];
+ $columns = $mapping['joinTableColumns'];
+ return 'DELETE FROM ' . $joinTable['name'] . ' 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)
+ {
+ $mapping = $coll->getMapping();
+ $joinTable = $mapping['joinTable'];
+ $columns = $mapping['joinTableColumns'];
+ return 'INSERT INTO ' . $joinTable['name'] . ' (' . 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) {
+ if (isset($mapping['relationToSourceKeyColumns'][$joinTableColumn])) {
+ if ($isComposite) {
+ $params[] = $identifier1[$class1->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]];
+ } else {
+ $params[] = array_pop($identifier1);
+ }
+ } else {
+ if ($isComposite) {
+ $params[] = $identifier2[$class2->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]];
+ } else {
+ $params[] = array_pop($identifier2);
+ }
+ }
+ }
+
+ return $params;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @override
+ */
+ protected function _getDeleteSQL(PersistentCollection $coll)
+ {
+ $mapping = $coll->getMapping();
+ $joinTable = $mapping['joinTable'];
+ $whereClause = '';
+ foreach ($mapping['relationToSourceKeyColumns'] as $relationColumn => $srcColumn) {
+ if ($whereClause !== '') $whereClause .= ' AND ';
+ $whereClause .= "$relationColumn = ?";
+ }
+ return 'DELETE FROM ' . $joinTable['name'] . ' WHERE ' . $whereClause;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @override
+ * @internal Order of the parameters must be the same as the order of the columns in
+ * _getDeleteSql.
+ */
+ protected function _getDeleteSQLParameters(PersistentCollection $coll)
+ {
+ $params = array();
+ $mapping = $coll->getMapping();
+ $identifier = $this->_uow->getEntityIdentifier($coll->getOwner());
+ if (count($mapping['relationToSourceKeyColumns']) > 1) {
+ $sourceClass = $this->_em->getClassMetadata(get_class($mapping->getOwner()));
+ foreach ($mapping['relationToSourceKeyColumns'] as $relColumn => $srcColumn) {
+ $params[] = $identifier[$sourceClass->fieldNames[$srcColumn]];
+ }
+ } else {
+ $params[] = array_pop($identifier);
+ }
+
+ return $params;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Persisters;
+
+use Doctrine\ORM\PersistentCollection;
+
+/**
+ * Persister for one-to-many collections.
+ *
+ * IMPORTANT:
+ * This persister is only used for uni-directional one-to-many mappings on a foreign key
+ * (which are not yet supported). So currently this persister is not used.
+ *
+ * @since 2.0
+ * @author Roman Borschel <roman@code-factory.org>
+ * @todo Remove
+ */
+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();
+ $targetClass = $this->_em->getClassMetadata($mapping->getTargetEntityName());
+ $table = $targetClass->getTableName();
+
+ $ownerMapping = $targetClass->getAssociationMapping($mapping['mappedBy']);
+
+ $setClause = '';
+ foreach ($ownerMapping->sourceToTargetKeyColumns as $sourceCol => $targetCol) {
+ if ($setClause != '') $setClause .= ', ';
+ $setClause .= "$sourceCol = NULL";
+ }
+
+ $whereClause = '';
+ foreach ($targetClass->getIdentifierColumnNames() as $idColumn) {
+ if ($whereClause != '') $whereClause .= ' AND ';
+ $whereClause .= "$idColumn = ?";
+ }
+
+ return array("UPDATE $table SET $setClause WHERE $whereClause", $this->_uow->getEntityIdentifier($element));
+ }
+
+ protected function _getInsertRowSQL(PersistentCollection $coll)
+ {
+ return "UPDATE xxx SET foreign_key = yyy WHERE foreign_key = zzz";
+ }
+
+ /* 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)
+ {}
+
+ /**
+ * 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)
+ {}
+
+ /**
+ * Gets the SQL parameters for the corresponding SQL statement to delete the given
+ * element from the given collection.
+ *
+ * @param PersistentCollection $coll
+ * @param mixed $element
+ */
+ protected function _getDeleteRowSQLParameters(PersistentCollection $coll, $element)
+ {}
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Persisters;
+
+use Doctrine\ORM\Mapping\ClassMetadata;
+
+/**
+ * Persister for entities that participate in a hierarchy mapped with the
+ * SINGLE_TABLE strategy.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @since 2.0
+ * @link http://martinfowler.com/eaaCatalog/singleTableInheritance.html
+ */
+class SingleTablePersister extends AbstractEntityInheritancePersister
+{
+ /** {@inheritdoc} */
+ protected function _getDiscriminatorColumnTableName()
+ {
+ return $this->_class->table['name'];
+ }
+
+ /** {@inheritdoc} */
+ protected function _getSelectColumnListSQL()
+ {
+ $columnList = parent::_getSelectColumnListSQL();
+
+ // Append discriminator column
+ $discrColumn = $this->_class->discriminatorColumn['name'];
+ $columnList .= ", $discrColumn";
+ $rootClass = $this->_em->getClassMetadata($this->_class->rootEntityName);
+ $tableAlias = $this->_getSQLTableAlias($rootClass->name);
+ $resultColumnName = $this->_platform->getSQLResultCasing($discrColumn);
+ $this->_resultColumnNames[$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) {
+ $columnAlias = $srcColumn . $this->_sqlAliasCounter++;
+ $columnList .= ', ' . $tableAlias . ".$srcColumn AS $columnAlias";
+ $resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
+ if ( ! isset($this->_resultColumnNames[$resultColumnName])) {
+ $this->_resultColumnNames[$resultColumnName] = $srcColumn;
+ }
+ }
+ }
+ }
+ }
+
+ return $columnList;
+ }
+
+ /** {@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)
+ {
+ return parent::_getSQLTableAlias($this->_class->rootEntityName);
+ }
+
+ /** {@inheritdoc} */
+ protected function _getSelectConditionSQL(array $criteria, $assoc = null)
+ {
+ $conditionSql = parent::_getSelectConditionSQL($criteria, $assoc);
+
+ // Append discriminator condition
+ if ($conditionSql) $conditionSql .= ' AND ';
+ $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]);
+ }
+ $conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.'
+ . $this->_class->discriminatorColumn['name']
+ . ' IN (' . implode(', ', $values) . ')';
+
+ return $conditionSql;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace Doctrine\ORM\Persisters;
+
+class UnionSubclassPersister extends BasicEntityPersister
+{
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\ORM;
+
+/**
+ * Pessimistic Lock Exception
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 1.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class PessimisticLockException extends ORMException
+{
+ public static function lockFailed()
+ {
+ return new self("The pessimistic lock failed.");
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Proxy;
+
+/**
+ * Interface for proxy classes.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @since 2.0
+ */
+interface Proxy {}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\ORM\Proxy;
+
+/**
+ * ORM Proxy Exception
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 1.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+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 proxyNamespaceRequired() {
+ return new self("You must configure a proxy namespace. See docs for details");
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Proxy;
+
+use Doctrine\ORM\EntityManager,
+ Doctrine\ORM\Mapping\ClassMetadata,
+ Doctrine\ORM\Mapping\AssociationMapping;
+
+/**
+ * This factory is used to create proxy objects for entities at runtime.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
+ * @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;
+
+ /**
+ * Initializes a new instance of the <tt>ProxyFactory</tt> class that is
+ * connected to the given <tt>EntityManager</tt>.
+ *
+ * @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)
+ {
+ $proxyClassName = str_replace('\\', '', $className) . 'Proxy';
+ $fqn = $this->_proxyNamespace . '\\' . $proxyClassName;
+
+ if (! class_exists($fqn, false)) {
+ $fileName = $this->_proxyDir . DIRECTORY_SEPARATOR . $proxyClassName . '.php';
+ if ($this->_autoGenerate) {
+ $this->_generateProxyClass($this->_em->getClassMetadata($className), $proxyClassName, $fileName, self::$_proxyClassTemplate);
+ }
+ require $fileName;
+ }
+
+ if ( ! $this->_em->getMetadataFactory()->hasMetadataFor($fqn)) {
+ $this->_em->getMetadataFactory()->setMetadataFor($fqn, $this->_em->getClassMetadata($className));
+ }
+
+ $entityPersister = $this->_em->getUnitOfWork()->getEntityPersister($className);
+
+ return new $fqn($entityPersister, $identifier);
+ }
+
+ /**
+ * 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.
+ */
+ public function generateProxyClasses(array $classes, $toDir = null)
+ {
+ $proxyDir = $toDir ?: $this->_proxyDir;
+ $proxyDir = rtrim($proxyDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
+ foreach ($classes as $class) {
+ /* @var $class ClassMetadata */
+ if ($class->isMappedSuperclass) {
+ continue;
+ }
+
+ $proxyClassName = str_replace('\\', '', $class->name) . 'Proxy';
+ $proxyFileName = $proxyDir . $proxyClassName . '.php';
+ $this->_generateProxyClass($class, $proxyClassName, $proxyFileName, self::$_proxyClassTemplate);
+ }
+ }
+
+ /**
+ * Generates a proxy class file.
+ *
+ * @param $class
+ * @param $originalClassName
+ * @param $proxyClassName
+ * @param $file The path of the file to write to.
+ */
+ private function _generateProxyClass($class, $proxyClassName, $fileName, $file)
+ {
+ $methods = $this->_generateMethods($class);
+ $sleepImpl = $this->_generateSleep($class);
+
+ $placeholders = array(
+ '<namespace>',
+ '<proxyClassName>', '<className>',
+ '<methods>', '<sleepImpl>'
+ );
+
+ if(substr($class->name, 0, 1) == "\\") {
+ $className = substr($class->name, 1);
+ } else {
+ $className = $class->name;
+ }
+
+ $replacements = array(
+ $this->_proxyNamespace,
+ $proxyClassName, $className,
+ $methods, $sleepImpl
+ );
+
+ $file = str_replace($placeholders, $replacements, $file);
+
+ file_put_contents($fileName, $file, LOCK_EX);
+ }
+
+ /**
+ * Generates the methods of a proxy class.
+ *
+ * @param ClassMetadata $class
+ * @return string The code of the generated methods.
+ */
+ private function _generateMethods(ClassMetadata $class)
+ {
+ $methods = '';
+
+ foreach ($class->reflClass->getMethods() as $method) {
+ /* @var $method ReflectionMethod */
+ if ($method->isConstructor() || strtolower($method->getName()) == "__sleep") {
+ continue;
+ }
+
+ if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) {
+ $methods .= PHP_EOL . ' 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 .= PHP_EOL . ' {' . PHP_EOL;
+ $methods .= ' $this->_load();' . PHP_EOL;
+ $methods .= ' return parent::' . $method->getName() . '(' . $argumentString . ');';
+ $methods .= PHP_EOL . ' }' . PHP_EOL;
+ }
+ }
+
+ return $methods;
+ }
+
+ /**
+ * 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 =
+'<?php
+
+namespace <namespace>;
+
+/**
+ * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
+ */
+class <proxyClassName> extends \<className> implements \Doctrine\ORM\Proxy\Proxy
+{
+ private $_entityPersister;
+ private $_identifier;
+ public $__isInitialized__ = false;
+ public function __construct($entityPersister, $identifier)
+ {
+ $this->_entityPersister = $entityPersister;
+ $this->_identifier = $identifier;
+ }
+ private function _load()
+ {
+ if (!$this->__isInitialized__ && $this->_entityPersister) {
+ $this->__isInitialized__ = true;
+ if ($this->_entityPersister->load($this->_identifier, $this) === null) {
+ throw new \Doctrine\ORM\EntityNotFoundException();
+ }
+ unset($this->_entityPersister, $this->_identifier);
+ }
+ }
+
+ <methods>
+
+ public function __sleep()
+ {
+ <sleepImpl>
+ }
+}';
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM;
+
+use Doctrine\DBAL\LockMode,
+ Doctrine\ORM\Query\Parser,
+ Doctrine\ORM\Query\QueryException;
+
+/**
+ * A Query object represents a DQL query.
+ *
+ * @since 1.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+final class Query extends AbstractQuery
+{
+ /* Query STATES */
+ /**
+ * 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';
+ /**
+ * 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;
+
+ // End of Caching Stuff
+
+ /**
+ * 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()
+ {
+ if ($this->_state === self::STATE_CLEAN) {
+ return $this->_parserResult;
+ }
+
+ // Check query cache.
+ if ($this->_useQueryCache && ($queryCache = $this->getQueryCacheDriver())) {
+ $hash = $this->_getQueryCacheId();
+ $cached = $this->_expireQueryCache ? false : $queryCache->fetch($hash);
+ if ($cached === false) {
+ // Cache miss.
+ $parser = new Parser($this);
+ $this->_parserResult = $parser->parse();
+ $queryCache->save($hash, $this->_parserResult, $this->_queryCacheTTL);
+ } else {
+ // Cache hit.
+ $this->_parserResult = $cached;
+ }
+ } else {
+ $parser = new Parser($this);
+ $this->_parserResult = $parser->parse();
+ }
+ $this->_state = self::STATE_CLEAN;
+
+ return $this->_parserResult;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _doExecute()
+ {
+ $executor = $this->_parse()->getSqlExecutor();
+
+ // Prepare parameters
+ $paramMappings = $this->_parserResult->getParameterMappings();
+
+ if (count($paramMappings) != count($this->_params)) {
+ throw QueryException::invalidParameterNumber();
+ }
+
+ $sqlParams = $types = array();
+
+ foreach ($this->_params as $key => $value) {
+ if ( ! isset($paramMappings[$key])) {
+ throw QueryException::unknownParameter($key);
+ }
+ if (isset($this->_paramTypes[$key])) {
+ foreach ($paramMappings[$key] as $position) {
+ $types[$position] = $this->_paramTypes[$key];
+ }
+ }
+
+ if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($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);
+ }
+ $sqlPositions = $paramMappings[$key];
+ $sqlParams += array_combine((array)$sqlPositions, $idValues);
+ } else {
+ foreach ($paramMappings[$key] as $position) {
+ $sqlParams[$position] = $value;
+ }
+ }
+ }
+
+ if ($sqlParams) {
+ ksort($sqlParams);
+ $sqlParams = array_values($sqlParams);
+ }
+
+ if ($this->_resultSetMapping === null) {
+ $this->_resultSetMapping = $this->_parserResult->getResultSetMapping();
+ }
+
+ return $executor->execute($this->_em->getConnection(), $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;
+ } else {
+ 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 array $params The query parameters.
+ * @param integer $hydrationMode The hydration mode to use.
+ * @return IterableResult
+ */
+ public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT)
+ {
+ $this->setHint(self::HINT_INTERNAL_ITERATION, true);
+ return parent::iterate($params, $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 ($lockMode == LockMode::PESSIMISTIC_READ || $lockMode == 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) .
+ '&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;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace Doctrine\ORM\Query\AST;
+
+use Doctrine\ORM\Query\QueryException;
+
+class ASTException extends QueryException
+{
+ public static function noDispatchForNode($node)
+ {
+ return new self("Double-dispatch for node " . get_class($node) . " is not supported.");
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * Description of AggregateExpression
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class ArithmeticTerm extends Node
+{
+ public $arithmeticFactors;
+
+ public function __construct(array $arithmeticFactors)
+ {
+ $this->arithmeticFactors = $arithmeticFactors;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkArithmeticTerm($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * Description of BetweenExpression
+ *
+ @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) |
+ * StringExpression ComparisonOperator (StringExpression | QuantifiedExpression) |
+ * BooleanExpression ("=" | "<>" | "!=") (BooleanExpression | QuantifiedExpression) |
+ * EnumExpression ("=" | "<>" | "!=") (EnumExpression | QuantifiedExpression) |
+ * DatetimeExpression ComparisonOperator (DatetimeExpression | QuantifiedExpression) |
+ * EntityExpression ("=" | "<>") (EntityExpression | QuantifiedExpression)
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class ConditionalExpression extends Node
+{
+ public $conditionalTerms = array();
+
+ public function __construct(array $conditionalTerms)
+ {
+ $this->conditionalTerms = $conditionalTerms;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkConditionalExpression($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * ConditionalFactor ::= ["NOT"] ConditionalPrimary
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class ConditionalFactor extends Node
+{
+ public $not = false;
+ public $conditionalPrimary;
+
+ public function __construct($conditionalPrimary)
+ {
+ $this->conditionalPrimary = $conditionalPrimary;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkConditionalFactor($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}*
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class ConditionalTerm extends Node
+{
+ public $conditionalFactors = array();
+
+ public function __construct(array $conditionalFactors)
+ {
+ $this->conditionalFactors = $conditionalFactors;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkConditionalTerm($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName [["AS"] AliasIdentificationVariable]
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class DeleteClause extends Node
+{
+ public $abstractSchemaName;
+ public $aliasIdentificationVariable;
+
+ public function __construct($abstractSchemaName)
+ {
+ $this->abstractSchemaName = $abstractSchemaName;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkDeleteClause($this);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.phpdoctrine.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * DeleteStatement = DeleteClause [WhereClause]
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class DeleteStatement extends Node
+{
+ public $deleteClause;
+ public $whereClause;
+
+ public function __construct($deleteClause)
+ {
+ $this->deleteClause = $deleteClause;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkDeleteStatement($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class EmptyCollectionComparisonExpression extends Node
+{
+ public $expression;
+ public $not;
+
+ public function __construct($expression)
+ {
+ $this->expression = $expression;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkEmptyCollectionComparisonExpression($this);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class ExistsExpression extends Node
+{
+ public $not;
+ public $subselect;
+
+ public function __construct($subselect)
+ {
+ $this->subselect = $subselect;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkExistsExpression($this);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class FromClause extends Node
+{
+ public $identificationVariableDeclarations = array();
+
+ public function __construct(array $identificationVariableDeclarations)
+ {
+ $this->identificationVariableDeclarations = $identificationVariableDeclarations;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkFromClause($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST\Functions;
+
+use Doctrine\ORM\Query\Lexer;
+
+/**
+ * "ABS" "(" SimpleArithmeticExpression ")"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+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);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST\Functions;
+
+use Doctrine\ORM\Query\Lexer;
+
+/**
+ * "CONCAT" "(" StringPrimary "," StringPrimary ")"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+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);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST\Functions;
+
+use Doctrine\ORM\Query\Lexer;
+
+/**
+ * "CURRENT_DATE"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+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);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST\Functions;
+
+use Doctrine\ORM\Query\Lexer;
+
+/**
+ * "CURRENT_TIME"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+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);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST\Functions;
+
+use Doctrine\ORM\Query\Lexer;
+
+/**
+ * "CURRENT_TIMESTAMP"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+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);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST\Functions;
+
+use Doctrine\ORM\Query\AST\Node;
+
+/**
+ * Abtract Function Node.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+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);
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST\Functions;
+
+use Doctrine\ORM\Query\Lexer;
+
+/**
+ * "LENGTH" "(" StringPrimary ")"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+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);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST\Functions;
+
+use Doctrine\ORM\Query\Lexer;
+
+/**
+ * "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+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);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST\Functions;
+
+use Doctrine\ORM\Query\Lexer;
+
+/**
+ * "LOWER" "(" StringPrimary ")"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+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);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST\Functions;
+
+use Doctrine\ORM\Query\Lexer;
+
+/**
+ * "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+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_MOD);
+ $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);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST\Functions;
+
+use Doctrine\ORM\Query\Lexer;
+
+/**
+ * "SIZE" "(" CollectionValuedPathExpression ")"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+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->getConnection()->getDatabasePlatform();
+ $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->table['name']);
+ $sourceTableAlias = $sqlWalker->getSqlTableAlias($class->table['name'], $dqlAlias);
+
+ $sql .= $targetClass->getQuotedTableName($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 . '.' . $class->getQuotedColumnName($class->fieldNames[$targetColumn], $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->table['name'], $dqlAlias);
+
+ // join to target table
+ $sql .= $targetClass->getQuotedJoinTableName($owningAssoc, $platform) . ' ' . $joinTableAlias . ' WHERE ';
+
+ $joinColumns = $assoc['isOwningSide']
+ ? $joinTable['joinColumns']
+ : $joinTable['inverseJoinColumns'];
+
+ $first = true;
+
+ foreach ($joinColumns as $joinColumn) {
+ if ($first) $first = false; else $sql .= ' AND ';
+
+ $sourceColumnName = $class->getQuotedColumnName(
+ $class->fieldNames[$joinColumn['referencedColumnName']], $platform
+ );
+
+ $sql .= $joinTableAlias . '.' . $joinColumn['name']
+ . ' = '
+ . $sourceTableAlias . '.' . $sourceColumnName;
+ }
+ }
+
+ return '(' . $sql . ')';
+ }
+
+ /**
+ * @override
+ */
+ public function parse(\Doctrine\ORM\Query\Parser $parser)
+ {
+ $lexer = $parser->getLexer();
+
+ $parser->match(Lexer::T_SIZE);
+ $parser->match(Lexer::T_OPEN_PARENTHESIS);
+
+ $this->collectionPathExpression = $parser->CollectionValuedPathExpression();
+
+ $parser->match(Lexer::T_CLOSE_PARENTHESIS);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST\Functions;
+
+use Doctrine\ORM\Query\Lexer;
+
+/**
+ * "SQRT" "(" SimpleArithmeticExpression ")"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class SqrtFunction extends FunctionNode
+{
+ public $simpleArithmeticExpression;
+
+ /**
+ * @override
+ */
+ public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
+ {
+ //TODO: Use platform to get SQL
+ return 'SQRT(' . $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);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST\Functions;
+
+use Doctrine\ORM\Query\Lexer;
+
+/**
+ * "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+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);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST\Functions;
+
+use Doctrine\ORM\Query\Lexer;
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+
+/**
+ * "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+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);
+ }
+
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST\Functions;
+
+use Doctrine\ORM\Query\Lexer;
+
+/**
+ * "UPPER" "(" StringPrimary ")"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+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);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * Description of GroupByClause
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class GroupByClause extends Node
+{
+ public $groupByItems = array();
+
+ public function __construct(array $groupByItems)
+ {
+ $this->groupByItems = $groupByItems;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkGroupByClause($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * Description of HavingClause
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class HavingClause extends Node
+{
+ public $conditionalExpression;
+
+ public function __construct($conditionalExpression)
+ {
+ $this->conditionalExpression = $conditionalExpression;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkHavingClause($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}*
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class IdentificationVariableDeclaration extends Node
+{
+ public $rangeVariableDeclaration = null;
+ public $indexBy = null;
+ public $joinVariableDeclarations = array();
+
+ public function __construct($rangeVariableDecl, $indexBy, array $joinVariableDecls)
+ {
+ $this->rangeVariableDeclaration = $rangeVariableDecl;
+ $this->indexBy = $indexBy;
+ $this->joinVariableDeclarations = $joinVariableDecls;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkIdentificationVariableDeclaration($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * InExpression ::= StateFieldPathExpression ["NOT"] "IN" "(" (Literal {"," Literal}* | Subselect) ")"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class InExpression extends Node
+{
+ public $not;
+ public $pathExpression;
+ public $literals = array();
+ public $subselect;
+
+ public function __construct($pathExpression)
+ {
+ $this->pathExpression = $pathExpression;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkInExpression($this);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class IndexBy extends Node
+{
+ public $simpleStateFieldPathExpression = null;
+
+ public function __construct($simpleStateFieldPathExpression)
+ {
+ $this->simpleStateFieldPathExpression = $simpleStateFieldPathExpression;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkIndexBy($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * Description of InputParameter
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (AbstractSchemaName | InputParameter)
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.phpdoctrine.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression
+ * ["AS"] AliasIdentificationVariable [("ON" | "WITH") ConditionalExpression]
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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 $joinAssociationPathExpression = null;
+ public $aliasIdentificationVariable = null;
+ public $conditionalExpression = null;
+
+ public function __construct($joinType, $joinAssocPathExpr, $aliasIdentVar)
+ {
+ $this->joinType = $joinType;
+ $this->joinAssociationPathExpression = $joinAssocPathExpr;
+ $this->aliasIdentificationVariable = $aliasIdentVar;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkJoin($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * JoinAssociationPathExpression ::= IdentificationVariable "." (SingleValuedAssociationField | CollectionValuedAssociationField)
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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->walkJoinPathExpression($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.phpdoctrine.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * JoinVariableDeclaration ::= Join [IndexBy]
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class JoinVariableDeclaration extends Node
+{
+ public $join = null;
+ public $indexBy = null;
+
+ public function __construct($join, $indexBy)
+ {
+ $this->join = $join;
+ $this->indexBy = $indexBy;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkJoinVariableDeclaration($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * LikeExpression ::= StringExpression ["NOT"] "LIKE" string ["ESCAPE" char]
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+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);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * Abstract class of an AST node
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class NullComparisonExpression extends Node
+{
+ public $not;
+ public $expression;
+
+ public function __construct($expression)
+ {
+ $this->expression = $expression;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkNullComparisonExpression($this);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class OrderByClause extends Node
+{
+ public $orderByItems = array();
+
+ public function __construct(array $orderByItems)
+ {
+ $this->orderByItems = $orderByItems;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkOrderByClause($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"]
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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);
+ }
+}
--- /dev/null
+<?php
+
+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;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\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 <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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);
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * SelectClause = "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.phpdoctrine.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * SelectExpression ::= IdentificationVariable ["." "*"] | StateFieldPathExpression |
+ * (AggregateExpression | "(" Subselect ")") [["AS"] FieldAliasIdentificationVariable]
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class SelectExpression extends Node
+{
+ public $expression;
+ public $fieldIdentificationVariable;
+
+ public function __construct($expression, $fieldIdentificationVariable)
+ {
+ $this->expression = $expression;
+ $this->fieldIdentificationVariable = $fieldIdentificationVariable;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkSelectExpression($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.phpdoctrine.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * SelectStatement = SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class SimpleArithmeticExpression extends Node
+{
+ public $arithmeticTerms = array();
+
+ public function __construct(array $arithmeticTerms)
+ {
+ $this->arithmeticTerms = $arithmeticTerms;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkSimpleArithmeticExpression($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * SimpleSelectExpression ::= StateFieldPathExpression | IdentificationVariable
+ * | (AggregateExpression [["AS"] FieldAliasIdentificationVariable])
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class SimpleSelectExpression extends Node
+{
+ public $expression;
+ public $fieldIdentificationVariable;
+
+ public function __construct($expression)
+ {
+ $this->expression = $expression;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkSimpleSelectExpression($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class SubselectFromClause extends Node
+{
+ public $identificationVariableDeclarations = array();
+
+ public function __construct(array $identificationVariableDeclarations)
+ {
+ $this->identificationVariableDeclarations = $identificationVariableDeclarations;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkSubselectFromClause($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * UpdateClause ::= "UPDATE" AbstractSchemaName [["AS"] AliasIdentificationVariable] "SET" UpdateItem {"," UpdateItem}*
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * UpdateItem ::= [IdentificationVariable "."] {StateField | SingleValuedAssociationField} "=" NewValue
+ * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary |
+ * EnumPrimary | SimpleEntityExpression | "NULL"
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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);
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * UpdateStatement = UpdateClause [WhereClause]
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class UpdateStatement extends Node
+{
+ public $updateClause;
+ public $whereClause;
+
+ public function __construct($updateClause)
+ {
+ $this->updateClause = $updateClause;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkUpdateStatement($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\AST;
+
+/**
+ * WhereClause ::= "WHERE" ConditionalExpression
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class WhereClause extends Node
+{
+ public $conditionalExpression;
+
+ public function __construct($conditionalExpression)
+ {
+ $this->conditionalExpression = $conditionalExpression;
+ }
+
+ public function dispatch($sqlWalker)
+ {
+ return $sqlWalker->walkWhereClause($this);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\Exec;
+
+use Doctrine\DBAL\Connection;
+
+/**
+ * Base class for SQL statement executors.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link http://www.doctrine-project.org
+ * @since 2.0
+ * @todo Rename: AbstractSQLExecutor
+ */
+abstract class AbstractSqlExecutor
+{
+ protected $_sqlStatements;
+
+ /**
+ * Gets the SQL statements that are executed by the executor.
+ *
+ * @return array All the SQL update statements.
+ */
+ public function getSqlStatements()
+ {
+ return $this->_sqlStatements;
+ }
+
+ /**
+ * Executes all sql statements.
+ *
+ * @param Doctrine\DBAL\Connection $conn The database connection that is used to execute the queries.
+ * @param array $params The parameters.
+ * @return Doctrine\DBAL\Driver\Statement
+ */
+ abstract public function execute(Connection $conn, array $params, array $types);
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\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 <roman@code-factory.org>
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link http://www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ */
+class MultiTableDeleteExecutor extends AbstractSqlExecutor
+{
+ private $_createTempTableSql;
+ private $_dropTempTableSql;
+ private $_insertSql;
+
+ /**
+ * Initializes a new <tt>MultiTableDeleteExecutor</tt>.
+ *
+ * @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();
+
+ $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()
+ $this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')'
+ . ' SELECT t0.' . implode(', t0.', $idColumnNames);
+ $sqlWalker->setSqlTableAlias($primaryClass->table['name'] . $primaryDqlAlias, 't0');
+ $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 = $em->getClassMetadata($className)->getQuotedTableName($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 = 'DROP TABLE ' . $tempTable;
+ }
+
+ /**
+ * Executes all SQL statements.
+ *
+ * @param Doctrine\DBAL\Connection $conn The database connection that is used to execute the queries.
+ * @param array $params The parameters.
+ * @override
+ */
+ public function execute(Connection $conn, array $params, array $types)
+ {
+ $numDeleted = 0;
+
+ // Create temporary id table
+ $conn->executeUpdate($this->_createTempTableSql);
+
+ // Insert identifiers
+ $numDeleted = $conn->executeUpdate($this->_insertSql, $params, $types);
+
+ // Execute DELETE statements
+ foreach ($this->_sqlStatements as $sql) {
+ $conn->executeUpdate($sql);
+ }
+
+ // Drop temporary table
+ $conn->executeUpdate($this->_dropTempTableSql);
+
+ return $numDeleted;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\Exec;
+
+use Doctrine\DBAL\Connection,
+ Doctrine\DBAL\Types\Type,
+ Doctrine\ORM\Query\AST;
+
+/**
+ * Executes the SQL statements for bulk DQL UPDATE statements on classes in
+ * Class Table Inheritance (JOINED).
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @since 2.0
+ */
+class MultiTableUpdateExecutor extends AbstractSqlExecutor
+{
+ private $_createTempTableSql;
+ private $_dropTempTableSql;
+ private $_insertSql;
+ private $_sqlParameters = array();
+ private $_numParametersInUpdateClause = 0;
+
+ /**
+ * Initializes a new <tt>MultiTableUpdateExecutor</tt>.
+ *
+ * @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();
+
+ $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()
+ $this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')'
+ . ' SELECT t0.' . implode(', t0.', $idColumnNames);
+ $sqlWalker->setSqlTableAlias($primaryClass->table['name'] . $updateClause->aliasIdentificationVariable, 't0');
+ $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 ' . $class->getQuotedTableName($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);
+
+ //FIXME: parameters can be more deeply nested. traverse the tree.
+ //FIXME (URGENT): With query cache the parameter is out of date. Move to execute() stage.
+ if ($newValue instanceof AST\InputParameter) {
+ $paramKey = $newValue->name;
+ $this->_sqlParameters[$i][] = $sqlWalker->getQuery()->getParameter($paramKey);
+ ++$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 = 'DROP TABLE ' . $tempTable;
+ }
+
+ /**
+ * Executes all SQL statements.
+ *
+ * @param Connection $conn The database connection that is used to execute the queries.
+ * @param array $params The parameters.
+ * @override
+ */
+ public function execute(Connection $conn, array $params, array $types)
+ {
+ $numUpdated = 0;
+
+ // Create temporary id table
+ $conn->executeUpdate($this->_createTempTableSql);
+
+ // Insert identifiers. Parameters from the update clause are cut off.
+ $numUpdated = $conn->executeUpdate($this->_insertSql, array_slice($params, $this->_numParametersInUpdateClause), $types);
+
+ // Execute UPDATE statements
+ for ($i=0, $count=count($this->_sqlStatements); $i<$count; ++$i) {
+ $conn->executeUpdate($this->_sqlStatements[$i], isset($this->_sqlParameters[$i]) ? $this->_sqlParameters[$i] : array());
+ }
+
+ // Drop temporary table
+ $conn->executeUpdate($this->_dropTempTableSql);
+
+ return $numUpdated;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\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 <roman@code-factory.org>
+ * @version $Revision$
+ * @link www.doctrine-project.org
+ * @since 2.0
+ */
+class SingleSelectExecutor extends AbstractSqlExecutor
+{
+ public function __construct(SelectStatement $AST, SqlWalker $sqlWalker)
+ {
+ $this->_sqlStatements = $sqlWalker->walkSelectStatement($AST);
+ }
+
+ public function execute(Connection $conn, array $params, array $types)
+ {
+ return $conn->executeQuery($this->_sqlStatements, $params, $types);
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\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 <roman@code-factory.org>
+ * @version $Revision$
+ * @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);
+ }
+ }
+
+ public function execute(Connection $conn, array $params, array $types)
+ {
+ return $conn->executeUpdate($this->_sqlStatements, $params, $types);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query;
+
+/**
+ * This class is used to generate DQL expressions via a set of PHP static functions
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @todo Rename: ExpressionBuilder
+ */
+class Expr
+{
+ /**
+ * Creates a conjunction of the given boolean expressions.
+ *
+ * Example:
+ *
+ * [php]
+ * // (u.type = ?1) AND (u.role = ?2)
+ * $expr->andX('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\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 OrderBy
+ */
+ public function asc($expr)
+ {
+ return new Expr\OrderBy($expr, 'ASC');
+ }
+
+ /**
+ * Creates a DESCending order expression.
+ *
+ * @param $sort
+ * @return 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 <left expr> = <right expr>. 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 <left expr> <> <right expr>. 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 <left expr> < <right expr>. 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 <left expr> <= <right expr>. 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 <left expr> > <right expr>. 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 <left expr> >= <right expr>. 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 <left expr> * <right expr>. 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 <left expr> - <right expr>. 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 <left expr> + <right expr>. 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 <left expr> / <right expr>. 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 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 {
+ 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);
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\Expr;
+
+/**
+ * Expression class for building DQL and parts
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class Andx extends Base
+{
+ protected $_separator = ') AND (';
+ protected $_allowedClasses = array(
+ 'Doctrine\ORM\Query\Expr\Comparison',
+ 'Doctrine\ORM\Query\Expr\Func',
+ 'Doctrine\ORM\Query\Expr\Orx',
+ );
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\Expr;
+
+/**
+ * Abstract base Expr class for building DQL parts
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+abstract class Base
+{
+ protected $_preSeparator = '(';
+ protected $_separator = ', ';
+ protected $_postSeparator = ')';
+ protected $_allowedClasses = array();
+
+ private $_parts = array();
+
+ public function __construct($args = array())
+ {
+ $this->addMultiple($args);
+ }
+
+ public function addMultiple($args = array())
+ {
+ foreach ((array) $args as $arg) {
+ $this->add($arg);
+ }
+ }
+
+ public function add($arg)
+ {
+ if ( ! empty($arg) || ($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;
+ }
+ }
+
+ public function count()
+ {
+ return count($this->_parts);
+ }
+
+ public function __toString()
+ {
+ if ($this->count() == 1) {
+ return (string) $this->_parts[0];
+ }
+
+ return $this->_preSeparator . implode($this->_separator, $this->_parts) . $this->_postSeparator;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\Expr;
+
+/**
+ * Expression class for DQL comparison expressions
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class Comparison
+{
+ const EQ = '=';
+ const NEQ = '<>';
+ const LT = '<';
+ const LTE = '<=';
+ const GT = '>';
+ const GTE = '>=';
+
+ private $_leftExpr;
+ private $_operator;
+ private $_rightExpr;
+
+ public function __construct($leftExpr, $operator, $rightExpr)
+ {
+ $this->_leftExpr = $leftExpr;
+ $this->_operator = $operator;
+ $this->_rightExpr = $rightExpr;
+ }
+
+ public function __toString()
+ {
+ return $this->_leftExpr . ' ' . $this->_operator . ' ' . $this->_rightExpr;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\Expr;
+
+/**
+ * Expression class for DQL from
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class From
+{
+ private $_from;
+ private $_alias;
+
+ public function __construct($from, $alias)
+ {
+ $this->_from = $from;
+ $this->_alias = $alias;
+ }
+
+ public function getFrom()
+ {
+ return $this->_from;
+ }
+
+ public function getAlias()
+ {
+ return $this->_alias;
+ }
+
+ public function __toString()
+ {
+ return $this->_from . ' ' . $this->_alias;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\Expr;
+
+/**
+ * Expression class for generating DQL functions
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class Func
+{
+ private $_name;
+ private $_arguments;
+
+ public function __construct($name, $arguments)
+ {
+ $this->_name = $name;
+ $this->_arguments = (array) $arguments;
+ }
+
+ public function __toString()
+ {
+ return $this->_name . '(' . implode(', ', $this->_arguments) . ')';
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\Expr;
+
+/**
+ * Expression class for building DQL Group By parts
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class GroupBy extends Base
+{
+ protected $_preSeparator = '';
+ protected $_postSeparator = '';
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\Expr;
+
+/**
+ * Expression class for DQL from
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class Join
+{
+ const INNER_JOIN = 'INNER';
+ const LEFT_JOIN = 'LEFT';
+
+ const ON = 'ON';
+ const WITH = 'WITH';
+
+ private $_joinType;
+ private $_join;
+ private $_alias;
+ private $_conditionType;
+ private $_condition;
+
+ public function __construct($joinType, $join, $alias = null, $conditionType = null, $condition = null)
+ {
+ $this->_joinType = $joinType;
+ $this->_join = $join;
+ $this->_alias = $alias;
+ $this->_conditionType = $conditionType;
+ $this->_condition = $condition;
+ }
+
+ public function __toString()
+ {
+ return strtoupper($this->_joinType) . ' JOIN ' . $this->_join
+ . ($this->_alias ? ' ' . $this->_alias : '')
+ . ($this->_condition ? ' ' . strtoupper($this->_conditionType) . ' ' . $this->_condition : '');
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace Doctrine\ORM\Query\Expr;
+
+class Literal extends Base
+{
+ protected $_preSeparator = '';
+ protected $_postSeparator = '';
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\Expr;
+
+/**
+ * Expression class for DQL math statements
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class Math
+{
+ private $_leftExpr;
+ private $_operator;
+ private $_rightExpr;
+
+ public function __construct($leftExpr, $operator, $rightExpr)
+ {
+ $this->_leftExpr = $leftExpr;
+ $this->_operator = $operator;
+ $this->_rightExpr = $rightExpr;
+ }
+
+ 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;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\Expr;
+
+/**
+ * Expression class for building DQL Order By parts
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class OrderBy
+{
+ protected $_preSeparator = '';
+ protected $_separator = ', ';
+ protected $_postSeparator = '';
+ protected $_allowedClasses = array();
+
+ private $_parts = array();
+
+ public function __construct($sort = null, $order = null)
+ {
+ if ($sort) {
+ $this->add($sort, $order);
+ }
+ }
+
+ public function add($sort, $order = null)
+ {
+ $order = ! $order ? 'ASC' : $order;
+ $this->_parts[] = $sort . ' '. $order;
+ }
+
+ public function count()
+ {
+ return count($this->_parts);
+ }
+
+ public function __tostring()
+ {
+ return $this->_preSeparator . implode($this->_separator, $this->_parts) . $this->_postSeparator;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\Expr;
+
+/**
+ * Expression class for building DQL OR clauses
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class Orx extends Base
+{
+ protected $_separator = ') OR (';
+ protected $_allowedClasses = array(
+ 'Doctrine\ORM\Query\Expr\Andx',
+ 'Doctrine\ORM\Query\Expr\Comparison',
+ 'Doctrine\ORM\Query\Expr\Func',
+ );
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query\Expr;
+
+/**
+ * Expression class for building DQL select statements
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class Select extends Base
+{
+ protected $_preSeparator = '';
+ protected $_postSeparator = '';
+ protected $_allowedClasses = array(
+ 'Doctrine\ORM\Query\Expr\Func'
+ );
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query;
+
+/**
+ * Scans a DQL query for tokens.
+ *
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Janne Vanhala <jpvanhal@cc.hut.fi>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @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_EMPTY = 116;
+ const T_ESCAPE = 117;
+ const T_EXISTS = 118;
+ const T_FALSE = 119;
+ const T_FROM = 120;
+ const T_GROUP = 121;
+ const T_HAVING = 122;
+ const T_IN = 123;
+ const T_INDEX = 124;
+ const T_INNER = 125;
+ const T_INSTANCE = 126;
+ const T_IS = 127;
+ const T_JOIN = 128;
+ const T_LEADING = 129;
+ const T_LEFT = 130;
+ const T_LIKE = 131;
+ const T_MAX = 132;
+ const T_MEMBER = 133;
+ const T_MIN = 134;
+ const T_NOT = 135;
+ const T_NULL = 136;
+ const T_NULLIF = 137;
+ const T_OF = 138;
+ const T_OR = 139;
+ const T_ORDER = 140;
+ const T_OUTER = 141;
+ const T_SELECT = 142;
+ const T_SET = 143;
+ const T_SIZE = 144;
+ const T_SOME = 145;
+ const T_SUM = 146;
+ const T_TRAILING = 147;
+ const T_TRUE = 148;
+ const T_UPDATE = 149;
+ const T_WHEN = 150;
+ const T_WHERE = 151;
+ const T_WITH = 153;
+ const T_PARTIAL = 154;
+ const T_MOD = 155;
+
+ /**
+ * 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]+)?',
+ "'(?:[^']|'')*'",
+ '\?[1-9][0-9]*|:[a-z][a-z0-9_]+'
+ );
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function getNonCatchablePatterns()
+ {
+ return array('\s+', '(.)');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function getType(&$value)
+ {
+ $type = self::T_NONE;
+
+ // Recognizing numeric values
+ if (is_numeric($value)) {
+ return (strpos($value, '.') !== false || stripos($value, 'e') !== false)
+ ? self::T_FLOAT : self::T_INTEGER;
+ }
+
+ // Differentiate between quoted names, identifiers, input parameters and symbols
+ if ($value[0] === "'") {
+ $value = str_replace("''", "'", substr($value, 1, strlen($value) - 2));
+ return self::T_STRING;
+ } else if (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;
+ } else if ($value[0] === '?' || $value[0] === ':') {
+ return self::T_INPUT_PARAMETER;
+ } else {
+ switch ($value) {
+ case '.': return self::T_DOT;
+ case ',': return self::T_COMMA;
+ case '(': return self::T_OPEN_PARENTHESIS;
+ case ')': return self::T_CLOSE_PARENTHESIS;
+ case '=': return self::T_EQUALS;
+ case '>': return self::T_GREATER_THAN;
+ case '<': return self::T_LOWER_THAN;
+ case '+': return self::T_PLUS;
+ case '-': return self::T_MINUS;
+ case '*': return self::T_MULTIPLY;
+ case '/': return self::T_DIVIDE;
+ case '!': return self::T_NEGATE;
+ case '{': return self::T_OPEN_CURLY_BRACE;
+ case '}': return self::T_CLOSE_CURLY_BRACE;
+ default:
+ // Do nothing
+ break;
+ }
+ }
+
+ return $type;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query;
+
+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 <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Janne Vanhala <jpvanhal@cc.hut.fi>
+ */
+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'
+ );
+
+ /** 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'
+ );
+
+ /** 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'
+ );
+
+ /**
+ * 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();
+
+ /**
+ * 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();
+ }
+
+ 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|string token type or value
+ * @return void
+ * @throws QueryException If the tokens dont match.
+ */
+ public function match($token)
+ {
+ // short-circuit on first condition, usually types match
+ if ($this->_lexer->lookahead['type'] !== $token &&
+ $token !== Lexer::T_IDENTIFIER &&
+ $this->_lexer->lookahead['type'] <= 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);
+ }
+
+ if ($AST instanceof AST\SelectStatement) {
+ $treeWalkerChain->walkSelectStatement($AST);
+ } else if ($AST instanceof AST\UpdateStatement) {
+ $treeWalkerChain->walkUpdateStatement($AST);
+ } else {
+ $treeWalkerChain->walkDeleteStatement($AST);
+ }
+ }
+
+ // 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.
+ if ( count($this->_identVariableExpressions) > 1) {
+ foreach ($this->_queryComponents as $dqlAlias => $qComp) {
+ if (isset($this->_identVariableExpressions[$dqlAlias])) {
+ $expr = $this->_identVariableExpressions[$dqlAlias];
+ $key = array_search($expr, $AST->selectClause->selectExpressions);
+ unset($AST->selectClause->selectExpressions[$key]);
+ $AST->selectClause->selectExpressions[] = $expr;
+ }
+ }
+ }
+
+ if ($this->_customOutputWalker) {
+ $outputWalker = new $this->_customOutputWalker(
+ $this->_query, $this->_parserResult, $this->_queryComponents
+ );
+ } else {
+ $outputWalker = new SqlWalker(
+ $this->_query, $this->_parserResult, $this->_queryComponents
+ );
+ }
+
+ // Assign an SQL executor to the parser result
+ $this->_parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
+
+ return $this->_parserResult;
+ }
+
+ /**
+ * 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: ";
+
+ if ($expected !== '') {
+ $message .= "Expected {$expected}, got ";
+ } else {
+ $message .= 'Unexpected ';
+ }
+
+ if ($this->_lexer->lookahead === null) {
+ $message .= 'end of string.';
+ } else {
+ $message .= "'{$token['value']}'";
+ }
+
+ throw QueryException::syntaxError($message);
+ }
+
+ /**
+ * 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;
+
+ // Building informative message
+ $message = 'line 0, col ' . (
+ (isset($token['position']) && $token['position'] > 0) ? $token['position'] : '-1'
+ ) . " near '" . substr($dql, $token['position'], $length) . "': Error: " . $message;
+
+ throw \Doctrine\ORM\Query\QueryException::semanticalError($message);
+ }
+
+ /**
+ * Peeks beyond the specified token and returns the first token after that one.
+ *
+ * @param array $token
+ * @return array
+ */
+ private function _peekBeyond($token)
+ {
+ $peek = $this->_lexer->peek();
+
+ while ($peek['value'] != $token) {
+ $peek = $this->_lexer->peek();
+ }
+
+ $peek = $this->_lexer->peek();
+ $this->_lexer->resetPeek();
+
+ return $peek;
+ }
+
+ /**
+ * Peek beyond the matched closing parenthesis and return the first token after that one.
+ *
+ * @return array
+ */
+ private function _peekBeyondClosingParenthesis()
+ {
+ $token = $this->_lexer->peek();
+ $numUnmatched = 1;
+
+ while ($numUnmatched > 0 && $token !== null) {
+ if ($token['value'] == ')') {
+ --$numUnmatched;
+ } else if ($token['value'] == '(') {
+ ++$numUnmatched;
+ }
+
+ $token = $this->_lexer->peek();
+ }
+
+ $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['value'], array("+", "-", "/", "*"));
+ }
+
+ /**
+ * 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['value'] === '(' && $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 $tokenType == Lexer::T_AVG || $tokenType == Lexer::T_MIN ||
+ $tokenType == Lexer::T_MAX || $tokenType == Lexer::T_SUM ||
+ $tokenType == 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 $this->_lexer->lookahead['type'] === Lexer::T_ALL ||
+ $this->_lexer->lookahead['type'] === Lexer::T_ANY ||
+ $this->_lexer->lookahead['type'] === Lexer::T_SOME;
+ }
+
+ /**
+ * Checks whether the next 2 tokens start a subselect.
+ *
+ * @return boolean TRUE if the next 2 tokens start a subselect, FALSE otherwise.
+ */
+ private function _isSubselect()
+ {
+ $la = $this->_lexer->lookahead;
+ $next = $this->_lexer->glimpse();
+
+ return ($la['value'] === '(' && $next['type'] === Lexer::T_SELECT);
+ }
+
+ /**
+ * Validates that the given <tt>IdentificationVariable</tt> 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 <tt>PartialObjectExpression</tt> 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])) {
+ $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 <tt>ResultVariable</tt> 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(
+ "'$identVariable' 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 <tt>PathExpression</tt> 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']
+ );
+ }
+
+ if (isset($class->fieldMappings[$field])) {
+ $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
+ } else {
+ $assoc = $class->associationMappings[$field];
+ $class = $this->_em->getClassMetadata($assoc['targetEntity']);
+
+ if ($assoc['type'] & ClassMetadata::TO_ONE) {
+ $fieldType = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
+ } else {
+ $fieldType = 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. ';
+
+ if (count($expectedStringTypes) == 1) {
+ $semanticalError .= 'Must be a ' . $expectedStringTypes[0] . '.';
+ } else {
+ $semanticalError .= implode(' or ', $expectedStringTypes) . ' expected.';
+ }
+
+ $this->semanticalError($semanticalError, $deferredItem['token']);
+ }
+
+ // We need to force the type in PathExpression
+ $pathExpression->type = $fieldType;
+ }
+ }
+
+ /**
+ * 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()
+ {
+ $token = $this->_lexer->lookahead;
+ $identVariable = $this->IdentificationVariable();
+
+ $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 ( ! isset($class->associationMappings[$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)
+ {
+ $token = $this->_lexer->lookahead;
+ $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 | 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) {
+ $token = $this->_lexer->lookahead;
+ $identVariable = $this->IdentificationVariable();
+
+ return $identVariable;
+ }
+
+ return $this->SingleValuedPathExpression();
+ }
+
+ /**
+ * OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"]
+ *
+ * @todo Post 2.0 release. Support general SingleValuedPathExpression instead
+ * of only StateFieldPathExpression.
+ *
+ * @return \Doctrine\ORM\Query\AST\OrderByItem
+ */
+ public function OrderByItem()
+ {
+ $type = 'ASC';
+
+ // We need to check if we are in a ResultVariable or StateFieldPathExpression
+ $glimpse = $this->_lexer->glimpse();
+
+ if ($glimpse['type'] != Lexer::T_DOT) {
+ $token = $this->_lexer->lookahead;
+ $expr = $this->ResultVariable();
+ } else {
+ $expr = $this->StateFieldPathExpression();
+ }
+
+ $item = new AST\OrderByItem($expr);
+
+ if ($this->_lexer->isNextToken(Lexer::T_ASC)) {
+ $this->match(Lexer::T_ASC);
+ } else if ($this->_lexer->isNextToken(Lexer::T_DESC)) {
+ $this->match(Lexer::T_DESC);
+ $type = 'DESC';
+ }
+
+ $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 SimplEntityExpression
+ */
+ public function NewValue()
+ {
+ if ($this->_lexer->isNextToken(Lexer::T_NULL)) {
+ $this->match(Lexer::T_NULL);
+ return null;
+ } else 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] {JoinVariableDeclaration}*
+ *
+ * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
+ */
+ public function IdentificationVariableDeclaration()
+ {
+ $rangeVariableDeclaration = $this->RangeVariableDeclaration();
+ $indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
+ $joinVariableDeclarations = array();
+
+ while (
+ $this->_lexer->isNextToken(Lexer::T_LEFT) ||
+ $this->_lexer->isNextToken(Lexer::T_INNER) ||
+ $this->_lexer->isNextToken(Lexer::T_JOIN)
+ ) {
+ $joinVariableDeclarations[] = $this->JoinVariableDeclaration();
+ }
+
+ return new AST\IdentificationVariableDeclaration(
+ $rangeVariableDeclaration, $indexBy, $joinVariableDeclarations
+ );
+ }
+
+ /**
+ * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
+ *
+ * @return \Doctrine\ORM\Query\AST\SubselectIdentificationVariableDeclaration |
+ * \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
+ */
+ public function SubselectIdentificationVariableDeclaration()
+ {
+ $glimpse = $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();
+ }
+
+ /**
+ * JoinVariableDeclaration ::= Join [IndexBy]
+ *
+ * @return \Doctrine\ORM\Query\AST\JoinVariableDeclaration
+ */
+ public function JoinVariableDeclaration()
+ {
+ $join = $this->Join();
+ $indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX)
+ ? $this->IndexBy() : null;
+
+ return new AST\JoinVariableDeclaration($join, $indexBy);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression
+ * ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression]
+ *
+ * @return Doctrine\ORM\Query\AST\Join
+ */
+ public function Join()
+ {
+ // Check Join type
+ $joinType = AST\Join::JOIN_TYPE_INNER;
+
+ if ($this->_lexer->isNextToken(Lexer::T_LEFT)) {
+ $this->match(Lexer::T_LEFT);
+
+ // Possible LEFT OUTER join
+ if ($this->_lexer->isNextToken(Lexer::T_OUTER)) {
+ $this->match(Lexer::T_OUTER);
+ $joinType = AST\Join::JOIN_TYPE_LEFTOUTER;
+ } else {
+ $joinType = AST\Join::JOIN_TYPE_LEFT;
+ }
+ } else if ($this->_lexer->isNextToken(Lexer::T_INNER)) {
+ $this->match(Lexer::T_INNER);
+ }
+
+ $this->match(Lexer::T_JOIN);
+
+ $joinPathExpression = $this->JoinAssociationPathExpression();
+
+ if ($this->_lexer->isNextToken(Lexer::T_AS)) {
+ $this->match(Lexer::T_AS);
+ }
+
+ $token = $this->_lexer->lookahead;
+ $aliasIdentificationVariable = $this->AliasIdentificationVariable();
+
+ // Verify that the association exists.
+ $parentClass = $this->_queryComponents[$joinPathExpression->identificationVariable]['metadata'];
+ $assocField = $joinPathExpression->associationField;
+
+ if ( ! $parentClass->hasAssociation($assocField)) {
+ $this->semanticalError(
+ "Class " . $parentClass->name . " has no association named '$assocField'."
+ );
+ }
+
+ $targetClassName = $parentClass->associationMappings[$assocField]['targetEntity'];
+
+ // Building queryComponent
+ $joinQueryComponent = array(
+ 'metadata' => $this->_em->getClassMetadata($targetClassName),
+ 'parent' => $joinPathExpression->identificationVariable,
+ 'relation' => $parentClass->getAssociationMapping($assocField),
+ 'map' => null,
+ 'nestingLevel' => $this->_nestingLevel,
+ 'token' => $token
+ );
+ $this->_queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
+
+ // Create AST node
+ $join = new AST\Join($joinType, $joinPathExpression, $aliasIdentificationVariable);
+
+ // Check for ad-hoc Join conditions
+ if ($this->_lexer->isNextToken(Lexer::T_WITH)) {
+ $this->match(Lexer::T_WITH);
+ $join->conditionalExpression = $this->ConditionalExpression();
+ }
+
+ return $join;
+ }
+
+ /**
+ * 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 |
+ * EntityTypeExpression
+ *
+ * @return mixed One of the possible expressions or subexpressions.
+ */
+ public function ScalarExpression()
+ {
+ $lookahead = $this->_lexer->lookahead['type'];
+ if ($lookahead === 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();
+ } else if ($lookahead == Lexer::T_INTEGER || $lookahead == Lexer::T_FLOAT) {
+ return $this->SimpleArithmeticExpression();
+ } else if ($this->_isFunction()) {
+ // 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();
+ }
+
+ return $this->FunctionDeclaration();
+ } else if ($lookahead == Lexer::T_STRING) {
+ return $this->StringPrimary();
+ } else if ($lookahead == Lexer::T_INPUT_PARAMETER) {
+ return $this->InputParameter();
+ } else if ($lookahead == Lexer::T_TRUE || $lookahead == Lexer::T_FALSE) {
+ $this->match($lookahead);
+ return new AST\Literal(AST\Literal::BOOLEAN, $this->_lexer->token['value']);
+ } else if ($lookahead == Lexer::T_CASE || $lookahead == Lexer::T_COALESCE || $lookahead == Lexer::T_NULLIF) {
+ return $this->CaseExpression();
+ } else {
+ $this->syntaxError();
+ }
+ }
+
+ public function CaseExpression()
+ {
+ // if "CASE" "WHEN" => GeneralCaseExpression
+ // else if "CASE" => SimpleCaseExpression
+ // else if "COALESCE" => CoalesceExpression
+ // else if "NULLIF" => NullifExpression
+ $this->semanticalError('CaseExpression not yet supported.');
+ }
+
+ /**
+ * SelectExpression ::=
+ * IdentificationVariable | StateFieldPathExpression |
+ * (AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
+ *
+ * @return Doctrine\ORM\Query\AST\SelectExpression
+ */
+ public function SelectExpression()
+ {
+ $expression = null;
+ $identVariable = null;
+ $fieldAliasIdentificationVariable = null;
+ $peek = $this->_lexer->glimpse();
+
+ $supportsAlias = true;
+
+ if ($peek['value'] != '(' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) {
+ if ($peek['value'] == '.') {
+ // ScalarExpression
+ $expression = $this->ScalarExpression();
+ } else {
+ $supportsAlias = false;
+ $expression = $identVariable = $this->IdentificationVariable();
+ }
+ } else if ($this->_lexer->lookahead['value'] == '(') {
+ if ($peek['type'] == Lexer::T_SELECT) {
+ // Subselect
+ $this->match(Lexer::T_OPEN_PARENTHESIS);
+ $expression = $this->Subselect();
+ $this->match(Lexer::T_CLOSE_PARENTHESIS);
+ } else {
+ // Shortcut: ScalarExpression => SimpleArithmeticExpression
+ $expression = $this->SimpleArithmeticExpression();
+ }
+ } else if ($this->_isFunction()) {
+ $this->_lexer->peek(); // "("
+ $beyond = $this->_peekBeyondClosingParenthesis();
+
+ if ($this->_isMathOperator($beyond)) {
+ $expression = $this->ScalarExpression();
+ } else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
+ $expression = $this->AggregateExpression();
+ } else {
+ // Shortcut: ScalarExpression => Function
+ $expression = $this->FunctionDeclaration();
+ }
+ } else if ($this->_lexer->lookahead['type'] == Lexer::T_PARTIAL) {
+ $supportsAlias = false;
+ $expression = $this->PartialObjectExpression();
+ $identVariable = $expression->identificationVariable;
+ } else if ($this->_lexer->lookahead['type'] == Lexer::T_INTEGER ||
+ $this->_lexer->lookahead['type'] == Lexer::T_FLOAT) {
+ // Shortcut: ScalarExpression => SimpleArithmeticExpression
+ $expression = $this->SimpleArithmeticExpression();
+ } else {
+ $this->syntaxError('IdentificationVariable | StateFieldPathExpression'
+ . ' | AggregateExpression | "(" Subselect ")" | ScalarExpression',
+ $this->_lexer->lookahead);
+ }
+
+ if ($supportsAlias) {
+ if ($this->_lexer->isNextToken(Lexer::T_AS)) {
+ $this->match(Lexer::T_AS);
+ }
+
+ if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
+ $token = $this->_lexer->lookahead;
+ $fieldAliasIdentificationVariable = $this->AliasResultVariable();
+
+ // Include AliasResultVariable in query components.
+ $this->_queryComponents[$fieldAliasIdentificationVariable] = array(
+ 'resultVariable' => $expression,
+ 'nestingLevel' => $this->_nestingLevel,
+ 'token' => $token,
+ );
+ }
+ }
+
+ $expr = new AST\SelectExpression($expression, $fieldAliasIdentificationVariable);
+ if (!$supportsAlias) {
+ $this->_identVariableExpressions[$identVariable] = $expr;
+ }
+ return $expr;
+ }
+
+ /**
+ * SimpleSelectExpression ::=
+ * StateFieldPathExpression | IdentificationVariable |
+ * ((AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable])
+ *
+ * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression
+ */
+ public function SimpleSelectExpression()
+ {
+ $peek = $this->_lexer->glimpse();
+
+ if ($peek['value'] != '(' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) {
+ // SingleValuedPathExpression | IdentificationVariable
+ if ($peek['value'] == '.') {
+ $expression = $this->StateFieldPathExpression();
+ } else {
+ $expression = $this->IdentificationVariable();
+ }
+
+ return new AST\SimpleSelectExpression($expression);
+ } else if ($this->_lexer->lookahead['value'] == '(') {
+ if ($peek['type'] == Lexer::T_SELECT) {
+ // Subselect
+ $this->match(Lexer::T_OPEN_PARENTHESIS);
+ $expression = $this->Subselect();
+ $this->match(Lexer::T_CLOSE_PARENTHESIS);
+ } else {
+ // Shortcut: ScalarExpression => SimpleArithmeticExpression
+ $expression = $this->SimpleArithmeticExpression();
+ }
+
+ return new AST\SimpleSelectExpression($expression);
+ }
+
+ $this->_lexer->peek();
+ $beyond = $this->_peekBeyondClosingParenthesis();
+
+ if ($this->_isMathOperator($beyond)) {
+ $expression = $this->ScalarExpression();
+ } else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
+ $expression = $this->AggregateExpression();
+ } else {
+ $expression = $this->FunctionDeclaration();
+ }
+
+ $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)) {
+ // Peek beyond the matching closing paranthesis ')'
+ $peek = $this->_peekBeyondClosingParenthesis();
+
+ if (in_array($peek['value'], array("=", "<", "<=", "<>", ">", ">=", "!=")) ||
+ $peek['type'] === Lexer::T_NOT ||
+ $peek['type'] === Lexer::T_BETWEEN ||
+ $peek['type'] === Lexer::T_LIKE ||
+ $peek['type'] === Lexer::T_IN ||
+ $peek['type'] === Lexer::T_IS ||
+ $peek['type'] === Lexer::T_EXISTS) {
+ $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
+ } else {
+ $this->match(Lexer::T_OPEN_PARENTHESIS);
+ $condPrimary->conditionalExpression = $this->ConditionalExpression();
+ $this->match(Lexer::T_CLOSE_PARENTHESIS);
+ }
+ } else {
+ $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
+ }
+
+ return $condPrimary;
+ }
+
+ /**
+ * SimpleConditionalExpression ::=
+ * ComparisonExpression | BetweenExpression | LikeExpression |
+ * InExpression | NullComparisonExpression | ExistsExpression |
+ * EmptyCollectionComparisonExpression | CollectionMemberExpression |
+ * InstanceOfExpression
+ */
+ public function SimpleConditionalExpression()
+ {
+ if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
+ $token = $this->_lexer->glimpse();
+ } else {
+ $token = $this->_lexer->lookahead;
+ }
+
+ 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();
+ } 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)) {
+ $not = true;
+ $this->match(Lexer::T_NOT);
+ }
+
+ $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
+ */
+ 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_IDENTIFIER:
+ $peek = $this->_lexer->glimpse();
+
+ if ($peek['value'] == '(') {
+ return $this->FunctionDeclaration();
+ }
+
+ if ($peek['value'] == '.') {
+ return $this->SingleValuedPathExpression();
+ }
+
+ 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();
+ } else {
+ 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
+ */
+ public function StringPrimary()
+ {
+ if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
+ $peek = $this->_lexer->glimpse();
+
+ if ($peek['value'] == '.') {
+ return $this->StateFieldPathExpression();
+ } else if ($peek['value'] == '(') {
+ return $this->FunctionsReturningStrings();
+ } else {
+ $this->syntaxError("'.' or '('");
+ }
+ } else if ($this->_lexer->isNextToken(Lexer::T_STRING)) {
+ $this->match(Lexer::T_STRING);
+
+ return $this->_lexer->token['value'];
+ } else if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
+ return $this->InputParameter();
+ } else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
+ 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->IdentificationVariable();
+ }
+
+ /**
+ * AggregateExpression ::=
+ * ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" |
+ * "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")"
+ *
+ * @return \Doctrine\ORM\Query\AST\AggregateExpression
+ */
+ public function AggregateExpression()
+ {
+ $isDistinct = false;
+ $functionName = '';
+
+ if ($this->_lexer->isNextToken(Lexer::T_COUNT)) {
+ $this->match(Lexer::T_COUNT);
+ $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 = $this->SingleValuedPathExpression();
+ $this->match(Lexer::T_CLOSE_PARENTHESIS);
+ } else {
+ if ($this->_lexer->isNextToken(Lexer::T_AVG)) {
+ $this->match(Lexer::T_AVG);
+ } else if ($this->_lexer->isNextToken(Lexer::T_MAX)) {
+ $this->match(Lexer::T_MAX);
+ } else if ($this->_lexer->isNextToken(Lexer::T_MIN)) {
+ $this->match(Lexer::T_MIN);
+ } else if ($this->_lexer->isNextToken(Lexer::T_SUM)) {
+ $this->match(Lexer::T_SUM);
+ } else {
+ $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
+ }
+
+ $functionName = $this->_lexer->token['value'];
+ $this->match(Lexer::T_OPEN_PARENTHESIS);
+ $pathExp = $this->StateFieldPathExpression();
+ $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()
+ {
+ $type = '';
+
+ if ($this->_lexer->isNextToken(Lexer::T_ALL)) {
+ $this->match(Lexer::T_ALL);
+ $type = 'ALL';
+ } else if ($this->_lexer->isNextToken(Lexer::T_ANY)) {
+ $this->match(Lexer::T_ANY);
+ $type = 'ANY';
+ } else if ($this->_lexer->isNextToken(Lexer::T_SOME)) {
+ $this->match(Lexer::T_SOME);
+ $type = 'SOME';
+ } else {
+ $this->syntaxError('ALL, ANY or SOME');
+ }
+
+ $this->match(Lexer::T_OPEN_PARENTHESIS);
+ $qExpr = new AST\QuantifiedExpression($this->Subselect());
+ $qExpr->type = $type;
+ $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()
+ {
+ $peek = $this->_lexer->glimpse();
+
+ $leftExpr = $this->ArithmeticExpression();
+ $operator = $this->ComparisonOperator();
+
+ if ($this->_isNextAllAnySome()) {
+ $rightExpr = $this->QuantifiedExpression();
+ } else {
+ $rightExpr = $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->SingleValuedPathExpression());
+
+ 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"] (AbstractSchemaName | InputParameter)
+ *
+ * @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);
+
+ if ($this->_lexer->isNextToken(Lexer::T_OF)) {
+ $this->match(Lexer::T_OF);
+ }
+
+ if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
+ $this->match(Lexer::T_INPUT_PARAMETER);
+ $exprValue = new AST\InputParameter($this->_lexer->token['value']);
+ } else {
+ $exprValue = $this->AliasIdentificationVariable();
+ }
+
+ $instanceOfExpression->value = $exprValue;
+
+ return $instanceOfExpression;
+ }
+
+ /**
+ * LikeExpression ::= StringExpression ["NOT"] "LIKE" (string | input_parameter) ["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 {
+ $this->match(Lexer::T_STRING);
+ $stringPattern = $this->_lexer->token['value'];
+ }
+
+ $escapeChar = null;
+
+ if ($this->_lexer->lookahead['type'] === Lexer::T_ESCAPE) {
+ $this->match(Lexer::T_ESCAPE);
+ $this->match(Lexer::T_STRING);
+ $escapeChar = $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!
+ if (isset(self::$_STRING_FUNCTIONS[$funcName])) {
+ return $this->FunctionsReturningStrings();
+ } else if (isset(self::$_NUMERIC_FUNCTIONS[$funcName])) {
+ return $this->FunctionsReturningNumerics();
+ } else if (isset(self::$_DATETIME_FUNCTIONS[$funcName])) {
+ return $this->FunctionsReturningDatetime();
+ }
+
+ // Check for custom functions afterwards
+ $config = $this->_em->getConfiguration();
+
+ if ($config->getCustomStringFunction($funcName) !== null) {
+ return $this->CustomFunctionsReturningStrings();
+ } else if ($config->getCustomNumericFunction($funcName) !== null) {
+ return $this->CustomFunctionsReturningNumerics();
+ } else if ($config->getCustomDatetimeFunction($funcName) !== null) {
+ return $this->CustomFunctionsReturningDatetime();
+ }
+
+ $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()
+ {
+ $funcName = strtolower($this->_lexer->lookahead['value']);
+ // getCustomNumericFunction is case-insensitive
+ $funcClass = $this->_em->getConfiguration()->getCustomNumericFunction($funcName);
+ $function = new $funcClass($funcName);
+ $function->parse($this);
+
+ return $function;
+ }
+
+ /**
+ * 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()
+ {
+ $funcName = $this->_lexer->lookahead['value'];
+ // getCustomDatetimeFunction is case-insensitive
+ $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()
+ {
+ $funcName = $this->_lexer->lookahead['value'];
+ // getCustomStringFunction is case-insensitive
+ $funcClass = $this->_em->getConfiguration()->getCustomStringFunction($funcName);
+ $function = new $funcClass($funcName);
+ $function->parse($this);
+
+ return $function;
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query;
+
+/**
+ * Encapsulates the resulting components from a DQL query parsing process that
+ * can be serialized.
+ *
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Janne Vanhala <jpvanhal@cc.hut.fi>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link http://www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ */
+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 <tt>ParserResult</tt> class.
+ * The new instance is initialized with an empty <tt>ResultSetMapping</tt>.
+ */
+ 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];
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.phpdoctrine.org>.
+ */
+
+namespace Doctrine\ORM\Query;
+
+/**
+ * A parse tree printer for Doctrine Query Language parser.
+ *
+ * @author Janne Vanhala <jpvanhal@cc.hut.fi>
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link http://www.phpdoctrine.org
+ * @since 2.0
+ * @version $Revision$
+ */
+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";
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query;
+
+use Doctrine\ORM\Query\AST\PathExpression;
+
+/**
+ * Description of QueryException
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision: 3938 $
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class QueryException extends \Doctrine\ORM\ORMException
+{
+ public static function syntaxError($message)
+ {
+ return new self('[Syntax Error] ' . $message);
+ }
+
+ public static function semanticalError($message)
+ {
+ return new self('[Semantical Error] ' . $message);
+ }
+
+ 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 :<name> or ?<num> expected.');
+ }
+
+ public static function unknownParameter($key)
+ {
+ return new self("Invalid parameter: token ".$key." is not defined in the query.");
+ }
+
+ public static function invalidPathExpression($pathExpr)
+ {
+ return new self(
+ "Invalid PathExpression '" . $pathExpr->identificationVariable .
+ "." . implode('.', $pathExpr->parts) . "'."
+ );
+ }
+
+ public static function invalidLiteral($literal) {
+ return new self("Invalid literal '$literal'");
+ }
+
+ /**
+ * @param Doctrine\ORM\Mapping\AssociationMapping $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."
+ );
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query;
+
+/**
+ * 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.
+ *
+ * <b>Users should use the public methods.</b>
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @since 2.0
+ * @todo Think about whether the number of lookup maps can be reduced.
+ */
+class ResultSetMapping
+{
+ /**
+ * Whether the result is mixed (contains scalar values together with field values).
+ *
+ * @ignore
+ * @var boolean
+ */
+ public $isMixed = false;
+ /**
+ * Maps alias names to class names.
+ *
+ * @ignore
+ * @var array
+ */
+ public $aliasMap = array();
+ /**
+ * Maps alias names to related association field names.
+ *
+ * @ignore
+ * @var array
+ */
+ public $relationMap = array();
+ /**
+ * Maps alias names to parent alias names.
+ *
+ * @ignore
+ * @var array
+ */
+ public $parentAliasMap = array();
+ /**
+ * Maps column names in the result set to field names for each class.
+ *
+ * @ignore
+ * @var array
+ */
+ public $fieldMappings = array();
+ /**
+ * Maps column names in the result set to the alias/field name to use in the mapped result.
+ *
+ * @ignore
+ * @var array
+ */
+ public $scalarMappings = array();
+ /**
+ * Maps column names of meta columns (foreign keys, discriminator columns, ...) to field names.
+ *
+ * @ignore
+ * @var array
+ */
+ public $metaMappings = array();
+ /**
+ * Maps column names in the result set to the alias they belong to.
+ *
+ * @ignore
+ * @var array
+ */
+ public $columnOwnerMap = array();
+ /**
+ * List of columns in the result set that are used as discriminator columns.
+ *
+ * @ignore
+ * @var array
+ */
+ public $discriminatorColumns = array();
+ /**
+ * Maps alias names to field names that should be used for indexing.
+ *
+ * @ignore
+ * @var array
+ */
+ public $indexByMap = array();
+ /**
+ * Map from column names to class names that declare the field the column is mapped to.
+ *
+ * @ignore
+ * @var array
+ */
+ public $declaringClasses = 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.
+ * @todo Rename: addRootEntity
+ */
+ public function addEntityResult($class, $alias)
+ {
+ $this->aliasMap[$alias] = $class;
+ }
+
+ /**
+ * 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.
+ * @todo Rename: addDiscriminatorColumn
+ */
+ public function setDiscriminatorColumn($alias, $discrColumn)
+ {
+ $this->discriminatorColumns[$alias] = $discrColumn;
+ $this->columnOwnerMap[$discrColumn] = $alias;
+ }
+
+ /**
+ * 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.
+ */
+ public function addIndexBy($alias, $fieldName)
+ {
+ $this->indexByMap[$alias] = $fieldName;
+ }
+
+ /**
+ * 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.
+ * @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;
+ }
+ }
+
+ /**
+ * 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.
+ * @todo Rename: addJoinedEntity
+ */
+ public function addJoinedEntityResult($class, $alias, $parentAlias, $relation)
+ {
+ $this->aliasMap[$alias] = $class;
+ $this->parentAliasMap[$alias] = $parentAlias;
+ $this->relationMap[$alias] = $relation;
+ }
+
+ /**
+ * 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.
+ * @todo Rename: addScalar
+ */
+ public function addScalarResult($columnName, $alias)
+ {
+ $this->scalarMappings[$columnName] = $alias;
+ if ( ! $this->isMixed && $this->fieldMappings) {
+ $this->isMixed = true;
+ }
+ }
+
+ /**
+ * 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 $alias
+ * @param $columnName
+ * @param $fieldName
+ */
+ public function addMetaResult($alias, $columnName, $fieldName)
+ {
+ $this->metaMappings[$columnName] = $fieldName;
+ $this->columnOwnerMap[$columnName] = $alias;
+ }
+}
+
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query;
+
+use Doctrine\DBAL\LockMode,
+ Doctrine\ORM\Mapping\ClassMetadata,
+ Doctrine\ORM\Query,
+ Doctrine\ORM\Query\QueryException;
+
+/**
+ * The SqlWalker is a TreeWalker that walks over a DQL AST and constructs
+ * the corresponding SQL.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @since 2.0
+ * @todo Rename: SQLWalker
+ */
+class SqlWalker implements TreeWalker
+{
+ /**
+ * @var ResultSetMapping
+ */
+ private $_rsm;
+
+ /** Counters for generating unique column aliases, table aliases and parameter indexes. */
+ private $_aliasCounter = 0;
+ private $_tableAliasCounter = 0;
+ private $_scalarResultCounter = 1;
+ private $_sqlParamIndex = 1;
+
+ /**
+ * @var ParserResult
+ */
+ private $_parserResult;
+
+ /**
+ * @var EntityManager
+ */
+ private $_em;
+
+ /**
+ * @var Doctrine\DBAL\Connection
+ */
+ private $_conn;
+
+ /**
+ * @var AbstractQuery
+ */
+ private $_query;
+
+ private $_tableAliasMap = array();
+
+ /** Map from result variable names to their SQL column alias names. */
+ private $_scalarResultAliasMap = array();
+
+ /** Map of all components/classes that appear in the DQL query. */
+ private $_queryComponents;
+
+ /** A list of classes that appear in non-scalar SelectExpressions. */
+ private $_selectedClasses = array();
+
+ /**
+ * The DQL alias of the root class of the currently traversed query.
+ */
+ 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.
+ */
+ private $_useSqlTableAliases = true;
+
+ /**
+ * The database platform abstraction.
+ *
+ * @var AbstractPlatform
+ */
+ private $_platform;
+
+ /**
+ * {@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();
+ }
+
+ /**
+ * 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)
+ {
+ $isDeleteStatement = $AST instanceof AST\DeleteStatement;
+ $isUpdateStatement = $AST instanceof AST\UpdateStatement;
+
+ if ($isDeleteStatement) {
+ $primaryClass = $this->_em->getClassMetadata(
+ $AST->deleteClause->abstractSchemaName
+ );
+
+ if ($primaryClass->isInheritanceTypeJoined()) {
+ return new Exec\MultiTableDeleteExecutor($AST, $this);
+ } else {
+ return new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
+ }
+ } else if ($isUpdateStatement) {
+ $primaryClass = $this->_em->getClassMetadata(
+ $AST->updateClause->abstractSchemaName
+ );
+
+ if ($primaryClass->isInheritanceTypeJoined()) {
+ return new Exec\MultiTableUpdateExecutor($AST, $this);
+ } else {
+ return new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
+ }
+ }
+
+ 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;
+
+ 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
+ */
+ public function setSqlTableAlias($tableName, $alias)
+ {
+ $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 $columnName . $this->_aliasCounter++;
+ }
+
+ /**
+ * 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->table['name'], $dqlAlias);
+
+ // INNER JOIN parent class tables
+ foreach ($class->parentClasses as $parentClassName) {
+ $parentClass = $this->_em->getClassMetadata($parentClassName);
+ $tableAlias = $this->getSQLTableAlias($parentClass->table['name'], $dqlAlias);
+ // If this is a joined association we must use left joins to preserve the correct result.
+ $sql .= isset($this->_queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER ';
+ $sql .= 'JOIN ' . $parentClass->getQuotedTableName($this->_platform)
+ . ' ' . $tableAlias . ' ON ';
+ $first = true;
+ foreach ($class->identifier as $idField) {
+ if ($first) $first = false; else $sql .= ' AND ';
+
+ $columnName = $class->getQuotedColumnName($idField, $this->_platform);
+ $sql .= $baseTableAlias . '.' . $columnName
+ . ' = '
+ . $tableAlias . '.' . $columnName;
+ }
+ }
+
+ // LEFT JOIN subclass tables, if partial objects disallowed.
+ if ( ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
+ foreach ($class->subClasses as $subClassName) {
+ $subClass = $this->_em->getClassMetadata($subClassName);
+ $tableAlias = $this->getSQLTableAlias($subClass->table['name'], $dqlAlias);
+ $sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform)
+ . ' ' . $tableAlias . ' ON ';
+ $first = true;
+ foreach ($class->identifier as $idField) {
+ if ($first) $first = false; else $sql .= ' AND ';
+
+ $columnName = $class->getQuotedColumnName($idField, $this->_platform);
+ $sql .= $baseTableAlias . '.' . $columnName
+ . ' = '
+ . $tableAlias . '.' . $columnName;
+ }
+ }
+ }
+
+ return $sql;
+ }
+
+ private function _generateOrderedCollectionOrderByItems()
+ {
+ $sql = '';
+ foreach ($this->_selectedClasses AS $dqlAlias => $class) {
+ $qComp = $this->_queryComponents[$dqlAlias];
+ if (isset($qComp['relation']['orderBy'])) {
+ foreach ($qComp['relation']['orderBy'] AS $fieldName => $orientation) {
+ if ($qComp['metadata']->isInheritanceTypeJoined()) {
+ $tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName);
+ } else {
+ $tableName = $qComp['metadata']->table['name'];
+ }
+
+ if ($sql != '') {
+ $sql .= ', ';
+ }
+ $sql .= $this->getSqlTableAlias($tableName, $dqlAlias) . '.' .
+ $qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform) . " $orientation";
+ }
+ }
+ }
+ return $sql;
+ }
+
+ /**
+ * 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)
+ {
+ $encapsulate = false;
+ $sql = '';
+
+ foreach ($dqlAliases as $dqlAlias) {
+ $class = $this->_queryComponents[$dqlAlias]['metadata'];
+
+ if ($class->isInheritanceTypeSingleTable()) {
+ $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);
+ }
+
+ if ($sql != '') {
+ $sql .= ' AND ';
+ $encapsulate = true;
+ }
+
+ $sql .= ($sql != '' ? ' AND ' : '')
+ . (($this->_useSqlTableAliases) ? $this->getSqlTableAlias($class->table['name'], $dqlAlias) . '.' : '')
+ . $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')';
+ }
+ }
+
+ return ($encapsulate) ? '(' . $sql . ')' : $sql;
+ }
+
+ /**
+ * 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);
+
+ if (($whereClause = $AST->whereClause) !== null) {
+ $sql .= $this->walkWhereClause($whereClause);
+ } else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_rootAliases)) !== '') {
+ $sql .= ' WHERE ' . $discSql;
+ }
+
+ $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) {
+ if ($lockMode == LockMode::PESSIMISTIC_READ) {
+ $sql .= " " . $this->_platform->getReadLockSQL();
+ } else if ($lockMode == LockMode::PESSIMISTIC_WRITE) {
+ $sql .= " " . $this->_platform->getWriteLockSQL();
+ } else if ($lockMode == LockMode::OPTIMISTIC) {
+ foreach ($this->_selectedClasses AS $class) {
+ if ( ! $class->isVersioned) {
+ throw \Doctrine\ORM\OptimisticLockException::lockFailed();
+ }
+ }
+ }
+ }
+
+ 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;
+ $sql = $this->walkUpdateClause($AST->updateClause);
+
+ if (($whereClause = $AST->whereClause) !== null) {
+ $sql .= $this->walkWhereClause($whereClause);
+ } else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_rootAliases)) !== '') {
+ $sql .= ' WHERE ' . $discSql;
+ }
+
+ return $sql;
+ }
+
+ /**
+ * 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;
+ $sql = $this->walkDeleteClause($AST->deleteClause);
+
+ if (($whereClause = $AST->whereClause) !== null) {
+ $sql .= $this->walkWhereClause($whereClause);
+ } else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_rootAliases)) !== '') {
+ $sql .= ' WHERE ' . $discSql;
+ }
+
+ return $sql;
+ }
+
+
+ /**
+ * 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->table['name'], $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 .= $class->getQuotedColumnName($fieldName, $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']) {
+ // COMPOSITE KEYS NOT (YET?) SUPPORTED
+ if (count($assoc['sourceToTargetKeyColumns']) > 1) {
+ throw QueryException::associationPathCompositeKeyNotSupported();
+ }
+
+ if ($this->_useSqlTableAliases) {
+ $sql .= $this->getSqlTableAlias($class->table['name'], $dqlAlias) . '.';
+ }
+
+ $sql .= reset($assoc['targetToSourceKeyColumns']);
+ } else {
+ throw QueryException::associationPathInverseSideNotSupported();
+ }
+ 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 ' : '') . implode(
+ ', ', array_map(array($this, 'walkSelectExpression'), $selectClause->selectExpressions)
+ );
+
+ $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 $dqlAlias => $class) {
+ // Register as entity or joined entity result
+ if ($this->_queryComponents[$dqlAlias]['relation'] === null) {
+ $this->_rsm->addEntityResult($class->name, $dqlAlias);
+ } 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->table['name'], $dqlAlias);
+ $discrColumn = $rootClass->discriminatorColumn;
+ $columnAlias = $this->getSqlColumnAlias($discrColumn['name']);
+ $sql .= ", $tblAlias." . $discrColumn['name'] . ' AS ' . $columnAlias;
+
+ $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
+ $this->_rsm->setDiscriminatorColumn($dqlAlias, $columnAlias);
+ $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $discrColumn['fieldName']);
+
+ // Add foreign key columns to SQL, if necessary
+ if ($addMetaColumns) {
+ //FIXME: Include foreign key columns of child classes also!!??
+ foreach ($class->associationMappings as $assoc) {
+ if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
+ if (isset($assoc['inherited'])) {
+ $owningClass = $this->_em->getClassMetadata($assoc['inherited']);
+ $sqlTableAlias = $this->getSqlTableAlias($owningClass->table['name'], $dqlAlias);
+ } else {
+ $sqlTableAlias = $this->getSqlTableAlias($class->table['name'], $dqlAlias);
+ }
+
+ foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
+ $columnAlias = $this->getSqlColumnAlias($srcColumn);
+ $sql .= ", $sqlTableAlias." . $srcColumn . ' AS ' . $columnAlias;
+ $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
+ $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn);
+ }
+ }
+ }
+ }
+ } else {
+ // Add foreign key columns to SQL, if necessary
+ if ($addMetaColumns) {
+ $sqlTableAlias = $this->getSqlTableAlias($class->table['name'], $dqlAlias);
+ foreach ($class->associationMappings as $assoc) {
+ if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
+ foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
+ $columnAlias = $this->getSqlColumnAlias($srcColumn);
+ $sql .= ', ' . $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias;
+ $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
+ $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ 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 = '';
+
+ $rangeDecl = $identificationVariableDecl->rangeVariableDeclaration;
+ $dqlAlias = $rangeDecl->aliasIdentificationVariable;
+
+ $this->_rootAliases[] = $dqlAlias;
+
+ $class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName);
+ $sql .= $class->getQuotedTableName($this->_platform) . ' '
+ . $this->getSqlTableAlias($class->table['name'], $dqlAlias);
+
+ if ($class->isInheritanceTypeJoined()) {
+ $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
+ }
+
+ foreach ($identificationVariableDecl->joinVariableDeclarations as $joinVarDecl) {
+ $sql .= $this->walkJoinVariableDeclaration($joinVarDecl);
+ }
+
+ if ($identificationVariableDecl->indexBy) {
+ $this->_rsm->addIndexBy(
+ $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->identificationVariable,
+ $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->field
+ );
+ }
+
+ $sqlParts[] = $this->_platform->appendLockHint($sql, $this->_query->getHint(Query::HINT_LOCK_MODE));
+ }
+
+ return ' FROM ' . implode(', ', $sqlParts);
+ }
+
+ /**
+ * 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)
+ {
+ $colSql = $this->_generateOrderedCollectionOrderByItems();
+ if ($colSql != '') {
+ $colSql = ", ".$colSql;
+ }
+
+ // OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
+ return ' ORDER BY ' . implode(
+ ', ', array_map(array($this, 'walkOrderByItem'), $orderByClause->orderByItems)
+ ) . $colSql;
+ }
+
+ /**
+ * Walks down an OrderByItem AST node, thereby generating the appropriate SQL.
+ *
+ * @param OrderByItem
+ * @return string The SQL.
+ */
+ public function walkOrderByItem($orderByItem)
+ {
+ $sql = '';
+ $expr = $orderByItem->expression;
+
+ if ($expr instanceof AST\PathExpression) {
+ $sql = $this->walkPathExpression($expr);
+ } else {
+ $columnName = $this->_queryComponents[$expr]['token']['value'];
+
+ $sql = $this->_scalarResultAliasMap[$columnName];
+ }
+
+ 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 JoinVariableDeclaration AST node and creates the corresponding SQL.
+ *
+ * @param JoinVariableDeclaration $joinVarDecl
+ * @return string The SQL.
+ */
+ public function walkJoinVariableDeclaration($joinVarDecl)
+ {
+ $join = $joinVarDecl->join;
+ $joinType = $join->joinType;
+
+ if ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) {
+ $sql = ' LEFT JOIN ';
+ } else {
+ $sql = ' INNER JOIN ';
+ }
+
+ $joinAssocPathExpr = $join->joinAssociationPathExpression;
+ $joinedDqlAlias = $join->aliasIdentificationVariable;
+ $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->table['name'], $joinedDqlAlias);
+ $sourceTableAlias = $this->getSqlTableAlias($sourceClass->table['name'], $joinAssocPathExpr->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) {
+ if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) {
+ throw QueryException::iterateWithFetchJoinNotAllowed($assoc);
+ }
+ }
+
+ if ($assoc['type'] & ClassMetadata::TO_ONE) {
+ $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON ';
+ $first = true;
+
+ foreach ($assoc['sourceToTargetKeyColumns'] as $sourceColumn => $targetColumn) {
+ if ( ! $first) $sql .= ' AND '; else $first = false;
+
+ if ($relation['isOwningSide']) {
+ $quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform);
+ $sql .= $sourceTableAlias . '.' . $sourceColumn
+ . ' = '
+ . $targetTableAlias . '.' . $quotedTargetColumn;
+ } else {
+ $quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform);
+ $sql .= $sourceTableAlias . '.' . $quotedTargetColumn
+ . ' = '
+ . $targetTableAlias . '.' . $sourceColumn;
+ }
+ }
+ } else if ($assoc['type'] == ClassMetadata::MANY_TO_MANY) {
+ // Join relation table
+ $joinTable = $assoc['joinTable'];
+ $joinTableAlias = $this->getSqlTableAlias($joinTable['name'], $joinedDqlAlias);
+ $sql .= $sourceClass->getQuotedJoinTableName($assoc, $this->_platform) . ' ' . $joinTableAlias . ' ON ';
+
+ $first = true;
+ if ($relation['isOwningSide']) {
+ foreach ($assoc['relationToSourceKeyColumns'] as $relationColumn => $sourceColumn) {
+ if ( ! $first) $sql .= ' AND '; else $first = false;
+
+ $sql .= $sourceTableAlias . '.' . $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$sourceColumn], $this->_platform)
+ . ' = '
+ . $joinTableAlias . '.' . $relationColumn;
+ }
+ } else {
+ foreach ($assoc['relationToTargetKeyColumns'] as $relationColumn => $targetColumn) {
+ if ( ! $first) $sql .= ' AND '; else $first = false;
+
+ $sql .= $sourceTableAlias . '.' . $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform)
+ . ' = '
+ . $joinTableAlias . '.' . $relationColumn;
+ }
+ }
+
+ // Join target table
+ $sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER)
+ ? ' LEFT JOIN ' : ' INNER JOIN ';
+ $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON ';
+
+ $first = true;
+ if ($relation['isOwningSide']) {
+ foreach ($assoc['relationToTargetKeyColumns'] as $relationColumn => $targetColumn) {
+ if ( ! $first) $sql .= ' AND '; else $first = false;
+
+ $sql .= $targetTableAlias . '.' . $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform)
+ . ' = '
+ . $joinTableAlias . '.' . $relationColumn;
+ }
+ } else {
+ foreach ($assoc['relationToSourceKeyColumns'] as $relationColumn => $sourceColumn) {
+ if ( ! $first) $sql .= ' AND '; else $first = false;
+
+ $sql .= $targetTableAlias . '.' . $targetClass->getQuotedColumnName($targetClass->fieldNames[$sourceColumn], $this->_platform)
+ . ' = '
+ . $joinTableAlias . '.' . $relationColumn;
+ }
+ }
+ }
+
+ // 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) . ')';
+ }
+
+ $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias));
+
+ if ($discrSql) {
+ $sql .= ' AND ' . $discrSql;
+ }
+
+ // FIXME: these should either be nested or all forced to be left joins (DDC-XXX)
+ if ($targetClass->isInheritanceTypeJoined()) {
+ $sql .= $this->_generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias);
+ }
+
+ 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;
+
+ if ($expr instanceof AST\PathExpression) {
+ if ($expr->type == AST\PathExpression::TYPE_STATE_FIELD) {
+ $fieldName = $expr->field;
+ $dqlAlias = $expr->identificationVariable;
+ $qComp = $this->_queryComponents[$dqlAlias];
+ $class = $qComp['metadata'];
+
+ if ( ! $selectExpression->fieldIdentificationVariable) {
+ $resultAlias = $fieldName;
+ } else {
+ $resultAlias = $selectExpression->fieldIdentificationVariable;
+ }
+
+ if ($class->isInheritanceTypeJoined()) {
+ $tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName);
+ } else {
+ $tableName = $class->getTableName();
+ }
+
+ $sqlTableAlias = $this->getSqlTableAlias($tableName, $dqlAlias);
+ $columnName = $class->getQuotedColumnName($fieldName, $this->_platform);
+
+ $columnAlias = $this->getSqlColumnAlias($columnName);
+ $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
+ $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
+ $this->_rsm->addScalarResult($columnAlias, $resultAlias);
+ } else {
+ throw QueryException::invalidPathExpression($expr->type);
+ }
+ }
+ else if ($expr instanceof AST\AggregateExpression) {
+ if ( ! $selectExpression->fieldIdentificationVariable) {
+ $resultAlias = $this->_scalarResultCounter++;
+ } else {
+ $resultAlias = $selectExpression->fieldIdentificationVariable;
+ }
+
+ $columnAlias = 'sclr' . $this->_aliasCounter++;
+ $sql .= $this->walkAggregateExpression($expr) . ' AS ' . $columnAlias;
+ $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
+
+ $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
+ $this->_rsm->addScalarResult($columnAlias, $resultAlias);
+ }
+ else if ($expr instanceof AST\Subselect) {
+ if ( ! $selectExpression->fieldIdentificationVariable) {
+ $resultAlias = $this->_scalarResultCounter++;
+ } else {
+ $resultAlias = $selectExpression->fieldIdentificationVariable;
+ }
+
+ $columnAlias = 'sclr' . $this->_aliasCounter++;
+ $sql .= '(' . $this->walkSubselect($expr) . ') AS '.$columnAlias;
+ $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
+
+ $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
+ $this->_rsm->addScalarResult($columnAlias, $resultAlias);
+ }
+ else if ($expr instanceof AST\Functions\FunctionNode) {
+ if ( ! $selectExpression->fieldIdentificationVariable) {
+ $resultAlias = $this->_scalarResultCounter++;
+ } else {
+ $resultAlias = $selectExpression->fieldIdentificationVariable;
+ }
+
+ $columnAlias = 'sclr' . $this->_aliasCounter++;
+ $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias;
+ $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
+
+ $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
+ $this->_rsm->addScalarResult($columnAlias, $resultAlias);
+ }
+ else if (
+ $expr instanceof AST\SimpleArithmeticExpression ||
+ $expr instanceof AST\ArithmeticTerm ||
+ $expr instanceof AST\ArithmeticFactor ||
+ $expr instanceof AST\ArithmeticPrimary
+ ) {
+ if ( ! $selectExpression->fieldIdentificationVariable) {
+ $resultAlias = $this->_scalarResultCounter++;
+ } else {
+ $resultAlias = $selectExpression->fieldIdentificationVariable;
+ }
+
+ $columnAlias = 'sclr' . $this->_aliasCounter++;
+ $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias;
+ $this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
+
+ $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
+ $this->_rsm->addScalarResult($columnAlias, $resultAlias);
+ } else {
+ // 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'];
+
+ if ( ! isset($this->_selectedClasses[$dqlAlias])) {
+ $this->_selectedClasses[$dqlAlias] = $class;
+ }
+
+ $beginning = true;
+ // Select all fields from the queried class
+ foreach ($class->fieldMappings as $fieldName => $mapping) {
+ if ($partialFieldSet && !in_array($fieldName, $partialFieldSet)) {
+ continue;
+ }
+
+ if (isset($mapping['inherited'])) {
+ $tableName = $this->_em->getClassMetadata($mapping['inherited'])->table['name'];
+ } else {
+ $tableName = $class->table['name'];
+ }
+
+ if ($beginning) $beginning = false; else $sql .= ', ';
+
+ $sqlTableAlias = $this->getSqlTableAlias($tableName, $dqlAlias);
+ $columnAlias = $this->getSqlColumnAlias($mapping['columnName']);
+ $sql .= $sqlTableAlias . '.' . $class->getQuotedColumnName($fieldName, $this->_platform)
+ . ' AS ' . $columnAlias;
+
+ $columnAlias = $this->_platform->getSQLResultCasing($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->table['name'], $dqlAlias);
+ foreach ($subClass->fieldMappings as $fieldName => $mapping) {
+ if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) {
+ continue;
+ }
+
+ if ($beginning) $beginning = false; else $sql .= ', ';
+
+ $columnAlias = $this->getSqlColumnAlias($mapping['columnName']);
+ $sql .= $sqlTableAlias . '.' . $subClass->getQuotedColumnName($fieldName, $this->_platform)
+ . ' AS ' . $columnAlias;
+
+ $columnAlias = $this->_platform->getSQLResultCasing($columnAlias);
+ $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
+ }
+
+ // Add join columns (foreign keys) of the subclass
+ //TODO: Probably better do this in walkSelectClause to honor the INCLUDE_META_COLUMNS hint
+ foreach ($subClass->associationMappings as $fieldName => $assoc) {
+ if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) {
+ foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
+ if ($beginning) $beginning = false; else $sql .= ', ';
+ $columnAlias = $this->getSqlColumnAlias($srcColumn);
+ $sql .= $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias;
+ $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ 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;
+ $this->_useSqlTableAliases = true;
+
+ $sql = $this->walkSimpleSelectClause($subselect->simpleSelectClause);
+ $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause);
+ $sql .= $subselect->whereClause ? $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->_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 = '';
+
+ $rangeDecl = $subselectIdVarDecl->rangeVariableDeclaration;
+ $dqlAlias = $rangeDecl->aliasIdentificationVariable;
+
+ $class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName);
+ $sql .= $class->getQuotedTableName($this->_platform) . ' '
+ . $this->getSqlTableAlias($class->table['name'], $dqlAlias);
+
+ if ($class->isInheritanceTypeJoined()) {
+ $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
+ }
+
+ foreach ($subselectIdVarDecl->joinVariableDeclarations as $joinVarDecl) {
+ $sql .= $this->walkJoinVariableDeclaration($joinVarDecl);
+ }
+
+ $sqlParts[] = $this->_platform->appendLockHint($sql, $this->_query->getHint(Query::HINT_LOCK_MODE));
+ }
+
+ 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)
+ {
+ $sql = '';
+ $expr = $simpleSelectExpression->expression;
+
+ if ($expr instanceof AST\PathExpression) {
+ $sql .= $this->walkPathExpression($expr);
+ } else if ($expr instanceof AST\AggregateExpression) {
+ if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
+ $alias = $this->_scalarResultCounter++;
+ } else {
+ $alias = $simpleSelectExpression->fieldIdentificationVariable;
+ }
+
+ $sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias;
+ } else if ($expr instanceof AST\Subselect) {
+ if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
+ $alias = $this->_scalarResultCounter++;
+ } else {
+ $alias = $simpleSelectExpression->fieldIdentificationVariable;
+ }
+
+ $columnAlias = 'sclr' . $this->_aliasCounter++;
+ $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
+ $this->_scalarResultAliasMap[$alias] = $columnAlias;
+ } else if ($expr instanceof AST\Functions\FunctionNode) {
+ if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
+ $alias = $this->_scalarResultCounter++;
+ } else {
+ $alias = $simpleSelectExpression->fieldIdentificationVariable;
+ }
+
+ $columnAlias = 'sclr' . $this->_aliasCounter++;
+ $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias;
+ $this->_scalarResultAliasMap[$alias] = $columnAlias;
+ } else if (
+ $expr instanceof AST\SimpleArithmeticExpression ||
+ $expr instanceof AST\ArithmeticTerm ||
+ $expr instanceof AST\ArithmeticFactor ||
+ $expr instanceof AST\ArithmeticPrimary
+ ) {
+ if ( ! $simpleSelectExpression->fieldIdentificationVariable) {
+ $alias = $this->_scalarResultCounter++;
+ } else {
+ $alias = $simpleSelectExpression->fieldIdentificationVariable;
+ }
+
+ $columnAlias = 'sclr' . $this->_aliasCounter++;
+ $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias;
+ $this->_scalarResultAliasMap[$alias] = $columnAlias;
+ } else {
+ // IdentificationVariable
+ $class = $this->_queryComponents[$expr]['metadata'];
+ $tableAlias = $this->getSqlTableAlias($class->getTableName(), $expr);
+ $first = true;
+
+ foreach ($class->identifier as $identifier) {
+ if ($first) $first = false; else $sql .= ', ';
+ $sql .= $tableAlias . '.' . $class->getQuotedColumnName($identifier, $this->_platform);
+ }
+ }
+
+ 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->walkPathExpression($aggExpression->pathExpression) . ')';
+ }
+
+ /**
+ * Walks down a GroupByClause AST node, thereby generating the appropriate SQL.
+ *
+ * @param GroupByClause
+ * @return string The SQL.
+ */
+ public function walkGroupByClause($groupByClause)
+ {
+ return ' GROUP BY ' . implode(
+ ', ', array_map(array($this, 'walkGroupByItem'), $groupByClause->groupByItems)
+ );
+ }
+
+ /**
+ * Walks down a GroupByItem AST node, thereby generating the appropriate SQL.
+ *
+ * @param GroupByItem
+ * @return string The SQL.
+ */
+ public function walkGroupByItem(AST\PathExpression $pathExpr)
+ {
+ return $this->walkPathExpression($pathExpr);
+ }
+
+ /**
+ * Walks down a DeleteClause AST node, thereby generating the appropriate SQL.
+ *
+ * @param DeleteClause
+ * @return string The SQL.
+ */
+ public function walkDeleteClause(AST\DeleteClause $deleteClause)
+ {
+ $sql = 'DELETE FROM ';
+ $class = $this->_em->getClassMetadata($deleteClause->abstractSchemaName);
+ $sql .= $class->getQuotedTableName($this->_platform);
+
+ if ($this->_useSqlTableAliases) {
+ $sql .= ' ' . $this->getSqlTableAlias($class->getTableName());
+ }
+
+ $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)
+ {
+ $sql = 'UPDATE ';
+ $class = $this->_em->getClassMetadata($updateClause->abstractSchemaName);
+ $sql .= $class->getQuotedTableName($this->_platform);
+
+ if ($this->_useSqlTableAliases) {
+ $sql .= ' ' . $this->getSqlTableAlias($class->getTableName());
+ }
+
+ $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;
+
+ if ($newValue === null) {
+ $sql .= 'NULL';
+ } else if ($newValue instanceof AST\Node) {
+ $sql .= $newValue->dispatch($this);
+ } else {
+ $sql .= $this->_conn->quote($newValue);
+ }
+
+ $this->_useSqlTableAliases = $useTableAliasesBefore;
+
+ return $sql;
+ }
+
+ /**
+ * Walks down a WhereClause AST node, thereby generating the appropriate SQL.
+ *
+ * @param WhereClause
+ * @return string The SQL.
+ */
+ public function walkWhereClause($whereClause)
+ {
+ $discrSql = $this->_generateDiscriminatorColumnConditionSql($this->_rootAliases);
+ $condSql = $this->walkConditionalExpression($whereClause->conditionalExpression);
+
+ return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
+ }
+
+ /**
+ * 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
+ return ( ! ($condExpr instanceof AST\ConditionalExpression))
+ ? $this->walkConditionalTerm($condExpr)
+ : 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
+ return ( ! ($condTerm instanceof AST\ConditionalTerm))
+ ? $this->walkConditionalFactor($condTerm)
+ : 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);
+ } else 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'];
+
+ if ($entityExpr instanceof AST\InputParameter) {
+ $dqlParamKey = $entityExpr->name;
+ $entity = $this->_query->getParameter($dqlParamKey);
+ } else {
+ //TODO
+ 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->table['name']);
+ $sourceTableAlias = $this->getSqlTableAlias($class->table['name'], $dqlAlias);
+
+ $sql .= $targetClass->getQuotedTableName($this->_platform)
+ . ' ' . $targetTableAlias . ' WHERE ';
+
+ $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']];
+
+ $first = true;
+
+ foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) {
+ if ($first) $first = false; else $sql .= ' AND ';
+
+ $sql .= $sourceTableAlias . '.' . $class->getQuotedColumnName($class->fieldNames[$targetColumn], $this->_platform)
+ . ' = '
+ . $targetTableAlias . '.' . $sourceColumn;
+ }
+
+ $sql .= ' AND ';
+ $first = true;
+
+ foreach ($targetClass->identifier as $idField) {
+ if ($first) $first = false; else $sql .= ' AND ';
+
+ $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++);
+ $sql .= $targetTableAlias . '.'
+ . $targetClass->getQuotedColumnName($idField, $this->_platform) . ' = ?';
+ }
+ } 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->table['name']);
+ $sourceTableAlias = $this->getSqlTableAlias($class->table['name'], $dqlAlias);
+
+ // join to target table
+ $sql .= $targetClass->getQuotedJoinTableName($owningAssoc, $this->_platform)
+ . ' ' . $joinTableAlias . ' INNER JOIN '
+ . $targetClass->getQuotedTableName($this->_platform)
+ . ' ' . $targetTableAlias . ' ON ';
+
+ // join conditions
+ $joinColumns = $assoc['isOwningSide']
+ ? $joinTable['inverseJoinColumns']
+ : $joinTable['joinColumns'];
+
+ $first = true;
+ foreach ($joinColumns as $joinColumn) {
+ if ($first) $first = false; else $sql .= ' AND ';
+
+ $sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = '
+ . $targetTableAlias . '.' . $targetClass->getQuotedColumnName(
+ $targetClass->fieldNames[$joinColumn['referencedColumnName']],
+ $this->_platform);
+ }
+
+ $sql .= ' WHERE ';
+
+ $joinColumns = $assoc['isOwningSide']
+ ? $joinTable['joinColumns']
+ : $joinTable['inverseJoinColumns'];
+
+ $first = true;
+ foreach ($joinColumns as $joinColumn) {
+ if ($first) $first = false; else $sql .= ' AND ';
+
+ $sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = '
+ . $sourceTableAlias . '.' . $class->getQuotedColumnName(
+ $class->fieldNames[$joinColumn['referencedColumnName']],
+ $this->_platform);
+ }
+
+ $sql .= ' AND ';
+ $first = true;
+
+ foreach ($targetClass->identifier as $idField) {
+ if ($first) $first = false; else $sql .= ' AND ';
+
+ $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++);
+ $sql .= $targetTableAlias . '.'
+ . $targetClass->getQuotedColumnName($idField, $this->_platform) . ' = ?';
+ }
+ }
+
+ 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->walkPathExpression($inExpr->pathExpression)
+ . ($inExpr->not ? ' NOT' : '') . ' IN (';
+
+ if ($inExpr->subselect) {
+ $sql .= $this->walkSubselect($inExpr->subselect);
+ } else {
+ $sql .= 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'];
+ $fieldName = null;
+
+ if ($class->discriminatorColumn) {
+ $discrClass = $this->_em->getClassMetadata($class->rootEntityName);
+ }
+
+ if ($this->_useSqlTableAliases) {
+ $sql .= $this->getSQLTableAlias($discrClass->table['name'], $dqlAlias) . '.';
+ }
+
+ $sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' <> ' : ' = ');
+
+ if ($instanceOfExpr->value instanceof AST\InputParameter) {
+ // We need to modify the parameter value to be its correspondent mapped value
+ $dqlParamKey = $instanceOfExpr->value->name;
+ $paramValue = $this->_query->getParameter($dqlParamKey);
+
+ 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($instanceOfExpr->value)->name;
+ }
+
+ if ($entityClassName == $class->name) {
+ $sql .= $this->_conn->quote($class->discriminatorValue);
+ } else {
+ $discrMap = array_flip($class->discriminatorMap);
+ $sql .= $this->_conn->quote($discrMap[$entityClassName]);
+ }
+
+ 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 is_string($boolVal) ? $this->_conn->quote($boolVal) : $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 .= '?';
+ } else {
+ $sql .= $this->_conn->quote($likeExpr->stringPattern);
+ }
+
+ if ($likeExpr->escapeChar) {
+ $sql .= ' ESCAPE ' . $this->_conn->quote($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)
+ {
+ $sql = '';
+ $leftExpr = $compExpr->leftExpression;
+ $rightExpr = $compExpr->rightExpression;
+
+ if ($leftExpr instanceof AST\Node) {
+ $sql .= $leftExpr->dispatch($this);
+ } else {
+ $sql .= is_numeric($leftExpr) ? $leftExpr : $this->_conn->quote($leftExpr);
+ }
+
+ $sql .= ' ' . $compExpr->operator . ' ';
+
+ if ($rightExpr instanceof AST\Node) {
+ $sql .= $rightExpr->dispatch($this);
+ } else {
+ $sql .= 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)
+ {
+ return ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression))
+ ? $this->walkArithmeticTerm($simpleArithmeticExpr)
+ : 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 $term;
+ }
+
+ // Phase 2 AST optimization: Skip processment of ArithmeticTerm
+ // if only one ArithmeticFactor is defined
+ return ( ! ($term instanceof AST\ArithmeticTerm))
+ ? $this->walkArithmeticFactor($term)
+ : 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
+ return ( ! ($factor instanceof AST\ArithmeticFactor))
+ ? $this->walkArithmeticPrimary($factor)
+ : ($factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : ''))
+ . $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) . ')';
+ } else if ($primary instanceof AST\Node) {
+ return $primary->dispatch($this);
+ }
+
+ // TODO: We need to deal with IdentificationVariable here
+ return '';
+ }
+
+ /**
+ * 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);
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query;
+
+/**
+ * Interface for walkers of DQL ASTs (abstract syntax trees).
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @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 JoinVariableDeclaration AST node and creates the corresponding SQL.
+ *
+ * @param JoinVariableDeclaration $joinVarDecl
+ * @return string The SQL.
+ */
+ function walkJoinVariableDeclaration($joinVarDecl);
+
+ /**
+ * 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(AST\PathExpression $pathExpr);
+
+ /**
+ * 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);
+
+ /**
+ * Gets an executor that can be used to execute the result of this walker.
+ *
+ * @return AbstractExecutor
+ */
+ function getExecutor($AST);
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query;
+
+/**
+ * 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 <roman@code-factory.org>
+ * @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 JoinVariableDeclaration AST node and creates the corresponding SQL.
+ *
+ * @param JoinVariableDeclaration $joinVarDecl
+ * @return string The SQL.
+ */
+ public function walkJoinVariableDeclaration($joinVarDecl) {}
+
+ /**
+ * 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(AST\PathExpression $pathExpr) {}
+
+ /**
+ * 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) {}
+
+ /**
+ * Gets an executor that can be used to execute the result of this walker.
+ *
+ * @return AbstractExecutor
+ */
+ public function getExecutor($AST) {}
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Query;
+
+/**
+ * Represents a chain of tree walkers that modify an AST and finally emit output.
+ * Only the last walker in the chain can emit output. Any previous walkers can modify
+ * the AST to influence the final output produced by the last walker.
+ *
+ * @author Roman Borschel <roman@code-factory.org>
+ * @since 2.0
+ */
+class TreeWalkerChain implements TreeWalker
+{
+ /** The tree walkers. */
+ private $_walkers = array();
+ /** The original Query. */
+ private $_query;
+ /** The ParserResult of the original query that was produced by the Parser. */
+ private $_parserResult;
+ /** The query components of the original query (the "symbol table") that was produced by the Parser. */
+ private $_queryComponents;
+
+ /**
+ * @inheritdoc
+ */
+ public function __construct($query, $parserResult, array $queryComponents)
+ {
+ $this->_query = $query;
+ $this->_parserResult = $parserResult;
+ $this->_queryComponents = $queryComponents;
+ }
+
+ /**
+ * Adds a tree walker to the chain.
+ *
+ * @param string $walkerClass The class of the walker to instantiate.
+ */
+ public function addTreeWalker($walkerClass)
+ {
+ $this->_walkers[] = new $walkerClass($this->_query, $this->_parserResult, $this->_queryComponents);
+ }
+
+ /**
+ * Walks down a SelectStatement AST node, thereby generating the appropriate SQL.
+ *
+ * @return string The SQL.
+ */
+ public function walkSelectStatement(AST\SelectStatement $AST)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkSelectStatement($AST);
+ }
+ }
+
+ /**
+ * Walks down a SelectClause AST node, thereby generating the appropriate SQL.
+ *
+ * @return string The SQL.
+ */
+ public function walkSelectClause($selectClause)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkSelectClause($selectClause);
+ }
+ }
+
+ /**
+ * Walks down a FromClause AST node, thereby generating the appropriate SQL.
+ *
+ * @return string The SQL.
+ */
+ public function walkFromClause($fromClause)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkFromClause($fromClause);
+ }
+ }
+
+ /**
+ * Walks down a FunctionNode AST node, thereby generating the appropriate SQL.
+ *
+ * @return string The SQL.
+ */
+ public function walkFunction($function)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkFunction($function);
+ }
+ }
+
+ /**
+ * Walks down an OrderByClause AST node, thereby generating the appropriate SQL.
+ *
+ * @param OrderByClause
+ * @return string The SQL.
+ */
+ public function walkOrderByClause($orderByClause)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkOrderByClause($orderByClause);
+ }
+ }
+
+ /**
+ * Walks down an OrderByItem AST node, thereby generating the appropriate SQL.
+ *
+ * @param OrderByItem
+ * @return string The SQL.
+ */
+ public function walkOrderByItem($orderByItem)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkOrderByItem($orderByItem);
+ }
+ }
+
+ /**
+ * Walks down a HavingClause AST node, thereby generating the appropriate SQL.
+ *
+ * @param HavingClause
+ * @return string The SQL.
+ */
+ public function walkHavingClause($havingClause)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkHavingClause($havingClause);
+ }
+ }
+
+ /**
+ * Walks down a JoinVariableDeclaration AST node and creates the corresponding SQL.
+ *
+ * @param JoinVariableDeclaration $joinVarDecl
+ * @return string The SQL.
+ */
+ public function walkJoinVariableDeclaration($joinVarDecl)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkJoinVariableDeclaration($joinVarDecl);
+ }
+ }
+
+ /**
+ * Walks down a SelectExpression AST node and generates the corresponding SQL.
+ *
+ * @param SelectExpression $selectExpression
+ * @return string The SQL.
+ */
+ public function walkSelectExpression($selectExpression)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkSelectExpression($selectExpression);
+ }
+ }
+
+ /**
+ * Walks down a QuantifiedExpression AST node, thereby generating the appropriate SQL.
+ *
+ * @param QuantifiedExpression
+ * @return string The SQL.
+ */
+ public function walkQuantifiedExpression($qExpr)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkQuantifiedExpression($qExpr);
+ }
+ }
+
+ /**
+ * Walks down a Subselect AST node, thereby generating the appropriate SQL.
+ *
+ * @param Subselect
+ * @return string The SQL.
+ */
+ public function walkSubselect($subselect)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkSubselect($subselect);
+ }
+ }
+
+ /**
+ * Walks down a SubselectFromClause AST node, thereby generating the appropriate SQL.
+ *
+ * @param SubselectFromClause
+ * @return string The SQL.
+ */
+ public function walkSubselectFromClause($subselectFromClause)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkSubselectFromClause($subselectFromClause);
+ }
+ }
+
+ /**
+ * Walks down a SimpleSelectClause AST node, thereby generating the appropriate SQL.
+ *
+ * @param SimpleSelectClause
+ * @return string The SQL.
+ */
+ public function walkSimpleSelectClause($simpleSelectClause)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkSimpleSelectClause($simpleSelectClause);
+ }
+ }
+
+ /**
+ * Walks down a SimpleSelectExpression AST node, thereby generating the appropriate SQL.
+ *
+ * @param SimpleSelectExpression
+ * @return string The SQL.
+ */
+ public function walkSimpleSelectExpression($simpleSelectExpression)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkSimpleSelectExpression($simpleSelectExpression);
+ }
+ }
+
+ /**
+ * Walks down an AggregateExpression AST node, thereby generating the appropriate SQL.
+ *
+ * @param AggregateExpression
+ * @return string The SQL.
+ */
+ public function walkAggregateExpression($aggExpression)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkAggregateExpression($aggExpression);
+ }
+ }
+
+ /**
+ * Walks down a GroupByClause AST node, thereby generating the appropriate SQL.
+ *
+ * @param GroupByClause
+ * @return string The SQL.
+ */
+ public function walkGroupByClause($groupByClause)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkGroupByClause($groupByClause);
+ }
+ }
+
+ /**
+ * Walks down a GroupByItem AST node, thereby generating the appropriate SQL.
+ *
+ * @param GroupByItem
+ * @return string The SQL.
+ */
+ public function walkGroupByItem(AST\PathExpression $pathExpr)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkGroupByItem($pathExpr);
+ }
+ }
+
+ /**
+ * Walks down an UpdateStatement AST node, thereby generating the appropriate SQL.
+ *
+ * @param UpdateStatement
+ * @return string The SQL.
+ */
+ public function walkUpdateStatement(AST\UpdateStatement $AST)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkUpdateStatement($AST);
+ }
+ }
+
+ /**
+ * Walks down a DeleteStatement AST node, thereby generating the appropriate SQL.
+ *
+ * @param DeleteStatement
+ * @return string The SQL.
+ */
+ public function walkDeleteStatement(AST\DeleteStatement $AST)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkDeleteStatement($AST);
+ }
+ }
+
+ /**
+ * Walks down a DeleteClause AST node, thereby generating the appropriate SQL.
+ *
+ * @param DeleteClause
+ * @return string The SQL.
+ */
+ public function walkDeleteClause(AST\DeleteClause $deleteClause)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkDeleteClause($deleteClause);
+ }
+ }
+
+ /**
+ * Walks down an UpdateClause AST node, thereby generating the appropriate SQL.
+ *
+ * @param UpdateClause
+ * @return string The SQL.
+ */
+ public function walkUpdateClause($updateClause)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkUpdateClause($updateClause);
+ }
+ }
+
+ /**
+ * Walks down an UpdateItem AST node, thereby generating the appropriate SQL.
+ *
+ * @param UpdateItem
+ * @return string The SQL.
+ */
+ public function walkUpdateItem($updateItem)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkUpdateItem($updateItem);
+ }
+ }
+
+ /**
+ * Walks down a WhereClause AST node, thereby generating the appropriate SQL.
+ *
+ * @param WhereClause
+ * @return string The SQL.
+ */
+ public function walkWhereClause($whereClause)
+ {
+ foreach ($this->_walkers as $walker) {
+ $walker->walkWhereClause($whereClause);
+ }
+ }
+
+ /**
+ * Walks down a 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);
+ }
+ }
+
+ /**
+ * Gets an executor that can be used to execute the result of this walker.
+ *
+ * @return AbstractExecutor
+ */
+ public function getExecutor($AST)
+ {}
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM;
+
+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 <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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(
+ '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 array The query parameters.
+ */
+ private $_params = array();
+
+ /**
+ * @var array The parameter type map of this query.
+ */
+ private $_paramTypes = 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;
+
+ /**
+ * Initializes a new <tt>QueryBuilder</tt> that uses the given <tt>EntityManager</tt>.
+ *
+ * @param EntityManager $em The EntityManager to use.
+ */
+ public function __construct(EntityManager $em)
+ {
+ $this->_em = $em;
+ }
+
+ /**
+ * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
+ * This producer method is intended for convenient inline usage. Example:
+ *
+ * <code>
+ * $qb = $em->createQueryBuilder()
+ * ->select('u')
+ * ->from('User', 'u')
+ * ->where($qb->expr()->eq('u.id', 1));
+ * </code>
+ *
+ * For more complex expression construction, consider storing the expression
+ * builder object in a local variable.
+ *
+ * @return 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.
+ *
+ * <code>
+ * $qb = $em->createQueryBuilder()
+ * ->select('u')
+ * ->from('User', 'u')
+ * echo $qb->getDql(); // SELECT u FROM User u
+ * </code>
+ *
+ * @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.
+ *
+ * <code>
+ * $qb = $em->createQueryBuilder()
+ * ->select('u')
+ * ->from('User', 'u');
+ * $q = $qb->getQuery();
+ * $results = $q->execute();
+ * </code>
+ *
+ * @return Query
+ */
+ public function getQuery()
+ {
+ return $this->_em->createQuery($this->getDQL())
+ ->setParameters($this->_params, $this->_paramTypes)
+ ->setFirstResult($this->_firstResult)
+ ->setMaxResults($this->_maxResults);
+ }
+
+ /**
+ * Gets the root alias of the query. This is the first entity alias involved
+ * in the construction of the query.
+ *
+ * <code>
+ * $qb = $em->createQueryBuilder()
+ * ->select('u')
+ * ->from('User', 'u');
+ *
+ * echo $qb->getRootAlias(); // u
+ * </code>
+ *
+ * @return string $rootAlias
+ * @todo Rename/Refactor: getRootAliases(), there can be multiple roots!
+ */
+ public function getRootAlias()
+ {
+ return $this->_dqlParts['from'][0]->getAlias();
+ }
+
+ /**
+ * Sets a query parameter for the query being constructed.
+ *
+ * <code>
+ * $qb = $em->createQueryBuilder()
+ * ->select('u')
+ * ->from('User', 'u')
+ * ->where('u.id = :user_id')
+ * ->setParameter(':user_id', 1);
+ * </code>
+ *
+ * @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)
+ {
+ if ($type !== null) {
+ $this->_paramTypes[$key] = $type;
+ }
+ $this->_params[$key] = $value;
+ return $this;
+ }
+
+ /**
+ * Sets a collection of query parameters for the query being constructed.
+ *
+ * <code>
+ * $qb = $em->createQueryBuilder()
+ * ->select('u')
+ * ->from('User', 'u')
+ * ->where('u.id = :user_id1 OR u.id = :user_id2')
+ * ->setParameters(array(
+ * ':user_id1' => 1,
+ * ':user_id2' => 2
+ * ));
+ * </code>
+ *
+ * @param array $params The query parameters to set.
+ * @return QueryBuilder This QueryBuilder instance.
+ */
+ public function setParameters(array $params, array $types = array())
+ {
+ $this->_paramTypes = $types;
+ $this->_params = $params;
+ return $this;
+ }
+
+ /**
+ * Gets all defined query parameters for the query being constructed.
+ *
+ * @return array The currently defined query parameters.
+ */
+ public function getParameters()
+ {
+ return $this->_params;
+ }
+
+ /**
+ * Gets a (previously set) query parameter of the query being constructed.
+ *
+ * @param mixed $key The key (index or name) of the bound parameter.
+ * @return mixed The value of the bound parameter.
+ */
+ public function getParameter($key)
+ {
+ return isset($this->_params[$key]) ? $this->_params[$key] : 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]);
+
+ if ($append && $isMultiple) {
+ $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.
+ *
+ * <code>
+ * $qb = $em->createQueryBuilder()
+ * ->select('u', 'p')
+ * ->from('User', 'u')
+ * ->leftJoin('u.Phonenumbers', 'p');
+ * </code>
+ *
+ * @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);
+ }
+
+ /**
+ * Adds an item that is to be returned in the query result.
+ *
+ * <code>
+ * $qb = $em->createQueryBuilder()
+ * ->select('u')
+ * ->addSelect('p')
+ * ->from('User', 'u')
+ * ->leftJoin('u.Phonenumbers', 'p');
+ * </code>
+ *
+ * @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.
+ *
+ * <code>
+ * $qb = $em->createQueryBuilder()
+ * ->delete('User', 'u')
+ * ->where('u.id = :user_id');
+ * ->setParameter(':user_id', 1);
+ * </code>
+ *
+ * @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.
+ *
+ * <code>
+ * $qb = $em->createQueryBuilder()
+ * ->update('User', 'u')
+ * ->set('u.password', md5('password'))
+ * ->where('u.id = ?');
+ * </code>
+ *
+ * @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.
+ *
+ * <code>
+ * $qb = $em->createQueryBuilder()
+ * ->select('u')
+ * ->from('User', 'u')
+ * </code>
+ *
+ * @param string $from The class name.
+ * @param string $alias The alias of the class.
+ * @return QueryBuilder This QueryBuilder instance.
+ */
+ public function from($from, $alias)
+ {
+ return $this->add('from', new Expr\From($from, $alias), 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.
+ *
+ * <code>
+ * $qb = $em->createQueryBuilder()
+ * ->select('u')
+ * ->from('User', 'u')
+ * ->join('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
+ * </code>
+ *
+ * @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
+ * @return QueryBuilder This QueryBuilder instance.
+ */
+ public function join($join, $alias, $conditionType = null, $condition = null)
+ {
+ return $this->innerJoin($join, $alias, $conditionType, $condition);
+ }
+
+ /**
+ * 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
+ * @return QueryBuilder This QueryBuilder instance.
+ */
+ public function innerJoin($join, $alias, $conditionType = null, $condition = null)
+ {
+ return $this->add('join', new Expr\Join(
+ Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition
+ ), 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.
+ *
+ * <code>
+ * $qb = $em->createQueryBuilder()
+ * ->select('u')
+ * ->from('User', 'u')
+ * ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
+ * </code>
+ *
+ * @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
+ * @return QueryBuilder This QueryBuilder instance.
+ */
+ public function leftJoin($join, $alias, $conditionType = null, $condition = null)
+ {
+ return $this->add('join', new Expr\Join(
+ Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition
+ ), true);
+ }
+
+ /**
+ * Sets a new value for a field in a bulk update query.
+ *
+ * <code>
+ * $qb = $em->createQueryBuilder()
+ * ->update('User', 'u')
+ * ->set('u.password', md5('password'))
+ * ->where('u.id = ?');
+ * </code>
+ *
+ * @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.
+ *
+ * <code>
+ * $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);
+ * </code>
+ *
+ * @param mixed $predicates The restriction predicates.
+ * @return QueryBuilder This QueryBuilder instance.
+ */
+ public function where($predicates)
+ {
+ if ( ! (func_num_args() == 1 && ($predicates instanceof Expr\Andx || $predicates instanceof Expr\Orx))) {
+ $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.
+ *
+ * <code>
+ * $qb = $em->createQueryBuilder()
+ * ->select('u')
+ * ->from('User', 'u')
+ * ->where('u.username LIKE ?')
+ * ->andWhere('u.is_active = 1');
+ * </code>
+ *
+ * @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.
+ *
+ * <code>
+ * $qb = $em->createQueryBuilder()
+ * ->select('u')
+ * ->from('User', 'u')
+ * ->where('u.id = 1')
+ * ->orWhere('u.id = 2');
+ * </code>
+ *
+ * @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.
+ *
+ * <code>
+ * $qb = $em->createQueryBuilder()
+ * ->select('u')
+ * ->from('User', 'u')
+ * ->groupBy('u.id');
+ * </code>
+ *
+ * @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.
+ *
+ * <code>
+ * $qb = $em->createQueryBuilder()
+ * ->select('u')
+ * ->from('User', 'u')
+ * ->groupBy('u.lastLogin');
+ * ->addGroupBy('u.createdAt')
+ * </code>
+ *
+ * @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)
+ {
+ return $this->add('orderBy', $sort instanceof Expr\OrderBy ? $sort
+ : new Expr\OrderBy($sort, $order));
+ }
+
+ /**
+ * 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()
+ {
+ return 'SELECT'
+ . $this->_getReducedDQLQueryPart('select', array('pre' => ' ', 'separator' => ', '))
+ . $this->_getReducedDQLQueryPart('from', array('pre' => ' FROM ', 'separator' => ', '))
+ . $this->_getReducedDQLQueryPart('join', array('pre' => ' ', 'separator' => ' '))
+ . $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' => ', '));
+ }
+
+ 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)
+ {
+ if (is_array($this->_dqlParts[$part])) {
+ $this->_dqlParts[$part] = array();
+ } else {
+ $this->_dqlParts[$part] = 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;
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Tools\Console\Command\ClearCache;
+
+use Symfony\Component\Console\Input\InputArgument,
+ Symfony\Component\Console\Input\InputOption,
+ Symfony\Component\Console;
+
+/**
+ * Command to clear the metadata cache of the various cache drivers.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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())
+ ->setHelp(<<<EOT
+Clear all metadata cache of the various cache drivers.
+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 \Doctrine\Common\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);
+
+ $cacheIds = $cacheDriver->deleteAll();
+
+ if ($cacheIds) {
+ foreach ($cacheIds as $cacheId) {
+ $output->write(' - ' . $cacheId . PHP_EOL);
+ }
+ } else {
+ $output->write('No entries to be deleted.' . PHP_EOL);
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Tools\Console\Command\ClearCache;
+
+use Symfony\Component\Console\Input\InputArgument,
+ Symfony\Component\Console\Input\InputOption,
+ Symfony\Component\Console;
+
+/**
+ * Command to clear the query cache of the various cache drivers.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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())
+ ->setHelp(<<<EOT
+Clear all query cache of the various cache drivers.
+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 \Doctrine\Common\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);
+
+ $cacheIds = $cacheDriver->deleteAll();
+
+ if ($cacheIds) {
+ foreach ($cacheIds as $cacheId) {
+ $output->write(' - ' . $cacheId . PHP_EOL);
+ }
+ } else {
+ $output->write('No entries to be deleted.' . PHP_EOL);
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Tools\Console\Command\ClearCache;
+
+use Symfony\Component\Console\Input\InputArgument,
+ Symfony\Component\Console\Input\InputOption,
+ Symfony\Component\Console;
+
+/**
+ * Command to clear the result cache of the various cache drivers.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class ResultCommand extends Console\Command\Command
+{
+ /**
+ * @see Console\Command\Command
+ */
+ protected function configure()
+ {
+ $this
+ ->setName('orm:clear-cache:result')
+ ->setDescription('Clear result cache of the various cache drivers.')
+ ->setDefinition(array(
+ new InputOption(
+ 'id', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
+ 'ID(s) of the cache entry to delete (accepts * wildcards).', array()
+ ),
+ new InputOption(
+ 'regex', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
+ 'Delete cache entries that match the given regular expression(s).', array()
+ ),
+ new InputOption(
+ 'prefix', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
+ 'Delete cache entries that have the given prefix(es).', array()
+ ),
+ new InputOption(
+ 'suffix', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
+ 'Delete cache entries that have the given suffix(es).', array()
+ ),
+ ))
+ ->setHelp(<<<EOT
+Clear result cache of the various cache drivers.
+If none of the options are defined, all cache entries will be removed.
+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 \Doctrine\Common\Cache\ApcCache) {
+ throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI.");
+ }
+
+ $outputed = false;
+
+ // Removing based on --id
+ if (($ids = $input->getOption('id')) !== null && $ids) {
+ foreach ($ids as $id) {
+ $output->write($outputed ? PHP_EOL : '');
+ $output->write(sprintf('Clearing Result cache entries that match the id "<info>%s</info>"', $id) . PHP_EOL);
+
+ $deleted = $cacheDriver->delete($id);
+
+ if (is_array($deleted)) {
+ $this->_printDeleted($output, $deleted);
+ } else if (is_bool($deleted) && $deleted) {
+ $this->_printDeleted($output, array($id));
+ }
+
+ $outputed = true;
+ }
+ }
+
+ // Removing based on --regex
+ if (($regexps = $input->getOption('regex')) !== null && $regexps) {
+ foreach($regexps as $regex) {
+ $output->write($outputed ? PHP_EOL : '');
+ $output->write(sprintf('Clearing Result cache entries that match the regular expression "<info>%s</info>"', $regex) . PHP_EOL);
+
+ $this->_printDeleted($output, $cacheDriver->deleteByRegex('/' . $regex. '/'));
+
+ $outputed = true;
+ }
+ }
+
+ // Removing based on --prefix
+ if (($prefixes = $input->getOption('prefix')) !== null & $prefixes) {
+ foreach ($prefixes as $prefix) {
+ $output->write($outputed ? PHP_EOL : '');
+ $output->write(sprintf('Clearing Result cache entries that have the prefix "<info>%s</info>"', $prefix) . PHP_EOL);
+
+ $this->_printDeleted($output, $cacheDriver->deleteByPrefix($prefix));
+
+ $outputed = true;
+ }
+ }
+
+ // Removing based on --suffix
+ if (($suffixes = $input->getOption('suffix')) !== null && $suffixes) {
+ foreach ($suffixes as $suffix) {
+ $output->write($outputed ? PHP_EOL : '');
+ $output->write(sprintf('Clearing Result cache entries that have the suffix "<info>%s</info>"', $suffix) . PHP_EOL);
+
+ $this->_printDeleted($output, $cacheDriver->deleteBySuffix($suffix));
+
+ $outputed = true;
+ }
+ }
+
+ // Removing ALL entries
+ if ( ! $ids && ! $regexps && ! $prefixes && ! $suffixes) {
+ $output->write($outputed ? PHP_EOL : '');
+ $output->write('Clearing ALL Result cache entries' . PHP_EOL);
+
+ $this->_printDeleted($output, $cacheDriver->deleteAll());
+
+ $outputed = true;
+ }
+ }
+
+ private function _printDeleted(Console\Output\OutputInterface $output, array $items)
+ {
+ if ($items) {
+ foreach ($items as $item) {
+ $output->write(' - ' . $item . PHP_EOL);
+ }
+ } else {
+ $output->write('No entries to be deleted.' . PHP_EOL);
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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(<<<EOT
+Converts Doctrine 1.X schema into a Doctrine 2.X schema.
+EOT
+ );
+ }
+
+ /**
+ * @see Console\Command\Command
+ */
+ protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output)
+ {
+ $em = $this->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 '<info>%s</info>' does not exist.", $dirName)
+ );
+ } else if ( ! is_readable($dirName)) {
+ throw new \InvalidArgumentException(
+ sprintf("Doctrine 1.X schema directory '<info>%s</info>' does not have read permissions.", $dirName)
+ );
+ }
+ }
+
+ if ( ! file_exists($destPath)) {
+ throw new \InvalidArgumentException(
+ sprintf("Doctrine 2.X mapping destination directory '<info>%s</info>' does not exist.", $destPath)
+ );
+ } else if ( ! is_writable($destPath)) {
+ throw new \InvalidArgumentException(
+ sprintf("Doctrine 2.X mapping destination directory '<info>%s</info>' 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 "<info>%s</info>"', $class->name) . PHP_EOL);
+ }
+
+ $exporter->setMetadata($metadata);
+ $exporter->export();
+
+ $output->write(PHP_EOL . sprintf(
+ 'Converting Doctrine 1.X schema to "<info>%s</info>" mapping type in "<info>%s</info>"', $toType, $destPath
+ ));
+ } else {
+ $output->write('No Metadata Classes to process.' . PHP_EOL);
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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(
+ '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
+ )
+ ))
+ ->setHelp(<<<EOT
+Convert mapping information between supported formats.
+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) {
+ $em->getConfiguration()->setMetadataDriverImpl(
+ new \Doctrine\ORM\Mapping\Driver\DatabaseDriver(
+ $em->getConnection()->getSchemaManager()
+ )
+ );
+ }
+
+ $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 '<info>%s</info>' does not exist.", $destPath)
+ );
+ } else if ( ! is_writable($destPath)) {
+ throw new \InvalidArgumentException(
+ sprintf("Mapping destination directory '<info>%s</info>' does not have write permissions.", $destPath)
+ );
+ }
+
+ $toType = strtolower($input->getArgument('to-type'));
+
+ $cme = new ClassMetadataExporter();
+ $exporter = $cme->getExporter($toType, $destPath);
+
+ 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 "<info>%s</info>"', $class->name) . PHP_EOL);
+ }
+
+ $exporter->setMetadata($metadata);
+ $exporter->export();
+
+ $output->write(PHP_EOL . sprintf(
+ 'Exporting "<info>%s</info>" mapping information to "<info>%s</info>"' . PHP_EOL, $toType, $destPath
+ ));
+ } else {
+ $output->write('No Metadata Classes to process.' . PHP_EOL);
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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(<<<EOT
+Verify that Doctrine is properly configured for a production environment.
+EOT
+ );
+ }
+
+ /**
+ * @see Console\Command\Command
+ */
+ protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output)
+ {
+ $em = $this->getHelper('em')->getEntityManager();
+
+ $error = false;
+ try {
+ $em->getConfiguration()->ensureProductionSettings();
+
+ if ($input->getOption('complete') !== null) {
+ $em->getConnection()->connect();
+ }
+ } catch (\Exception $e) {
+ $error = true;
+ $output->writeln('<error>' . $e->getMessage() . '</error>');
+ }
+
+ if ($error === false) {
+ $output->write('<info>Environment is correctly configured for production.</info>' . PHP_EOL);
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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(<<<EOT
+Generate entity classes and method stubs from your mapping information.
+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 '<info>%s</info>' does not exist.", $destPath)
+ );
+ } else if ( ! is_writable($destPath)) {
+ throw new \InvalidArgumentException(
+ sprintf("Entities destination directory '<info>%s</info>' 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 "<info>%s</info>"', $metadata->name) . PHP_EOL
+ );
+ }
+
+ // Generating Entities
+ $entityGenerator->generate($metadatas, $destPath);
+
+ // Outputting information message
+ $output->write(PHP_EOL . sprintf('Entity classes generated to "<info>%s</INFO>"', $destPath) . PHP_EOL);
+ } else {
+ $output->write('No Metadata Classes to process.' . PHP_EOL);
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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(<<<EOT
+Generates proxy classes for entity classes.
+EOT
+ );
+ }
+
+ /**
+ * @see Console\Command\Command
+ */
+ protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output)
+ {
+ $em = $this->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 '<info>%s</info>' does not exist.", $destPath)
+ );
+ } else if ( ! is_writable($destPath)) {
+ throw new \InvalidArgumentException(
+ sprintf("Proxies destination directory '<info>%s</info>' does not have write permissions.", $destPath)
+ );
+ }
+
+ if ( count($metadatas)) {
+ foreach ($metadatas as $metadata) {
+ $output->write(
+ sprintf('Processing entity "<info>%s</info>"', $metadata->name) . PHP_EOL
+ );
+ }
+
+ // Generating Proxies
+ $em->getProxyFactory()->generateProxyClasses($metadatas, $destPath);
+
+ // Outputting information message
+ $output->write(PHP_EOL . sprintf('Proxy classes generated to "<info>%s</INFO>"', $destPath) . PHP_EOL);
+ } else {
+ $output->write('No Metadata Classes to process.' . PHP_EOL);
+ }
+
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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(<<<EOT
+Generate repository classes from your mapping information.
+EOT
+ );
+ }
+
+ /**
+ * @see Console\Command\Command
+ */
+ protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output)
+ {
+ $em = $this->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 '<info>%s</info>' does not exist.", $destPath)
+ );
+ } else if ( ! is_writable($destPath)) {
+ throw new \InvalidArgumentException(
+ sprintf("Entities destination directory '<info>%s</info>' 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 "<info>%s</info>"', $metadata->customRepositoryClassName) . PHP_EOL
+ );
+
+ $generator->writeEntityRepositoryClass($metadata->customRepositoryClassName, $destPath);
+
+ $numRepositories++;
+ }
+ }
+
+ if ($numRepositories) {
+ // Outputting information message
+ $output->write(PHP_EOL . sprintf('Repository classes generated to "<info>%s</INFO>"', $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);
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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(<<<EOT
+Executes arbitrary DQL directly from the command line.
+EOT
+ );
+ }
+
+ /**
+ * @see Console\Command\Command
+ */
+ protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output)
+ {
+ $em = $this->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'));
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\ORM\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,
+ Symfony\Component\Console\Command\Command,
+ Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper,
+ Doctrine\ORM\Tools\SchemaTool,
+ Doctrine\ORM\Mapping\Driver\AbstractFileDriver;
+
+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);
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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(<<<EOT
+Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output.
+EOT
+ );
+ }
+
+ protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas)
+ {
+ $output->write('ATTENTION: This operation should not be executed in an production enviroment.' . PHP_EOL . PHP_EOL);
+
+ if ($input->getOption('dump-sql') === true) {
+ $sqls = $schemaTool->getCreateSchemaSql($metadatas);
+ $output->write(implode(';' . PHP_EOL, $sqls) . PHP_EOL);
+ } else {
+ $output->write('Creating database schema...' . PHP_EOL);
+ $schemaTool->createSchema($metadatas);
+ $output->write('Database schema created successfully!' . PHP_EOL);
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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(<<<EOT
+Processes the schema and either drop the database schema of EntityManager Storage Connection or generate the SQL output.
+Beware that the complete database is dropped by this command, even tables that are not relevant to your metadata model.
+EOT
+ );
+ }
+
+ protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas)
+ {
+ $isFullDatabaseDrop = ($input->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 an production enviroment.' . 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);
+ }
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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 update the database schema for a set of classes based on their mappings.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class UpdateCommand extends AbstractCommand
+{
+ /**
+ * @see Console\Command\Command
+ */
+ protected function configure()
+ {
+ $this
+ ->setName('orm:schema-tool:update')
+ ->setDescription(
+ 'Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output.'
+ )
+ ->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,
+ '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 incremental update of the database, but force the operation to run."
+ ),
+ ))
+ ->setHelp(<<<EOT
+Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output.
+Beware that if --complete is not defined, it will do a save update, which does not delete any tables, sequences or affected foreign keys.
+If defined, all assets of the database which are not relevant to the current metadata are dropped by this command.
+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);
+
+ if ($input->getOption('dump-sql') === true) {
+ $sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode);
+ $output->write(implode(';' . PHP_EOL, $sqls) . PHP_EOL);
+ } else if ($input->getOption('force') === true) {
+ $output->write('Updating database schema...' . PHP_EOL);
+ $schemaTool->updateSchema($metadatas, $saveMode);
+ $output->write('Database schema updated successfully!' . PHP_EOL);
+ } else {
+ $output->write('ATTENTION: This operation should not be executed in an production enviroment.' . PHP_EOL);
+ $output->write('Use the incremental update to detect changes during development and use' . PHP_EOL);
+ $output->write('this SQL DDL to manually update your database in production.' . PHP_EOL . PHP_EOL);
+
+ $sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode);
+
+ if (count($sqls)) {
+ $output->write('Schema-Tool would execute ' . count($sqls) . ' queries to update 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 update. The database is in sync with the current entity metadata.' . PHP_EOL);
+ }
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\ORM\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
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class ValidateSchemaCommand extends Console\Command\Command
+{
+ /**
+ * @see Console\Command\Command
+ */
+ protected function configure()
+ {
+ $this
+ ->setName('orm:validate-schema')
+ ->setDescription('Validate that the mapping files.')
+ ->setHelp(<<<EOT
+'Validate that the mapping files are correct and in sync with the database.'
+EOT
+ );
+ }
+
+ /**
+ * @see Console\Command\Command
+ */
+ protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output)
+ {
+ $em = $this->getHelper('em')->getEntityManager();
+
+ $validator = new \Doctrine\ORM\Tools\SchemaValidator($em);
+ $errors = $validator->validateMapping();
+
+ $exit = 0;
+ if ($errors) {
+ foreach ($errors AS $className => $errorMessages) {
+ $output->write("<error>[Mapping] FAIL - The entity-class '" . $className . "' mapping is invalid:</error>\n");
+ foreach ($errorMessages AS $errorMessage) {
+ $output->write('* ' . $errorMessage . "\n");
+ }
+ $output->write("\n");
+ }
+ $exit += 1;
+ } else {
+ $output->write('<info>[Mapping] OK - The mapping files are correct.</info>' . "\n");
+ }
+
+ if (!$validator->schemaInSyncWithMetadata()) {
+ $output->write('<error>[Database] FAIL - The database schema is not in sync with the current mapping file.</error>' . "\n");
+ $exit += 2;
+ } else {
+ $output->write('<info>[Database] OK - The database schema is in sync with the mapping files.</info>' . "\n");
+ }
+
+ exit($exit);
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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
+ * @return void
+ */
+ static public function run(HelperSet $helperSet)
+ {
+ $cli = new Application('Doctrine Command Line Interface', \Doctrine\ORM\Version::VERSION);
+ $cli->setCatchExceptions(true);
+ $cli->setHelperSet($helperSet);
+ self::addCommands($cli);
+ $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(),
+ ));
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Tools\Console\Helper;
+
+use Symfony\Component\Console\Helper\Helper,
+ Doctrine\ORM\EntityManager;
+
+/**
+ * Doctrine CLI Connection Helper.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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';
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\ORM\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
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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());
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+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::load($file));
+ }
+ } else {
+ $schema = array_merge($schema, (array) \Symfony\Component\Yaml\Yaml::load($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);
+ $metadata->setSequenceGeneratorDefinition($definition);
+ $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'];
+ }
+ }
+ 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,
+ 'onUpdate' => isset($relation['onUpdate']) ? $relation['onUpdate'] : 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);
+ }
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class DisconnectedClassMetadataFactory extends ClassMetadataFactory
+{
+ /**
+ * @override
+ */
+ protected function newClassMetadataInstance($className)
+ {
+ $metadata = new ClassMetadataInfo($className);
+ if (strpos($className, "\\") !== false) {
+ $metadata->namespace = strrev(substr( strrev($className), strpos(strrev($className), "\\")+1 ));
+ } else {
+ $metadata->namespace = "";
+ }
+ return $metadata;
+ }
+
+ /**
+ * @override
+ */
+ protected function getParentClasses($name)
+ {
+ return array();
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Tools;
+
+use Doctrine\ORM\Mapping\ClassMetadataInfo,
+ Doctrine\ORM\Mapping\AssociationMapping,
+ Doctrine\Common\Util\Inflector;
+
+/**
+ * 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');
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class EntityGenerator
+{
+ /** The extension to use for written php files */
+ private $_extension = '.php';
+
+ /** Whether or not the current ClassMetadataInfo instance is new or old */
+ private $_isNew = true;
+
+ /** If isNew is false then this variable contains instance of ReflectionClass for current entity */
+ private $_reflection;
+
+ /** Number of spaces to use for indention in generated code */
+ private $_numSpaces = 4;
+
+ /** The actual spaces to use for indention */
+ private $_spaces = ' ';
+
+ /** The class all generated entities should extend */
+ private $_classToExtend;
+
+ /** Whether or not to generation annotations */
+ private $_generateAnnotations = false;
+
+ /** Whether or not to generated sub methods */
+ private $_generateEntityStubMethods = false;
+
+ /** Whether or not to update the entity class if it exists already */
+ private $_updateEntityIfExists = false;
+
+ /** Whether or not to re-generate entity class if it exists already */
+ private $_regenerateEntityIfExists = false;
+
+ private static $_classTemplate =
+'<?php
+
+<namespace>
+
+<entityAnnotation>
+<entityClassName>
+{
+<entityBody>
+}';
+
+ private static $_getMethodTemplate =
+'/**
+ * <description>
+ *
+ * @return <variableType>$<variableName>
+ */
+public function <methodName>()
+{
+<spaces>return $this-><fieldName>;
+}';
+
+ private static $_setMethodTemplate =
+'/**
+ * <description>
+ *
+ * @param <variableType>$<variableName>
+ */
+public function <methodName>(<methodTypeHint>$<variableName>)
+{
+<spaces>$this-><fieldName> = $<variableName>;
+}';
+
+ private static $_addMethodTemplate =
+'/**
+ * <description>
+ *
+ * @param <variableType>$<variableName>
+ */
+public function <methodName>(<methodTypeHint>$<variableName>)
+{
+<spaces>$this-><fieldName>[] = $<variableName>;
+}';
+
+ private static $_lifecycleCallbackMethodTemplate =
+'/**
+ * @<name>
+ */
+public function <methodName>()
+{
+<spaces>// Add your code here
+}';
+
+ /**
+ * 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);
+
+ if ( ! $this->_isNew) {
+ require_once $path;
+
+ $this->_reflection = new \ReflectionClass($metadata->name);
+ }
+
+ // If entity doesn't exist or we're re-generating the entities entirely
+ if ($this->_isNew || ( ! $this->_isNew && $this->_regenerateEntityIfExists)) {
+ 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(
+ '<namespace>',
+ '<entityAnnotation>',
+ '<entityClassName>',
+ '<entityBody>'
+ );
+
+ $replacements = array(
+ $this->_generateEntityNamespace($metadata),
+ $this->_generateEntityDocBlock($metadata),
+ $this->_generateEntityClassName($metadata),
+ $this->_generateEntityBody($metadata)
+ );
+
+ $code = str_replace($placeHolders, $replacements, self::$_classTemplate);
+ return str_replace('<spaces>', $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('<spaces>', $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 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;
+ }
+
+ 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;
+ }
+
+ if ($stubMethods) {
+ $code[] = $stubMethods;
+ }
+
+ if ($lifecycleCallbackMethods) {
+ $code[] = $lifecycleCallbackMethods;
+ }
+
+ return implode("\n", $code);
+ }
+
+ private function _hasProperty($property, ClassMetadataInfo $metadata)
+ {
+ return ($this->_isNew) ? false : $this->_reflection->hasProperty($property);
+ }
+
+ private function _hasMethod($method, ClassMetadataInfo $metadata)
+ {
+ return ($this->_isNew) ? false : $this->_reflection->hasMethod($method);
+ }
+
+ 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[] = ' * '.$metadata->name;
+
+ 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[] = ' * @MappedSupperClass';
+ } else {
+ $lines[] = ' * @Entity';
+ }
+
+ if ($metadata->customRepositoryClassName) {
+ $lines[count($lines) - 1] .= '(repositoryClass="' . $metadata->customRepositoryClassName . '")';
+ }
+
+ if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) {
+ $lines[] = ' * @HasLifecycleCallbacks';
+ }
+ }
+
+ $lines[] = ' */';
+
+ return implode("\n", $lines);
+ }
+
+ private function _generateTableAnnotation($metadata)
+ {
+ $table = array();
+ if ($metadata->table['name']) {
+ $table[] = 'name="' . $metadata->table['name'] . '"';
+ }
+
+ if (isset($metadata->table['schema'])) {
+ $table[] = 'schema="' . $metadata->table['schema'] . '"';
+ }
+
+ return '@Table(' . implode(', ', $table) . ')';
+ }
+
+ private function _generateInheritanceAnnotation($metadata)
+ {
+ if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
+ return '@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 '@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 '@DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})';
+ }
+ }
+
+ private function _generateEntityStubMethods(ClassMetadataInfo $metadata)
+ {
+ $methods = array();
+
+ foreach ($metadata->fieldMappings as $fieldMapping) {
+ if ( ! isset($fieldMapping['id']) || ! $fieldMapping['id']) {
+ 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) {
+ if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
+ $methods[] = $code;
+ }
+ if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
+ $methods[] = $code;
+ }
+ } else if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_MANY) {
+ if ($associationMapping['isOwningSide']) {
+ if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
+ $methods[] = $code;
+ }
+ if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
+ $methods[] = $code;
+ }
+ } else {
+ if ($code = $this->_generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
+ $methods[] = $code;
+ }
+ if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], 'Doctrine\Common\Collections\Collection')) {
+ $methods[] = $code;
+ }
+ }
+ } else if ($associationMapping['type'] == ClassMetadataInfo::MANY_TO_MANY) {
+ if ($code = $this->_generateEntityStubMethod($metadata, 'add', $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 _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('', $methods);
+ }
+ }
+
+ 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 . 'private $' . $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)) {
+ continue;
+ }
+
+ $lines[] = $this->_generateFieldMappingPropertyDocBlock($fieldMapping, $metadata);
+ $lines[] = $this->_spaces . 'private $' . $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)
+ {
+ $methodName = $type . Inflector::classify($fieldName);
+
+ if ($this->_hasMethod($methodName, $metadata)) {
+ return;
+ }
+
+ $var = sprintf('_%sMethodTemplate', $type);
+ $template = self::$$var;
+
+ $variableType = $typeHint ? $typeHint . ' ' : null;
+
+ $types = \Doctrine\DBAL\Types\Type::getTypesMap();
+ $methodTypeHint = $typeHint && ! isset($types[$typeHint]) ? '\\' . $typeHint . ' ' : null;
+
+ $replacements = array(
+ '<description>' => ucfirst($type) . ' ' . $fieldName,
+ '<methodTypeHint>' => $methodTypeHint,
+ '<variableType>' => $variableType,
+ '<variableName>' => Inflector::camelize($fieldName),
+ '<methodName>' => $methodName,
+ '<fieldName>' => $fieldName
+ );
+
+ $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;
+ }
+
+ $replacements = array(
+ '<name>' => $name,
+ '<methodName>' => $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'] ? 'true' : 'false');
+ }
+
+ if (isset($joinColumn['onUpdate'])) {
+ $joinColumnAnnot[] = 'onUpdate=' . ($joinColumn['onUpdate'] ? 'true' : 'false');
+ }
+
+ if (isset($joinColumn['columnDefinition'])) {
+ $joinColumnAnnot[] = 'columnDefinition="' . $joinColumn['columnDefinition'] . '"';
+ }
+
+ return '@JoinColumn(' . implode(', ', $joinColumnAnnot) . ')';
+ }
+
+ private function _generateAssociationMappingPropertyDocBlock(array $associationMapping, ClassMetadataInfo $metadata)
+ {
+ $lines = array();
+ $lines[] = $this->_spaces . '/**';
+ $lines[] = $this->_spaces . ' * @var ' . $associationMapping['targetEntity'];
+
+ if ($this->_generateAnnotations) {
+ $lines[] = $this->_spaces . ' *';
+
+ $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 . ' * @' . $type . '(' . implode(', ', $typeOptions) . ')';
+
+ if (isset($associationMapping['joinColumns']) && $associationMapping['joinColumns']) {
+ $lines[] = $this->_spaces . ' * @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 . ' * @JoinTable(' . implode(', ', $joinTable) . ',';
+ $lines[] = $this->_spaces . ' * joinColumns={';
+
+ foreach ($associationMapping['joinTable']['joinColumns'] as $joinColumn) {
+ $lines[] = $this->_spaces . ' * ' . $this->_generateJoinColumnAnnotation($joinColumn);
+ }
+
+ $lines[] = $this->_spaces . ' * },';
+ $lines[] = $this->_spaces . ' * inverseJoinColumns={';
+
+ foreach ($associationMapping['joinTable']['inverseJoinColumns'] as $joinColumn) {
+ $lines[] = $this->_spaces . ' * ' . $this->_generateJoinColumnAnnotation($joinColumn);
+ }
+
+ $lines[] = $this->_spaces . ' * }';
+ $lines[] = $this->_spaces . ' * )';
+ }
+
+ if (isset($associationMapping['orderBy'])) {
+ $lines[] = $this->_spaces . ' * @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 ' . $fieldMapping['type'] . ' $' . $fieldMapping['fieldName'];
+
+ 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['options'])) {
+ $options = array();
+
+ foreach ($fieldMapping['options'] as $key => $value) {
+ $value = var_export($value, true);
+ $value = str_replace("'", '"', $value);
+ $options[] = ! is_numeric($key) ? $key . '=' . $value:$value;
+ }
+
+ if ($options) {
+ $column[] = 'options={' . implode(', ', $options) . '}';
+ }
+ }
+
+ if (isset($fieldMapping['unique'])) {
+ $column[] = 'unique=' . var_export($fieldMapping['unique'], true);
+ }
+
+ $lines[] = $this->_spaces . ' * @Column(' . implode(', ', $column) . ')';
+
+ if (isset($fieldMapping['id']) && $fieldMapping['id']) {
+ $lines[] = $this->_spaces . ' * @Id';
+
+ if ($generatorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
+ $lines[] = $this->_spaces.' * @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 . ' * @SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')';
+ }
+ }
+
+ if (isset($fieldMapping['version']) && $fieldMapping['version']) {
+ $lines[] = $this->_spaces . ' * @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);
+ }
+
+ private function _getInheritanceTypeString($type)
+ {
+ switch ($type) {
+ case ClassMetadataInfo::INHERITANCE_TYPE_NONE:
+ return 'NONE';
+
+ case ClassMetadataInfo::INHERITANCE_TYPE_JOINED:
+ return 'JOINED';
+
+ case ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE:
+ return 'SINGLE_TABLE';
+
+ case ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS:
+ return 'PER_CLASS';
+
+ default:
+ throw new \InvalidArgumentException('Invalid provided InheritanceType: ' . $type);
+ }
+ }
+
+ private function _getChangeTrackingPolicyString($policy)
+ {
+ switch ($policy) {
+ case ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT:
+ return 'DEFERRED_IMPLICIT';
+
+ case ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT:
+ return 'DEFERRED_EXPLICIT';
+
+ case ClassMetadataInfo::CHANGETRACKING_NOTIFY:
+ return 'NOTIFY';
+
+ default:
+ throw new \InvalidArgumentException('Invalid provided ChangeTrackingPolicy: ' . $policy);
+ }
+ }
+
+ private function _getIdGeneratorTypeString($type)
+ {
+ switch ($type) {
+ case ClassMetadataInfo::GENERATOR_TYPE_AUTO:
+ return 'AUTO';
+
+ case ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE:
+ return 'SEQUENCE';
+
+ case ClassMetadataInfo::GENERATOR_TYPE_TABLE:
+ return 'TABLE';
+
+ case ClassMetadataInfo::GENERATOR_TYPE_IDENTITY:
+ return 'IDENTITY';
+
+ case ClassMetadataInfo::GENERATOR_TYPE_NONE:
+ return 'NONE';
+
+ default:
+ throw new \InvalidArgumentException('Invalid provided IdGeneratorType: ' . $type);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Tools;
+
+/**
+ * Class to generate entity repository classes
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class EntityRepositoryGenerator
+{
+ protected static $_template =
+'<?php
+
+namespace <namespace>;
+
+use Doctrine\ORM\EntityRepository;
+
+/**
+ * <className>
+ *
+ * This class was generated by the Doctrine ORM. Add your own custom
+ * repository methods below.
+ */
+class <className> extends EntityRepository
+{
+}';
+
+ public function generateEntityRepositoryClass($fullClassName)
+ {
+ $namespace = substr($fullClassName, 0, strrpos($fullClassName, '\\'));
+ $className = substr($fullClassName, strrpos($fullClassName, '\\') + 1, strlen($fullClassName));
+
+ $variables = array(
+ '<namespace>' => $namespace,
+ '<className>' => $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);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\ORM\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
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+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;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\ORM\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
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+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;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\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.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan Wage <jonwage@gmail.com>
+ */
+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);
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Tools\Export\Driver;
+
+use Doctrine\ORM\Mapping\ClassMetadataInfo;
+
+/**
+ * Abstract base class which is to be used for the Exporter drivers
+ * which can be found in Doctrine\ORM\Tools\Export\Driver
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan Wage <jonwage@gmail.com>
+ */
+abstract class AbstractExporter
+{
+ protected $_metadata = array();
+ protected $_outputDir;
+ protected $_extension;
+
+ public function __construct($dir = null)
+ {
+ $this->_outputDir = $dir;
+ }
+
+ /**
+ * 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) {
+ $output = $this->exportClassMetadata($metadata);
+ $path = $this->_generateOutputPath($metadata);
+ $dir = dirname($path);
+ if ( ! is_dir($dir)) {
+ mkdir($dir, 0777, true);
+ }
+ 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;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Tools\Export\Driver;
+
+use Doctrine\ORM\Mapping\ClassMetadataInfo,
+ Doctrine\ORM\Mapping\AssociationMapping,
+ Doctrine\ORM\Tools\EntityGenerator;
+
+/**
+ * ClassMetadata exporter for PHP classes with annotations
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan Wage <jonwage@gmail.com>
+ */
+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;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Tools\Export\Driver;
+
+use Doctrine\ORM\Mapping\ClassMetadataInfo;
+
+/**
+ * ClassMetadata exporter for PHP code
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan Wage <jonwage@gmail.com>
+ */
+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[] = '<?php';
+ $lines[] = null;
+ $lines[] = 'use Doctrine\ORM\Mapping\ClassMetadataInfo;';
+ $lines[] = null;
+
+ if ($metadata->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 ($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';
+ $oneToManyMappingArray = array(
+ 'mappedBy' => $associationMapping['mappedBy'],
+ 'orphanRemoval' => $associationMapping['orphanRemoval'],
+ 'orderBy' => $associationMapping['orderBy']
+ );
+
+ $associationMappingArray = array_merge($associationMappingArray, $oneToManyMappingArray);
+ } else if ($associationMapping['type'] == ClassMetadataInfo::MANY_TO_MANY) {
+ $method = 'mapManyToMany';
+ $manyToManyMappingArray = array(
+ 'mappedBy' => $associationMapping['mappedBy'],
+ 'joinTable' => $associationMapping['joinTable'],
+ 'orderBy' => $associationMapping['orderBy']
+ );
+
+ $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;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Tools\Export\Driver;
+
+use Doctrine\ORM\Mapping\ClassMetadataInfo;
+
+/**
+ * ClassMetadata exporter for Doctrine XML mapping files
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan Wage <jonwage@gmail.com>
+ */
+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 version=\"1.0\" encoding=\"utf-8\"?><doctrine-mapping ".
+ "xmlns=\"http://doctrine-project.org/schemas/orm/doctrine-mapping\" " .
+ "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ".
+ "xsi:schemaLocation=\"http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd\" />");
+
+ /*$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('discriminiator-column');
+ $discriminatorColumnXml->addAttribute('name', $metadata->discriminatorColumn['name']);
+ $discriminatorColumnXml->addAttribute('type', $metadata->discriminatorColumn['type']);
+ $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);
+ }
+ }
+
+ $root->addChild('change-tracking-policy', $this->_getChangeTrackingPolicyString($metadata->changeTrackingPolicy));
+
+ 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 $unique) {
+ $uniqueConstraintXml = $uniqueConstraintsXml->addChild('unique-constraint');
+ $uniqueConstraintXml->addAttribute('name', $unique['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 ($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 ($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']);
+ }
+ }
+ }
+
+ 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['orphanRemoval'])) {
+ $associationMappingXml->addAttribute('orphan-removal', $associationMapping['orphanRemoval']);
+ }
+ 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']);
+ }
+ if (isset($joinColumn['onUpdate'])) {
+ $joinColumnXml->addAttribute('on-update', $joinColumn['onUpdate']);
+ }
+ }
+ $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['onUpdate'])) {
+ $inverseJoinColumnXml->addAttribute('on-update', $inverseJoinColumn['onUpdate']);
+ }
+ 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['onUpdate'])) {
+ $joinColumnXml->addAttribute('on-update', $joinColumn['onUpdate']);
+ }
+ 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 ($cascade) {
+ $cascadeXml = $associationMappingXml->addChild('cascade');
+ foreach ($cascade as $type) {
+ $cascadeXml->addChild($type);
+ }
+ }
+ }
+
+ if (isset($metadata->lifecycleCallbacks)) {
+ $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;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Tools\Export\Driver;
+
+use Doctrine\ORM\Mapping\ClassMetadataInfo;
+
+/**
+ * ClassMetadata exporter for Doctrine YAML mapping files
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan Wage <jonwage@gmail.com>
+ */
+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 (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 ($idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
+ $ids[$metadata->getSingleIdentifierFieldName()]['generator']['strategy'] = $this->_getIdGeneratorTypeString($metadata->generatorType);
+ }
+
+ if ($ids) {
+ $array['fields'] = $ids;
+ }
+
+ if ($fieldMappings) {
+ if ( ! isset($array['fields'])) {
+ $array['fields'] = array();
+ }
+ $array['fields'] = array_merge($array['fields'], $fieldMappings);
+ }
+
+ $associations = array();
+ 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';
+ }
+ $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'];
+ }
+ if (isset($joinColumn['onUpdate'])) {
+ $newJoinColumns[$joinColumn['name']]['onUpdate'] = $joinColumn['onUpdate'];
+ }
+ }
+ $oneToOneMappingArray = array(
+ 'mappedBy' => $associationMapping['mappedBy'],
+ 'inversedBy' => $associationMapping['inversedBy'],
+ 'joinColumns' => $newJoinColumns,
+ 'orphanRemoval' => $associationMapping['orphanRemoval'],
+ );
+
+ $associationMappingArray = array_merge($associationMappingArray, $oneToOneMappingArray);
+ $array['oneToOne'][$name] = $associationMappingArray;
+ } else if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_MANY) {
+ $oneToManyMappingArray = array(
+ 'mappedBy' => $associationMapping['mappedBy'],
+ 'inversedBy' => $associationMapping['inversedBy'],
+ 'orphanRemoval' => $associationMapping['orphanRemoval'],
+ 'orderBy' => $associationMapping['orderBy']
+ );
+
+ $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' => $associationMapping['joinTable'],
+ '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);
+ }
+}
--- /dev/null
+<?php
+
+namespace Doctrine\ORM\Tools\Export;
+
+use Doctrine\ORM\ORMException;
+
+class ExportException extends ORMException
+{
+ public static function invalidExporterDriverType($type)
+ {
+ return new self("The specified export driver '$type' does not exist");
+ }
+
+ public static function invalidMappingDriverType($type)
+ {
+ return new self("The mapping driver '$type' does not exist");
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Tools;
+
+use Doctrine\ORM\ORMException,
+ Doctrine\DBAL\Types\Type,
+ 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
+ * <tt>ClassMetadata</tt> class descriptors.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class SchemaTool
+{
+ /**
+ * @var \Doctrine\ORM\EntityManager
+ */
+ private $_em;
+
+ /**
+ * @var \Doctrine\DBAL\Platforms\AbstractPlatform
+ */
+ private $_platform;
+
+ /**
+ * 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();
+ }
+
+ /**
+ * Creates the database schema for the given array of ClassMetadata instances.
+ *
+ * @param array $classes
+ */
+ public function createSchema(array $classes)
+ {
+ $createSchemaSql = $this->getCreateSchemaSql($classes);
+ $conn = $this->_em->getConnection();
+
+ foreach ($createSchemaSql as $sql) {
+ $conn->executeQuery($sql);
+ }
+ }
+
+ /**
+ * 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)
+ {
+ $processedClasses = array(); // Reminder for processed classes, used for hierarchies
+
+ $sm = $this->_em->getConnection()->getSchemaManager();
+ $metadataSchemaConfig = $sm->createSchemaConfig();
+ $metadataSchemaConfig->setExplicitForeignKeyIndexes(false);
+ $schema = new \Doctrine\DBAL\Schema\Schema(array(), array(), $metadataSchemaConfig);
+
+ $evm = $this->_em->getEventManager();
+
+ foreach ($classes as $class) {
+ if ($this->processingNotRequired($class, $processedClasses)) {
+ continue;
+ }
+
+ $table = $schema->createTable($class->getQuotedTableName($this->_platform));
+
+ // TODO: Remove
+ /**if ($class->isIdGeneratorIdentity()) {
+ $table->setIdGeneratorType(\Doctrine\DBAL\Schema\Table::ID_IDENTITY);
+ } else if ($class->isIdGeneratorSequence()) {
+ $table->setIdGeneratorType(\Doctrine\DBAL\Schema\Table::ID_SEQUENCE);
+ }*/
+
+ $columns = array(); // table columns
+
+ if ($class->isInheritanceTypeSingleTable()) {
+ $columns = $this->_gatherColumns($class, $table);
+ $this->_gatherRelationsSql($class, $table, $schema);
+
+ // Add the discriminator column
+ $discrColumnDef = $this->_getDiscriminatorColumnDefinition($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 = $class->getQuotedColumnName($mapping['fieldName'], $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) {
+ $discrColumnDef = $this->_getDiscriminatorColumnDefinition($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 = $class->getQuotedColumnName($class->identifier[0], $this->_platform);
+ // TODO: This seems rather hackish, can we optimize it?
+ $table->getColumn($class->identifier[0])->setAutoincrement(false);
+
+ $pkColumns[] = $columnName;
+ // TODO: REMOVE
+ /*if ($table->isIdGeneratorIdentity()) {
+ $table->setIdGeneratorType(\Doctrine\DBAL\Schema\Table::ID_NONE);
+ }*/
+
+ // Add a FK constraint on the ID column
+ $table->addUnnamedForeignKeyConstraint(
+ $this->_em->getClassMetadata($class->rootEntityName)->getTableName(),
+ 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);
+ }
+
+ if (isset($class->table['indexes'])) {
+ foreach ($class->table['indexes'] AS $indexName => $indexData) {
+ $table->addIndex($indexData['columns'], $indexName);
+ }
+ }
+
+ if (isset($class->table['uniqueConstraints'])) {
+ foreach ($class->table['uniqueConstraints'] AS $indexName => $indexData) {
+ $table->addUniqueIndex($indexData['columns'], $indexName);
+ }
+ }
+
+ $processedClasses[$class->name] = true;
+
+ if ($class->isIdGeneratorSequence() && $class->name == $class->rootEntityName) {
+ $seqDef = $class->sequenceGeneratorDefinition;
+
+ if (!$schema->hasSequence($seqDef['sequenceName'])) {
+ $schema->createSequence(
+ $seqDef['sequenceName'],
+ $seqDef['allocationSize'],
+ $seqDef['initialValue']
+ );
+ }
+ }
+
+ if ($evm->hasListeners(ToolEvents::postGenerateSchemaTable)) {
+ $evm->dispatchEvent(ToolEvents::postGenerateSchemaTable, new GenerateSchemaTableEventArgs($class, $schema, $table));
+ }
+ }
+
+ if ($evm->hasListeners(ToolEvents::postGenerateSchema)) {
+ $evm->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 _getDiscriminatorColumnDefinition($class, $table)
+ {
+ $discrColumn = $class->discriminatorColumn;
+
+ if (!isset($discrColumn['type']) || (strtolower($discrColumn['type']) == 'string' && $discrColumn['length'] === null)) {
+ $discrColumn['type'] = 'string';
+ $discrColumn['length'] = 255;
+ }
+
+ $table->addColumn(
+ $discrColumn['name'],
+ $discrColumn['type'],
+ array('length' => $discrColumn['length'], 'notnull' => true)
+ );
+ }
+
+ /**
+ * 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) {
+ $column = $this->_gatherColumn($class, $mapping, $table);
+
+ if ($class->isIdentifier($mapping['fieldName'])) {
+ $pkColumns[] = $class->getQuotedColumnName($mapping['fieldName'], $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 = $class->getQuotedColumnName($mapping['fieldName'], $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 ($class->isIdGeneratorIdentity() && $class->getIdentifierFieldNames() == array($mapping['fieldName'])) {
+ $options['autoincrement'] = true;
+ }
+
+ 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($foreignClass->getQuotedJoinTableName($mapping, $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);
+ }
+ }
+ }
+ }
+
+ /**
+ * 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 \Doctrine\ORM\Mapping\AssociationMapping $mapping
+ * @param array $primaryKeyColumns
+ * @param array $uniqueConstraints
+ */
+ private function _gatherRelationJoinColumns($joinColumns, $theJoinTable, $class, $mapping, &$primaryKeyColumns, &$uniqueConstraints)
+ {
+ $localColumns = array();
+ $foreignColumns = array();
+ $fkOptions = array();
+
+ foreach ($joinColumns as $joinColumn) {
+ $columnName = $joinColumn['name'];
+ $referencedFieldName = $class->getFieldName($joinColumn['referencedColumnName']);
+
+ if ( ! $class->hasField($referencedFieldName)) {
+ throw new \Doctrine\ORM\ORMException(
+ "Column name `".$joinColumn['referencedColumnName']."` referenced for relation from ".
+ $mapping['sourceEntity'] . " towards ". $mapping['targetEntity'] . " does not exist."
+ );
+ }
+
+ $primaryKeyColumns[] = $columnName;
+ $localColumns[] = $columnName;
+ $foreignColumns[] = $joinColumn['referencedColumnName'];
+
+ if ( ! $theJoinTable->hasColumn($joinColumn['name'])) {
+ // 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 = $class->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 ($fieldMapping['type'] == "string") {
+ $columnOptions['length'] = $fieldMapping['length'];
+ } else if ($fieldMapping['type'] == "decimal") {
+ $columnOptions['scale'] = $fieldMapping['scale'];
+ $columnOptions['precision'] = $fieldMapping['precision'];
+ }
+
+ $theJoinTable->addColumn(
+ $columnName, $class->getTypeOfColumn($joinColumn['referencedColumnName']), $columnOptions
+ );
+ }
+
+ if (isset($joinColumn['unique']) && $joinColumn['unique'] == true) {
+ $uniqueConstraints[] = array('columns' => array($columnName));
+ }
+
+ if (isset($joinColumn['onUpdate'])) {
+ $fkOptions['onUpdate'] = $joinColumn['onUpdate'];
+ }
+
+ if (isset($joinColumn['onDelete'])) {
+ $fkOptions['onDelete'] = $joinColumn['onDelete'];
+ }
+ }
+
+ $theJoinTable->addUnnamedForeignKeyConstraint(
+ $class->getTableName(), $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) {
+ $conn->executeQuery($sql);
+ }
+ }
+
+ /**
+ * 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();
+ }
+
+ /**
+ *
+ * @param array $classes
+ * @return array
+ */
+ public function getDropSchemaSQL(array $classes)
+ {
+ $sm = $this->_em->getConnection()->getSchemaManager();
+
+ $sql = array();
+ $orderedTables = array();
+
+ foreach ($classes AS $class) {
+ if ($class->isIdGeneratorSequence() && $class->name == $class->rootEntityName && $this->_platform->supportsSequences()) {
+ $sql[] = $this->_platform->getDropSequenceSQL($class->sequenceGeneratorDefinition['sequenceName']);
+ }
+ }
+
+ $commitOrder = $this->_getCommitOrder($classes);
+ $associationTables = $this->_getAssociationTables($commitOrder);
+
+ // Drop association tables first
+ foreach ($associationTables as $associationTable) {
+ if (!in_array($associationTable, $orderedTables)) {
+ $orderedTables[] = $associationTable;
+ }
+ }
+
+ // Drop tables in reverse commit order
+ for ($i = count($commitOrder) - 1; $i >= 0; --$i) {
+ $class = $commitOrder[$i];
+
+ if (($class->isInheritanceTypeSingleTable() && $class->name != $class->rootEntityName)
+ || $class->isMappedSuperclass) {
+ continue;
+ }
+
+ if (!in_array($class->getTableName(), $orderedTables)) {
+ $orderedTables[] = $class->getTableName();
+ }
+ }
+
+ $dropTablesSql = array();
+ foreach ($orderedTables AS $tableName) {
+ /* @var $sm \Doctrine\DBAL\Schema\AbstractSchemaManager */
+ $foreignKeys = $sm->listTableForeignKeys($tableName);
+ foreach ($foreignKeys AS $foreignKey) {
+ $sql[] = $this->_platform->getDropForeignKeySQL($foreignKey, $tableName);
+ }
+ $dropTablesSql[] = $this->_platform->getDropTableSQL($tableName);
+ }
+
+ return array_merge($sql, $dropTablesSql);
+ }
+
+ /**
+ * Updates the database schema of the given classes by comparing the ClassMetadata
+ * ins$tableNametances to the current database schema that is inspected.
+ *
+ * @param array $classes
+ * @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.
+ *
+ * @param array $classes The classes to consider.
+ * @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);
+ }
+ }
+
+ private function _getCommitOrder(array $classes)
+ {
+ $calc = new CommitOrderCalculator;
+
+ // Calculate dependencies
+ foreach ($classes as $class) {
+ $calc->addClass($class);
+
+ foreach ($class->associationMappings as $assoc) {
+ if ($assoc['isOwningSide']) {
+ $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']);
+
+ if ( ! $calc->hasClass($targetClass->name)) {
+ $calc->addClass($targetClass);
+ }
+
+ // add dependency ($targetClass before $class)
+ $calc->addDependency($targetClass, $class);
+ }
+ }
+ }
+
+ return $calc->getCommitOrder();
+ }
+
+ private function _getAssociationTables(array $classes)
+ {
+ $associationTables = array();
+
+ foreach ($classes as $class) {
+ foreach ($class->associationMappings as $assoc) {
+ if ($assoc['isOwningSide'] && $assoc['type'] == ClassMetadata::MANY_TO_MANY) {
+ $associationTables[] = $assoc['joinTable']['name'];
+ }
+ }
+ }
+
+ return $associationTables;
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\ORM\Tools;
+
+use Doctrine\ORM\EntityManager;
+use Doctrine\ORM\Mapping\ClassMetadataInfo;
+
+/**
+ * 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
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class SchemaValidator
+{
+ /**
+ * @var EntityManager
+ */
+ private $em;
+
+ /**
+ * @param EntityManager $em
+ */
+ public function __construct(EntityManager $em)
+ {
+ $this->em = $em;
+ }
+
+ /**
+ * Checks the internal consistency of 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) {
+ $ce = array();
+ /* @var $class ClassMetadata */
+ foreach ($class->associationMappings AS $fieldName => $assoc) {
+ if (!$cmf->hasMetadataFor($assoc['targetEntity'])) {
+ $ce[] = "The target entity '" . $assoc['targetEntity'] . "' specified on " . $class->name . '#' . $fieldName . ' is unknown.';
+ }
+
+ if ($assoc['mappedBy'] && $assoc['inversedBy']) {
+ $ce[] = "The association " . $class . "#" . $fieldName . " cannot be defined as both inverse and owning.";
+ }
+
+ $targetMetadata = $cmf->getMetadataFor($assoc['targetEntity']);
+
+ /* @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' 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.";
+ }
+ }
+
+ if ($assoc['isOwningSide']) {
+ if ($assoc['type'] == ClassMetadataInfo::MANY_TO_MANY) {
+ foreach ($assoc['joinTable']['joinColumns'] AS $joinColumn) {
+ if (!isset($class->fieldNames[$joinColumn['referencedColumnName']])) {
+ $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' does not " .
+ "have a corresponding field with this column name on the class '" . $class->name . "'.";
+ break;
+ }
+
+ $fieldName = $class->fieldNames[$joinColumn['referencedColumnName']];
+ if (!in_array($fieldName, $class->identifier)) {
+ $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " .
+ "has to be a primary key column.";
+ }
+ }
+ foreach ($assoc['joinTable']['inverseJoinColumns'] AS $inverseJoinColumn) {
+ $targetClass = $cmf->getMetadataFor($assoc['targetEntity']);
+ if (!isset($targetClass->fieldNames[$inverseJoinColumn['referencedColumnName']])) {
+ $ce[] = "The inverse referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' does not " .
+ "have a corresponding field with this column name on the class '" . $targetClass->name . "'.";
+ break;
+ }
+
+ $fieldName = $targetClass->fieldNames[$inverseJoinColumn['referencedColumnName']];
+ if (!in_array($fieldName, $targetClass->identifier)) {
+ $ce[] = "The referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' " .
+ "has to be a primary key column.";
+ }
+ }
+ } else if ($assoc['type'] & ClassMetadataInfo::TO_ONE) {
+ foreach ($assoc['joinColumns'] AS $joinColumn) {
+ $targetClass = $cmf->getMetadataFor($assoc['targetEntity']);
+ if (!isset($targetClass->fieldNames[$joinColumn['referencedColumnName']])) {
+ $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' does not " .
+ "have a corresponding field with this column name on the class '" . $targetClass->name . "'.";
+ break;
+ }
+
+ $fieldName = $targetClass->fieldNames[$joinColumn['referencedColumnName']];
+ if (!in_array($fieldName, $targetClass->identifier)) {
+ $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " .
+ "has to be a primary key column.";
+ }
+ }
+ }
+ }
+
+ if (isset($assoc['orderBy']) && $assoc['orderBy'] !== null) {
+ $targetClass = $cmf->getMetadataFor($assoc['targetEntity']);
+ foreach ($assoc['orderBy'] AS $orderField => $orientation) {
+ if (!$targetClass->hasField($orderField)) {
+ $ce[] = "The association " . $class->name."#".$fieldName." is ordered by a foreign field " .
+ $orderField . " that is not a field on the target entity " . $targetClass->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.";
+ }
+ }
+
+ if ($ce) {
+ $errors[$class->name] = $ce;
+ }
+ }
+
+ return $errors;
+ }
+
+ /**
+ * 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, false)) == 0);
+ }
+}
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\ORM\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';
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace Doctrine\ORM\Tools;
+
+use Doctrine\ORM\ORMException;
+
+class ToolsException extends ORMException
+{
+ public static function couldNotMapDoctrine1Type($type)
+ {
+ return new self("Could not map doctrine 1 type '$type'!");
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\ORM;
+
+/**
+ * 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
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class TransactionRequiredException extends ORMException
+{
+ static public function transactionRequired()
+ {
+ return new self('An open transaction is required for this operation.');
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM;
+
+use Exception, InvalidArgumentException, UnexpectedValueException,
+ Doctrine\Common\Collections\ArrayCollection,
+ Doctrine\Common\Collections\Collection,
+ Doctrine\Common\NotifyPropertyChanged,
+ Doctrine\Common\PropertyChangedListener,
+ 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 <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @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 EventManager
+ */
+ private $evm;
+
+ /**
+ * Orphaned entities that are scheduled for removal.
+ *
+ * @var array
+ */
+ private $orphanRemovals = array();
+
+ //private $_readOnlyObjects = 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
+ *
+ */
+ public function commit()
+ {
+ // Compute changes done since last commit.
+ $this->computeChangeSets();
+
+ 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();
+ }
+
+ // Clear up
+ $this->entityInsertions =
+ $this->entityUpdates =
+ $this->entityDeletions =
+ $this->extraUpdates =
+ $this->entityChangeSets =
+ $this->collectionUpdates =
+ $this->collectionDeletions =
+ $this->visitedCollections =
+ $this->scheduledForDirtyCheck =
+ $this->orphanRemovals = array();
+ }
+
+ /**
+ * 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.
+ *
+ * @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.
+ *
+ * @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)
+ {
+ if ( ! $class->isInheritanceTypeNone()) {
+ $class = $this->em->getClassMetadata(get_class($entity));
+ }
+
+ $oid = spl_object_hash($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
+ $coll = new PersistentCollection(
+ $this->em,
+ $this->em->getClassMetadata($assoc['targetEntity']),
+ $value
+ );
+
+ $coll->setOwner($entity, $assoc);
+ $coll->setDirty( ! $coll->isEmpty());
+ $class->reflFields[$name]->setValue($entity, $coll);
+ $actualData[$name] = $coll;
+ } else if ( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) {
+ $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])) {
+ $assoc = $class->associationMappings[$propName];
+ if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
+ $changeSet[$propName] = array(null, $actualValue);
+ }
+ } else {
+ $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 ? $this->entityChangeSets[$oid] : array();
+
+ foreach ($actualData as $propName => $actualValue) {
+ $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null;
+ if (isset($class->associationMappings[$propName])) {
+ $assoc = $class->associationMappings[$propName];
+ if ($assoc['type'] & ClassMetadata::TO_ONE && $orgValue !== $actualValue) {
+ if ($assoc['isOwningSide']) {
+ $changeSet[$propName] = array($orgValue, $actualValue);
+ }
+ if ($orgValue !== null && $assoc['orphanRemoval']) {
+ $this->scheduleOrphanRemoval($orgValue);
+ }
+ } else if ($orgValue instanceof PersistentCollection && $orgValue !== $actualValue) {
+ // A PersistentCollection was de-referenced, so delete it.
+ if ( ! in_array($orgValue, $this->collectionDeletions, true)) {
+ $this->collectionDeletions[] = $orgValue;
+ }
+ }
+ } else if ($isChangeTrackingNotify) {
+ continue;
+ } else 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) {
+ $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) {
+ $val = $class->reflFields[$field]->getValue($entity);
+ if ($val !== null) {
+ $this->computeAssociationChanges($assoc, $val);
+ }
+ }
+ }
+
+ /**
+ * 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.
+ foreach ($this->entityInsertions as $entity) {
+ $class = $this->em->getClassMetadata(get_class($entity));
+ $this->computeChangeSet($class, $entity);
+ }
+
+ // 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.
+ $entitiesToProcess = ! $class->isChangeTrackingDeferredImplicit() ?
+ (isset($this->scheduledForDirtyCheck[$className]) ?
+ $this->scheduledForDirtyCheck[$className] : array())
+ : $entities;
+
+ foreach ($entitiesToProcess as $entity) {
+ // Ignore uninitialized proxy objects
+ if (/* $entity is readOnly || */ $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 AssociationMapping $assoc
+ * @param mixed $value The value of the association.
+ */
+ private function computeAssociationChanges($assoc, $value)
+ {
+ if ($value instanceof PersistentCollection && $value->isDirty()) {
+ if ($assoc['isOwningSide']) {
+ $this->collectionUpdates[] = $value;
+ }
+ $this->visitedCollections[] = $value;
+ }
+
+ // Look through the entities, and in any of their associations, for transient (new)
+ // enities, recursively. ("Persistence by reachability")
+ if ($assoc['type'] & ClassMetadata::TO_ONE) {
+ if ($value instanceof Proxy && ! $value->__isInitialized__) {
+ return; // Ignore uninitialized proxy objects
+ }
+ $value = array($value);
+ } else if ($value instanceof PersistentCollection) {
+ // Unwrap. Uninitialized collections will simply be empty.
+ $value = $value->unwrap();
+ }
+
+ $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
+ foreach ($value as $entry) {
+ $state = $this->getEntityState($entry, self::STATE_NEW);
+ $oid = spl_object_hash($entry);
+ if ($state == self::STATE_NEW) {
+ if ( ! $assoc['isCascadePersist']) {
+ throw new InvalidArgumentException("A new entity was found through a relationship that was not"
+ . " configured to cascade persist operations: " . self::objToStr($entry) . "."
+ . " Explicitly persist the new entity or configure cascading persist operations"
+ . " on the relationship.");
+ }
+ $this->persistNew($targetClass, $entry);
+ $this->computeChangeSet($targetClass, $entry);
+ } else if ($state == self::STATE_REMOVED) {
+ return new InvalidArgumentException("Removed entity detected during flush: "
+ . self::objToStr($entry).". Remove deleted entities from associations.");
+ } else if ($state == 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 new InvalidArgumentException("A detached entity was found through a "
+ . "relationship during cascading a persist operation.");
+ }
+ // MANAGED associated entities are already taken into account
+ // during changeset calculation anyway, since they are in the identity map.
+ }
+ }
+
+ 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) {
+ $this->entityIdentifiers[$oid] = array($class->identifier[0] => $idValue);
+ $class->setIdentifierValues($entity, $this->entityIdentifiers[$oid]);
+ } else {
+ $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 InvalidArgumentException If the passed entity is not MANAGED.
+ */
+ public function recomputeSingleEntityChangeSet($class, $entity)
+ {
+ $oid = spl_object_hash($entity);
+
+ if ( ! isset($this->entityStates[$oid]) || $this->entityStates[$oid] != self::STATE_MANAGED) {
+ throw new InvalidArgumentException('Entity must be managed.');
+ }
+
+ /* TODO: Just return if changetracking policy 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);
+
+ $hasLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postPersist]);
+ $hasListeners = $this->evm->hasListeners(Events::postPersist);
+ if ($hasLifecycleCallbacks || $hasListeners) {
+ $entities = array();
+ }
+
+ foreach ($this->entityInsertions as $oid => $entity) {
+ if (get_class($entity) === $className) {
+ $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);
+ }
+ }
+
+ if ($hasLifecycleCallbacks || $hasListeners) {
+ 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 (get_class($entity) == $className || $entity instanceof Proxy && $entity instanceof $className) {
+
+ 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])
+ );
+ }
+
+ $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 (get_class($entity) == $className || $entity instanceof Proxy && $entity instanceof $className) {
+ $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.
+ *
+ * @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).
+ $newNodes = array();
+ foreach ($entityChangeSet as $oid => $entity) {
+ $className = get_class($entity);
+ if ( ! $calc->hasClass($className)) {
+ $class = $this->em->getClassMetadata($className);
+ $calc->addClass($class);
+ $newNodes[] = $class;
+ }
+ }
+
+ // Calculate dependencies for new nodes
+ foreach ($newNodes as $class) {
+ foreach ($class->associationMappings as $assoc) {
+ if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
+ $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
+ if ( ! $calc->hasClass($targetClass->name)) {
+ $calc->addClass($targetClass);
+ }
+ $calc->addDependency($targetClass, $class);
+ // If the target class has mapped subclasses,
+ // these share the same dependency.
+ if ($targetClass->subClasses) {
+ foreach ($targetClass->subClasses as $subClassName) {
+ $targetSubClass = $this->em->getClassMetadata($subClassName);
+ if ( ! $calc->hasClass($subClassName)) {
+ $calc->addClass($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.
+ */
+ 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 new InvalidArgumentException("Removed entity can not be scheduled for insertion.");
+ }
+ if (isset($this->entityInsertions[$oid])) {
+ throw new InvalidArgumentException("Entity can not be scheduled for insertion twice.");
+ }
+
+ $this->entityInsertions[$oid] = $entity;
+
+ if (isset($this->entityIdentifiers[$oid])) {
+ $this->addToIdentityMap($entity);
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public function scheduleForUpdate($entity)
+ {
+ $oid = spl_object_hash($entity);
+ if ( ! isset($this->entityIdentifiers[$oid])) {
+ throw new InvalidArgumentException("Entity has no identity.");
+ }
+ if (isset($this->entityDeletions[$oid])) {
+ throw new InvalidArgumentException("Entity is removed.");
+ }
+
+ 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);
+ if (isset($this->extraUpdates[$oid])) {
+ list($ignored, $changeset2) = $this->extraUpdates[$oid];
+ $this->extraUpdates[$oid] = array($entity, $changeset + $changeset2);
+ } else {
+ $this->extraUpdates[$oid] = array($entity, $changeset);
+ }
+ }
+
+ /**
+ * 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)]);
+ }
+
+ 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]);
+ return; // entity has not been persisted yet, so nothing more to do.
+ }
+
+ if ( ! $this->isInIdentityMap($entity)) {
+ return; // ignore
+ }
+
+ $this->removeFromIdentityMap($entity);
+
+ if (isset($this->entityUpdates[$oid])) {
+ unset($this->entityUpdates[$oid]);
+ }
+ if ( ! isset($this->entityDeletions[$oid])) {
+ $this->entityDeletions[$oid] = $entity;
+ }
+ }
+
+ /**
+ * 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.
+ * @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 new InvalidArgumentException("The given entity has no identity.");
+ }
+ $className = $classMetadata->rootEntityName;
+ if (isset($this->identityMap[$className][$idHash])) {
+ return false;
+ }
+ $this->identityMap[$className][$idHash] = $entity;
+ if ($entity instanceof NotifyPropertyChanged) {
+ $entity->addPropertyChangedListener($this);
+ }
+ 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])) {
+ // 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.
+ if ($assume === null) {
+ $class = $this->em->getClassMetadata(get_class($entity));
+ $id = $class->getIdentifierValues($entity);
+ if ( ! $id) {
+ return self::STATE_NEW;
+ } else if ($class->isIdentifierNatural()) {
+ // Check for a version field, if available, to avoid a db lookup.
+ if ($class->isVersioned) {
+ if ($class->getFieldValue($entity, $class->versionField)) {
+ return self::STATE_DETACHED;
+ } else {
+ return self::STATE_NEW;
+ }
+ } else {
+ // Last try before db lookup: check the identity map.
+ if ($this->tryGetById($id, $class->rootEntityName)) {
+ return self::STATE_DETACHED;
+ } else {
+ // db lookup
+ if ($this->getEntityPersister(get_class($entity))->exists($entity)) {
+ return self::STATE_DETACHED;
+ } else {
+ return self::STATE_NEW;
+ }
+ }
+ }
+ } else {
+ return self::STATE_DETACHED;
+ }
+ } else {
+ return $assume;
+ }
+ }
+ return $this->entityStates[$oid];
+ }
+
+ /**
+ * INTERNAL:
+ * Removes an entity from the identity map. This effectively detaches the
+ * entity from the persistence management of Doctrine.
+ *
+ * @ignore
+ * @param object $entity
+ * @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 new InvalidArgumentException("The given entity has no identity.");
+ }
+ $className = $classMetadata->rootEntityName;
+ if (isset($this->identityMap[$className][$idHash])) {
+ unset($this->identityMap[$className][$idHash]);
+ //$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)
+ {
+ return isset($this->identityMap[$rootClassName][$idHash]) ?
+ $this->identityMap[$rootClassName][$idHash] : 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.
+ */
+ 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 new InvalidArgumentException("Detached entity passed to persist().");
+ default:
+ throw new UnexpectedValueException("Unexpected entity state: $entityState.");
+ }
+
+ $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 InvalidArgumentException If the instance is a detached entity.
+ */
+ private function doRemove($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));
+ $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 new InvalidArgumentException("A detached entity can not be removed.");
+ default:
+ throw new UnexpectedValueException("Unexpected entity state: $entityState.");
+ }
+
+ $this->cascadeRemove($entity, $visited);
+ }
+
+ /**
+ * Merges the state of the given detached entity into this UnitOfWork.
+ *
+ * @param object $entity
+ * @return object The managed copy of the entity.
+ * @throws OptimisticLockException If the entity uses optimistic locking through a version
+ * attribute and the version check against the managed copy fails.
+ *
+ * @todo Require active transaction!? OptimisticLockException may result in undefined state!?
+ */
+ public function merge($entity)
+ {
+ $visited = array();
+ return $this->doMerge($entity, $visited);
+ }
+
+ /**
+ * Executes a merge operation on an entity.
+ *
+ * @param object $entity
+ * @param array $visited
+ * @return object The managed copy of the entity.
+ * @throws OptimisticLockException If the entity uses optimistic locking through a version
+ * attribute and the version check against the managed copy fails.
+ * @throws InvalidArgumentException If the entity instance is NEW.
+ */
+ private function doMerge($entity, array &$visited, $prevManagedCopy = null, $assoc = null)
+ {
+ $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));
+
+ // 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.
+ if ($this->getEntityState($entity, self::STATE_DETACHED) == self::STATE_MANAGED) {
+ $managedCopy = $entity;
+ } else {
+ // 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 = $class->newInstance();
+ $this->persistNew($class, $managedCopy);
+ } else {
+ $managedCopy = $this->tryGetById($id, $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 new InvalidArgumentException('Removed entity detected during merge.'
+ . ' Can not merge with a removed entity.');
+ }
+ } else {
+ // We need to fetch the managed copy in order to merge.
+ $managedCopy = $this->em->find($class->name, $id);
+ }
+
+ if ($managedCopy === null) {
+ // If the identifier is ASSIGNED, it is NEW, otherwise an error
+ // since the managed entity was not found.
+ if ($class->isIdentifierNatural()) {
+ $managedCopy = $class->newInstance();
+ $class->setIdentifierValues($managedCopy, $id);
+ $this->persistNew($class, $managedCopy);
+ } else {
+ throw new EntityNotFoundException;
+ }
+ }
+ }
+
+ 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($entityVersion, $managedCopyVersion);
+ }
+ }
+
+ // Merge state of $entity into existing (managed) entity
+ foreach ($class->reflFields as $name => $prop) {
+ 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) {
+ $prop->setValue($managedCopy, $other);
+ } else {
+ $targetClass = $this->em->getClassMetadata($assoc2['targetEntity']);
+ $id = $targetClass->getIdentifierValues($other);
+ $proxy = $this->em->getProxyFactory()->getProxy($assoc2['targetEntity'], $id);
+ $prop->setValue($managedCopy, $proxy);
+ $this->registerManaged($proxy, $id, array());
+ }
+ }
+ } 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();
+ if (!$managedCol->isEmpty()) {
+ $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
+ */
+ private function doDetach($entity, array &$visited)
+ {
+ $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:
+ $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;
+ }
+
+ $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 InvalidArgumentException 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) {
+ $this->getEntityPersister($class->name)->refresh(
+ array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
+ $entity
+ );
+ } else {
+ throw new InvalidArgumentException("Entity is not MANAGED.");
+ }
+
+ $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));
+ foreach ($class->associationMappings as $assoc) {
+ if ( ! $assoc['isCascadeRefresh']) {
+ continue;
+ }
+ $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
+ if ($relatedEntities instanceof Collection) {
+ if ($relatedEntities instanceof PersistentCollection) {
+ // Unwrap so that foreach() does not initialize
+ $relatedEntities = $relatedEntities->unwrap();
+ }
+ foreach ($relatedEntities as $relatedEntity) {
+ $this->doRefresh($relatedEntity, $visited);
+ }
+ } else if ($relatedEntities !== null) {
+ $this->doRefresh($relatedEntities, $visited);
+ }
+ }
+ }
+
+ /**
+ * 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));
+ foreach ($class->associationMappings as $assoc) {
+ if ( ! $assoc['isCascadeDetach']) {
+ continue;
+ }
+ $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
+ if ($relatedEntities instanceof Collection) {
+ if ($relatedEntities instanceof PersistentCollection) {
+ // Unwrap so that foreach() does not initialize
+ $relatedEntities = $relatedEntities->unwrap();
+ }
+ foreach ($relatedEntities as $relatedEntity) {
+ $this->doDetach($relatedEntity, $visited);
+ }
+ } else if ($relatedEntities !== null) {
+ $this->doDetach($relatedEntities, $visited);
+ }
+ }
+ }
+
+ /**
+ * 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));
+ foreach ($class->associationMappings as $assoc) {
+ if ( ! $assoc['isCascadeMerge']) {
+ continue;
+ }
+ $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
+ if ($relatedEntities instanceof Collection) {
+ 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
+ * @param array $insertNow
+ */
+ private function cascadePersist($entity, array &$visited)
+ {
+ $class = $this->em->getClassMetadata(get_class($entity));
+ foreach ($class->associationMappings as $assoc) {
+ if ( ! $assoc['isCascadePersist']) {
+ continue;
+ }
+ $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
+ if (($relatedEntities instanceof Collection || is_array($relatedEntities))) {
+ if ($relatedEntities instanceof PersistentCollection) {
+ // Unwrap so that foreach() does not initialize
+ $relatedEntities = $relatedEntities->unwrap();
+ }
+ foreach ($relatedEntities as $relatedEntity) {
+ $this->doPersist($relatedEntity, $visited);
+ }
+ } else if ($relatedEntities !== null) {
+ $this->doPersist($relatedEntities, $visited);
+ }
+ }
+ }
+
+ /**
+ * 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));
+ foreach ($class->associationMappings as $assoc) {
+ if ( ! $assoc['isCascadeRemove']) {
+ continue;
+ }
+ //TODO: If $entity instanceof Proxy => Initialize ?
+ $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
+ if ($relatedEntities instanceof Collection || is_array($relatedEntities)) {
+ // If its a PersistentCollection initialization is intended! No unwrap!
+ foreach ($relatedEntities as $relatedEntity) {
+ $this->doRemove($relatedEntity, $visited);
+ }
+ } else if ($relatedEntities !== null) {
+ $this->doRemove($relatedEntities, $visited);
+ }
+ }
+ }
+
+ /**
+ * Acquire a lock on the given entity.
+ *
+ * @param object $entity
+ * @param int $lockMode
+ * @param int $lockVersion
+ */
+ public function lock($entity, $lockMode, $lockVersion = null)
+ {
+ if ($this->getEntityState($entity) != self::STATE_MANAGED) {
+ throw new InvalidArgumentException("Entity is not MANAGED.");
+ }
+
+ $entityName = get_class($entity);
+ $class = $this->em->getClassMetadata($entityName);
+
+ if ($lockMode == \Doctrine\DBAL\LockMode::OPTIMISTIC) {
+ if (!$class->isVersioned) {
+ throw OptimisticLockException::notVersioned($entityName);
+ }
+
+ if ($lockVersion != null) {
+ $entityVersion = $class->reflFields[$class->versionField]->getValue($entity);
+ if ($entityVersion != $lockVersion) {
+ throw OptimisticLockException::lockFailedVersionMissmatch($entity, $lockVersion, $entityVersion);
+ }
+ }
+ } else if (in_array($lockMode, array(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ, \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
+ );
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public function clear()
+ {
+ $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->orphanRemovals = array();
+ if ($this->commitOrderCalculator !== null) {
+ $this->commitOrderCalculator->clear();
+ }
+ }
+
+ /**
+ * 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)
+ {
+ //TODO: if $coll is already scheduled for recreation ... what to do?
+ // Just remove $coll from the scheduled recreations?
+ $this->collectionDeletions[] = $coll;
+ }
+
+ public function isCollectionScheduledForDeletion(PersistentCollection $coll)
+ {
+ return in_array($coll, $this->collectionsDeletions, true);
+ }
+
+ /**
+ * 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] = $data[$fieldName];
+ }
+ $idHash = implode(' ', $id);
+ } else {
+ $idHash = $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;
+ $this->originalEntityData[$oid] = $data;
+ if ($entity instanceof NotifyPropertyChanged) {
+ $entity->addPropertyChangedListener($this);
+ }
+ } else {
+ $overrideLocalValues = isset($hints[Query::HINT_REFRESH]);
+ }
+ } else {
+ $entity = $class->newInstance();
+ $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) {
+ foreach ($data as $field => $value) {
+ if (isset($class->fieldMappings[$field])) {
+ $class->reflFields[$field]->setValue($entity, $value);
+ }
+ }
+
+ // Properly initialize any unfetched associations, if partial objects are not allowed.
+ if ( ! isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) {
+ foreach ($class->associationMappings as $field => $assoc) {
+ // Check if the association is not among the fetch-joined associations already.
+ if (isset($hints['fetched'][$className][$field])) {
+ continue;
+ }
+
+ $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
+
+ if ($assoc['type'] & ClassMetadata::TO_ONE) {
+ if ($assoc['isOwningSide']) {
+ $associatedId = array();
+ foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) {
+ $joinColumnValue = isset($data[$srcColumn]) ? $data[$srcColumn] : null;
+ if ($joinColumnValue !== null) {
+ $associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue;
+ }
+ }
+ if ( ! $associatedId) {
+ // Foreign key is NULL
+ $class->reflFields[$field]->setValue($entity, null);
+ $this->originalEntityData[$oid][$field] = null;
+ } else {
+ // 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);
+ if (isset($this->identityMap[$targetClass->rootEntityName][$relatedIdHash])) {
+ $newValue = $this->identityMap[$targetClass->rootEntityName][$relatedIdHash];
+ } else {
+ if ($targetClass->subClasses) {
+ // If it might be a subtype, it can not be lazy
+ $newValue = $this->getEntityPersister($assoc['targetEntity'])
+ ->loadOneToOneEntity($assoc, $entity, null, $associatedId);
+ } else {
+ if ($assoc['fetch'] == ClassMetadata::FETCH_EAGER) {
+ // TODO: Maybe it could be optimized to do an eager fetch with a JOIN inside
+ // the persister instead of this rather unperformant approach.
+ $newValue = $this->em->find($assoc['targetEntity'], $associatedId);
+ } else {
+ $newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId);
+ }
+ // PERF: Inlined & optimized code from UnitOfWork#registerManaged()
+ $newValueOid = spl_object_hash($newValue);
+ $this->entityIdentifiers[$newValueOid] = $associatedId;
+ $this->identityMap[$targetClass->rootEntityName][$relatedIdHash] = $newValue;
+ $this->entityStates[$newValueOid] = self::STATE_MANAGED;
+ // make sure that when an proxy is then finally loaded, $this->originalEntityData is set also!
+ }
+ }
+ $this->originalEntityData[$oid][$field] = $newValue;
+ $class->reflFields[$field]->setValue($entity, $newValue);
+ }
+ } else {
+ // Inverse side of x-to-one can never be lazy
+ $class->reflFields[$field]->setValue($entity, $this->getEntityPersister($assoc['targetEntity'])
+ ->loadOneToOneEntity($assoc, $entity, null));
+ }
+ } else {
+ // Inject collection
+ $pColl = new PersistentCollection($this->em, $targetClass, new ArrayCollection);
+ $pColl->setOwner($entity, $assoc);
+
+ $reflField = $class->reflFields[$field];
+ $reflField->setValue($entity, $pColl);
+
+ if ($assoc['fetch'] == ClassMetadata::FETCH_LAZY) {
+ $pColl->setInitialized(false);
+ } else {
+ $this->loadCollection($pColl);
+ $pColl->takeSnapshot();
+ }
+ $this->originalEntityData[$oid][$field] = $pColl;
+ }
+ }
+ }
+ }
+
+ //TODO: These should be invoked later, after hydration, because associations may not yet be loaded here.
+ 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;
+ }
+
+ /**
+ * Initializes (loads) an uninitialized persistent collection of an entity.
+ *
+ * @param PeristentCollection $collection The collection to initialize.
+ * @todo Maybe later move to EntityManager#initialize($proxyOrCollection). See DDC-733.
+ */
+ public function loadCollection(PersistentCollection $collection)
+ {
+ $assoc = $collection->getMapping();
+ switch ($assoc['type']) {
+ case ClassMetadata::ONE_TO_MANY:
+ $this->getEntityPersister($assoc['targetEntity'])->loadOneToManyCollection(
+ $assoc, $collection->getOwner(), $collection);
+ break;
+ case ClassMetadata::MANY_TO_MANY:
+ $this->getEntityPersister($assoc['targetEntity'])->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()
+ {
+ $count = 0;
+ foreach ($this->identityMap as $entitySet) {
+ $count += count($entitySet);
+ }
+ return $count;
+ }
+
+ /**
+ * Gets the EntityPersister for an Entity.
+ *
+ * @param string $entityName The name of the Entity.
+ * @return Doctrine\ORM\Persister\AbstractEntityPersister
+ */
+ public function getEntityPersister($entityName)
+ {
+ if ( ! isset($this->persisters[$entityName])) {
+ $class = $this->em->getClassMetadata($entityName);
+ if ($class->isInheritanceTypeNone()) {
+ $persister = new Persisters\BasicEntityPersister($this->em, $class);
+ } else if ($class->isInheritanceTypeSingleTable()) {
+ $persister = new Persisters\SingleTablePersister($this->em, $class);
+ } else if ($class->isInheritanceTypeJoined()) {
+ $persister = new Persisters\JoinedSubclassPersister($this->em, $class);
+ } else {
+ $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 AssociationMapping $association
+ * @return AbstractCollectionPersister
+ */
+ public function getCollectionPersister(array $association)
+ {
+ $type = $association['type'];
+ if ( ! isset($this->collectionPersisters[$type])) {
+ if ($type == ClassMetadata::ONE_TO_MANY) {
+ $persister = new Persisters\OneToManyPersister($this->em);
+ } else if ($type == ClassMetadata::MANY_TO_MANY) {
+ $persister = new Persisters\ManyToManyPersister($this->em);
+ }
+ $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);
+ }
+
+ /**
+ * INTERNAL:
+ * Clears the property changeset of the entity with the given OID.
+ *
+ * @param string $oid The entity's OID.
+ */
+ public function clearEntityChangeSet($oid)
+ {
+ unset($this->entityChangeSets[$oid]);
+ }
+
+ /* 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;
+ }
+
+ private static function objToStr($obj)
+ {
+ return method_exists($obj, '__toString') ? (string)$obj : get_class($obj).'@'.spl_object_hash($obj);
+ }
+}
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM;
+
+/**
+ * Class to store and retrieve the version of Doctrine
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ */
+class Version
+{
+ /**
+ * Current Doctrine Version
+ */
+ const VERSION = '2.0.0RC1';
+
+ /**
+ * 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);
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console;
+
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\ArgvInput;
+use Symfony\Component\Console\Input\ArrayInput;
+use Symfony\Component\Console\Input\InputDefinition;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Output\Output;
+use Symfony\Component\Console\Output\ConsoleOutput;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Command\HelpCommand;
+use Symfony\Component\Console\Command\ListCommand;
+use Symfony\Component\Console\Helper\HelperSet;
+use Symfony\Component\Console\Helper\FormatterHelper;
+use Symfony\Component\Console\Helper\DialogHelper;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * An Application is the container for a collection of commands.
+ *
+ * It is the main entry point of a Console application.
+ *
+ * This class is optimized for a standard CLI environment.
+ *
+ * Usage:
+ *
+ * $app = new Application('myapp', '1.0 (stable)');
+ * $app->add(new SimpleCommand());
+ * $app->run();
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class Application
+{
+ protected $commands;
+ protected $aliases;
+ protected $wantHelps = false;
+ protected $runningCommand;
+ protected $name;
+ protected $version;
+ protected $catchExceptions;
+ protected $autoExit;
+ protected $definition;
+ protected $helperSet;
+
+ /**
+ * Constructor.
+ *
+ * @param string $name The name of the application
+ * @param string $version The version of the application
+ */
+ public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
+ {
+ $this->name = $name;
+ $this->version = $version;
+ $this->catchExceptions = true;
+ $this->autoExit = true;
+ $this->commands = array();
+ $this->aliases = array();
+ $this->helperSet = new HelperSet(array(
+ new FormatterHelper(),
+ new DialogHelper(),
+ ));
+
+ $this->add(new HelpCommand());
+ $this->add(new ListCommand());
+
+ $this->definition = new InputDefinition(array(
+ new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
+
+ new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message.'),
+ new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message.'),
+ new InputOption('--verbose', '-v', InputOption::VALUE_NONE, 'Increase verbosity of messages.'),
+ new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this program version.'),
+ new InputOption('--ansi', '-a', InputOption::VALUE_NONE, 'Force ANSI output.'),
+ new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question.'),
+ ));
+ }
+
+ /**
+ * Runs the current application.
+ *
+ * @param InputInterface $input An Input instance
+ * @param OutputInterface $output An Output instance
+ *
+ * @return integer 0 if everything went fine, or an error code
+ *
+ * @throws \Exception When doRun returns Exception
+ */
+ public function run(InputInterface $input = null, OutputInterface $output = null)
+ {
+ if (null === $input) {
+ $input = new ArgvInput();
+ }
+
+ if (null === $output) {
+ $output = new ConsoleOutput();
+ }
+
+ try {
+ $statusCode = $this->doRun($input, $output);
+ } catch (\Exception $e) {
+ if (!$this->catchExceptions) {
+ throw $e;
+ }
+
+ $this->renderException($e, $output);
+ $statusCode = $e->getCode();
+
+ $statusCode = is_numeric($statusCode) && $statusCode ? $statusCode : 1;
+ }
+
+ if ($this->autoExit) {
+ if ($statusCode > 255) {
+ $statusCode = 255;
+ }
+ // @codeCoverageIgnoreStart
+ exit($statusCode);
+ // @codeCoverageIgnoreEnd
+ } else {
+ return $statusCode;
+ }
+ }
+
+ /**
+ * Runs the current application.
+ *
+ * @param InputInterface $input An Input instance
+ * @param OutputInterface $output An Output instance
+ *
+ * @return integer 0 if everything went fine, or an error code
+ */
+ public function doRun(InputInterface $input, OutputInterface $output)
+ {
+ $name = $this->getCommandName($input);
+
+ if (true === $input->hasParameterOption(array('--ansi', '-a'))) {
+ $output->setDecorated(true);
+ }
+
+ if (true === $input->hasParameterOption(array('--help', '-h'))) {
+ if (!$name) {
+ $name = 'help';
+ $input = new ArrayInput(array('command' => 'help'));
+ } else {
+ $this->wantHelps = true;
+ }
+ }
+
+ if (true === $input->hasParameterOption(array('--no-interaction', '-n'))) {
+ $input->setInteractive(false);
+ }
+
+ if (true === $input->hasParameterOption(array('--quiet', '-q'))) {
+ $output->setVerbosity(Output::VERBOSITY_QUIET);
+ } elseif (true === $input->hasParameterOption(array('--verbose', '-v'))) {
+ $output->setVerbosity(Output::VERBOSITY_VERBOSE);
+ }
+
+ if (true === $input->hasParameterOption(array('--version', '-V'))) {
+ $output->writeln($this->getLongVersion());
+
+ return 0;
+ }
+
+ if (!$name) {
+ $name = 'list';
+ $input = new ArrayInput(array('command' => 'list'));
+ }
+
+ // the command name MUST be the first element of the input
+ $command = $this->find($name);
+
+ $this->runningCommand = $command;
+ $statusCode = $command->run($input, $output);
+ $this->runningCommand = null;
+
+ return is_numeric($statusCode) ? $statusCode : 0;
+ }
+
+ /**
+ * Set a helper set to be used with the command.
+ *
+ * @param HelperSet $helperSet The helper set
+ */
+ public function setHelperSet(HelperSet $helperSet)
+ {
+ $this->helperSet = $helperSet;
+ }
+
+ /**
+ * Get the helper set associated with the command
+ *
+ * @return HelperSet The HelperSet instance associated with this command
+ */
+ public function getHelperSet()
+ {
+ return $this->helperSet;
+ }
+
+ /**
+ * Gets the InputDefinition related to this Application.
+ *
+ * @return InputDefinition The InputDefinition instance
+ */
+ public function getDefinition()
+ {
+ return $this->definition;
+ }
+
+ /**
+ * Gets the help message.
+ *
+ * @return string A help message.
+ */
+ public function getHelp()
+ {
+ $messages = array(
+ $this->getLongVersion(),
+ '',
+ '<comment>Usage:</comment>',
+ sprintf(" [options] command [arguments]\n"),
+ '<comment>Options:</comment>',
+ );
+
+ foreach ($this->definition->getOptions() as $option) {
+ $messages[] = sprintf(' %-29s %s %s',
+ '<info>--'.$option->getName().'</info>',
+ $option->getShortcut() ? '<info>-'.$option->getShortcut().'</info>' : ' ',
+ $option->getDescription()
+ );
+ }
+
+ return implode("\n", $messages);
+ }
+
+ /**
+ * Sets whether to catch exceptions or not during commands execution.
+ *
+ * @param Boolean $boolean Whether to catch exceptions or not during commands execution
+ */
+ public function setCatchExceptions($boolean)
+ {
+ $this->catchExceptions = (Boolean) $boolean;
+ }
+
+ /**
+ * Sets whether to automatically exit after a command execution or not.
+ *
+ * @param Boolean $boolean Whether to automatically exit after a command execution or not
+ */
+ public function setAutoExit($boolean)
+ {
+ $this->autoExit = (Boolean) $boolean;
+ }
+
+ /**
+ * Gets the name of the application.
+ *
+ * @return string The application name
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Sets the application name.
+ *
+ * @param string $name The application name
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * Gets the application version.
+ *
+ * @return string The application version
+ */
+ public function getVersion()
+ {
+ return $this->version;
+ }
+
+ /**
+ * Sets the application version.
+ *
+ * @param string $version The application version
+ */
+ public function setVersion($version)
+ {
+ $this->version = $version;
+ }
+
+ /**
+ * Returns the long version of the application.
+ *
+ * @return string The long application version
+ */
+ public function getLongVersion()
+ {
+ if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) {
+ return sprintf('<info>%s</info> version <comment>%s</comment>', $this->getName(), $this->getVersion());
+ } else {
+ return '<info>Console Tool</info>';
+ }
+ }
+
+ /**
+ * Registers a new command.
+ *
+ * @param string $name The command name
+ *
+ * @return Command The newly created command
+ */
+ public function register($name)
+ {
+ return $this->add(new Command($name));
+ }
+
+ /**
+ * Adds an array of command objects.
+ *
+ * @param Command[] $commands An array of commands
+ */
+ public function addCommands(array $commands)
+ {
+ foreach ($commands as $command) {
+ $this->add($command);
+ }
+ }
+
+ /**
+ * Adds a command object.
+ *
+ * If a command with the same name already exists, it will be overridden.
+ *
+ * @param Command $command A Command object
+ *
+ * @return Command The registered command
+ */
+ public function add(Command $command)
+ {
+ $command->setApplication($this);
+
+ $this->commands[$command->getFullName()] = $command;
+
+ foreach ($command->getAliases() as $alias) {
+ $this->aliases[$alias] = $command;
+ }
+
+ return $command;
+ }
+
+ /**
+ * Returns a registered command by name or alias.
+ *
+ * @param string $name The command name or alias
+ *
+ * @return Command A Command object
+ *
+ * @throws \InvalidArgumentException When command name given does not exist
+ */
+ public function get($name)
+ {
+ if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
+ throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
+ }
+
+ $command = isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name];
+
+ if ($this->wantHelps) {
+ $this->wantHelps = false;
+
+ $helpCommand = $this->get('help');
+ $helpCommand->setCommand($command);
+
+ return $helpCommand;
+ }
+
+ return $command;
+ }
+
+ /**
+ * Returns true if the command exists, false otherwise
+ *
+ * @param string $name The command name or alias
+ *
+ * @return Boolean true if the command exists, false otherwise
+ */
+ public function has($name)
+ {
+ return isset($this->commands[$name]) || isset($this->aliases[$name]);
+ }
+
+ /**
+ * Returns an array of all unique namespaces used by currently registered commands.
+ *
+ * It does not returns the global namespace which always exists.
+ *
+ * @return array An array of namespaces
+ */
+ public function getNamespaces()
+ {
+ $namespaces = array();
+ foreach ($this->commands as $command) {
+ if ($command->getNamespace()) {
+ $namespaces[$command->getNamespace()] = true;
+ }
+ }
+
+ return array_keys($namespaces);
+ }
+
+ /**
+ * Finds a registered namespace by a name or an abbreviation.
+ *
+ * @return string A registered namespace
+ *
+ * @throws \InvalidArgumentException When namespace is incorrect or ambiguous
+ */
+ public function findNamespace($namespace)
+ {
+ $abbrevs = static::getAbbreviations($this->getNamespaces());
+
+ if (!isset($abbrevs[$namespace])) {
+ throw new \InvalidArgumentException(sprintf('There are no commands defined in the "%s" namespace.', $namespace));
+ }
+
+ if (count($abbrevs[$namespace]) > 1) {
+ throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions($abbrevs[$namespace])));
+ }
+
+ return $abbrevs[$namespace][0];
+ }
+
+ /**
+ * Finds a command by name or alias.
+ *
+ * Contrary to get, this command tries to find the best
+ * match if you give it an abbreviation of a name or alias.
+ *
+ * @param string $name A command name or a command alias
+ *
+ * @return Command A Command instance
+ *
+ * @throws \InvalidArgumentException When command name is incorrect or ambiguous
+ */
+ public function find($name)
+ {
+ // namespace
+ $namespace = '';
+ if (false !== $pos = strrpos($name, ':')) {
+ $namespace = $this->findNamespace(substr($name, 0, $pos));
+ $name = substr($name, $pos + 1);
+ }
+
+ $fullName = $namespace ? $namespace.':'.$name : $name;
+
+ // name
+ $commands = array();
+ foreach ($this->commands as $command) {
+ if ($command->getNamespace() == $namespace) {
+ $commands[] = $command->getName();
+ }
+ }
+
+ $abbrevs = static::getAbbreviations($commands);
+ if (isset($abbrevs[$name]) && 1 == count($abbrevs[$name])) {
+ return $this->get($namespace ? $namespace.':'.$abbrevs[$name][0] : $abbrevs[$name][0]);
+ }
+
+ if (isset($abbrevs[$name]) && count($abbrevs[$name]) > 1) {
+ $suggestions = $this->getAbbreviationSuggestions(array_map(function ($command) use ($namespace) { return $namespace.':'.$command; }, $abbrevs[$name]));
+
+ throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $fullName, $suggestions));
+ }
+
+ // aliases
+ $abbrevs = static::getAbbreviations(array_keys($this->aliases));
+ if (!isset($abbrevs[$fullName])) {
+ throw new \InvalidArgumentException(sprintf('Command "%s" is not defined.', $fullName));
+ }
+
+ if (count($abbrevs[$fullName]) > 1) {
+ throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $fullName, $this->getAbbreviationSuggestions($abbrevs[$fullName])));
+ }
+
+ return $this->get($abbrevs[$fullName][0]);
+ }
+
+ /**
+ * Gets the commands (registered in the given namespace if provided).
+ *
+ * The array keys are the full names and the values the command instances.
+ *
+ * @param string $namespace A namespace name
+ *
+ * @return array An array of Command instances
+ */
+ public function all($namespace = null)
+ {
+ if (null === $namespace) {
+ return $this->commands;
+ }
+
+ $commands = array();
+ foreach ($this->commands as $name => $command) {
+ if ($namespace === $command->getNamespace()) {
+ $commands[$name] = $command;
+ }
+ }
+
+ return $commands;
+ }
+
+ /**
+ * Returns an array of possible abbreviations given a set of names.
+ *
+ * @param array $names An array of names
+ *
+ * @return array An array of abbreviations
+ */
+ static public function getAbbreviations($names)
+ {
+ $abbrevs = array();
+ foreach ($names as $name) {
+ for ($len = strlen($name) - 1; $len > 0; --$len) {
+ $abbrev = substr($name, 0, $len);
+ if (!isset($abbrevs[$abbrev])) {
+ $abbrevs[$abbrev] = array($name);
+ } else {
+ $abbrevs[$abbrev][] = $name;
+ }
+ }
+ }
+
+ // Non-abbreviations always get entered, even if they aren't unique
+ foreach ($names as $name) {
+ $abbrevs[$name] = array($name);
+ }
+
+ return $abbrevs;
+ }
+
+ /**
+ * Returns a text representation of the Application.
+ *
+ * @param string $namespace An optional namespace name
+ *
+ * @return string A string representing the Application
+ */
+ public function asText($namespace = null)
+ {
+ $commands = $namespace ? $this->all($this->findNamespace($namespace)) : $this->commands;
+
+ $messages = array($this->getHelp(), '');
+ if ($namespace) {
+ $messages[] = sprintf("<comment>Available commands for the \"%s\" namespace:</comment>", $namespace);
+ } else {
+ $messages[] = '<comment>Available commands:</comment>';
+ }
+
+ $width = 0;
+ foreach ($commands as $command) {
+ $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width;
+ }
+ $width += 2;
+
+ // add commands by namespace
+ foreach ($this->sortCommands($commands) as $space => $commands) {
+ if (!$namespace && '_global' !== $space) {
+ $messages[] = '<comment>'.$space.'</comment>';
+ }
+
+ foreach ($commands as $command) {
+ $aliases = $command->getAliases() ? '<comment> ('.implode(', ', $command->getAliases()).')</comment>' : '';
+
+ $messages[] = sprintf(" <info>%-${width}s</info> %s%s", ($command->getNamespace() ? ':' : '').$command->getName(), $command->getDescription(), $aliases);
+ }
+ }
+
+ return implode("\n", $messages);
+ }
+
+ /**
+ * Returns an XML representation of the Application.
+ *
+ * @param string $namespace An optional namespace name
+ * @param Boolean $asDom Whether to return a DOM or an XML string
+ *
+ * @return string|DOMDocument An XML string representing the Application
+ */
+ public function asXml($namespace = null, $asDom = false)
+ {
+ $commands = $namespace ? $this->all($this->findNamespace($namespace)) : $this->commands;
+
+ $dom = new \DOMDocument('1.0', 'UTF-8');
+ $dom->formatOutput = true;
+ $dom->appendChild($xml = $dom->createElement('symfony'));
+
+ $xml->appendChild($commandsXML = $dom->createElement('commands'));
+
+ if ($namespace) {
+ $commandsXML->setAttribute('namespace', $namespace);
+ } else {
+ $xml->appendChild($namespacesXML = $dom->createElement('namespaces'));
+ }
+
+ // add commands by namespace
+ foreach ($this->sortCommands($commands) as $space => $commands) {
+ if (!$namespace) {
+ $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace'));
+ $namespaceArrayXML->setAttribute('id', $space);
+ }
+
+ foreach ($commands as $command) {
+ if (!$namespace) {
+ $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command'));
+ $commandXML->appendChild($dom->createTextNode($command->getName()));
+ }
+
+ $node = $command->asXml(true)->getElementsByTagName('command')->item(0);
+ $node = $dom->importNode($node, true);
+
+ $commandsXML->appendChild($node);
+ }
+ }
+
+ return $asDom ? $dom : $dom->saveXml();
+ }
+
+ /**
+ * Renders a catched exception.
+ *
+ * @param Exception $e An exception instance
+ * @param OutputInterface $output An OutputInterface instance
+ */
+ public function renderException($e, $output)
+ {
+ $strlen = function ($string)
+ {
+ return function_exists('mb_strlen') ? mb_strlen($string) : strlen($string);
+ };
+
+ $title = sprintf(' [%s] ', get_class($e));
+ $len = $strlen($title);
+ $lines = array();
+ foreach (explode("\n", $e->getMessage()) as $line) {
+ $lines[] = sprintf(' %s ', $line);
+ $len = max($strlen($line) + 4, $len);
+ }
+
+ $messages = array(str_repeat(' ', $len), $title.str_repeat(' ', $len - $strlen($title)));
+
+ foreach ($lines as $line) {
+ $messages[] = $line.str_repeat(' ', $len - $strlen($line));
+ }
+
+ $messages[] = str_repeat(' ', $len);
+
+ $output->writeln("\n");
+ foreach ($messages as $message) {
+ $output->writeln('<error>'.$message.'</error>');
+ }
+ $output->writeln("\n");
+
+ if (null !== $this->runningCommand) {
+ $output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())));
+ $output->writeln("\n");
+ }
+
+ if (Output::VERBOSITY_VERBOSE === $output->getVerbosity()) {
+ $output->writeln('</comment>Exception trace:</comment>');
+
+ // exception related properties
+ $trace = $e->getTrace();
+ array_unshift($trace, array(
+ 'function' => '',
+ 'file' => $e->getFile() != null ? $e->getFile() : 'n/a',
+ 'line' => $e->getLine() != null ? $e->getLine() : 'n/a',
+ 'args' => array(),
+ ));
+
+ for ($i = 0, $count = count($trace); $i < $count; $i++) {
+ $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
+ $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
+ $function = $trace[$i]['function'];
+ $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
+ $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
+
+ $output->writeln(sprintf(' %s%s%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line));
+ }
+
+ $output->writeln("\n");
+ }
+ }
+
+ protected function getCommandName(InputInterface $input)
+ {
+ return $input->getFirstArgument('command');
+ }
+
+ protected function sortCommands($commands)
+ {
+ $namespacedCommands = array();
+ foreach ($commands as $name => $command) {
+ $key = $command->getNamespace() ? $command->getNamespace() : '_global';
+
+ if (!isset($namespacedCommands[$key])) {
+ $namespacedCommands[$key] = array();
+ }
+
+ $namespacedCommands[$key][$name] = $command;
+ }
+ ksort($namespacedCommands);
+
+ foreach ($namespacedCommands as $name => &$commands) {
+ ksort($commands);
+ }
+
+ return $namespacedCommands;
+ }
+
+ protected function getAbbreviationSuggestions($abbrevs)
+ {
+ return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Command;
+
+use Symfony\Component\Console\Input\InputDefinition;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Application;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * Base class for all commands.
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class Command
+{
+ protected $name;
+ protected $namespace;
+ protected $aliases;
+ protected $definition;
+ protected $help;
+ protected $application;
+ protected $description;
+ protected $ignoreValidationErrors;
+ protected $applicationDefinitionMerged;
+ protected $code;
+
+ /**
+ * Constructor.
+ *
+ * @param string $name The name of the command
+ *
+ * @throws \LogicException When the command name is empty
+ */
+ public function __construct($name = null)
+ {
+ $this->definition = new InputDefinition();
+ $this->ignoreValidationErrors = false;
+ $this->applicationDefinitionMerged = false;
+ $this->aliases = array();
+
+ if (null !== $name) {
+ $this->setName($name);
+ }
+
+ $this->configure();
+
+ if (!$this->name) {
+ throw new \LogicException('The command name cannot be empty.');
+ }
+ }
+
+ /**
+ * Sets the application instance for this command.
+ *
+ * @param Application $application An Application instance
+ */
+ public function setApplication(Application $application = null)
+ {
+ $this->application = $application;
+ }
+
+ /**
+ * Configures the current command.
+ */
+ protected function configure()
+ {
+ }
+
+ /**
+ * Executes the current command.
+ *
+ * @param InputInterface $input An InputInterface instance
+ * @param OutputInterface $output An OutputInterface instance
+ *
+ * @return integer 0 if everything went fine, or an error code
+ *
+ * @throws \LogicException When this abstract class is not implemented
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ throw new \LogicException('You must override the execute() method in the concrete command class.');
+ }
+
+ /**
+ * Interacts with the user.
+ *
+ * @param InputInterface $input An InputInterface instance
+ * @param OutputInterface $output An OutputInterface instance
+ */
+ protected function interact(InputInterface $input, OutputInterface $output)
+ {
+ }
+
+ /**
+ * Initializes the command just after the input has been validated.
+ *
+ * This is mainly useful when a lot of commands extends one main command
+ * where some things need to be initialized based on the input arguments and options.
+ *
+ * @param InputInterface $input An InputInterface instance
+ * @param OutputInterface $output An OutputInterface instance
+ */
+ protected function initialize(InputInterface $input, OutputInterface $output)
+ {
+ }
+
+ /**
+ * Runs the command.
+ *
+ * @param InputInterface $input An InputInterface instance
+ * @param OutputInterface $output An OutputInterface instance
+ */
+ public function run(InputInterface $input, OutputInterface $output)
+ {
+ // add the application arguments and options
+ $this->mergeApplicationDefinition();
+
+ // bind the input against the command specific arguments/options
+ try {
+ $input->bind($this->definition);
+ } catch (\Exception $e) {
+ if (!$this->ignoreValidationErrors) {
+ throw $e;
+ }
+ }
+
+ $this->initialize($input, $output);
+
+ if ($input->isInteractive()) {
+ $this->interact($input, $output);
+ }
+
+ $input->validate();
+
+ if ($this->code) {
+ return call_user_func($this->code, $input, $output);
+ } else {
+ return $this->execute($input, $output);
+ }
+ }
+
+ /**
+ * Sets the code to execute when running this command.
+ *
+ * @param \Closure $code A \Closure
+ *
+ * @return Command The current instance
+ */
+ public function setCode(\Closure $code)
+ {
+ $this->code = $code;
+
+ return $this;
+ }
+
+ /**
+ * Merges the application definition with the command definition.
+ */
+ protected function mergeApplicationDefinition()
+ {
+ if (null === $this->application || true === $this->applicationDefinitionMerged) {
+ return;
+ }
+
+ $this->definition->setArguments(array_merge(
+ $this->application->getDefinition()->getArguments(),
+ $this->definition->getArguments()
+ ));
+
+ $this->definition->addOptions($this->application->getDefinition()->getOptions());
+
+ $this->applicationDefinitionMerged = true;
+ }
+
+ /**
+ * Sets an array of argument and option instances.
+ *
+ * @param array|Definition $definition An array of argument and option instances or a definition instance
+ *
+ * @return Command The current instance
+ */
+ public function setDefinition($definition)
+ {
+ if ($definition instanceof InputDefinition) {
+ $this->definition = $definition;
+ } else {
+ $this->definition->setDefinition($definition);
+ }
+
+ $this->applicationDefinitionMerged = false;
+
+ return $this;
+ }
+
+ /**
+ * Gets the InputDefinition attached to this Command.
+ *
+ * @return InputDefinition An InputDefinition instance
+ */
+ public function getDefinition()
+ {
+ return $this->definition;
+ }
+
+ /**
+ * Adds an argument.
+ *
+ * @param string $name The argument name
+ * @param integer $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
+ * @param string $description A description text
+ * @param mixed $default The default value (for InputArgument::OPTIONAL mode only)
+ *
+ * @return Command The current instance
+ */
+ public function addArgument($name, $mode = null, $description = '', $default = null)
+ {
+ $this->definition->addArgument(new InputArgument($name, $mode, $description, $default));
+
+ return $this;
+ }
+
+ /**
+ * Adds an option.
+ *
+ * @param string $name The option name
+ * @param string $shortcut The shortcut (can be null)
+ * @param integer $mode The option mode: One of the InputOption::VALUE_* constants
+ * @param string $description A description text
+ * @param mixed $default The default value (must be null for InputOption::VALUE_REQUIRED or self::VALUE_NONE)
+ *
+ * @return Command The current instance
+ */
+ public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null)
+ {
+ $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
+
+ return $this;
+ }
+
+ /**
+ * Sets the name of the command.
+ *
+ * This method can set both the namespace and the name if
+ * you separate them by a colon (:)
+ *
+ * $command->setName('foo:bar');
+ *
+ * @param string $name The command name
+ *
+ * @return Command The current instance
+ *
+ * @throws \InvalidArgumentException When command name given is empty
+ */
+ public function setName($name)
+ {
+ if (false !== $pos = strrpos($name, ':')) {
+ $namespace = substr($name, 0, $pos);
+ $name = substr($name, $pos + 1);
+ } else {
+ $namespace = $this->namespace;
+ }
+
+ if (!$name) {
+ throw new \InvalidArgumentException('A command name cannot be empty.');
+ }
+
+ $this->namespace = $namespace;
+ $this->name = $name;
+
+ return $this;
+ }
+
+ /**
+ * Returns the command namespace.
+ *
+ * @return string The command namespace
+ */
+ public function getNamespace()
+ {
+ return $this->namespace;
+ }
+
+ /**
+ * Returns the command name
+ *
+ * @return string The command name
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Returns the fully qualified command name.
+ *
+ * @return string The fully qualified command name
+ */
+ public function getFullName()
+ {
+ return $this->getNamespace() ? $this->getNamespace().':'.$this->getName() : $this->getName();
+ }
+
+ /**
+ * Sets the description for the command.
+ *
+ * @param string $description The description for the command
+ *
+ * @return Command The current instance
+ */
+ public function setDescription($description)
+ {
+ $this->description = $description;
+
+ return $this;
+ }
+
+ /**
+ * Returns the description for the command.
+ *
+ * @return string The description for the command
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ /**
+ * Sets the help for the command.
+ *
+ * @param string $help The help for the command
+ *
+ * @return Command The current instance
+ */
+ public function setHelp($help)
+ {
+ $this->help = $help;
+
+ return $this;
+ }
+
+ /**
+ * Returns the help for the command.
+ *
+ * @return string The help for the command
+ */
+ public function getHelp()
+ {
+ return $this->help;
+ }
+
+ /**
+ * Returns the processed help for the command replacing the %command.name% and
+ * %command.full_name% patterns with the real values dynamically.
+ *
+ * @return string The processed help for the command
+ */
+ public function getProcessedHelp()
+ {
+ $name = $this->namespace.':'.$this->name;
+
+ $placeholders = array(
+ '%command.name%',
+ '%command.full_name%'
+ );
+ $replacements = array(
+ $name,
+ $_SERVER['PHP_SELF'].' '.$name
+ );
+
+ return str_replace($placeholders, $replacements, $this->getHelp());
+ }
+
+ /**
+ * Sets the aliases for the command.
+ *
+ * @param array $aliases An array of aliases for the command
+ *
+ * @return Command The current instance
+ */
+ public function setAliases($aliases)
+ {
+ $this->aliases = $aliases;
+
+ return $this;
+ }
+
+ /**
+ * Returns the aliases for the command.
+ *
+ * @return array An array of aliases for the command
+ */
+ public function getAliases()
+ {
+ return $this->aliases;
+ }
+
+ /**
+ * Returns the synopsis for the command.
+ *
+ * @return string The synopsis
+ */
+ public function getSynopsis()
+ {
+ return sprintf('%s %s', $this->getFullName(), $this->definition->getSynopsis());
+ }
+
+ /**
+ * Gets a helper instance by name.
+ *
+ * @param string $name The helper name
+ *
+ * @return mixed The helper value
+ *
+ * @throws \InvalidArgumentException if the helper is not defined
+ */
+ protected function getHelper($name)
+ {
+ return $this->application->getHelperSet()->get($name);
+ }
+
+ /**
+ * Gets a helper instance by name.
+ *
+ * @param string $name The helper name
+ *
+ * @return mixed The helper value
+ *
+ * @throws \InvalidArgumentException if the helper is not defined
+ */
+ public function __get($name)
+ {
+ return $this->application->getHelperSet()->get($name);
+ }
+
+ /**
+ * Returns a text representation of the command.
+ *
+ * @return string A string representing the command
+ */
+ public function asText()
+ {
+ $messages = array(
+ '<comment>Usage:</comment>',
+ ' '.$this->getSynopsis(),
+ '',
+ );
+
+ if ($this->getAliases()) {
+ $messages[] = '<comment>Aliases:</comment> <info>'.implode(', ', $this->getAliases()).'</info>';
+ }
+
+ $messages[] = $this->definition->asText();
+
+ if ($help = $this->getProcessedHelp()) {
+ $messages[] = '<comment>Help:</comment>';
+ $messages[] = ' '.implode("\n ", explode("\n", $help))."\n";
+ }
+
+ return implode("\n", $messages);
+ }
+
+ /**
+ * Returns an XML representation of the command.
+ *
+ * @param Boolean $asDom Whether to return a DOM or an XML string
+ *
+ * @return string|DOMDocument An XML string representing the command
+ */
+ public function asXml($asDom = false)
+ {
+ $dom = new \DOMDocument('1.0', 'UTF-8');
+ $dom->formatOutput = true;
+ $dom->appendChild($commandXML = $dom->createElement('command'));
+ $commandXML->setAttribute('id', $this->getFullName());
+ $commandXML->setAttribute('namespace', $this->getNamespace() ? $this->getNamespace() : '_global');
+ $commandXML->setAttribute('name', $this->getName());
+
+ $commandXML->appendChild($usageXML = $dom->createElement('usage'));
+ $usageXML->appendChild($dom->createTextNode(sprintf($this->getSynopsis(), '')));
+
+ $commandXML->appendChild($descriptionXML = $dom->createElement('description'));
+ $descriptionXML->appendChild($dom->createTextNode(implode("\n ", explode("\n", $this->getDescription()))));
+
+ $commandXML->appendChild($helpXML = $dom->createElement('help'));
+ $help = $this->help;
+ $helpXML->appendChild($dom->createTextNode(implode("\n ", explode("\n", $help))));
+
+ $commandXML->appendChild($aliasesXML = $dom->createElement('aliases'));
+ foreach ($this->getAliases() as $alias) {
+ $aliasesXML->appendChild($aliasXML = $dom->createElement('alias'));
+ $aliasXML->appendChild($dom->createTextNode($alias));
+ }
+
+ $definition = $this->definition->asXml(true);
+ $commandXML->appendChild($dom->importNode($definition->getElementsByTagName('arguments')->item(0), true));
+ $commandXML->appendChild($dom->importNode($definition->getElementsByTagName('options')->item(0), true));
+
+ return $asDom ? $dom : $dom->saveXml();
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Command;
+
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Output\Output;
+use Symfony\Component\Console\Command\Command;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * HelpCommand displays the help for a given command.
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class HelpCommand extends Command
+{
+ protected $command;
+
+ /**
+ * @see Command
+ */
+ protected function configure()
+ {
+ $this->ignoreValidationErrors = true;
+
+ $this
+ ->setDefinition(array(
+ new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
+ new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'),
+ ))
+ ->setName('help')
+ ->setAliases(array('?'))
+ ->setDescription('Displays help for a command')
+ ->setHelp(<<<EOF
+The <info>help</info> command displays help for a given command:
+
+ <info>./symfony help list</info>
+
+You can also output the help as XML by using the <comment>--xml</comment> option:
+
+ <info>./symfony help --xml list</info>
+EOF
+ );
+ }
+
+ public function setCommand(Command $command)
+ {
+ $this->command = $command;
+ }
+
+ /**
+ * @see Command
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ if (null === $this->command) {
+ $this->command = $this->application->get($input->getArgument('command_name'));
+ }
+
+ if ($input->getOption('xml')) {
+ $output->writeln($this->command->asXml(), Output::OUTPUT_RAW);
+ } else {
+ $output->writeln($this->command->asText());
+ }
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Command;
+
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Output\Output;
+use Symfony\Component\Console\Command\Command;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * ListCommand displays the list of all available commands for the application.
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class ListCommand extends Command
+{
+ /**
+ * @see Command
+ */
+ protected function configure()
+ {
+ $this
+ ->setDefinition(array(
+ new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
+ new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'),
+ ))
+ ->setName('list')
+ ->setDescription('Lists commands')
+ ->setHelp(<<<EOF
+The <info>list</info> command lists all commands:
+
+ <info>./symfony list</info>
+
+You can also display the commands for a specific namespace:
+
+ <info>./symfony list test</info>
+
+You can also output the information as XML by using the <comment>--xml</comment> option:
+
+ <info>./symfony list --xml</info>
+EOF
+ );
+ }
+
+ /**
+ * @see Command
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ if ($input->getOption('xml')) {
+ $output->writeln($this->application->asXml($input->getArgument('namespace')), Output::OUTPUT_RAW);
+ } else {
+ $output->writeln($this->application->asText($input->getArgument('namespace')));
+ }
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Output\OutputInterface;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * The Dialog class provides helpers to interact with the user.
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class DialogHelper extends Helper
+{
+ /**
+ * Asks a question to the user.
+ *
+ * @param OutputInterface $output
+ * @param string|array $question The question to ask
+ * @param string $default The default answer if none is given by the user
+ *
+ * @return string The user answer
+ */
+ public function ask(OutputInterface $output, $question, $default = null)
+ {
+ // @codeCoverageIgnoreStart
+ $output->writeln($question);
+
+ $ret = trim(fgets(STDIN));
+
+ return $ret ? $ret : $default;
+ // @codeCoverageIgnoreEnd
+ }
+
+ /**
+ * Asks a confirmation to the user.
+ *
+ * The question will be asked until the user answer by nothing, yes, or no.
+ *
+ * @param OutputInterface $output
+ * @param string|array $question The question to ask
+ * @param Boolean $default The default answer if the user enters nothing
+ *
+ * @return Boolean true if the user has confirmed, false otherwise
+ */
+ public function askConfirmation(OutputInterface $output, $question, $default = true)
+ {
+ // @codeCoverageIgnoreStart
+ $answer = 'z';
+ while ($answer && !in_array(strtolower($answer[0]), array('y', 'n'))) {
+ $answer = $this->ask($output, $question);
+ }
+
+ if (false === $default) {
+ return $answer && 'y' == strtolower($answer[0]);
+ } else {
+ return !$answer || 'y' == strtolower($answer[0]);
+ }
+ // @codeCoverageIgnoreEnd
+ }
+
+ /**
+ * Asks for a value and validates the response.
+ *
+ * @param OutputInterface $output
+ * @param string|array $question
+ * @param Closure $validator
+ * @param integer $attempts Max number of times to ask before giving up (false by default, which means infinite)
+ *
+ * @return mixed
+ *
+ * @throws \Exception When any of the validator returns an error
+ */
+ public function askAndValidate(OutputInterface $output, $question, \Closure $validator, $attempts = false)
+ {
+ // @codeCoverageIgnoreStart
+ $error = null;
+ while (false === $attempts || $attempts--) {
+ if (null !== $error) {
+ $output->writeln($this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'));
+ }
+
+ $value = $this->ask($output, $question, null);
+
+ try {
+ return $validator($value);
+ } catch (\Exception $error) {
+ }
+ }
+
+ throw $error;
+ // @codeCoverageIgnoreEnd
+ }
+
+ /**
+ * Returns the helper's canonical name
+ */
+ public function getName()
+ {
+ return 'dialog';
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Helper;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * The Formatter class provides helpers to format messages.
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class FormatterHelper extends Helper
+{
+ /**
+ * Formats a message within a section.
+ *
+ * @param string $section The section name
+ * @param string $message The message
+ * @param string $style The style to apply to the section
+ */
+ public function formatSection($section, $message, $style = 'info')
+ {
+ return sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message);
+ }
+
+ /**
+ * Formats a message as a block of text.
+ *
+ * @param string|array $messages The message to write in the block
+ * @param string $style The style to apply to the whole block
+ * @param Boolean $large Whether to return a large block
+ *
+ * @return string The formatter message
+ */
+ public function formatBlock($messages, $style, $large = false)
+ {
+ if (!is_array($messages)) {
+ $messages = array($messages);
+ }
+
+ $len = 0;
+ $lines = array();
+ foreach ($messages as $message) {
+ $lines[] = sprintf($large ? ' %s ' : ' %s ', $message);
+ $len = max($this->strlen($message) + ($large ? 4 : 2), $len);
+ }
+
+ $messages = $large ? array(str_repeat(' ', $len)) : array();
+ foreach ($lines as $line) {
+ $messages[] = $line.str_repeat(' ', $len - $this->strlen($line));
+ }
+ if ($large) {
+ $messages[] = str_repeat(' ', $len);
+ }
+
+ foreach ($messages as &$message) {
+ $message = sprintf('<%s>%s</%s>', $style, $message, $style);
+ }
+
+ return implode("\n", $messages);
+ }
+
+ protected function strlen($string)
+ {
+ return function_exists('mb_strlen') ? mb_strlen($string) : strlen($string);
+ }
+
+ /**
+ * Returns the helper's canonical name
+ */
+ public function getName()
+ {
+ return 'formatter';
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Helper;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * Helper is the base class for all helper classes.
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+abstract class Helper implements HelperInterface
+{
+ protected $helperSet = null;
+
+ /**
+ * Sets the helper set associated with this helper.
+ *
+ * @param HelperSet $helperSet A HelperSet instance
+ */
+ public function setHelperSet(HelperSet $helperSet = null)
+ {
+ $this->helperSet = $helperSet;
+ }
+
+ /**
+ * Gets the helper set associated with this helper.
+ *
+ * @return HelperSet A HelperSet instance
+ */
+ public function getHelperSet()
+ {
+ return $this->helperSet;
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Helper;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * HelperInterface is the interface all helpers must implement.
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+interface HelperInterface
+{
+ /**
+ * Sets the helper set associated with this helper.
+ *
+ * @param HelperSet $helperSet A HelperSet instance
+ */
+ function setHelperSet(HelperSet $helperSet = null);
+
+ /**
+ * Gets the helper set associated with this helper.
+ *
+ * @return HelperSet A HelperSet instance
+ */
+ function getHelperSet();
+
+ /**
+ * Returns the canonical name of this helper.
+ *
+ * @return string The canonical name
+ */
+ function getName();
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Command\Command;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * HelperSet represents a set of helpers to be used with a command.
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class HelperSet
+{
+ protected $helpers;
+ protected $command;
+
+ /**
+ * @param Helper[] $helpers An array of helper.
+ */
+ public function __construct(array $helpers = array())
+ {
+ $this->helpers = array();
+ foreach ($helpers as $alias => $helper) {
+ $this->set($helper, is_int($alias) ? null : $alias);
+ }
+ }
+
+ /**
+ * Sets a helper.
+ *
+ * @param HelperInterface $value The helper instance
+ * @param string $alias An alias
+ */
+ public function set(HelperInterface $helper, $alias = null)
+ {
+ $this->helpers[$helper->getName()] = $helper;
+ if (null !== $alias) {
+ $this->helpers[$alias] = $helper;
+ }
+
+ $helper->setHelperSet($this);
+ }
+
+ /**
+ * Returns true if the helper if defined.
+ *
+ * @param string $name The helper name
+ *
+ * @return Boolean true if the helper is defined, false otherwise
+ */
+ public function has($name)
+ {
+ return isset($this->helpers[$name]);
+ }
+
+ /**
+ * Gets a helper value.
+ *
+ * @param string $name The helper name
+ *
+ * @return HelperInterface The helper instance
+ *
+ * @throws \InvalidArgumentException if the helper is not defined
+ */
+ public function get($name)
+ {
+ if (!$this->has($name)) {
+ throw new \InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name));
+ }
+
+ return $this->helpers[$name];
+ }
+
+ /**
+ * Sets the command associated with this helper set.
+ *
+ * @param Command $command A Command instance
+ */
+ public function setCommand(Command $command = null)
+ {
+ $this->command = $command;
+ }
+
+ /**
+ * Gets the command associated with this helper set.
+ *
+ * @return Command A Command instance
+ */
+ public function getCommand()
+ {
+ return $this->command;
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Input;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * ArgvInput represents an input coming from the CLI arguments.
+ *
+ * Usage:
+ *
+ * $input = new ArgvInput();
+ *
+ * By default, the `$_SERVER['argv']` array is used for the input values.
+ *
+ * This can be overridden by explicitly passing the input values in the constructor:
+ *
+ * $input = new ArgvInput($_SERVER['argv']);
+ *
+ * If you pass it yourself, don't forget that the first element of the array
+ * is the name of the running program.
+ *
+ * When passing an argument to the constructor, be sure that it respects
+ * the same rules as the argv one. It's almost always better to use the
+ * `StringInput` when you want to provide your own input.
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
+ * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02
+ */
+class ArgvInput extends Input
+{
+ protected $tokens;
+ protected $parsed;
+
+ /**
+ * Constructor.
+ *
+ * @param array $argv An array of parameters from the CLI (in the argv format)
+ * @param InputDefinition $definition A InputDefinition instance
+ */
+ public function __construct(array $argv = null, InputDefinition $definition = null)
+ {
+ if (null === $argv) {
+ $argv = $_SERVER['argv'];
+ }
+
+ // strip the program name
+ array_shift($argv);
+
+ $this->tokens = $argv;
+
+ parent::__construct($definition);
+ }
+
+ /**
+ * Processes command line arguments.
+ */
+ protected function parse()
+ {
+ $this->parsed = $this->tokens;
+ while (null !== $token = array_shift($this->parsed)) {
+ if ('--' === substr($token, 0, 2)) {
+ $this->parseLongOption($token);
+ } elseif ('-' === $token[0]) {
+ $this->parseShortOption($token);
+ } else {
+ $this->parseArgument($token);
+ }
+ }
+ }
+
+ /**
+ * Parses a short option.
+ *
+ * @param string $token The current token.
+ */
+ protected function parseShortOption($token)
+ {
+ $name = substr($token, 1);
+
+ if (strlen($name) > 1) {
+ if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) {
+ // an option with a value (with no space)
+ $this->addShortOption($name[0], substr($name, 1));
+ } else {
+ $this->parseShortOptionSet($name);
+ }
+ } else {
+ $this->addShortOption($name, null);
+ }
+ }
+
+ /**
+ * Parses a short option set.
+ *
+ * @param string $token The current token
+ *
+ * @throws \RuntimeException When option given doesn't exist
+ */
+ protected function parseShortOptionSet($name)
+ {
+ $len = strlen($name);
+ for ($i = 0; $i < $len; $i++) {
+ if (!$this->definition->hasShortcut($name[$i])) {
+ throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i]));
+ }
+
+ $option = $this->definition->getOptionForShortcut($name[$i]);
+ if ($option->acceptValue()) {
+ $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));
+
+ break;
+ } else {
+ $this->addLongOption($option->getName(), true);
+ }
+ }
+ }
+
+ /**
+ * Parses a long option.
+ *
+ * @param string $token The current token
+ */
+ protected function parseLongOption($token)
+ {
+ $name = substr($token, 2);
+
+ if (false !== $pos = strpos($name, '=')) {
+ $this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1));
+ } else {
+ $this->addLongOption($name, null);
+ }
+ }
+
+ /**
+ * Parses an argument.
+ *
+ * @param string $token The current token
+ *
+ * @throws \RuntimeException When too many arguments are given
+ */
+ protected function parseArgument($token)
+ {
+ if (!$this->definition->hasArgument(count($this->arguments))) {
+ throw new \RuntimeException('Too many arguments.');
+ }
+
+ $this->arguments[$this->definition->getArgument(count($this->arguments))->getName()] = $token;
+ }
+
+ /**
+ * Adds a short option value.
+ *
+ * @param string $shortcut The short option key
+ * @param mixed $value The value for the option
+ *
+ * @throws \RuntimeException When option given doesn't exist
+ */
+ protected function addShortOption($shortcut, $value)
+ {
+ if (!$this->definition->hasShortcut($shortcut)) {
+ throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
+ }
+
+ $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
+ }
+
+ /**
+ * Adds a long option value.
+ *
+ * @param string $name The long option key
+ * @param mixed $value The value for the option
+ *
+ * @throws \RuntimeException When option given doesn't exist
+ */
+ protected function addLongOption($name, $value)
+ {
+ if (!$this->definition->hasOption($name)) {
+ throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name));
+ }
+
+ $option = $this->definition->getOption($name);
+
+ if (null === $value && $option->acceptValue()) {
+ // if option accepts an optional or mandatory argument
+ // let's see if there is one provided
+ $next = array_shift($this->parsed);
+ if ('-' !== $next[0]) {
+ $value = $next;
+ } else {
+ array_unshift($this->parsed, $next);
+ }
+ }
+
+ if (null === $value) {
+ if ($option->isValueRequired()) {
+ throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name));
+ }
+
+ $value = $option->isValueOptional() ? $option->getDefault() : true;
+ }
+
+ $this->options[$name] = $value;
+ }
+
+ /**
+ * Returns the first argument from the raw parameters (not parsed).
+ *
+ * @return string The value of the first argument or null otherwise
+ */
+ public function getFirstArgument()
+ {
+ foreach ($this->tokens as $token) {
+ if ($token && '-' === $token[0]) {
+ continue;
+ }
+
+ return $token;
+ }
+ }
+
+ /**
+ * Returns true if the raw parameters (not parsed) contains a value.
+ *
+ * This method is to be used to introspect the input parameters
+ * before it has been validated. It must be used carefully.
+ *
+ * @param string|array $values The value(s) to look for in the raw parameters (can be an array)
+ *
+ * @return Boolean true if the value is contained in the raw parameters
+ */
+ public function hasParameterOption($values)
+ {
+ if (!is_array($values)) {
+ $values = array($values);
+ }
+
+ foreach ($this->tokens as $v) {
+ if (in_array($v, $values)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Input;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * ArrayInput represents an input provided as an array.
+ *
+ * Usage:
+ *
+ * $input = new ArrayInput(array('name' => 'foo', '--bar' => 'foobar'));
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class ArrayInput extends Input
+{
+ protected $parameters;
+
+ /**
+ * Constructor.
+ *
+ * @param array $param An array of parameters
+ * @param InputDefinition $definition A InputDefinition instance
+ */
+ public function __construct(array $parameters, InputDefinition $definition = null)
+ {
+ $this->parameters = $parameters;
+
+ parent::__construct($definition);
+ }
+
+ /**
+ * Returns the first argument from the raw parameters (not parsed).
+ *
+ * @return string The value of the first argument or null otherwise
+ */
+ public function getFirstArgument()
+ {
+ foreach ($this->parameters as $key => $value) {
+ if ($key && '-' === $key[0]) {
+ continue;
+ }
+
+ return $value;
+ }
+ }
+
+ /**
+ * Returns true if the raw parameters (not parsed) contains a value.
+ *
+ * This method is to be used to introspect the input parameters
+ * before it has been validated. It must be used carefully.
+ *
+ * @param string|array $value The values to look for in the raw parameters (can be an array)
+ *
+ * @return Boolean true if the value is contained in the raw parameters
+ */
+ public function hasParameterOption($values)
+ {
+ if (!is_array($values)) {
+ $values = array($values);
+ }
+
+ foreach ($this->parameters as $k => $v) {
+ if (!is_int($k)) {
+ $v = $k;
+ }
+
+ if (in_array($v, $values)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Processes command line arguments.
+ */
+ protected function parse()
+ {
+ foreach ($this->parameters as $key => $value) {
+ if ('--' === substr($key, 0, 2)) {
+ $this->addLongOption(substr($key, 2), $value);
+ } elseif ('-' === $key[0]) {
+ $this->addShortOption(substr($key, 1), $value);
+ } else {
+ $this->addArgument($key, $value);
+ }
+ }
+ }
+
+ /**
+ * Adds a short option value.
+ *
+ * @param string $shortcut The short option key
+ * @param mixed $value The value for the option
+ *
+ * @throws \RuntimeException When option given doesn't exist
+ */
+ protected function addShortOption($shortcut, $value)
+ {
+ if (!$this->definition->hasShortcut($shortcut)) {
+ throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
+ }
+
+ $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
+ }
+
+ /**
+ * Adds a long option value.
+ *
+ * @param string $name The long option key
+ * @param mixed $value The value for the option
+ *
+ * @throws \InvalidArgumentException When option given doesn't exist
+ * @throws \InvalidArgumentException When a required value is missing
+ */
+ protected function addLongOption($name, $value)
+ {
+ if (!$this->definition->hasOption($name)) {
+ throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name));
+ }
+
+ $option = $this->definition->getOption($name);
+
+ if (null === $value) {
+ if ($option->isValueRequired()) {
+ throw new \InvalidArgumentException(sprintf('The "--%s" option requires a value.', $name));
+ }
+
+ $value = $option->isValueOptional() ? $option->getDefault() : true;
+ }
+
+ $this->options[$name] = $value;
+ }
+
+ /**
+ * Adds an argument value.
+ *
+ * @param string $name The argument name
+ * @param mixed $value The value for the argument
+ *
+ * @throws \InvalidArgumentException When argument given doesn't exist
+ */
+ protected function addArgument($name, $value)
+ {
+ if (!$this->definition->hasArgument($name)) {
+ throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
+ }
+
+ $this->arguments[$name] = $value;
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Input;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * Input is the base class for all concrete Input classes.
+ *
+ * Three concrete classes are provided by default:
+ *
+ * * `ArgvInput`: The input comes from the CLI arguments (argv)
+ * * `StringInput`: The input is provided as a string
+ * * `ArrayInput`: The input is provided as an array
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+abstract class Input implements InputInterface
+{
+ protected $definition;
+ protected $options;
+ protected $arguments;
+ protected $interactive = true;
+
+ /**
+ * Constructor.
+ *
+ * @param InputDefinition $definition A InputDefinition instance
+ */
+ public function __construct(InputDefinition $definition = null)
+ {
+ if (null === $definition) {
+ $this->definition = new InputDefinition();
+ } else {
+ $this->bind($definition);
+ $this->validate();
+ }
+ }
+
+ /**
+ * Binds the current Input instance with the given arguments and options.
+ *
+ * @param InputDefinition $definition A InputDefinition instance
+ */
+ public function bind(InputDefinition $definition)
+ {
+ $this->arguments = array();
+ $this->options = array();
+ $this->definition = $definition;
+
+ $this->parse();
+ }
+
+ /**
+ * Processes command line arguments.
+ */
+ abstract protected function parse();
+
+ /**
+ * @throws \RuntimeException When not enough arguments are given
+ */
+ public function validate()
+ {
+ if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) {
+ throw new \RuntimeException('Not enough arguments.');
+ }
+ }
+
+ public function isInteractive()
+ {
+ return $this->interactive;
+ }
+
+ public function setInteractive($interactive)
+ {
+ $this->interactive = (Boolean) $interactive;
+ }
+
+ /**
+ * Returns the argument values.
+ *
+ * @return array An array of argument values
+ */
+ public function getArguments()
+ {
+ return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
+ }
+
+ /**
+ * Returns the argument value for a given argument name.
+ *
+ * @param string $name The argument name
+ *
+ * @return mixed The argument value
+ *
+ * @throws \InvalidArgumentException When argument given doesn't exist
+ */
+ public function getArgument($name)
+ {
+ if (!$this->definition->hasArgument($name)) {
+ throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
+ }
+
+ return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault();
+ }
+
+ /**
+ * Sets an argument value by name.
+ *
+ * @param string $name The argument name
+ * @param string $value The argument value
+ *
+ * @throws \InvalidArgumentException When argument given doesn't exist
+ */
+ public function setArgument($name, $value)
+ {
+ if (!$this->definition->hasArgument($name)) {
+ throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
+ }
+
+ $this->arguments[$name] = $value;
+ }
+
+ /**
+ * Returns true if an InputArgument object exists by name or position.
+ *
+ * @param string|integer $name The InputArgument name or position
+ *
+ * @return Boolean true if the InputArgument object exists, false otherwise
+ */
+ public function hasArgument($name)
+ {
+ return $this->definition->hasArgument($name);
+ }
+
+ /**
+ * Returns the options values.
+ *
+ * @return array An array of option values
+ */
+ public function getOptions()
+ {
+ return array_merge($this->definition->getOptionDefaults(), $this->options);
+ }
+
+ /**
+ * Returns the option value for a given option name.
+ *
+ * @param string $name The option name
+ *
+ * @return mixed The option value
+ *
+ * @throws \InvalidArgumentException When option given doesn't exist
+ */
+ public function getOption($name)
+ {
+ if (!$this->definition->hasOption($name)) {
+ throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
+ }
+
+ return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
+ }
+
+ /**
+ * Sets an option value by name.
+ *
+ * @param string $name The option name
+ * @param string $value The option value
+ *
+ * @throws \InvalidArgumentException When option given doesn't exist
+ */
+ public function setOption($name, $value)
+ {
+ if (!$this->definition->hasOption($name)) {
+ throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
+ }
+
+ $this->options[$name] = $value;
+ }
+
+ /**
+ * Returns true if an InputOption object exists by name.
+ *
+ * @param string $name The InputOption name
+ *
+ * @return Boolean true if the InputOption object exists, false otherwise
+ */
+ public function hasOption($name)
+ {
+ return $this->definition->hasOption($name);
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Input;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * Represents a command line argument.
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class InputArgument
+{
+ const REQUIRED = 1;
+ const OPTIONAL = 2;
+ const IS_ARRAY = 4;
+
+ protected $name;
+ protected $mode;
+ protected $default;
+ protected $description;
+
+ /**
+ * Constructor.
+ *
+ * @param string $name The argument name
+ * @param integer $mode The argument mode: self::REQUIRED or self::OPTIONAL
+ * @param string $description A description text
+ * @param mixed $default The default value (for self::OPTIONAL mode only)
+ *
+ * @throws \InvalidArgumentException When argument mode is not valid
+ */
+ public function __construct($name, $mode = null, $description = '', $default = null)
+ {
+ if (null === $mode) {
+ $mode = self::OPTIONAL;
+ } else if (is_string($mode) || $mode > 7) {
+ throw new \InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode));
+ }
+
+ $this->name = $name;
+ $this->mode = $mode;
+ $this->description = $description;
+
+ $this->setDefault($default);
+ }
+
+ /**
+ * Returns the argument name.
+ *
+ * @return string The argument name
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Returns true if the argument is required.
+ *
+ * @return Boolean true if parameter mode is self::REQUIRED, false otherwise
+ */
+ public function isRequired()
+ {
+ return self::REQUIRED === (self::REQUIRED & $this->mode);
+ }
+
+ /**
+ * Returns true if the argument can take multiple values.
+ *
+ * @return Boolean true if mode is self::IS_ARRAY, false otherwise
+ */
+ public function isArray()
+ {
+ return self::IS_ARRAY === (self::IS_ARRAY & $this->mode);
+ }
+
+ /**
+ * Sets the default value.
+ *
+ * @param mixed $default The default value
+ *
+ * @throws \LogicException When incorrect default value is given
+ */
+ public function setDefault($default = null)
+ {
+ if (self::REQUIRED === $this->mode && null !== $default) {
+ throw new \LogicException('Cannot set a default value except for Parameter::OPTIONAL mode.');
+ }
+
+ if ($this->isArray()) {
+ if (null === $default) {
+ $default = array();
+ } else if (!is_array($default)) {
+ throw new \LogicException('A default value for an array argument must be an array.');
+ }
+ }
+
+ $this->default = $default;
+ }
+
+ /**
+ * Returns the default value.
+ *
+ * @return mixed The default value
+ */
+ public function getDefault()
+ {
+ return $this->default;
+ }
+
+ /**
+ * Returns the description text.
+ *
+ * @return string The description text
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Input;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * A InputDefinition represents a set of valid command line arguments and options.
+ *
+ * Usage:
+ *
+ * $definition = new InputDefinition(array(
+ * new InputArgument('name', InputArgument::REQUIRED),
+ * new InputOption('foo', 'f', InputOption::VALUE_REQUIRED),
+ * ));
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class InputDefinition
+{
+ protected $arguments;
+ protected $requiredCount;
+ protected $hasAnArrayArgument = false;
+ protected $hasOptional;
+ protected $options;
+ protected $shortcuts;
+
+ /**
+ * Constructor.
+ *
+ * @param array $definition An array of InputArgument and InputOption instance
+ */
+ public function __construct(array $definition = array())
+ {
+ $this->setDefinition($definition);
+ }
+
+ public function setDefinition(array $definition)
+ {
+ $arguments = array();
+ $options = array();
+ foreach ($definition as $item) {
+ if ($item instanceof InputOption) {
+ $options[] = $item;
+ } else {
+ $arguments[] = $item;
+ }
+ }
+
+ $this->setArguments($arguments);
+ $this->setOptions($options);
+ }
+
+ /**
+ * Sets the InputArgument objects.
+ *
+ * @param array $arguments An array of InputArgument objects
+ */
+ public function setArguments($arguments = array())
+ {
+ $this->arguments = array();
+ $this->requiredCount = 0;
+ $this->hasOptional = false;
+ $this->hasAnArrayArgument = false;
+ $this->addArguments($arguments);
+ }
+
+ /**
+ * Add an array of InputArgument objects.
+ *
+ * @param InputArgument[] $arguments An array of InputArgument objects
+ */
+ public function addArguments($arguments = array())
+ {
+ if (null !== $arguments) {
+ foreach ($arguments as $argument) {
+ $this->addArgument($argument);
+ }
+ }
+ }
+
+ /**
+ * Add an InputArgument object.
+ *
+ * @param InputArgument $argument An InputArgument object
+ *
+ * @throws \LogicException When incorrect argument is given
+ */
+ public function addArgument(InputArgument $argument)
+ {
+ if (isset($this->arguments[$argument->getName()])) {
+ throw new \LogicException(sprintf('An argument with name "%s" already exist.', $argument->getName()));
+ }
+
+ if ($this->hasAnArrayArgument) {
+ throw new \LogicException('Cannot add an argument after an array argument.');
+ }
+
+ if ($argument->isRequired() && $this->hasOptional) {
+ throw new \LogicException('Cannot add a required argument after an optional one.');
+ }
+
+ if ($argument->isArray()) {
+ $this->hasAnArrayArgument = true;
+ }
+
+ if ($argument->isRequired()) {
+ ++$this->requiredCount;
+ } else {
+ $this->hasOptional = true;
+ }
+
+ $this->arguments[$argument->getName()] = $argument;
+ }
+
+ /**
+ * Returns an InputArgument by name or by position.
+ *
+ * @param string|integer $name The InputArgument name or position
+ *
+ * @return InputArgument An InputArgument object
+ *
+ * @throws \InvalidArgumentException When argument given doesn't exist
+ */
+ public function getArgument($name)
+ {
+ $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;
+
+ if (!$this->hasArgument($name)) {
+ throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
+ }
+
+ return $arguments[$name];
+ }
+
+ /**
+ * Returns true if an InputArgument object exists by name or position.
+ *
+ * @param string|integer $name The InputArgument name or position
+ *
+ * @return Boolean true if the InputArgument object exists, false otherwise
+ */
+ public function hasArgument($name)
+ {
+ $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;
+
+ return isset($arguments[$name]);
+ }
+
+ /**
+ * Gets the array of InputArgument objects.
+ *
+ * @return array An array of InputArgument objects
+ */
+ public function getArguments()
+ {
+ return $this->arguments;
+ }
+
+ /**
+ * Returns the number of InputArguments.
+ *
+ * @return integer The number of InputArguments
+ */
+ public function getArgumentCount()
+ {
+ return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments);
+ }
+
+ /**
+ * Returns the number of required InputArguments.
+ *
+ * @return integer The number of required InputArguments
+ */
+ public function getArgumentRequiredCount()
+ {
+ return $this->requiredCount;
+ }
+
+ /**
+ * Gets the default values.
+ *
+ * @return array An array of default values
+ */
+ public function getArgumentDefaults()
+ {
+ $values = array();
+ foreach ($this->arguments as $argument) {
+ $values[$argument->getName()] = $argument->getDefault();
+ }
+
+ return $values;
+ }
+
+ /**
+ * Sets the InputOption objects.
+ *
+ * @param array $options An array of InputOption objects
+ */
+ public function setOptions($options = array())
+ {
+ $this->options = array();
+ $this->shortcuts = array();
+ $this->addOptions($options);
+ }
+
+ /**
+ * Add an array of InputOption objects.
+ *
+ * @param InputOption[] $options An array of InputOption objects
+ */
+ public function addOptions($options = array())
+ {
+ foreach ($options as $option) {
+ $this->addOption($option);
+ }
+ }
+
+ /**
+ * Add an InputOption object.
+ *
+ * @param InputOption $option An InputOption object
+ *
+ * @throws \LogicException When option given already exist
+ */
+ public function addOption(InputOption $option)
+ {
+ if (isset($this->options[$option->getName()])) {
+ throw new \LogicException(sprintf('An option named "%s" already exist.', $option->getName()));
+ } else if (isset($this->shortcuts[$option->getShortcut()])) {
+ throw new \LogicException(sprintf('An option with shortcut "%s" already exist.', $option->getShortcut()));
+ }
+
+ $this->options[$option->getName()] = $option;
+ if ($option->getShortcut()) {
+ $this->shortcuts[$option->getShortcut()] = $option->getName();
+ }
+ }
+
+ /**
+ * Returns an InputOption by name.
+ *
+ * @param string $name The InputOption name
+ *
+ * @return InputOption A InputOption object
+ */
+ public function getOption($name)
+ {
+ if (!$this->hasOption($name)) {
+ throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name));
+ }
+
+ return $this->options[$name];
+ }
+
+ /**
+ * Returns true if an InputOption object exists by name.
+ *
+ * @param string $name The InputOption name
+ *
+ * @return Boolean true if the InputOption object exists, false otherwise
+ */
+ public function hasOption($name)
+ {
+ return isset($this->options[$name]);
+ }
+
+ /**
+ * Gets the array of InputOption objects.
+ *
+ * @return array An array of InputOption objects
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * Returns true if an InputOption object exists by shortcut.
+ *
+ * @param string $name The InputOption shortcut
+ *
+ * @return Boolean true if the InputOption object exists, false otherwise
+ */
+ public function hasShortcut($name)
+ {
+ return isset($this->shortcuts[$name]);
+ }
+
+ /**
+ * Gets an InputOption by shortcut.
+ *
+ * @return InputOption An InputOption object
+ */
+ public function getOptionForShortcut($shortcut)
+ {
+ return $this->getOption($this->shortcutToName($shortcut));
+ }
+
+ /**
+ * Gets an array of default values.
+ *
+ * @return array An array of all default values
+ */
+ public function getOptionDefaults()
+ {
+ $values = array();
+ foreach ($this->options as $option) {
+ $values[$option->getName()] = $option->getDefault();
+ }
+
+ return $values;
+ }
+
+ /**
+ * Returns the InputOption name given a shortcut.
+ *
+ * @param string $shortcut The shortcut
+ *
+ * @return string The InputOption name
+ *
+ * @throws \InvalidArgumentException When option given does not exist
+ */
+ protected function shortcutToName($shortcut)
+ {
+ if (!isset($this->shortcuts[$shortcut])) {
+ throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
+ }
+
+ return $this->shortcuts[$shortcut];
+ }
+
+ /**
+ * Gets the synopsis.
+ *
+ * @return string The synopsis
+ */
+ public function getSynopsis()
+ {
+ $elements = array();
+ foreach ($this->getOptions() as $option) {
+ $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : '';
+ $elements[] = sprintf('['.($option->isValueRequired() ? '%s--%s="..."' : ($option->isValueOptional() ? '%s--%s[="..."]' : '%s--%s')).']', $shortcut, $option->getName());
+ }
+
+ foreach ($this->getArguments() as $argument) {
+ $elements[] = sprintf($argument->isRequired() ? '%s' : '[%s]', $argument->getName().($argument->isArray() ? '1' : ''));
+
+ if ($argument->isArray()) {
+ $elements[] = sprintf('... [%sN]', $argument->getName());
+ }
+ }
+
+ return implode(' ', $elements);
+ }
+
+ /**
+ * Returns a textual representation of the InputDefinition.
+ *
+ * @return string A string representing the InputDefinition
+ */
+ public function asText()
+ {
+ // find the largest option or argument name
+ $max = 0;
+ foreach ($this->getOptions() as $option) {
+ $max = strlen($option->getName()) + 2 > $max ? strlen($option->getName()) + 2 : $max;
+ }
+ foreach ($this->getArguments() as $argument) {
+ $max = strlen($argument->getName()) > $max ? strlen($argument->getName()) : $max;
+ }
+ ++$max;
+
+ $text = array();
+
+ if ($this->getArguments()) {
+ $text[] = '<comment>Arguments:</comment>';
+ foreach ($this->getArguments() as $argument) {
+ if (null !== $argument->getDefault() && (!is_array($argument->getDefault()) || count($argument->getDefault()))) {
+ $default = sprintf('<comment> (default: %s)</comment>', is_array($argument->getDefault()) ? str_replace("\n", '', var_export($argument->getDefault(), true)): $argument->getDefault());
+ } else {
+ $default = '';
+ }
+
+ $text[] = sprintf(" <info>%-${max}s</info> %s%s", $argument->getName(), $argument->getDescription(), $default);
+ }
+
+ $text[] = '';
+ }
+
+ if ($this->getOptions()) {
+ $text[] = '<comment>Options:</comment>';
+
+ foreach ($this->getOptions() as $option) {
+ if ($option->acceptValue() && null !== $option->getDefault() && (!is_array($option->getDefault()) || count($option->getDefault()))) {
+ $default = sprintf('<comment> (default: %s)</comment>', is_array($option->getDefault()) ? str_replace("\n", '', print_r($option->getDefault(), true)): $option->getDefault());
+ } else {
+ $default = '';
+ }
+
+ $multiple = $option->isArray() ? '<comment> (multiple values allowed)</comment>' : '';
+ $text[] = sprintf(' %-'.$max.'s %s%s%s%s', '<info>--'.$option->getName().'</info>', $option->getShortcut() ? sprintf('(-%s) ', $option->getShortcut()) : '', $option->getDescription(), $default, $multiple);
+ }
+
+ $text[] = '';
+ }
+
+ return implode("\n", $text);
+ }
+
+ /**
+ * Returns an XML representation of the InputDefinition.
+ *
+ * @param Boolean $asDom Whether to return a DOM or an XML string
+ *
+ * @return string|DOMDocument An XML string representing the InputDefinition
+ */
+ public function asXml($asDom = false)
+ {
+ $dom = new \DOMDocument('1.0', 'UTF-8');
+ $dom->formatOutput = true;
+ $dom->appendChild($definitionXML = $dom->createElement('definition'));
+
+ $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments'));
+ foreach ($this->getArguments() as $argument) {
+ $argumentsXML->appendChild($argumentXML = $dom->createElement('argument'));
+ $argumentXML->setAttribute('name', $argument->getName());
+ $argumentXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0);
+ $argumentXML->setAttribute('is_array', $argument->isArray() ? 1 : 0);
+ $argumentXML->appendChild($descriptionXML = $dom->createElement('description'));
+ $descriptionXML->appendChild($dom->createTextNode($argument->getDescription()));
+
+ $argumentXML->appendChild($defaultsXML = $dom->createElement('defaults'));
+ $defaults = is_array($argument->getDefault()) ? $argument->getDefault() : ($argument->getDefault() ? array($argument->getDefault()) : array());
+ foreach ($defaults as $default) {
+ $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
+ $defaultXML->appendChild($dom->createTextNode($default));
+ }
+ }
+
+ $definitionXML->appendChild($optionsXML = $dom->createElement('options'));
+ foreach ($this->getOptions() as $option) {
+ $optionsXML->appendChild($optionXML = $dom->createElement('option'));
+ $optionXML->setAttribute('name', '--'.$option->getName());
+ $optionXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : '');
+ $optionXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0);
+ $optionXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0);
+ $optionXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0);
+ $optionXML->appendChild($descriptionXML = $dom->createElement('description'));
+ $descriptionXML->appendChild($dom->createTextNode($option->getDescription()));
+
+ if ($option->acceptValue()) {
+ $optionXML->appendChild($defaultsXML = $dom->createElement('defaults'));
+ $defaults = is_array($option->getDefault()) ? $option->getDefault() : ($option->getDefault() ? array($option->getDefault()) : array());
+ foreach ($defaults as $default) {
+ $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
+ $defaultXML->appendChild($dom->createTextNode($default));
+ }
+ }
+ }
+
+ return $asDom ? $dom : $dom->saveXml();
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Input;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * InputInterface is the interface implemented by all input classes.
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+interface InputInterface
+{
+ /**
+ * Returns the first argument from the raw parameters (not parsed).
+ *
+ * @return string The value of the first argument or null otherwise
+ */
+ function getFirstArgument();
+
+ /**
+ * Returns true if the raw parameters (not parsed) contains a value.
+ *
+ * This method is to be used to introspect the input parameters
+ * before it has been validated. It must be used carefully.
+ *
+ * @param string $value The value to look for in the raw parameters
+ *
+ * @return Boolean true if the value is contained in the raw parameters
+ */
+ function hasParameterOption($value);
+
+ /**
+ * Binds the current Input instance with the given arguments and options.
+ *
+ * @param InputDefinition $definition A InputDefinition instance
+ */
+ function bind(InputDefinition $definition);
+
+ function validate();
+
+ function getArguments();
+
+ function getArgument($name);
+
+ function getOptions();
+
+ function getOption($name);
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Input;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * Represents a command line option.
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class InputOption
+{
+ const VALUE_NONE = 1;
+ const VALUE_REQUIRED = 2;
+ const VALUE_OPTIONAL = 4;
+ const VALUE_IS_ARRAY = 8;
+
+ protected $name;
+ protected $shortcut;
+ protected $mode;
+ protected $default;
+ protected $description;
+
+ /**
+ * Constructor.
+ *
+ * @param string $name The option name
+ * @param string $shortcut The shortcut (can be null)
+ * @param integer $mode The option mode: One of the VALUE_* constants
+ * @param string $description A description text
+ * @param mixed $default The default value (must be null for self::VALUE_REQUIRED or self::VALUE_NONE)
+ *
+ * @throws \InvalidArgumentException If option mode is invalid or incompatible
+ */
+ public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null)
+ {
+ if ('--' === substr($name, 0, 2)) {
+ $name = substr($name, 2);
+ }
+
+ if (empty($shortcut)) {
+ $shortcut = null;
+ }
+
+ if (null !== $shortcut) {
+ if ('-' === $shortcut[0]) {
+ $shortcut = substr($shortcut, 1);
+ }
+ }
+
+ if (null === $mode) {
+ $mode = self::VALUE_NONE;
+ } else if (!is_int($mode) || $mode > 15) {
+ throw new \InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode));
+ }
+
+ $this->name = $name;
+ $this->shortcut = $shortcut;
+ $this->mode = $mode;
+ $this->description = $description;
+
+ if ($this->isArray() && !$this->acceptValue()) {
+ throw new \InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.');
+ }
+
+ $this->setDefault($default);
+ }
+
+ /**
+ * Returns the shortcut.
+ *
+ * @return string The shortcut
+ */
+ public function getShortcut()
+ {
+ return $this->shortcut;
+ }
+
+ /**
+ * Returns the name.
+ *
+ * @return string The name
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Returns true if the option accepts a value.
+ *
+ * @return Boolean true if value mode is not self::VALUE_NONE, false otherwise
+ */
+ public function acceptValue()
+ {
+ return $this->isValueRequired() || $this->isValueOptional();
+ }
+
+ /**
+ * Returns true if the option requires a value.
+ *
+ * @return Boolean true if value mode is self::VALUE_REQUIRED, false otherwise
+ */
+ public function isValueRequired()
+ {
+ return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode);
+ }
+
+ /**
+ * Returns true if the option takes an optional value.
+ *
+ * @return Boolean true if value mode is self::VALUE_OPTIONAL, false otherwise
+ */
+ public function isValueOptional()
+ {
+ return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode);
+ }
+
+ /**
+ * Returns true if the option can take multiple values.
+ *
+ * @return Boolean true if mode is self::VALUE_IS_ARRAY, false otherwise
+ */
+ public function isArray()
+ {
+ return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode);
+ }
+
+ /**
+ * Sets the default value.
+ *
+ * @param mixed $default The default value
+ */
+ public function setDefault($default = null)
+ {
+ if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) {
+ throw new \LogicException('Cannot set a default value when using Option::VALUE_NONE mode.');
+ }
+
+ if ($this->isArray()) {
+ if (null === $default) {
+ $default = array();
+ } elseif (!is_array($default)) {
+ throw new \LogicException('A default value for an array option must be an array.');
+ }
+ }
+
+ $this->default = $this->acceptValue() ? $default : false;
+ }
+
+ /**
+ * Returns the default value.
+ *
+ * @return mixed The default value
+ */
+ public function getDefault()
+ {
+ return $this->default;
+ }
+
+ /**
+ * Returns the description text.
+ *
+ * @return string The description text
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Input;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * StringInput represents an input provided as a string.
+ *
+ * Usage:
+ *
+ * $input = new StringInput('foo --bar="foobar"');
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class StringInput extends ArgvInput
+{
+ const REGEX_STRING = '([^ ]+?)(?: |(?<!\\\\)"|(?<!\\\\)\'|$)';
+ const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')';
+
+ /**
+ * Constructor.
+ *
+ * @param string $input An array of parameters from the CLI (in the argv format)
+ * @param InputDefinition $definition A InputDefinition instance
+ */
+ public function __construct($input, InputDefinition $definition = null)
+ {
+ parent::__construct(array(), $definition);
+
+ $this->tokens = $this->tokenize($input);
+ }
+
+ /**
+ * @throws \InvalidArgumentException When unable to parse input (should never happen)
+ */
+ protected function tokenize($input)
+ {
+ $input = preg_replace('/(\r\n|\r|\n|\t)/', ' ', $input);
+
+ $tokens = array();
+ $length = strlen($input);
+ $cursor = 0;
+ while ($cursor < $length) {
+ if (preg_match('/\s+/A', $input, $match, null, $cursor)) {
+ } elseif (preg_match('/([^="\' ]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, null, $cursor)) {
+ $tokens[] = $match[1].$match[2].stripcslashes(str_replace(array('"\'', '\'"', '\'\'', '""'), '', substr($match[3], 1, strlen($match[3]) - 2)));
+ } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor)) {
+ $tokens[] = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2));
+ } elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) {
+ $tokens[] = stripcslashes($match[1]);
+ } else {
+ // should never happen
+ // @codeCoverageIgnoreStart
+ throw new \InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10)));
+ // @codeCoverageIgnoreEnd
+ }
+
+ $cursor += strlen($match[0]);
+ }
+
+ return $tokens;
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Output;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * ConsoleOutput is the default class for all CLI output. It uses STDOUT.
+ *
+ * This class is a convenient wrapper around `StreamOutput`.
+ *
+ * $output = new ConsoleOutput();
+ *
+ * This is equivalent to:
+ *
+ * $output = new StreamOutput(fopen('php://stdout', 'w'));
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class ConsoleOutput extends StreamOutput
+{
+ /**
+ * Constructor.
+ *
+ * @param integer $verbosity The verbosity level (self::VERBOSITY_QUIET, self::VERBOSITY_NORMAL, self::VERBOSITY_VERBOSE)
+ * @param Boolean $decorated Whether to decorate messages or not (null for auto-guessing)
+ */
+ public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null)
+ {
+ parent::__construct(fopen('php://stdout', 'w'), $verbosity, $decorated);
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Output;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * NullOutput suppresses all output.
+ *
+ * $output = new NullOutput();
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class NullOutput extends Output
+{
+ /**
+ * Writes a message to the output.
+ *
+ * @param string $message A message to write to the output
+ * @param Boolean $newline Whether to add a newline or not
+ */
+ public function doWrite($message, $newline)
+ {
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Output;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * Base class for output classes.
+ *
+ * There is three level of verbosity:
+ *
+ * * normal: no option passed (normal output - information)
+ * * verbose: -v (more output - debug)
+ * * quiet: -q (no output)
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+abstract class Output implements OutputInterface
+{
+ const VERBOSITY_QUIET = 0;
+ const VERBOSITY_NORMAL = 1;
+ const VERBOSITY_VERBOSE = 2;
+
+ const OUTPUT_NORMAL = 0;
+ const OUTPUT_RAW = 1;
+ const OUTPUT_PLAIN = 2;
+
+ protected $verbosity;
+ protected $decorated;
+
+ static protected $styles = array(
+ 'error' => array('bg' => 'red', 'fg' => 'white'),
+ 'info' => array('fg' => 'green'),
+ 'comment' => array('fg' => 'yellow'),
+ 'question' => array('bg' => 'cyan', 'fg' => 'black'),
+ );
+ static protected $options = array('bold' => 1, 'underscore' => 4, 'blink' => 5, 'reverse' => 7, 'conceal' => 8);
+ static protected $foreground = array('black' => 30, 'red' => 31, 'green' => 32, 'yellow' => 33, 'blue' => 34, 'magenta' => 35, 'cyan' => 36, 'white' => 37);
+ static protected $background = array('black' => 40, 'red' => 41, 'green' => 42, 'yellow' => 43, 'blue' => 44, 'magenta' => 45, 'cyan' => 46, 'white' => 47);
+
+ /**
+ * Constructor.
+ *
+ * @param integer $verbosity The verbosity level (self::VERBOSITY_QUIET, self::VERBOSITY_NORMAL, self::VERBOSITY_VERBOSE)
+ * @param Boolean $decorated Whether to decorate messages or not (null for auto-guessing)
+ */
+ public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null)
+ {
+ $this->decorated = (Boolean) $decorated;
+ $this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity;
+ }
+
+ /**
+ * Sets a new style.
+ *
+ * @param string $name The style name
+ * @param array $options An array of options
+ */
+ static public function setStyle($name, $options = array())
+ {
+ static::$styles[strtolower($name)] = $options;
+ }
+
+ /**
+ * Sets the decorated flag.
+ *
+ * @param Boolean $decorated Whether to decorated the messages or not
+ */
+ public function setDecorated($decorated)
+ {
+ $this->decorated = (Boolean) $decorated;
+ }
+
+ /**
+ * Gets the decorated flag.
+ *
+ * @return Boolean true if the output will decorate messages, false otherwise
+ */
+ public function isDecorated()
+ {
+ return $this->decorated;
+ }
+
+ /**
+ * Sets the verbosity of the output.
+ *
+ * @param integer $level The level of verbosity
+ */
+ public function setVerbosity($level)
+ {
+ $this->verbosity = (int) $level;
+ }
+
+ /**
+ * Gets the current verbosity of the output.
+ *
+ * @return integer The current level of verbosity
+ */
+ public function getVerbosity()
+ {
+ return $this->verbosity;
+ }
+
+ /**
+ * Writes a message to the output and adds a newline at the end.
+ *
+ * @param string|array $messages The message as an array of lines of a single string
+ * @param integer $type The type of output
+ */
+ public function writeln($messages, $type = 0)
+ {
+ $this->write($messages, true, $type);
+ }
+
+ /**
+ * Writes a message to the output.
+ *
+ * @param string|array $messages The message as an array of lines of a single string
+ * @param Boolean $newline Whether to add a newline or not
+ * @param integer $type The type of output
+ *
+ * @throws \InvalidArgumentException When unknown output type is given
+ */
+ public function write($messages, $newline = false, $type = 0)
+ {
+ if (self::VERBOSITY_QUIET === $this->verbosity) {
+ return;
+ }
+
+ if (!is_array($messages)) {
+ $messages = array($messages);
+ }
+
+ foreach ($messages as $message) {
+ switch ($type) {
+ case Output::OUTPUT_NORMAL:
+ $message = $this->format($message);
+ break;
+ case Output::OUTPUT_RAW:
+ break;
+ case Output::OUTPUT_PLAIN:
+ $message = strip_tags($this->format($message));
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type));
+ }
+
+ $this->doWrite($message, $newline);
+ }
+ }
+
+ /**
+ * Writes a message to the output.
+ *
+ * @param string $message A message to write to the output
+ * @param Boolean $newline Whether to add a newline or not
+ */
+ abstract public function doWrite($message, $newline);
+
+ /**
+ * Formats a message according to the given styles.
+ *
+ * @param string $message The message to style
+ *
+ * @return string The styled message
+ */
+ protected function format($message)
+ {
+ $message = preg_replace_callback('#<([a-z][a-z0-9\-_=;]+)>#i', array($this, 'replaceStartStyle'), $message);
+
+ return preg_replace_callback('#</([a-z][a-z0-9\-_]*)?>#i', array($this, 'replaceEndStyle'), $message);
+ }
+
+ /**
+ * @throws \InvalidArgumentException When style is unknown
+ */
+ protected function replaceStartStyle($match)
+ {
+ if (!$this->decorated) {
+ return '';
+ }
+
+ if (isset(static::$styles[strtolower($match[1])])) {
+ $parameters = static::$styles[strtolower($match[1])];
+ } else {
+ // bg=blue;fg=red
+ if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($match[1]), $matches, PREG_SET_ORDER)) {
+ throw new \InvalidArgumentException(sprintf('Unknown style "%s".', $match[1]));
+ }
+
+ $parameters = array();
+ foreach ($matches as $match) {
+ $parameters[$match[1]] = $match[2];
+ }
+ }
+
+ $codes = array();
+
+ if (isset($parameters['fg'])) {
+ $codes[] = static::$foreground[$parameters['fg']];
+ }
+
+ if (isset($parameters['bg'])) {
+ $codes[] = static::$background[$parameters['bg']];
+ }
+
+ foreach (static::$options as $option => $value) {
+ if (isset($parameters[$option]) && $parameters[$option]) {
+ $codes[] = $value;
+ }
+ }
+
+ return "\033[".implode(';', $codes).'m';
+ }
+
+ protected function replaceEndStyle($match)
+ {
+ if (!$this->decorated) {
+ return '';
+ }
+
+ return "\033[0m";
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Output;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * OutputInterface is the interface implemented by all Output classes.
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+interface OutputInterface
+{
+ /**
+ * Writes a message to the output.
+ *
+ * @param string|array $messages The message as an array of lines of a single string
+ * @param Boolean $newline Whether to add a newline or not
+ * @param integer $type The type of output
+ *
+ * @throws \InvalidArgumentException When unknown output type is given
+ */
+ function write($messages, $newline = false, $type = 0);
+
+ /**
+ * Sets the verbosity of the output.
+ *
+ * @param integer $level The level of verbosity
+ */
+ function setVerbosity($level);
+
+ /**
+ * Sets the decorated flag.
+ *
+ * @param Boolean $decorated Whether to decorated the messages or not
+ */
+ function setDecorated($decorated);
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Output;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * StreamOutput writes the output to a given stream.
+ *
+ * Usage:
+ *
+ * $output = new StreamOutput(fopen('php://stdout', 'w'));
+ *
+ * As `StreamOutput` can use any stream, you can also use a file:
+ *
+ * $output = new StreamOutput(fopen('/path/to/output.log', 'a', false));
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class StreamOutput extends Output
+{
+ protected $stream;
+
+ /**
+ * Constructor.
+ *
+ * @param mixed $stream A stream resource
+ * @param integer $verbosity The verbosity level (self::VERBOSITY_QUIET, self::VERBOSITY_NORMAL, self::VERBOSITY_VERBOSE)
+ * @param Boolean $decorated Whether to decorate messages or not (null for auto-guessing)
+ *
+ * @throws \InvalidArgumentException When first argument is not a real stream
+ */
+ public function __construct($stream, $verbosity = self::VERBOSITY_NORMAL, $decorated = null)
+ {
+ if (!is_resource($stream) || 'stream' !== get_resource_type($stream)) {
+ throw new \InvalidArgumentException('The StreamOutput class needs a stream as its first argument.');
+ }
+
+ $this->stream = $stream;
+
+ if (null === $decorated) {
+ $decorated = $this->hasColorSupport($decorated);
+ }
+
+ parent::__construct($verbosity, $decorated);
+ }
+
+ /**
+ * Gets the stream attached to this StreamOutput instance.
+ *
+ * @return resource A stream resource
+ */
+ public function getStream()
+ {
+ return $this->stream;
+ }
+
+ /**
+ * Writes a message to the output.
+ *
+ * @param string $message A message to write to the output
+ * @param Boolean $newline Whether to add a newline or not
+ *
+ * @throws \RuntimeException When unable to write output (should never happen)
+ */
+ public function doWrite($message, $newline)
+ {
+ if (false === @fwrite($this->stream, $message.($newline ? PHP_EOL : ''))) {
+ // @codeCoverageIgnoreStart
+ // should never happen
+ throw new \RuntimeException('Unable to write output.');
+ // @codeCoverageIgnoreEnd
+ }
+
+ flush();
+ }
+
+ /**
+ * Returns true if the stream supports colorization.
+ *
+ * Colorization is disabled if not supported by the stream:
+ *
+ * - windows without ansicon
+ * - non tty consoles
+ *
+ * @return Boolean true if the stream supports colorization, false otherwise
+ */
+ protected function hasColorSupport()
+ {
+ // @codeCoverageIgnoreStart
+ if (DIRECTORY_SEPARATOR == '\\') {
+ return false !== getenv('ANSICON');
+ } else {
+ return function_exists('posix_isatty') && @posix_isatty($this->stream);
+ }
+ // @codeCoverageIgnoreEnd
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Input\StringInput;
+use Symfony\Component\Console\Output\ConsoleOutput;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * A Shell wraps an Application to add shell capabilities to it.
+ *
+ * This class only works with a PHP compiled with readline support
+ * (either --with-readline or --with-libedit)
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class Shell
+{
+ protected $application;
+ protected $history;
+ protected $output;
+
+ /**
+ * Constructor.
+ *
+ * If there is no readline support for the current PHP executable
+ * a \RuntimeException exception is thrown.
+ *
+ * @param Application $application An application instance
+ *
+ * @throws \RuntimeException When Readline extension is not enabled
+ */
+ public function __construct(Application $application)
+ {
+ if (!function_exists('readline')) {
+ throw new \RuntimeException('Unable to start the shell as the Readline extension is not enabled.');
+ }
+
+ $this->application = $application;
+ $this->history = getenv('HOME').'/.history_'.$application->getName();
+ $this->output = new ConsoleOutput();
+ }
+
+ /**
+ * Runs the shell.
+ */
+ public function run()
+ {
+ $this->application->setAutoExit(false);
+ $this->application->setCatchExceptions(true);
+
+ readline_read_history($this->history);
+ readline_completion_function(array($this, 'autocompleter'));
+
+ $this->output->writeln($this->getHeader());
+ while (true) {
+ $command = readline($this->application->getName().' > ');
+
+ if (false === $command) {
+ $this->output->writeln("\n");
+
+ break;
+ }
+
+ readline_add_history($command);
+ readline_write_history($this->history);
+
+ if (0 !== $ret = $this->application->run(new StringInput($command), $this->output)) {
+ $this->output->writeln(sprintf('<error>The command terminated with an error status (%s)</error>', $ret));
+ }
+ }
+ }
+
+ /**
+ * Tries to return autocompletion for the current entered text.
+ *
+ * @param string $text The last segment of the entered text
+ * @param integer $position The current position
+ */
+ protected function autocompleter($text, $position)
+ {
+ $info = readline_info();
+ $text = substr($info['line_buffer'], 0, $info['end']);
+
+ if ($info['point'] !== $info['end']) {
+ return true;
+ }
+
+ // task name?
+ if (false === strpos($text, ' ') || !$text) {
+ return array_keys($this->application->all());
+ }
+
+ // options and arguments?
+ try {
+ $command = $this->application->findCommand(substr($text, 0, strpos($text, ' ')));
+ } catch (\Exception $e) {
+ return true;
+ }
+
+ $list = array('--help');
+ foreach ($command->getDefinition()->getOptions() as $option) {
+ $list[] = '--'.$option->getName();
+ }
+
+ return $list;
+ }
+
+ /**
+ * Returns the shell header.
+ *
+ * @return string The header string
+ */
+ protected function getHeader()
+ {
+ return <<<EOF
+
+Welcome to the <info>{$this->application->getName()}</info> shell (<comment>{$this->application->getVersion()}</comment>).
+
+At the prompt, type <comment>help</comment> for some help,
+or <comment>list</comment> to get a list available commands.
+
+To exit the shell, type <comment>^D</comment>.
+
+EOF;
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Tester;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Input\ArrayInput;
+use Symfony\Component\Console\Output\StreamOutput;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class ApplicationTester
+{
+ protected $application;
+ protected $display;
+ protected $input;
+ protected $output;
+
+ /**
+ * Constructor.
+ *
+ * @param Application $application A Application instance to test.
+ */
+ public function __construct(Application $application)
+ {
+ $this->application = $application;
+ }
+
+ /**
+ * Executes the application.
+ *
+ * Available options:
+ *
+ * * interactive: Sets the input interactive flag
+ * * decorated: Sets the output decorated flag
+ * * verbosity: Sets the output verbosity flag
+ *
+ * @param array $input An array of arguments and options
+ * @param array $options An array of options
+ */
+ public function run(array $input, $options = array())
+ {
+ $this->input = new ArrayInput($input);
+ if (isset($options['interactive'])) {
+ $this->input->setInteractive($options['interactive']);
+ }
+
+ $this->output = new StreamOutput(fopen('php://memory', 'w', false));
+ if (isset($options['decorated'])) {
+ $this->output->setDecorated($options['decorated']);
+ }
+ if (isset($options['verbosity'])) {
+ $this->output->setVerbosity($options['verbosity']);
+ }
+
+ $ret = $this->application->run($this->input, $this->output);
+
+ rewind($this->output->getStream());
+
+ return $this->display = stream_get_contents($this->output->getStream());
+ }
+
+ /**
+ * Gets the display returned by the last execution of the application.
+ *
+ * @return string The display
+ */
+ public function getDisplay()
+ {
+ return $this->display;
+ }
+
+ /**
+ * Gets the input instance used by the last execution of the application.
+ *
+ * @return InputInterface The current input instance
+ */
+ public function getInput()
+ {
+ return $this->input;
+ }
+
+ /**
+ * Gets the output instance used by the last execution of the application.
+ *
+ * @return OutputInterface The current output instance
+ */
+ public function getOutput()
+ {
+ return $this->output;
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Console\Tester;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\ArrayInput;
+use Symfony\Component\Console\Output\StreamOutput;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class CommandTester
+{
+ protected $command;
+ protected $display;
+ protected $input;
+ protected $output;
+
+ /**
+ * Constructor.
+ *
+ * @param Command $command A Command instance to test.
+ */
+ public function __construct(Command $command)
+ {
+ $this->command = $command;
+ }
+
+ /**
+ * Executes the command.
+ *
+ * Available options:
+ *
+ * * interactive: Sets the input interactive flag
+ * * decorated: Sets the output decorated flag
+ * * verbosity: Sets the output verbosity flag
+ *
+ * @param array $input An array of arguments and options
+ * @param array $options An array of options
+ */
+ public function execute(array $input, array $options = array())
+ {
+ $this->input = new ArrayInput($input);
+ if (isset($options['interactive'])) {
+ $this->input->setInteractive($options['interactive']);
+ }
+
+ $this->output = new StreamOutput(fopen('php://memory', 'w', false));
+ if (isset($options['decorated'])) {
+ $this->output->setDecorated($options['decorated']);
+ }
+ if (isset($options['verbosity'])) {
+ $this->output->setVerbosity($options['verbosity']);
+ }
+
+ $ret = $this->command->run($this->input, $this->output);
+
+ rewind($this->output->getStream());
+
+ return $this->display = stream_get_contents($this->output->getStream());
+ }
+
+ /**
+ * Gets the display returned by the last execution of the command.
+ *
+ * @return string The display
+ */
+ public function getDisplay()
+ {
+ return $this->display;
+ }
+
+ /**
+ * Gets the input instance used by the last execution of the command.
+ *
+ * @return InputInterface The current input instance
+ */
+ public function getInput()
+ {
+ return $this->input;
+ }
+
+ /**
+ * Gets the output instance used by the last execution of the command.
+ *
+ * @return OutputInterface The current output instance
+ */
+ public function getOutput()
+ {
+ return $this->output;
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Yaml;
+
+/*
+ * This file is part of the symfony package.
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Dumper dumps PHP variables to YAML strings.
+ *
+ * @package symfony
+ * @subpackage yaml
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class Dumper
+{
+ /**
+ * Dumps a PHP value to YAML.
+ *
+ * @param mixed $input The PHP value
+ * @param integer $inline The level where you switch to inline YAML
+ * @param integer $indent The level o indentation indentation (used internally)
+ *
+ * @return string The YAML representation of the PHP value
+ */
+ public function dump($input, $inline = 0, $indent = 0)
+ {
+ $output = '';
+ $prefix = $indent ? str_repeat(' ', $indent) : '';
+
+ if ($inline <= 0 || !is_array($input) || empty($input))
+ {
+ $output .= $prefix.Inline::dump($input);
+ }
+ else
+ {
+ $isAHash = array_keys($input) !== range(0, count($input) - 1);
+
+ foreach ($input as $key => $value)
+ {
+ $willBeInlined = $inline - 1 <= 0 || !is_array($value) || empty($value);
+
+ $output .= sprintf('%s%s%s%s',
+ $prefix,
+ $isAHash ? Inline::dump($key).':' : '-',
+ $willBeInlined ? ' ' : "\n",
+ $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + 2)
+ ).($willBeInlined ? "\n" : '');
+ }
+ }
+
+ return $output;
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Yaml;
+
+/*
+ * This file is part of the symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Exception class used by all exceptions thrown by the component.
+ *
+ * @package symfony
+ * @subpackage yaml
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class Exception extends \Exception
+{
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Yaml;
+
+/*
+ * This file is part of the symfony package.
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Inline implements a YAML parser/dumper for the YAML inline syntax.
+ *
+ * @package symfony
+ * @subpackage yaml
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class Inline
+{
+ const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\']*(?:\'\'[^\']*)*)\')';
+
+ /**
+ * Convert a YAML string to a PHP array.
+ *
+ * @param string $value A YAML string
+ *
+ * @return array A PHP array representing the YAML string
+ */
+ static public function load($value)
+ {
+ $value = trim($value);
+
+ if (0 == strlen($value))
+ {
+ return '';
+ }
+
+ switch ($value[0])
+ {
+ case '[':
+ return self::parseSequence($value);
+ case '{':
+ return self::parseMapping($value);
+ default:
+ return self::parseScalar($value);
+ }
+ }
+
+ /**
+ * Dumps a given PHP variable to a YAML string.
+ *
+ * @param mixed $value The PHP variable to convert
+ *
+ * @return string The YAML string representing the PHP array
+ */
+ static public function dump($value)
+ {
+ $trueValues = '1.1' == Yaml::getSpecVersion() ? array('true', 'on', '+', 'yes', 'y') : array('true');
+ $falseValues = '1.1' == Yaml::getSpecVersion() ? array('false', 'off', '-', 'no', 'n') : array('false');
+
+ switch (true)
+ {
+ case is_resource($value):
+ throw new Exception('Unable to dump PHP resources in a YAML file.');
+ case is_object($value):
+ return '!!php/object:'.serialize($value);
+ case is_array($value):
+ return self::dumpArray($value);
+ case null === $value:
+ return 'null';
+ case true === $value:
+ return 'true';
+ case false === $value:
+ return 'false';
+ case ctype_digit($value):
+ return is_string($value) ? "'$value'" : (int) $value;
+ case is_numeric($value):
+ return is_infinite($value) ? str_ireplace('INF', '.Inf', strval($value)) : (is_string($value) ? "'$value'" : $value);
+ case false !== strpos($value, "\n") || false !== strpos($value, "\r"):
+ return sprintf('"%s"', str_replace(array('"', "\n", "\r"), array('\\"', '\n', '\r'), $value));
+ case preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ - ? | < > = ! % @ ` ]/x', $value):
+ return sprintf("'%s'", str_replace('\'', '\'\'', $value));
+ case '' == $value:
+ return "''";
+ case preg_match(self::getTimestampRegex(), $value):
+ return "'$value'";
+ case in_array(strtolower($value), $trueValues):
+ return "'$value'";
+ case in_array(strtolower($value), $falseValues):
+ return "'$value'";
+ case in_array(strtolower($value), array('null', '~')):
+ return "'$value'";
+ default:
+ return $value;
+ }
+ }
+
+ /**
+ * Dumps a PHP array to a YAML string.
+ *
+ * @param array $value The PHP array to dump
+ *
+ * @return string The YAML string representing the PHP array
+ */
+ static protected function dumpArray($value)
+ {
+ // array
+ $keys = array_keys($value);
+ if (
+ (1 == count($keys) && '0' == $keys[0])
+ ||
+ (count($keys) > 1 && array_reduce($keys, function ($v, $w) { return (integer) $v + $w; }, 0) == count($keys) * (count($keys) - 1) / 2))
+ {
+ $output = array();
+ foreach ($value as $val)
+ {
+ $output[] = self::dump($val);
+ }
+
+ return sprintf('[%s]', implode(', ', $output));
+ }
+
+ // mapping
+ $output = array();
+ foreach ($value as $key => $val)
+ {
+ $output[] = sprintf('%s: %s', self::dump($key), self::dump($val));
+ }
+
+ return sprintf('{ %s }', implode(', ', $output));
+ }
+
+ /**
+ * Parses a scalar to a YAML string.
+ *
+ * @param scalar $scalar
+ * @param string $delimiters
+ * @param array $stringDelimiter
+ * @param integer $i
+ * @param boolean $evaluate
+ *
+ * @return string A YAML string
+ */
+ static public function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true)
+ {
+ if (in_array($scalar[$i], $stringDelimiters))
+ {
+ // quoted scalar
+ $output = self::parseQuotedScalar($scalar, $i);
+ }
+ else
+ {
+ // "normal" string
+ if (!$delimiters)
+ {
+ $output = substr($scalar, $i);
+ $i += strlen($output);
+
+ // remove comments
+ if (false !== $strpos = strpos($output, ' #'))
+ {
+ $output = rtrim(substr($output, 0, $strpos));
+ }
+ }
+ else if (preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match))
+ {
+ $output = $match[1];
+ $i += strlen($output);
+ }
+ else
+ {
+ throw new ParserException(sprintf('Malformed inline YAML string (%s).', $scalar));
+ }
+
+ $output = $evaluate ? self::evaluateScalar($output) : $output;
+ }
+
+ return $output;
+ }
+
+ /**
+ * Parses a quoted scalar to YAML.
+ *
+ * @param string $scalar
+ * @param integer $i
+ *
+ * @return string A YAML string
+ */
+ static protected function parseQuotedScalar($scalar, &$i)
+ {
+ if (!preg_match('/'.self::REGEX_QUOTED_STRING.'/A', substr($scalar, $i), $match))
+ {
+ throw new ParserException(sprintf('Malformed inline YAML string (%s).', substr($scalar, $i)));
+ }
+
+ $output = substr($match[0], 1, strlen($match[0]) - 2);
+
+ if ('"' == $scalar[$i])
+ {
+ // evaluate the string
+ $output = str_replace(array('\\"', '\\n', '\\r'), array('"', "\n", "\r"), $output);
+ }
+ else
+ {
+ // unescape '
+ $output = str_replace('\'\'', '\'', $output);
+ }
+
+ $i += strlen($match[0]);
+
+ return $output;
+ }
+
+ /**
+ * Parses a sequence to a YAML string.
+ *
+ * @param string $sequence
+ * @param integer $i
+ *
+ * @return string A YAML string
+ */
+ static protected function parseSequence($sequence, &$i = 0)
+ {
+ $output = array();
+ $len = strlen($sequence);
+ $i += 1;
+
+ // [foo, bar, ...]
+ while ($i < $len)
+ {
+ switch ($sequence[$i])
+ {
+ case '[':
+ // nested sequence
+ $output[] = self::parseSequence($sequence, $i);
+ break;
+ case '{':
+ // nested mapping
+ $output[] = self::parseMapping($sequence, $i);
+ break;
+ case ']':
+ return $output;
+ case ',':
+ case ' ':
+ break;
+ default:
+ $isQuoted = in_array($sequence[$i], array('"', "'"));
+ $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i);
+
+ if (!$isQuoted && false !== strpos($value, ': '))
+ {
+ // embedded mapping?
+ try
+ {
+ $value = self::parseMapping('{'.$value.'}');
+ }
+ catch (\InvalidArgumentException $e)
+ {
+ // no, it's not
+ }
+ }
+
+ $output[] = $value;
+
+ --$i;
+ }
+
+ ++$i;
+ }
+
+ throw new ParserException(sprintf('Malformed inline YAML string %s', $sequence));
+ }
+
+ /**
+ * Parses a mapping to a YAML string.
+ *
+ * @param string $mapping
+ * @param integer $i
+ *
+ * @return string A YAML string
+ */
+ static protected function parseMapping($mapping, &$i = 0)
+ {
+ $output = array();
+ $len = strlen($mapping);
+ $i += 1;
+
+ // {foo: bar, bar:foo, ...}
+ while ($i < $len)
+ {
+ switch ($mapping[$i])
+ {
+ case ' ':
+ case ',':
+ ++$i;
+ continue 2;
+ case '}':
+ return $output;
+ }
+
+ // key
+ $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false);
+
+ // value
+ $done = false;
+ while ($i < $len)
+ {
+ switch ($mapping[$i])
+ {
+ case '[':
+ // nested sequence
+ $output[$key] = self::parseSequence($mapping, $i);
+ $done = true;
+ break;
+ case '{':
+ // nested mapping
+ $output[$key] = self::parseMapping($mapping, $i);
+ $done = true;
+ break;
+ case ':':
+ case ' ':
+ break;
+ default:
+ $output[$key] = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i);
+ $done = true;
+ --$i;
+ }
+
+ ++$i;
+
+ if ($done)
+ {
+ continue 2;
+ }
+ }
+ }
+
+ throw new ParserException(sprintf('Malformed inline YAML string %s', $mapping));
+ }
+
+ /**
+ * Evaluates scalars and replaces magic values.
+ *
+ * @param string $scalar
+ *
+ * @return string A YAML string
+ */
+ static protected function evaluateScalar($scalar)
+ {
+ $scalar = trim($scalar);
+
+ $trueValues = '1.1' == Yaml::getSpecVersion() ? array('true', 'on', '+', 'yes', 'y') : array('true');
+ $falseValues = '1.1' == Yaml::getSpecVersion() ? array('false', 'off', '-', 'no', 'n') : array('false');
+
+ switch (true)
+ {
+ case 'null' == strtolower($scalar):
+ case '' == $scalar:
+ case '~' == $scalar:
+ return null;
+ case 0 === strpos($scalar, '!str'):
+ return (string) substr($scalar, 5);
+ case 0 === strpos($scalar, '! '):
+ return intval(self::parseScalar(substr($scalar, 2)));
+ case 0 === strpos($scalar, '!!php/object:'):
+ return unserialize(substr($scalar, 13));
+ case ctype_digit($scalar):
+ $raw = $scalar;
+ $cast = intval($scalar);
+ return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw);
+ case in_array(strtolower($scalar), $trueValues):
+ return true;
+ case in_array(strtolower($scalar), $falseValues):
+ return false;
+ case is_numeric($scalar):
+ return '0x' == $scalar[0].$scalar[1] ? hexdec($scalar) : floatval($scalar);
+ case 0 == strcasecmp($scalar, '.inf'):
+ case 0 == strcasecmp($scalar, '.NaN'):
+ return -log(0);
+ case 0 == strcasecmp($scalar, '-.inf'):
+ return log(0);
+ case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar):
+ return floatval(str_replace(',', '', $scalar));
+ case preg_match(self::getTimestampRegex(), $scalar):
+ return strtotime($scalar);
+ default:
+ return (string) $scalar;
+ }
+ }
+
+ static protected function getTimestampRegex()
+ {
+ return <<<EOF
+ ~^
+ (?P<year>[0-9][0-9][0-9][0-9])
+ -(?P<month>[0-9][0-9]?)
+ -(?P<day>[0-9][0-9]?)
+ (?:(?:[Tt]|[ \t]+)
+ (?P<hour>[0-9][0-9]?)
+ :(?P<minute>[0-9][0-9])
+ :(?P<second>[0-9][0-9])
+ (?:\.(?P<fraction>[0-9]*))?
+ (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
+ (?::(?P<tz_minute>[0-9][0-9]))?))?)?
+ $~x
+EOF;
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Yaml;
+
+/*
+ * This file is part of the symfony package.
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Parser parses YAML strings to convert them to PHP arrays.
+ *
+ * @package symfony
+ * @subpackage yaml
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class Parser
+{
+ protected $offset = 0;
+ protected $lines = array();
+ protected $currentLineNb = -1;
+ protected $currentLine = '';
+ protected $refs = array();
+
+ /**
+ * Constructor
+ *
+ * @param integer $offset The offset of YAML document (used for line numbers in error messages)
+ */
+ public function __construct($offset = 0)
+ {
+ $this->offset = $offset;
+ }
+
+ /**
+ * Parses a YAML string to a PHP value.
+ *
+ * @param string $value A YAML string
+ *
+ * @return mixed A PHP value
+ *
+ * @throws \InvalidArgumentException If the YAML is not valid
+ */
+ public function parse($value)
+ {
+ $this->currentLineNb = -1;
+ $this->currentLine = '';
+ $this->lines = explode("\n", $this->cleanup($value));
+
+ $data = array();
+ while ($this->moveToNextLine())
+ {
+ if ($this->isCurrentLineEmpty())
+ {
+ continue;
+ }
+
+ // tab?
+ if (preg_match('#^\t+#', $this->currentLine))
+ {
+ throw new ParserException(sprintf('A YAML file cannot contain tabs as indentation at line %d (%s).', $this->getRealCurrentLineNb() + 1, $this->currentLine));
+ }
+
+ $isRef = $isInPlace = $isProcessed = false;
+ if (preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+?))?\s*$#', $this->currentLine, $values))
+ {
+ if (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#', $values['value'], $matches))
+ {
+ $isRef = $matches['ref'];
+ $values['value'] = $matches['value'];
+ }
+
+ // array
+ if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#'))
+ {
+ $c = $this->getRealCurrentLineNb() + 1;
+ $parser = new Parser($c);
+ $parser->refs =& $this->refs;
+ $data[] = $parser->parse($this->getNextEmbedBlock());
+ }
+ else
+ {
+ if (isset($values['leadspaces'])
+ && ' ' == $values['leadspaces']
+ && preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{].*?) *\:(\s+(?P<value>.+?))?\s*$#', $values['value'], $matches))
+ {
+ // this is a compact notation element, add to next block and parse
+ $c = $this->getRealCurrentLineNb();
+ $parser = new Parser($c);
+ $parser->refs =& $this->refs;
+
+ $block = $values['value'];
+ if (!$this->isNextLineIndented())
+ {
+ $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + 2);
+ }
+
+ $data[] = $parser->parse($block);
+ }
+ else
+ {
+ $data[] = $this->parseValue($values['value']);
+ }
+ }
+ }
+ else if (preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"].*?) *\:(\s+(?P<value>.+?))?\s*$#', $this->currentLine, $values))
+ {
+ $key = Inline::parseScalar($values['key']);
+
+ if ('<<' === $key)
+ {
+ if (isset($values['value']) && '*' === substr($values['value'], 0, 1))
+ {
+ $isInPlace = substr($values['value'], 1);
+ if (!array_key_exists($isInPlace, $this->refs))
+ {
+ throw new ParserException(sprintf('Reference "%s" does not exist at line %s (%s).', $isInPlace, $this->getRealCurrentLineNb() + 1, $this->currentLine));
+ }
+ }
+ else
+ {
+ if (isset($values['value']) && $values['value'] !== '')
+ {
+ $value = $values['value'];
+ }
+ else
+ {
+ $value = $this->getNextEmbedBlock();
+ }
+ $c = $this->getRealCurrentLineNb() + 1;
+ $parser = new Parser($c);
+ $parser->refs =& $this->refs;
+ $parsed = $parser->parse($value);
+
+ $merged = array();
+ if (!is_array($parsed))
+ {
+ throw new ParserException(sprintf("YAML merge keys used with a scalar value instead of an array at line %s (%s)", $this->getRealCurrentLineNb() + 1, $this->currentLine));
+ }
+ else if (isset($parsed[0]))
+ {
+ // Numeric array, merge individual elements
+ foreach (array_reverse($parsed) as $parsedItem)
+ {
+ if (!is_array($parsedItem))
+ {
+ throw new ParserException(sprintf("Merge items must be arrays at line %s (%s).", $this->getRealCurrentLineNb() + 1, $parsedItem));
+ }
+ $merged = array_merge($parsedItem, $merged);
+ }
+ }
+ else
+ {
+ // Associative array, merge
+ $merged = array_merge($merge, $parsed);
+ }
+
+ $isProcessed = $merged;
+ }
+ }
+ else if (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#', $values['value'], $matches))
+ {
+ $isRef = $matches['ref'];
+ $values['value'] = $matches['value'];
+ }
+
+ if ($isProcessed)
+ {
+ // Merge keys
+ $data = $isProcessed;
+ }
+ // hash
+ else if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#'))
+ {
+ // if next line is less indented or equal, then it means that the current value is null
+ if ($this->isNextLineIndented())
+ {
+ $data[$key] = null;
+ }
+ else
+ {
+ $c = $this->getRealCurrentLineNb() + 1;
+ $parser = new Parser($c);
+ $parser->refs =& $this->refs;
+ $data[$key] = $parser->parse($this->getNextEmbedBlock());
+ }
+ }
+ else
+ {
+ if ($isInPlace)
+ {
+ $data = $this->refs[$isInPlace];
+ }
+ else
+ {
+ $data[$key] = $this->parseValue($values['value']);
+ }
+ }
+ }
+ else
+ {
+ // 1-liner followed by newline
+ if (2 == count($this->lines) && empty($this->lines[1]))
+ {
+ $value = Inline::load($this->lines[0]);
+ if (is_array($value))
+ {
+ $first = reset($value);
+ if ('*' === substr($first, 0, 1))
+ {
+ $data = array();
+ foreach ($value as $alias)
+ {
+ $data[] = $this->refs[substr($alias, 1)];
+ }
+ $value = $data;
+ }
+ }
+
+ return $value;
+ }
+
+ switch (preg_last_error())
+ {
+ case PREG_INTERNAL_ERROR:
+ $error = 'Internal PCRE error on line';
+ break;
+ case PREG_BACKTRACK_LIMIT_ERROR:
+ $error = 'pcre.backtrack_limit reached on line';
+ break;
+ case PREG_RECURSION_LIMIT_ERROR:
+ $error = 'pcre.recursion_limit reached on line';
+ break;
+ case PREG_BAD_UTF8_ERROR:
+ $error = 'Malformed UTF-8 data on line';
+ break;
+ case PREG_BAD_UTF8_OFFSET_ERROR:
+ $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point on line';
+ break;
+ default:
+ $error = 'Unable to parse line';
+ }
+
+ throw new ParserException(sprintf('%s %d (%s).', $error, $this->getRealCurrentLineNb() + 1, $this->currentLine));
+ }
+
+ if ($isRef)
+ {
+ $this->refs[$isRef] = end($data);
+ }
+ }
+
+ return empty($data) ? null : $data;
+ }
+
+ /**
+ * Returns the current line number (takes the offset into account).
+ *
+ * @return integer The current line number
+ */
+ protected function getRealCurrentLineNb()
+ {
+ return $this->currentLineNb + $this->offset;
+ }
+
+ /**
+ * Returns the current line indentation.
+ *
+ * @return integer The current line indentation
+ */
+ protected function getCurrentLineIndentation()
+ {
+ return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' '));
+ }
+
+ /**
+ * Returns the next embed block of YAML.
+ *
+ * @param integer $indentation The indent level at which the block is to be read, or null for default
+ *
+ * @return string A YAML string
+ */
+ protected function getNextEmbedBlock($indentation = null)
+ {
+ $this->moveToNextLine();
+
+ if (null === $indentation)
+ {
+ $newIndent = $this->getCurrentLineIndentation();
+
+ if (!$this->isCurrentLineEmpty() && 0 == $newIndent)
+ {
+ throw new ParserException(sprintf('Indentation problem at line %d (%s)', $this->getRealCurrentLineNb() + 1, $this->currentLine));
+ }
+ }
+ else
+ {
+ $newIndent = $indentation;
+ }
+
+ $data = array(substr($this->currentLine, $newIndent));
+
+ while ($this->moveToNextLine())
+ {
+ if ($this->isCurrentLineEmpty())
+ {
+ if ($this->isCurrentLineBlank())
+ {
+ $data[] = substr($this->currentLine, $newIndent);
+ }
+
+ continue;
+ }
+
+ $indent = $this->getCurrentLineIndentation();
+
+ if (preg_match('#^(?P<text> *)$#', $this->currentLine, $match))
+ {
+ // empty line
+ $data[] = $match['text'];
+ }
+ else if ($indent >= $newIndent)
+ {
+ $data[] = substr($this->currentLine, $newIndent);
+ }
+ else if (0 == $indent)
+ {
+ $this->moveToPreviousLine();
+
+ break;
+ }
+ else
+ {
+ throw new ParserException(sprintf('Indentation problem at line %d (%s)', $this->getRealCurrentLineNb() + 1, $this->currentLine));
+ }
+ }
+
+ return implode("\n", $data);
+ }
+
+ /**
+ * Moves the parser to the next line.
+ */
+ protected function moveToNextLine()
+ {
+ if ($this->currentLineNb >= count($this->lines) - 1)
+ {
+ return false;
+ }
+
+ $this->currentLine = $this->lines[++$this->currentLineNb];
+
+ return true;
+ }
+
+ /**
+ * Moves the parser to the previous line.
+ */
+ protected function moveToPreviousLine()
+ {
+ $this->currentLine = $this->lines[--$this->currentLineNb];
+ }
+
+ /**
+ * Parses a YAML value.
+ *
+ * @param string $value A YAML value
+ *
+ * @return mixed A PHP value
+ */
+ protected function parseValue($value)
+ {
+ if ('*' === substr($value, 0, 1))
+ {
+ if (false !== $pos = strpos($value, '#'))
+ {
+ $value = substr($value, 1, $pos - 2);
+ }
+ else
+ {
+ $value = substr($value, 1);
+ }
+
+ if (!array_key_exists($value, $this->refs))
+ {
+ throw new ParserException(sprintf('Reference "%s" does not exist (%s).', $value, $this->currentLine));
+ }
+ return $this->refs[$value];
+ }
+
+ if (preg_match('/^(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?$/', $value, $matches))
+ {
+ $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';
+
+ return $this->parseFoldedScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), intval(abs($modifiers)));
+ }
+ else
+ {
+ return Inline::load($value);
+ }
+ }
+
+ /**
+ * Parses a folded scalar.
+ *
+ * @param string $separator The separator that was used to begin this folded scalar (| or >)
+ * @param string $indicator The indicator that was used to begin this folded scalar (+ or -)
+ * @param integer $indentation The indentation that was used to begin this folded scalar
+ *
+ * @return string The text value
+ */
+ protected function parseFoldedScalar($separator, $indicator = '', $indentation = 0)
+ {
+ $separator = '|' == $separator ? "\n" : ' ';
+ $text = '';
+
+ $notEOF = $this->moveToNextLine();
+
+ while ($notEOF && $this->isCurrentLineBlank())
+ {
+ $text .= "\n";
+
+ $notEOF = $this->moveToNextLine();
+ }
+
+ if (!$notEOF)
+ {
+ return '';
+ }
+
+ if (!preg_match('#^(?P<indent>'.($indentation ? str_repeat(' ', $indentation) : ' +').')(?P<text>.*)$#', $this->currentLine, $matches))
+ {
+ $this->moveToPreviousLine();
+
+ return '';
+ }
+
+ $textIndent = $matches['indent'];
+ $previousIndent = 0;
+
+ $text .= $matches['text'].$separator;
+ while ($this->currentLineNb + 1 < count($this->lines))
+ {
+ $this->moveToNextLine();
+
+ if (preg_match('#^(?P<indent> {'.strlen($textIndent).',})(?P<text>.+)$#', $this->currentLine, $matches))
+ {
+ if (' ' == $separator && $previousIndent != $matches['indent'])
+ {
+ $text = substr($text, 0, -1)."\n";
+ }
+ $previousIndent = $matches['indent'];
+
+ $text .= str_repeat(' ', $diff = strlen($matches['indent']) - strlen($textIndent)).$matches['text'].($diff ? "\n" : $separator);
+ }
+ else if (preg_match('#^(?P<text> *)$#', $this->currentLine, $matches))
+ {
+ $text .= preg_replace('#^ {1,'.strlen($textIndent).'}#', '', $matches['text'])."\n";
+ }
+ else
+ {
+ $this->moveToPreviousLine();
+
+ break;
+ }
+ }
+
+ if (' ' == $separator)
+ {
+ // replace last separator by a newline
+ $text = preg_replace('/ (\n*)$/', "\n$1", $text);
+ }
+
+ switch ($indicator)
+ {
+ case '':
+ $text = preg_replace('#\n+$#s', "\n", $text);
+ break;
+ case '+':
+ break;
+ case '-':
+ $text = preg_replace('#\n+$#s', '', $text);
+ break;
+ }
+
+ return $text;
+ }
+
+ /**
+ * Returns true if the next line is indented.
+ *
+ * @return Boolean Returns true if the next line is indented, false otherwise
+ */
+ protected function isNextLineIndented()
+ {
+ $currentIndentation = $this->getCurrentLineIndentation();
+ $notEOF = $this->moveToNextLine();
+
+ while ($notEOF && $this->isCurrentLineEmpty())
+ {
+ $notEOF = $this->moveToNextLine();
+ }
+
+ if (false === $notEOF)
+ {
+ return false;
+ }
+
+ $ret = false;
+ if ($this->getCurrentLineIndentation() <= $currentIndentation)
+ {
+ $ret = true;
+ }
+
+ $this->moveToPreviousLine();
+
+ return $ret;
+ }
+
+ /**
+ * Returns true if the current line is blank or if it is a comment line.
+ *
+ * @return Boolean Returns true if the current line is empty or if it is a comment line, false otherwise
+ */
+ protected function isCurrentLineEmpty()
+ {
+ return $this->isCurrentLineBlank() || $this->isCurrentLineComment();
+ }
+
+ /**
+ * Returns true if the current line is blank.
+ *
+ * @return Boolean Returns true if the current line is blank, false otherwise
+ */
+ protected function isCurrentLineBlank()
+ {
+ return '' == trim($this->currentLine, ' ');
+ }
+
+ /**
+ * Returns true if the current line is a comment line.
+ *
+ * @return Boolean Returns true if the current line is a comment line, false otherwise
+ */
+ protected function isCurrentLineComment()
+ {
+ //checking explicitly the first char of the trim is faster than loops or strpos
+ $ltrimmedLine = ltrim($this->currentLine, ' ');
+ return $ltrimmedLine[0] === '#';
+ }
+
+ /**
+ * Cleanups a YAML string to be parsed.
+ *
+ * @param string $value The input YAML string
+ *
+ * @return string A cleaned up YAML string
+ */
+ protected function cleanup($value)
+ {
+ $value = str_replace(array("\r\n", "\r"), "\n", $value);
+
+ if (!preg_match("#\n$#", $value))
+ {
+ $value .= "\n";
+ }
+
+ // strip YAML header
+ $count = 0;
+ $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#s', '', $value, -1, $count);
+ $this->offset += $count;
+
+ // remove leading comments and/or ---
+ $trimmedValue = preg_replace('#^((\#.*?\n)|(\-\-\-.*?\n))*#s', '', $value, -1, $count);
+ if ($count == 1)
+ {
+ // items have been removed, update the offset
+ $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
+ $value = $trimmedValue;
+ }
+
+ return $value;
+ }
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Yaml;
+
+/*
+ * This file is part of the symfony package.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Exception class used by all exceptions thrown by the component.
+ *
+ * @package symfony
+ * @subpackage yaml
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class ParserException extends Exception
+{
+}
--- /dev/null
+<?php
+
+namespace Symfony\Component\Yaml;
+
+/*
+ * This file is part of the symfony package.
+ * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Yaml offers convenience methods to load and dump YAML.
+ *
+ * @package symfony
+ * @subpackage yaml
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class Yaml
+{
+ static protected $spec = '1.2';
+
+ /**
+ * Sets the YAML specification version to use.
+ *
+ * @param string $version The YAML specification version
+ */
+ static public function setSpecVersion($version)
+ {
+ if (!in_array($version, array('1.1', '1.2')))
+ {
+ throw new \InvalidArgumentException(sprintf('Version %s of the YAML specifications is not supported', $version));
+ }
+
+ self::$spec = $version;
+ }
+
+ /**
+ * Gets the YAML specification version to use.
+ *
+ * @return string The YAML specification version
+ */
+ static public function getSpecVersion()
+ {
+ return self::$spec;
+ }
+
+ /**
+ * Loads YAML into a PHP array.
+ *
+ * The load method, when supplied with a YAML stream (string or file),
+ * will do its best to convert YAML in a file into a PHP array.
+ *
+ * Usage:
+ * <code>
+ * $array = Yaml::load('config.yml');
+ * print_r($array);
+ * </code>
+ *
+ * @param string $input Path of YAML file or string containing YAML
+ *
+ * @return array The YAML converted to a PHP array
+ *
+ * @throws \InvalidArgumentException If the YAML is not valid
+ */
+ public static function load($input)
+ {
+ $file = '';
+
+ // if input is a file, process it
+ if (strpos($input, "\n") === false && is_file($input))
+ {
+ $file = $input;
+
+ ob_start();
+ $retval = include($input);
+ $content = ob_get_clean();
+
+ // if an array is returned by the config file assume it's in plain php form else in YAML
+ $input = is_array($retval) ? $retval : $content;
+ }
+
+ // if an array is returned by the config file assume it's in plain php form else in YAML
+ if (is_array($input))
+ {
+ return $input;
+ }
+
+ $yaml = new Parser();
+
+ try
+ {
+ $ret = $yaml->parse($input);
+ }
+ catch (\Exception $e)
+ {
+ throw new \InvalidArgumentException(sprintf('Unable to parse %s: %s', $file ? sprintf('file "%s"', $file) : 'string', $e->getMessage()));
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Dumps a PHP array to a YAML string.
+ *
+ * The dump method, when supplied with an array, will do its best
+ * to convert the array into friendly YAML.
+ *
+ * @param array $array PHP array
+ * @param integer $inline The level where you switch to inline YAML
+ *
+ * @return string A YAML string representing the original PHP array
+ */
+ public static function dump($array, $inline = 2)
+ {
+ $yaml = new Dumper();
+
+ return $yaml->dump($array, $inline);
+ }
+}
--- /dev/null
+<?php
+
+namespace Proxies;
+
+/**
+ * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
+ */
+class ScoreProxy extends \Score implements \Doctrine\ORM\Proxy\Proxy
+{
+ private $_entityPersister;
+ private $_identifier;
+ public $__isInitialized__ = false;
+ public function __construct($entityPersister, $identifier)
+ {
+ $this->_entityPersister = $entityPersister;
+ $this->_identifier = $identifier;
+ }
+ private function _load()
+ {
+ if (!$this->__isInitialized__ && $this->_entityPersister) {
+ $this->__isInitialized__ = true;
+ if ($this->_entityPersister->load($this->_identifier, $this) === null) {
+ throw new \Doctrine\ORM\EntityNotFoundException();
+ }
+ unset($this->_entityPersister, $this->_identifier);
+ }
+ }
+
+
+
+ public function __sleep()
+ {
+ return array('__isInitialized__', 'gscore', 'id', 'user_id');
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace Proxies;
+
+/**
+ * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
+ */
+class SessionProxy extends \Session implements \Doctrine\ORM\Proxy\Proxy
+{
+ private $_entityPersister;
+ private $_identifier;
+ public $__isInitialized__ = false;
+ public function __construct($entityPersister, $identifier)
+ {
+ $this->_entityPersister = $entityPersister;
+ $this->_identifier = $identifier;
+ }
+ private function _load()
+ {
+ if (!$this->__isInitialized__ && $this->_entityPersister) {
+ $this->__isInitialized__ = true;
+ if ($this->_entityPersister->load($this->_identifier, $this) === null) {
+ throw new \Doctrine\ORM\EntityNotFoundException();
+ }
+ unset($this->_entityPersister, $this->_identifier);
+ }
+ }
+
+
+
+ public function __sleep()
+ {
+ return array('__isInitialized__', 'sessionkey', 'stime', 'id', 'user_id');
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace Proxies;
+
+/**
+ * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
+ */
+class UserProxy extends \User implements \Doctrine\ORM\Proxy\Proxy
+{
+ private $_entityPersister;
+ private $_identifier;
+ public $__isInitialized__ = false;
+ public function __construct($entityPersister, $identifier)
+ {
+ $this->_entityPersister = $entityPersister;
+ $this->_identifier = $identifier;
+ }
+ private function _load()
+ {
+ if (!$this->__isInitialized__ && $this->_entityPersister) {
+ $this->__isInitialized__ = true;
+ if ($this->_entityPersister->load($this->_identifier, $this) === null) {
+ throw new \Doctrine\ORM\EntityNotFoundException();
+ }
+ unset($this->_entityPersister, $this->_identifier);
+ }
+ }
+
+
+ public function id()
+ {
+ $this->_load();
+ return parent::id();
+ }
+
+ public function getName()
+ {
+ $this->_load();
+ return parent::getName();
+ }
+
+ public function setName($name)
+ {
+ $this->_load();
+ return parent::setName($name);
+ }
+
+ public function getPass()
+ {
+ $this->_load();
+ return parent::getPass();
+ }
+
+ public function setPass($pass)
+ {
+ $this->_load();
+ return parent::setPass($pass);
+ }
+
+ public function addScore($score)
+ {
+ $this->_load();
+ return parent::addScore($score);
+ }
+
+ public function getScores()
+ {
+ $this->_load();
+ return parent::getScores();
+ }
+
+ public function addSession($session)
+ {
+ $this->_load();
+ return parent::addSession($session);
+ }
+
+ public function getSessions()
+ {
+ $this->_load();
+ return parent::getSessions();
+ }
+
+
+ public function __sleep()
+ {
+ return array('__isInitialized__', 'name', 'pass', 'id', 'scores', 'sessions');
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class Score {
+ protected $id;
+ protected $gscore;
+ protected $user;
+
+ function id()
+ {
+ return $this->id;
+ }
+
+ function getGscore()
+ {
+ return $this->gscore;
+ }
+
+ function setGscore($gscore)
+ {
+ $this->gscore = $gscore;
+ }
+
+ function getUser()
+ {
+ return $this->user;
+ }
+
+ function setUser($user)
+ {
+ $this->user = $user;
+ }
+}
+?>
\ No newline at end of file
--- /dev/null
+<?php
+
+class Session{
+ protected $id;
+ protected $sessionkey;
+ protected $stime;
+ protected $user;
+
+ function id()
+ {
+ return $this->id;
+ }
+
+ function getSessionkey()
+ {
+ return $this->sessionkey;
+ }
+
+ function setSessionkey($sessionkey)
+ {
+ $this->sessionkey = $sessionkey;
+ }
+
+ function getStime()
+ {
+ return $this->stime;
+ }
+
+ function setStime($stime)
+ {
+ $this->stime = $stime;
+ }
+
+ function getUser()
+ {
+ return $this->user;
+ }
+
+ function setUser($user)
+ {
+ $this->user = $user;
+ }
+}
+?>
\ No newline at end of file
--- /dev/null
+<?php
+
+use Doctrine\Common\Collections\ArrayCollection;
+
+class User {
+ protected $id;
+ protected $name;
+ protected $pass;
+ protected $scores = null;
+ protected $sessions = null;
+
+
+ public function __construct()
+ {
+ $this->scores = new ArrayCollection();
+ $this->sessions = new ArrayCollection();
+ }
+
+ function id()
+ {
+ return $this->id;
+ }
+
+ function getName()
+ {
+ return $this->name;
+ }
+
+ function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ function getPass()
+ {
+ return $this->pass;
+ }
+
+ function setPass($pass)
+ {
+ $this->pass = $pass;
+ }
+
+ public function addScore($score)
+ {
+ $this->scores[] =$score;
+ }
+
+ public function getScores()
+ {
+ return $this->scores;
+ }
+
+ public function addSession($session)
+ {
+ $this->sessions[] = $session;
+ }
+
+ public function getSessions()
+ {
+ return $this->sessions;
+ }
+}
+?>
\ No newline at end of file
--- /dev/null
+<?php
+error_reporting(E_ALL);
+
+$address = '0.0.0.0';
+$port = 8547;
+
+/* Allow the script to hang around waiting for connections. */
+set_time_limit(0);
+
+/* Turn on implicit output flushing so we see what we're getting
+ * as it comes in. */
+ob_implicit_flush();
+
+require_once 'init.php';
+
+function auth($sessionkey)
+{
+ global $entityManager;
+
+ $dql = "SELECT s FROM Session s WHERE s.sessionkey=?1";
+
+ $result = $entityManager->createQuery($dql)
+ ->setParameter(1, $sessionkey)
+ ->setMaxResults(1)
+ ->getResult();
+
+ if (!$result)
+ return false;
+
+ return $result[0]->getUser()->getName();
+}
+
+// create a streaming socket, of type TCP/IP
+$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
+
+// set the option to reuse the port
+socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1);
+
+// "bind" the socket to the address to "localhost", on port $port
+// so this means that all connections on this port are now our resposibility to send/recv data, disconnect, etc..
+socket_bind($sock, 0, $port);
+
+// start listen for connections
+socket_listen($sock);
+
+// create a list of all the clients that will be connected to us..
+// add the listening socket to this list
+$clients = array($sock);
+
+function genkey()
+{
+ global $clients;
+ static $counter = 0;
+
+ do {
+ $key = md5(++$counter . uniqid());
+ } while (array_key_exists($key, $clients));
+ return $key;
+}
+
+
+$client_names = array();
+
+while (true) {
+ // create a copy, so $clients doesn't get modified by socket_select()
+ $read = $clients;
+
+ // get a list of all the clients that have data to be read from
+ // if there are no clients with data, go to next iteration
+ if (socket_select($read, $write = NULL, $except = NULL, 0) < 1)
+ continue;
+
+ // check if there is a client trying to connect
+ if (in_array($sock, $read)) {
+ // accept the client, and add him to the $clients array
+ $newkey = genkey();
+ $clients[$newkey] = $newsock = socket_accept($sock);
+ $client_names[$newkey] = '';
+
+ // send the client a welcome message
+ socket_write($newsock, ">> Connected. There are ".(count($clients) - 1)." client(s) connected to the server\n>> Type /quit to closse chat view.\n");
+
+ socket_getpeername($newsock, $ip);
+ echo "New client connected: {$ip}\n";
+
+ // remove the listening socket from the clients-with-data array
+ $key = array_search($sock, $read);
+ unset($read[$key]);
+ }
+
+ // loop through all the clients that have data to read from
+ foreach ($read as $read_sock) {
+ // read until newline or 1024 bytes
+ // socket_read while show errors when the client is disconnected, so silence the error messages
+ $data = @socket_read($read_sock, 1024, PHP_NORMAL_READ);
+
+ $key = array_search($read_sock, $clients);
+
+ // check if the client is disconnected
+ if ($data === false) {
+ // remove client for $clients array
+ unset($clients[$key]);
+ unset($client_names[$key]);
+ echo "client disconnected.\n";
+ // continue to the next client to read from, if any
+ continue;
+ }
+
+ // trim off the trailing/beginning white spaces
+ $data = trim($data);
+
+ // check if there is any data after trimming off the spaces
+ if (!empty($data)) {
+
+ if (empty($client_names[$key]))
+ {
+ $result = auth($data);
+ if ($result !== false)
+ {
+ $client_names[$key] = $result;
+ continue;
+ }
+ socket_write($read_sock, ">> AUTH ERROR\n");
+ continue;
+ }
+
+ $sender_name = $client_names[$key];
+ // send this to all the clients in the $clients array (except the first one, which is a listening socket)
+ foreach ($clients as $send_sock) {
+
+ // if its the listening sock or the client that we got the message from, go to the next one in the list
+ if ($send_sock == $sock || $send_sock == $read_sock)
+ continue;
+
+ // write the message to the client -- add a newline character to the end of the message
+ socket_write($send_sock, $sender_name . ": " . $data."\n");
+
+ } // end of broadcast foreach
+
+ }
+
+ } // end of reading foreach
+}
+
+// close the listening socket
+socket_close($sock);
\ No newline at end of file
--- /dev/null
+<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
+ http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
+
+ <entity name="Score" table="tim_scores">
+ <id name="id" type="integer" column="score_id">
+ <generator strategy="AUTO" />
+ </id>
+
+ <field name="gscore" column="game_score" type="integer" />
+ <many-to-one target-entity="User" field="user" inversed-by="scores">
+ <join-column name="user_id" referenced-column-name="user_id" />
+ </many-to-one>
+ </entity>
+
+</doctrine-mapping>
\ No newline at end of file
--- /dev/null
+<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
+ http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
+
+ <entity name="Session" table="tim_sessions">
+ <id name="id" type="integer" column="session_id">
+ <generator strategy="AUTO" />
+ </id>
+
+ <field name="sessionkey" column="session_key" type="string" />
+ <field name="stime" column="session_time" type="datetime" />
+ <many-to-one target-entity="User" field="user" inversed-by="sessions">
+ <join-column name="user_id" referenced-column-name="user_id" />
+ </many-to-one>
+ </entity>
+
+</doctrine-mapping>
\ No newline at end of file
--- /dev/null
+<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
+ http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
+
+ <entity name="User" table="tim_users">
+ <id name="id" type="integer" column="user_id">
+ <generator strategy="AUTO" />
+ </id>
+
+ <field name="name" column="user_name" type="string" />
+ <field name="pass" column="user_pass" type="string" />
+
+ <one-to-many target-entity="Score" field="scores" mapped-by="user" />
+ <one-to-many target-entity="Session" field="sessions" mapped-by="user" />
+
+ </entity>
+
+</doctrine-mapping>
\ No newline at end of file
--- /dev/null
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
--- /dev/null
+#!/usr/bin/env php
+<?php
+
+include('doctrine.php');
\ No newline at end of file
--- /dev/null
+@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" %*
--- /dev/null
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+require_once 'Doctrine/Common/ClassLoader.php';
+
+$classLoader = new \Doctrine\Common\ClassLoader('Doctrine');
+$classLoader->register();
+
+$classLoader = new \Doctrine\Common\ClassLoader('Symfony', 'Doctrine');
+$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);
--- /dev/null
+<?php
+require_once 'init.php';
+
+$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
+ 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($entityManager)
+));
+
+$helperSet = ($helperSet) ?: new \Symfony\Component\Console\Helper\HelperSet();
+
+\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet);
--- /dev/null
+<?php
+require 'init.php';
+
+if (!isset($_GET['a'])) $_GET['a']='';
+
+switch($_GET['a'])
+{
+ case 'login':
+ return login();
+ case 'register':
+ return register();
+ case 'setscore':
+ return set_score();
+ case 'score':
+ default:
+ return score();
+ die();
+}
+
+function login() {
+ global $entityManager;
+
+ if (!isset($_GET['user']) || !isset($_GET['pass']))
+ return error('No username or password provided');
+
+ $dql = "SELECT u FROM User u WHERE u.name=?1";
+
+ $result = $entityManager->createQuery($dql)
+ ->setParameter(1, $_GET['user'])
+ ->setMaxResults(1)
+ ->getResult();
+
+ if (!$result)
+ return error('No such user');
+
+ $user = $result[0];
+
+ if ($user->getPass() != $_GET['pass'])
+ return error('Wrong password');
+
+ $session = new Session();
+ $session->setSessionkey(md5(time() . uniqid()));
+ $session->setStime(new DateTime("now"));
+ $session->setUser($user);
+
+ $entityManager->persist($session);
+ $entityManager->flush();
+ return success("<login>\n\t\t<sessionkey>" . $session->getSessionkey() . "</sessionkey>\n\t\t<user><![CDATA[" . $user->getName() . "]]></user>\n\t\t<userid>" . $user->id() . "</userid>\n\t</login>");
+}
+
+function register() {
+ global $entityManager;
+
+ if (!isset($_GET['user']) || !isset($_GET['pass']))
+ return error('No username or password provided');
+
+ $dql = "SELECT u FROM User u WHERE u.name=?1";
+
+ $result = $entityManager->createQuery($dql)
+ ->setParameter(1, $_GET['user'])
+ ->setMaxResults(1)
+ ->getResult();
+
+ if ($result)
+ return error('User already exists');
+
+ $user = new User;
+ $user->setName($_GET['user']);
+ $user->setPass($_GET['pass']);
+
+ $entityManager->persist($user);
+ $entityManager->flush();
+ return success('<register />');
+}
+
+function error($msg)
+{
+ print '<?xml version="1.0" encoding="utf-8"?>
+<message>
+ <error><![CDATA[' . $msg . ']]></error>
+</message>
+';
+}
+
+function success($message)
+{
+ print '<?xml version="1.0" encoding="utf-8"?>
+<message>
+ ' . $message . '
+</message>';
+}
+
+function score()
+{
+ global $entityManager;
+
+ $dql = "SELECT s, u FROM Score s JOIN s.user u ORDER BY s.gscore DESC";
+
+ $myScores = $entityManager->createQuery($dql)
+ ->setMaxResults(20)
+ ->getResult();
+
+ print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<message>\n\t<scores>\n";
+
+ $position = 0;
+ foreach ($myScores AS $score)
+ {
+ print "\t\t<entry>\n"
+ . "\t\t\t<position>" . ++$position . "</position>\n"
+ . "\t\t\t<user-id>" . $score->getUser()->id() . "</user-id>\n"
+ . "\t\t\t<user-name>" . $score->getUser()->getName() . "</user-name>\n"
+ . "\t\t\t<score>" . $score->getGscore() . "</score>\n"
+ . "\t\t</entry>\n";
+ }
+ print "\t</scores>\n</message>\n";
+
+}
+
+function set_score()
+{
+ global $entityManager;
+
+ if (!isset($_GET['s']))
+ return error('Log in first');
+ if (!isset($_GET['score']))
+ return error('No score provided');
+
+ $dql = "SELECT s FROM Session s WHERE s.sessionkey=?1";
+
+ $result = $entityManager->createQuery($dql)
+ ->setParameter(1, $_GET['s'])
+ ->setMaxResults(1)
+ ->getResult();
+
+ if (!$result)
+ return error('Log in first');
+
+ $session = $result[0];
+
+
+ $score = new Score();
+ $score->setGscore((int) $_GET['score']);
+ $score->setUser($session->getUser());
+
+ $entityManager->persist($score);
+ $entityManager->flush();
+ return success('<setscore />');
+}
--- /dev/null
+<?php
+require_once 'Doctrine/Common/ClassLoader.php';
+
+define('APPLICATION_ENV', 'development');
+
+$conn = array(
+ 'dbname' => 'tim',
+ 'user' => 'tim',
+ 'password' => 'tim',
+ 'host' => 'localhost',
+ 'driver' => 'pdo_pgsql',
+);
+
+$classLoader = new \Doctrine\Common\ClassLoader('Doctrine');
+$classLoader->register();
+
+$classLoader = new \Doctrine\Common\ClassLoader('Symfony', 'Doctrine');
+$classLoader->register();
+
+$classLoader = new \Doctrine\Common\ClassLoader(null, 'Tim');
+$classLoader->register();
+
+$config = new Doctrine\ORM\Configuration();
+
+// Mapping Configuration
+$driverImpl = new Doctrine\ORM\Mapping\Driver\XmlDriver('config/mappings/');
+$config->setMetadataDriverImpl($driverImpl);
+
+$config->setProxyDir(__DIR__.'/Proxies');
+$config->setProxyNamespace('Proxies');
+$config->setAutoGenerateProxyClasses((APPLICATION_ENV == "development"));
+
+// Caching Configuration
+if (APPLICATION_ENV == "development") {
+ $cache = new \Doctrine\Common\Cache\ArrayCache();
+} else {
+ $cache = new \Doctrine\Common\Cache\ApcCache();
+}
+$config->setMetadataCacheImpl($cache);
+$config->setQueryCacheImpl($cache);
+
+
+// Obtaining the entity manager
+$evm = new Doctrine\Common\EventManager();
+$entityManager = \Doctrine\ORM\EntityManager::create($conn, $config, $evm);