From 9075f10bf5da3058f6ef82ff08e4783ff70424a4 Mon Sep 17 00:00:00 2001 From: romanb Date: Mon, 6 Jul 2009 20:34:54 +0000 Subject: [PATCH] [2.0] Moved cache drivers to Common package. Added new annotation parser implementation to Common package. AnnotationDriver in ORM not yet migrated. --- .../Common/Annotations/Annotation.php | 37 ++ .../Common/Annotations/AnnotationReader.php | 140 ++++++++ lib/Doctrine/Common/Annotations/Lexer.php | 236 +++++++++++++ lib/Doctrine/Common/Annotations/Parser.php | 322 ++++++++++++++++++ .../{ORM => Common}/Cache/ApcCache.php | 2 +- .../{ORM => Common}/Cache/ArrayCache.php | 2 +- lib/Doctrine/{ORM => Common}/Cache/Cache.php | 2 +- .../{ORM => Common}/Cache/MemcacheCache.php | 2 +- .../{ORM => Common}/Cache/XcacheCache.php | 2 +- lib/Doctrine/ORM/AbstractQuery.php | 6 +- lib/Doctrine/ORM/Cache/DbCache.php | 181 ---------- .../ORM/Mapping/ClassMetadataFactory.php | 4 +- .../Mapping/Driver/DoctrineAnnotations.php | 1 + lib/Doctrine/ORM/Query/AbstractResult.php | 2 +- lib/Doctrine/ORM/Query/Lexer.php | 2 +- tests/Doctrine/Tests/Common/AllTests.php | 2 + .../Tests/Common/Annotations/AllTests.php | 32 ++ .../Annotations/AnnotationReaderTest.php | 98 ++++++ .../Tests/Common/Annotations/LexerTest.php | 29 ++ .../Tests/Common/Annotations/ParserTest.php | 92 +++++ .../Tests/ORM/Functional/QueryCacheTest.php | 2 +- .../Doctrine/Tests/OrmFunctionalTestCase.php | 4 +- tests/Doctrine/Tests/OrmTestCase.php | 4 +- 23 files changed, 1006 insertions(+), 198 deletions(-) create mode 100644 lib/Doctrine/Common/Annotations/Annotation.php create mode 100644 lib/Doctrine/Common/Annotations/AnnotationReader.php create mode 100644 lib/Doctrine/Common/Annotations/Lexer.php create mode 100644 lib/Doctrine/Common/Annotations/Parser.php rename lib/Doctrine/{ORM => Common}/Cache/ApcCache.php (98%) rename lib/Doctrine/{ORM => Common}/Cache/ArrayCache.php (98%) rename lib/Doctrine/{ORM => Common}/Cache/Cache.php (98%) rename lib/Doctrine/{ORM => Common}/Cache/MemcacheCache.php (98%) rename lib/Doctrine/{ORM => Common}/Cache/XcacheCache.php (98%) delete mode 100644 lib/Doctrine/ORM/Cache/DbCache.php create mode 100644 tests/Doctrine/Tests/Common/Annotations/AllTests.php create mode 100644 tests/Doctrine/Tests/Common/Annotations/AnnotationReaderTest.php create mode 100644 tests/Doctrine/Tests/Common/Annotations/LexerTest.php create mode 100644 tests/Doctrine/Tests/Common/Annotations/ParserTest.php diff --git a/lib/Doctrine/Common/Annotations/Annotation.php b/lib/Doctrine/Common/Annotations/Annotation.php new file mode 100644 index 000000000..2a91e5d99 --- /dev/null +++ b/lib/Doctrine/Common/Annotations/Annotation.php @@ -0,0 +1,37 @@ +getName(); + if (isset(self::$creationStack[$class])) { + trigger_error("Circular annotation reference on '$class'", E_USER_ERROR); + return; + } + self::$creationStack[$class] = true; + foreach ($data as $key => $value) { + $this->$key = $value; + } + unset(self::$creationStack[$class]); + } + + private function createName($target) + { + if ($target instanceof ReflectionMethod) { + return $target->getDeclaringClass()->getName().'::'.$target->getName(); + } else if ($target instanceof ReflectionProperty) { + return $target->getDeclaringClass()->getName().'::$'.$target->getName(); + } else { + return $target->getName(); + } + } + + //protected function checkConstraints($target) {} +} \ No newline at end of file diff --git a/lib/Doctrine/Common/Annotations/AnnotationReader.php b/lib/Doctrine/Common/Annotations/AnnotationReader.php new file mode 100644 index 000000000..8b780a521 --- /dev/null +++ b/lib/Doctrine/Common/Annotations/AnnotationReader.php @@ -0,0 +1,140 @@ +"; + private $_parser; + private $_cache; + private $_annotations = array(); + + public function __construct(Cache $cache) + { + $this->_parser = new Parser; + $this->_cache = $cache; + } + + public function setDefaultAnnotationNamespace($defaultNamespace) + { + $this->_parser->setDefaultAnnotationNamespace($defaultNamespace); + } + + /** + * 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($class) + { + if (is_string($class)) { + $className = $class; + } else { + $className = $class->getName(); + } + + if (isset($this->_annotations[$className])) { + return $this->_annotations[$className]; + } else if ($this->_cache->contains($className . self::$CACHE_SALT)) { + $this->_annotations[$className] = $this->_cacheDriver->get($className . self::$CACHE_SALT); + return $this->_annotations[$className]; + } + + if (is_string($class)) { + $class = new ReflectionClass($className); + } + + $this->_annotations[$className] = $this->_parser->parse($class->getDocComment()); + + return $this->_annotations[$className]; + } + + public function getClassAnnotation($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($class, $property) + { + $className = is_string($class) ? $class : $class->getName(); + if (is_string($property)) { + $propertyName = $className . '$' . $property; + } else { + $propertyName = $className . '$' . $property->getName(); + } + + if (isset($this->_annotations[$propertyName])) { + return $this->_annotations[$propertyName]; + } else if ($this->_cache->contains($propertyName . self::$CACHE_SALT)) { + $this->_annotations[$propertyName] = $this->_cacheDriver->get($propertyName . self::$CACHE_SALT); + return $this->_annotations[$propertyName]; + } + + if (is_string($property)) { + $property = new ReflectionProperty($className, $property); + } + + $this->_annotations[$propertyName] = $this->_parser->parse($property->getDocComment()); + + return $this->_annotations[$propertyName]; + } + + public function getPropertyAnnotation($class, $property, $annotation) + { + $annotations = $this->getPropertyAnnotations($class, $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($class, $method) + { + $className = is_string($class) ? $class : $class->getName(); + if (is_string($method)) { + $methodName = $className . '#' . $method; + } else { + $methodName = $className . '#' . $method->getName(); + } + + if (isset($this->_annotations[$methodName])) { + return $this->_annotations[$methodName]; + } else if ($this->_cache->contains($methodName . self::$CACHE_SALT)) { + $this->_annotations[$methodName] = $this->_cacheDriver->get($methodName . self::$CACHE_SALT); + return $this->_annotations[$methodName]; + } + + if (is_string($method)) { + $method = new ReflectionMethod($className, $method); + } + + $this->_annotations[$methodName] = $this->_parser->parse($method->getDocComment()); + + return $this->_annotations[$methodName]; + } + + public function getMethodAnnotation($class, $method, $annotation) + { + $annotations = $this->getMethodAnnotations($class, $method); + return isset($annotations[$annotation]) ? $annotations[$annotation] : null; + } +} \ No newline at end of file diff --git a/lib/Doctrine/Common/Annotations/Lexer.php b/lib/Doctrine/Common/Annotations/Lexer.php new file mode 100644 index 000000000..78360e65a --- /dev/null +++ b/lib/Doctrine/Common/Annotations/Lexer.php @@ -0,0 +1,236 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * Simple lexer for docblock annotations. + * + * @author Roman Borschel + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 2.0 + * @version $Revision$ + */ +class Lexer +{ + const T_NONE = 1; + const T_FLOAT = 2; + const T_INTEGER = 3; + const T_STRING = 4; + const T_IDENTIFIER = 5; + + /** + * Array of scanned tokens. + * + * @var array + */ + private $_tokens = array(); + private $_position = 0; + private $_peek = 0; + + /** + * @var array The next token in the query string. + */ + public $lookahead; + + /** + * @var array The last matched/seen token. + */ + public $token; + + public function setInput($input) + { + $this->_tokens = array(); + $this->_scan($input); + } + + public function reset() + { + $this->lookahead = null; + $this->token = null; + $this->_peek = 0; + $this->_position = 0; + } + + /** + * Checks whether a given token matches the current lookahead. + * + * @param integer|string $token + * @return boolean + */ + public function isNextToken($token) + { + $la = $this->lookahead; + return ($la['type'] === $token || $la['value'] === $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->token = $this->lookahead; + $this->_peek = 0; + if (isset($this->_tokens[$this->_position])) { + $this->lookahead = $this->_tokens[$this->_position++]; + return true; + } else { + $this->lookahead = null; + return false; + } + } + + /** + * Tells the lexer to skip input tokens until it sees a token with the given value. + * + * @param $value The value to skip until. + */ + public function skipUntil($value) + { + while ($this->lookahead !== null && $this->lookahead['value'] !== $value) { + $this->moveNext(); + } + } + + /** + * Checks if an identifier is a keyword and returns its correct type. + * + * @param string $identifier identifier name + * @return int token type + */ + private function _checkLiteral($identifier) + { + $name = 'Doctrine\Common\Annotations\Lexer::T_' . strtoupper($identifier); + + if (defined($name)) { + return constant($name); + } + + return self::T_IDENTIFIER; + } + + /** + * Scans the input string for tokens. + * + * @param string $input a query string + */ + private function _scan($input) + { + static $regex; + + if ( ! isset($regex)) { + $patterns = array( + '[a-z_][a-z0-9_\\\]*', + '(?:[0-9]+(?:[\.][0-9]+)*)(?:e[+-]?[0-9]+)?', + '"(?:[^"]|"")*"' + ); + $regex = '/(' . implode(')|(', $patterns) . ')|\s+|(.)/i'; + } + + $matches = preg_split($regex, $input, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + + foreach ($matches as $match) { + $type = $this->_getType($match); + $this->_tokens[] = array( + 'value' => $match, + 'type' => $type + ); + } + } + + /** + * @todo Doc + */ + private function _getType(&$value) + { + // $value is referenced because it can be changed if it is numeric. + $type = self::T_NONE; + + $newVal = $this->_getNumeric($value); + if ($newVal !== false){ + $value = $newVal; + if (strpos($value, '.') !== false || stripos($value, 'e') !== false) { + $type = self::T_FLOAT; + } else { + $type = self::T_INTEGER; + } + } + if ($value[0] === '"') { + $type = self::T_STRING; + $value = str_replace('""', '"', substr($value, 1, strlen($value) - 2)); + } else if (ctype_alpha($value[0]) || $value[0] === '_') { + $type = $this->_checkLiteral($value); + } + + return $type; + } + + /** + * @todo Doc + */ + 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; + } + + /** + * 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; + } +} \ No newline at end of file diff --git a/lib/Doctrine/Common/Annotations/Parser.php b/lib/Doctrine/Common/Annotations/Parser.php new file mode 100644 index 000000000..4f903933c --- /dev/null +++ b/lib/Doctrine/Common/Annotations/Parser.php @@ -0,0 +1,322 @@ + + */ +class Parser +{ + /** Tags that are stripped prior to parsing in order to reduce parsing overhead. */ + private static $_strippedInlineTags = array( + "{@example", "{@id", "{@internal", "{@inheritdoc", + "{@link", "{@source", "{@toc", "{@tutorial", "*/" + ); + + private $_lexer; + private $_isNestedAnnotation = false; + private $_defaultAnnotationNamespace = ''; + private $_namespaceAliases = array(); + + /** + * Constructs a new AnnotationParser. + */ + public function __construct() + { + $this->_lexer = new Lexer; + } + + /** + * Sets the default namespace that is assumed for an annotation that does not + * define a namespace prefix. + * + * @param $defaultNamespace + */ + public function setDefaultAnnotationNamespace($defaultNamespace) + { + $this->_defaultAnnotationNamespace = $defaultNamespace; + } + + public function setAnnotationNamespaceAlias($namespace, $alias) + { + $this->_namespaceAliases[$alias] = $namespace; + } + + /** + * Parses the given docblock string for annotations. + * + * @param $docBlockString + * @return array An array of Annotation instances. If no annotations are found, an empty + * array is returned. + */ + public function parse($docBlockString) + { + // Strip out some known inline tags. + $input = str_replace(self::$_strippedInlineTags, '', $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(); + + return $this->Annotations(); + } + + /** + * 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 bool True, if tokens match; false otherwise. + */ + public function match($token) + { + if (is_string($token)) { + $isMatch = ($this->_lexer->lookahead['value'] === $token); + } else { + $isMatch = ($this->_lexer->lookahead['type'] === $token); + } + + if ( ! $isMatch) { + $this->syntaxError($token['value']); + } + + $this->_lexer->moveNext(); + } + + /** + * Raises a syntax error. + * + * @param $expected + * @throws Exception + */ + private function syntaxError($expected) + { + throw new \Exception("Expected: $expected."); + } + + /** + * Annotations ::= Annotation ("*")* (Annotation)* + */ + public function Annotations() + { + $this->_isNestedAnnotation = false; + $annotations = array(); + $annot = $this->Annotation(); + if ($annot === false) { + // Not a valid annotation. Go on to next annotation. + $this->_lexer->skipUntil('@'); + } else { + $annotations[get_class($annot)] = $annot; + } + + while (true) { + if ($this->_lexer->lookahead['value'] == '*') { + $this->match('*'); + } else if ($this->_lexer->lookahead['value'] == '@') { + $this->_isNestedAnnotation = false; + $annot = $this->Annotation(); + if ($annot === false) { + // Not a valid annotation. Go on to next annotation. + $this->_lexer->skipUntil('@'); + } else { + $annotations[get_class($annot)] = $annot; + } + } else { + break; + } + } + + return $annotations; + } + + /** + * Annotation ::= "@" AnnotationName [ "(" [Values] ")" ] + * AnnotationName ::= SimpleName | QualifiedName + * SimpleName ::= identifier + * QualifiedName ::= NameSpacePart "." (NameSpacePart ".")* SimpleName + * NameSpacePart ::= identifier + */ + public function Annotation() + { + $values = array(); + + $nameParts = array(); + $this->match('@'); + $this->match(Lexer::T_IDENTIFIER); + $nameParts[] = $this->_lexer->token['value']; + while ($this->_lexer->isNextToken('.')) { + $this->match('.'); + $this->match(Lexer::T_IDENTIFIER); + $nameParts[] = $this->_lexer->token['value']; + } + + if (count($nameParts) == 1) { + $name = $this->_defaultAnnotationNamespace . $nameParts[0]; + } else { + if (count($nameParts) == 2 && isset($this->_namespaceAliases[$nameParts[0]])) { + $name = $this->_namespaceAliases[$nameParts[0]] . $nameParts[1]; + } else { + $name = implode('\\', $nameParts); + } + } + + if ( ! $this->_isNestedAnnotation && $this->_lexer->lookahead != null + && $this->_lexer->lookahead['value'] != '(' + || ! is_subclass_of($name, 'Doctrine\Common\Annotations\Annotation')) { + return false; + } + + $this->_isNestedAnnotation = true; // Next will be nested + + if ($this->_lexer->isNextToken('(')) { + $this->match('('); + if ($this->_lexer->isNextToken(')')) { + $this->match(')'); + } else { + $values = $this->Values(); + $this->match(')'); + } + } + + return new $name($values); + } + + /** + * Values ::= Value ("," Value)* + */ + public function Values() + { + $values = array(); + + $value = $this->Value(); + if (is_array($value)) { + $k = key($value); + $v = $value[$k]; + if (is_string($k)) { + // FieldAssignment + $values[$k] = $v; + } else { + $values['value'][] = $value; + } + } else { + $values['value'][] = $value; + } + + while ($this->_lexer->isNextToken(',')) { + $this->match(','); + $value = $this->Value(); + if (is_array($value)) { + $k = key($value); + $v = $value[$k]; + if (is_string($k)) { + // FieldAssignment + $values[$k] = $v; + } else { + $values['value'][] = $value; + } + } else { + $values['value'][] = $value; + } + } + + return $values; + } + + /** + * Value ::= PlainValue | FieldAssignment + */ + public function Value() + { + $peek = $this->_lexer->glimpse(); + if ($peek['value'] === '=') { + return $this->FieldAssignment(); + } + return $this->PlainValue(); + } + + /** + * PlainValue ::= integer | string | float | Array | Annotation + */ + public function PlainValue() + { + if ($this->_lexer->lookahead['value'] == '{') { + return $this->Array_(); + } + if ($this->_lexer->lookahead['value'] == '@') { + 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']; + default: + var_dump($this->_lexer->lookahead); + throw new \Exception("Invalid value."); + } + } + + /** + * fieldAssignment ::= fieldName "=" plainValue + * fieldName ::= identifier + */ + public function FieldAssignment() + { + $this->match(Lexer::T_IDENTIFIER); + $fieldName = $this->_lexer->token['value']; + $this->match('='); + return array($fieldName => $this->PlainValue()); + } + + /** + * Array ::= "{" arrayEntry ("," arrayEntry)* "}" + */ + public function Array_() + { + $this->match('{'); + $array = array(); + $this->ArrayEntry($array); + + while ($this->_lexer->isNextToken(',')) { + $this->match(','); + $this->ArrayEntry($array); + } + $this->match('}'); + + return $array; + } + + /** + * ArrayEntry ::= Value | KeyValuePair + * KeyValuePair ::= Key "=" Value + * Key ::= string | integer + */ + public function ArrayEntry(array &$array) + { + $peek = $this->_lexer->glimpse(); + if ($peek['value'] == '=') { + if ($this->_lexer->lookahead['type'] === Lexer::T_INTEGER) { + $this->match(Lexer::T_INTEGER); + } else { + $this->match(Lexer::T_STRING); + } + $key = $this->_lexer->token['value']; + $this->match('='); + return $array[$key] = $this->Value(); + } else { + return $array[] = $this->Value(); + } + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/ApcCache.php b/lib/Doctrine/Common/Cache/ApcCache.php similarity index 98% rename from lib/Doctrine/ORM/Cache/ApcCache.php rename to lib/Doctrine/Common/Cache/ApcCache.php index f62093e92..53a4bc844 100644 --- a/lib/Doctrine/ORM/Cache/ApcCache.php +++ b/lib/Doctrine/Common/Cache/ApcCache.php @@ -19,7 +19,7 @@ * . */ -namespace Doctrine\ORM\Cache; +namespace Doctrine\Common\Cache; /** * APC cache driver. diff --git a/lib/Doctrine/ORM/Cache/ArrayCache.php b/lib/Doctrine/Common/Cache/ArrayCache.php similarity index 98% rename from lib/Doctrine/ORM/Cache/ArrayCache.php rename to lib/Doctrine/Common/Cache/ArrayCache.php index 010361158..d31caa5ae 100644 --- a/lib/Doctrine/ORM/Cache/ArrayCache.php +++ b/lib/Doctrine/Common/Cache/ArrayCache.php @@ -19,7 +19,7 @@ * . */ -namespace Doctrine\ORM\Cache; +namespace Doctrine\Common\Cache; /** * Array cache driver. diff --git a/lib/Doctrine/ORM/Cache/Cache.php b/lib/Doctrine/Common/Cache/Cache.php similarity index 98% rename from lib/Doctrine/ORM/Cache/Cache.php rename to lib/Doctrine/Common/Cache/Cache.php index 8d00867f8..fc1672fa1 100644 --- a/lib/Doctrine/ORM/Cache/Cache.php +++ b/lib/Doctrine/Common/Cache/Cache.php @@ -19,7 +19,7 @@ * . */ -namespace Doctrine\ORM\Cache; +namespace Doctrine\Common\Cache; /** * Interface for cache drivers. diff --git a/lib/Doctrine/ORM/Cache/MemcacheCache.php b/lib/Doctrine/Common/Cache/MemcacheCache.php similarity index 98% rename from lib/Doctrine/ORM/Cache/MemcacheCache.php rename to lib/Doctrine/Common/Cache/MemcacheCache.php index 64d3e7758..0f2625a2e 100644 --- a/lib/Doctrine/ORM/Cache/MemcacheCache.php +++ b/lib/Doctrine/Common/Cache/MemcacheCache.php @@ -19,7 +19,7 @@ * . */ -namespace Doctrine\ORM\Cache; +namespace Doctrine\Common\Cache; /** * Memcache cache driver. diff --git a/lib/Doctrine/ORM/Cache/XcacheCache.php b/lib/Doctrine/Common/Cache/XcacheCache.php similarity index 98% rename from lib/Doctrine/ORM/Cache/XcacheCache.php rename to lib/Doctrine/Common/Cache/XcacheCache.php index 33fee5a92..207fb29d7 100644 --- a/lib/Doctrine/ORM/Cache/XcacheCache.php +++ b/lib/Doctrine/Common/Cache/XcacheCache.php @@ -19,7 +19,7 @@ * . */ -namespace Doctrine\ORM\Cache; +namespace Doctrine\Common\Cache; /** * Xcache cache driver. diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 9513ed7bb..a9dc16ae3 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -199,12 +199,12 @@ abstract class AbstractQuery /** * Defines a cache driver to be used for caching result sets. * - * @param Doctrine\ORM\Cache\Cache $driver Cache driver + * @param Doctrine\Common\Cache\Cache $driver Cache driver * @return Doctrine\ORM\Query */ public function setResultCache($resultCache = null) { - if ($resultCache !== null && ! ($resultCache instanceof \Doctrine\ORM\Cache\Cache)) { + if ($resultCache !== null && ! ($resultCache instanceof \Doctrine\Common\Cache\Cache)) { throw DoctrineException::updateMe( 'Method setResultCache() accepts only an instance of Doctrine_Cache_Interface or null.' ); @@ -216,7 +216,7 @@ abstract class AbstractQuery /** * Returns the cache driver used for caching result sets. * - * @return Doctrine_Cache_Interface Cache driver + * @return Doctrine\Common\Cache\Cache Cache driver */ public function getResultCacheDriver() { diff --git a/lib/Doctrine/ORM/Cache/DbCache.php b/lib/Doctrine/ORM/Cache/DbCache.php deleted file mode 100644 index f6fba9720..000000000 --- a/lib/Doctrine/ORM/Cache/DbCache.php +++ /dev/null @@ -1,181 +0,0 @@ -. - */ - -namespace Doctrine\ORM\Cache; - -/** - * Doctrine_Cache_Db - * - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.doctrine-project.org - * @since 1.0 - * @version $Revision: 3931 $ - * @author Konsta Vesterinen - * @todo Needs some maintenance. Any takers? - */ -class DbCache implements Cache, \Countable -{ - private $_options = array(); - - /** - * {@inheritdoc} - */ - public function __construct($options) - { - if ( ! isset($options['connection']) || - ! ($options['connection'] instanceof Doctrine_DBAL_Connection)) { - - throw \Doctrine\Common\DoctrineException::updateMe('Connection option not set.'); - } - - if ( ! isset($options['tableName']) || - ! is_string($options['tableName'])) { - - throw \Doctrine\Common\DoctrineException::updateMe('Table name option not set.'); - } - - $this->_options = $options; - } - - /** - * getConnection - * returns the connection object associated with this cache driver - * - * @return Doctrine_Connection connection object - */ - public function getConnection() - { - return $this->_options['connection']; - } - - /** - * {@inheritdoc} - */ - public function fetch($id) - { - $sql = 'SELECT data, expire FROM ' . $this->_options['tableName'] - . ' WHERE id = ?'; - - if ($testCacheValidity) { - $sql .= ' AND (expire=0 OR expire > ' . time() . ')'; - } - - $result = $this->getConnection()->fetchAssoc($sql, array($id)); - - if ( ! isset($result[0])) { - return false; - } - - return unserialize($result[0]['data']); - } - - /** - * {@inheritdoc} - */ - public function contains($id) - { - $sql = 'SELECT expire FROM ' . $this->_options['tableName'] - . ' WHERE id = ? AND (expire=0 OR expire > ' . time() . ')'; - - return $this->getConnection()->fetchOne($sql, array($id)); - } - - /** - * {@inheritdoc} - */ - public function save($data, $id, $lifeTime = false) - { - $sql = 'INSERT INTO ' . $this->_options['tableName'] - . ' (id, data, expire) VALUES (?, ?, ?)'; - - if ($lifeTime) { - $expire = time() + $lifeTime; - } else { - $expire = 0; - } - - $params = array($id, serialize($data), $expire); - - return (bool) $this->getConnection()->exec($sql, $params); - } - - /** - * {@inheritdoc} - */ - public function delete($id) - { - $sql = 'DELETE FROM ' . $this->_options['tableName'] . ' WHERE id = ?'; - - return (bool) $this->getConnection()->exec($sql, array($id)); - } - - /** - * Removes all cache records - * - * $return bool true on success, false on failure - */ - public function deleteAll() - { - $sql = 'DELETE FROM ' . $this->_options['tableName']; - - return (bool) $this->getConnection()->exec($sql); - } - - /** - * count - * returns the number of cached elements - * - * @return integer - */ - public function count() - { - $sql = 'SELECT COUNT(*) FROM ' . $this->_options['tableName']; - - return (int) $this->getConnection()->fetchOne($sql); - } - - /** - * Creates the cache table. - */ - public function createTable() - { - $name = $this->_options['tableName']; - - $fields = array( - 'id' => array( - 'type' => 'string', - 'length' => 255 - ), - 'data' => array( - 'type' => 'blob' - ), - 'expire' => array( - 'type' => 'timestamp' - ) - ); - - $options = array( - 'primary' => array('id') - ); - - $this->getConnection()->export->createTable($name, $fields, $options); - } -} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index 49f02aea5..f382f7933 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -61,7 +61,7 @@ class ClassMetadataFactory * Sets the cache driver used by the factory to cache ClassMetadata instances * and invokes the preload() method of the metadata driver to prepopulate the cache. * - * @param Doctrine\ORM\Cache\Cache $cacheDriver + * @param Doctrine\Common\Cache\Cache $cacheDriver */ public function setCacheDriver($cacheDriver) { @@ -75,7 +75,7 @@ class ClassMetadataFactory /** * Gets the cache driver used by the factory to cache ClassMetadata instances. * - * @return Doctrine\ORM\Cache\Cache + * @return Doctrine\Common\Cache\Cache */ public function getCacheDriver() { diff --git a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php index 5b533424a..984fa3141 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php @@ -100,3 +100,4 @@ final class SequenceGenerator extends \Annotation { public $initialValue = 1; } final class ChangeTrackingPolicy extends \Annotation {} +final class DoctrineX extends \Annotation {} diff --git a/lib/Doctrine/ORM/Query/AbstractResult.php b/lib/Doctrine/ORM/Query/AbstractResult.php index 679a90169..4edaeb226 100644 --- a/lib/Doctrine/ORM/Query/AbstractResult.php +++ b/lib/Doctrine/ORM/Query/AbstractResult.php @@ -1,6 +1,6 @@ addTestSuite('Doctrine\Tests\Common\EventManagerTest'); $suite->addTest(Collections\AllTests::suite()); + $suite->addTest(Annotations\AllTests::suite()); return $suite; } diff --git a/tests/Doctrine/Tests/Common/Annotations/AllTests.php b/tests/Doctrine/Tests/Common/Annotations/AllTests.php new file mode 100644 index 000000000..f7dcfbb9e --- /dev/null +++ b/tests/Doctrine/Tests/Common/Annotations/AllTests.php @@ -0,0 +1,32 @@ +addTestSuite('Doctrine\Tests\Common\Annotations\LexerTest'); + $suite->addTestSuite('Doctrine\Tests\Common\Annotations\ParserTest'); + $suite->addTestSuite('Doctrine\Tests\Common\Annotations\AnnotationReaderTest'); + + return $suite; + } +} + +if (PHPUnit_MAIN_METHOD == 'Common_Annotations_AllTests::main') { + AllTests::main(); +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Common/Annotations/AnnotationReaderTest.php b/tests/Doctrine/Tests/Common/Annotations/AnnotationReaderTest.php new file mode 100644 index 000000000..8613a6183 --- /dev/null +++ b/tests/Doctrine/Tests/Common/Annotations/AnnotationReaderTest.php @@ -0,0 +1,98 @@ +setDefaultAnnotationNamespace('Doctrine\Tests\Common\Annotations\\'); + + $class = new \ReflectionClass('Doctrine\Tests\Common\Annotations\DummyClass'); + $classAnnots = $reader->getClassAnnotations($class); + + $annotName = 'Doctrine\Tests\Common\Annotations\DummyAnnotation'; + $this->assertEquals(1, count($classAnnots)); + $this->assertTrue($classAnnots[$annotName] instanceof DummyAnnotation); + $this->assertEquals("hello", $classAnnots[$annotName]->dummyValue); + + $propAnnots = $reader->getPropertyAnnotations($class, 'field1'); + $this->assertEquals(1, count($propAnnots)); + $this->assertTrue($propAnnots[$annotName] instanceof DummyAnnotation); + $this->assertEquals("fieldHello", $propAnnots[$annotName]->dummyValue); + + $methodAnnots = $reader->getMethodAnnotations($class, 'getField1'); + $this->assertEquals(1, count($methodAnnots)); + $this->assertTrue($methodAnnots[$annotName] instanceof DummyAnnotation); + $this->assertEquals("methodHello", $methodAnnots[$annotName]->dummyValue); + $this->assertEquals(array(array(1, 2, "three")), $methodAnnots[$annotName]->value); + + $propAnnots = $reader->getPropertyAnnotations($class, 'field2'); + $this->assertEquals(1, count($propAnnots)); + $this->assertTrue(isset($propAnnots['Doctrine\Tests\Common\Annotations\DummyJoinTable'])); + $joinTableAnnot = $propAnnots['Doctrine\Tests\Common\Annotations\DummyJoinTable']; + $this->assertEquals(1, count($joinTableAnnot->joinColumns)); + $this->assertEquals(1, count($joinTableAnnot->inverseJoinColumns)); + $this->assertTrue($joinTableAnnot->joinColumns[0] instanceof DummyJoinColumn); + $this->assertTrue($joinTableAnnot->inverseJoinColumns[0] instanceof DummyJoinColumn); + $this->assertEquals('col1', $joinTableAnnot->joinColumns[0]->name); + $this->assertEquals('col2', $joinTableAnnot->joinColumns[0]->referencedColumnName); + $this->assertEquals('col3', $joinTableAnnot->inverseJoinColumns[0]->name); + $this->assertEquals('col4', $joinTableAnnot->inverseJoinColumns[0]->referencedColumnName); + } +} + +/** + * A description of this class. + * + * @author robo + * @since 2.0 + * @DummyAnnotation(dummyValue="hello") + */ +class DummyClass { + /** + * A nice property. + * + * @var mixed + * @DummyAnnotation(dummyValue="fieldHello") + */ + private $field1; + + /** + * @DummyJoinTable(name="join_table", + joinColumns={ + @DummyJoinColumn(name="col1", referencedColumnName="col2") + }, + inverseJoinColumns={ + @DummyJoinColumn(name="col3", referencedColumnName="col4") + }) + */ + private $field2; + + /** + * Gets the value of field1. + * + * @return mixed + * @DummyAnnotation({1,2,"three"}, dummyValue="methodHello") + */ + public function getField1() { + } +} + +class DummyAnnotation extends \Doctrine\Common\Annotations\Annotation { + public $dummyValue; +} +class DummyJoinColumn extends \Doctrine\Common\Annotations\Annotation { + public $name; + public $referencedColumnName; +} +class DummyJoinTable extends \Doctrine\Common\Annotations\Annotation { + public $name; + public $joinColumns; + public $inverseJoinColumns; +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Common/Annotations/LexerTest.php b/tests/Doctrine/Tests/Common/Annotations/LexerTest.php new file mode 100644 index 000000000..01acc8dc5 --- /dev/null +++ b/tests/Doctrine/Tests/Common/Annotations/LexerTest.php @@ -0,0 +1,29 @@ +setInput("@Name"); + $this->assertNull($lexer->token); + $this->assertNull($lexer->lookahead); + + $this->assertTrue($lexer->moveNext()); + $this->assertNull($lexer->token); + $this->assertEquals('@', $lexer->lookahead['value']); + + $this->assertTrue($lexer->moveNext()); + $this->assertEquals('@', $lexer->token['value']); + $this->assertEquals('Name', $lexer->lookahead['value']); + + $this->assertFalse($lexer->moveNext()); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Common/Annotations/ParserTest.php b/tests/Doctrine/Tests/Common/Annotations/ParserTest.php new file mode 100644 index 000000000..db300fa32 --- /dev/null +++ b/tests/Doctrine/Tests/Common/Annotations/ParserTest.php @@ -0,0 +1,92 @@ +setDefaultAnnotationNamespace('Doctrine\Tests\Common\Annotations\\'); + + // Marker annotation + $result = $parser->parse("@Name"); + $annot = $result['Doctrine\Tests\Common\Annotations\Name']; + $this->assertTrue($annot instanceof Name); + $this->assertNull($annot->value); + $this->assertNull($annot->foo); + + // Associative arrays + $result = $parser->parse('@Name(foo={"key1" = "value1"})'); + $annot = $result['Doctrine\Tests\Common\Annotations\Name']; + $this->assertNull($annot->value); + $this->assertTrue(is_array($annot->foo)); + $this->assertTrue(isset($annot->foo['key1'])); + + // Nested arrays with nested annotations + $result = $parser->parse('@Name(foo=1, 2, 3, {1,2, {"key"=@Name}})'); + $annot = $result['Doctrine\Tests\Common\Annotations\Name']; + + $this->assertTrue($annot instanceof Name); + $this->assertEquals(3, count($annot->value)); + $this->assertEquals(1, $annot->foo); + $this->assertEquals(2, $annot->value[0]); + $this->assertEquals(3, $annot->value[1]); + $this->assertTrue(is_array($annot->value[2])); + + $nestedArray = $annot->value[2]; + $this->assertEquals(3, count($nestedArray)); + $this->assertEquals(1, $nestedArray[0]); + $this->assertEquals(2, $nestedArray[1]); + $this->assertTrue(is_array($nestedArray[2])); + + $nestedArray2 = $nestedArray[2]; + $this->assertTrue(isset($nestedArray2['key'])); + $this->assertTrue($nestedArray2['key'] instanceof Name); + + // Complete docblock + $docblock = <<parse($docblock); + $this->assertEquals(1, count($result)); + $annot = $result['Doctrine\Tests\Common\Annotations\Name']; + $this->assertTrue($annot instanceof Name); + $this->assertEquals("bar", $annot->foo); + $this->assertNull($annot->value); + } + + public function testNamespacedAnnotations() + { + $parser = new Parser; + + $docblock = <<parse($docblock); + $this->assertEquals(1, count($result)); + $annot = $result['Doctrine\Tests\Common\Annotations\Name']; + $this->assertTrue($annot instanceof Name); + $this->assertEquals("bar", $annot->foo); + } +} + +class Name extends \Doctrine\Common\Annotations\Annotation { + public $foo; +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php index 6e8e251d0..e4360836e 100644 --- a/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/QueryCacheTest.php @@ -3,7 +3,7 @@ namespace Doctrine\Tests\ORM\Functional; use Doctrine\Tests\Models\CMS\CmsUser; -use Doctrine\ORM\Cache\ArrayCache; +use Doctrine\Common\Cache\ArrayCache; require_once __DIR__ . '/../../TestInit.php'; diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 694f8dd64..810261116 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -147,10 +147,10 @@ class OrmFunctionalTestCase extends OrmTestCase // the actual database platform used during execution has effect on some // metadata mapping behaviors (like the choice of the ID generation). if (is_null(self::$_metadataCacheImpl)) { - self::$_metadataCacheImpl = new \Doctrine\ORM\Cache\ArrayCache; + self::$_metadataCacheImpl = new \Doctrine\Common\Cache\ArrayCache; } if (is_null(self::$_queryCacheImpl)) { - self::$_queryCacheImpl = new \Doctrine\ORM\Cache\ArrayCache; + self::$_queryCacheImpl = new \Doctrine\Common\Cache\ArrayCache; } $config = new \Doctrine\ORM\Configuration(); $config->setMetadataCacheImpl(self::$_metadataCacheImpl); diff --git a/tests/Doctrine/Tests/OrmTestCase.php b/tests/Doctrine/Tests/OrmTestCase.php index c56e0684d..4d5f914dd 100644 --- a/tests/Doctrine/Tests/OrmTestCase.php +++ b/tests/Doctrine/Tests/OrmTestCase.php @@ -42,7 +42,7 @@ class OrmTestCase extends DoctrineTestCase private static function getSharedMetadataCacheImpl() { if (self::$_metadataCacheImpl === null) { - self::$_metadataCacheImpl = new \Doctrine\ORM\Cache\ArrayCache; + self::$_metadataCacheImpl = new \Doctrine\Common\Cache\ArrayCache; } return self::$_metadataCacheImpl; } @@ -50,7 +50,7 @@ class OrmTestCase extends DoctrineTestCase private static function getSharedQueryCacheImpl() { if (self::$_queryCacheImpl === null) { - self::$_queryCacheImpl = new \Doctrine\ORM\Cache\ArrayCache; + self::$_queryCacheImpl = new \Doctrine\Common\Cache\ArrayCache; } return self::$_queryCacheImpl; }