diff --git a/UPGRADE.md b/UPGRADE.md index ae9509cd5..de29a33cd 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,6 +1,9 @@ # Upgrade from 1.0.x to 2.0.x -`DocLexer::peek()` and `DocLexer::glimpse` now return +- `DocLexer::peek()` and `DocLexer::glimpse` now return `Doctrine\Common\Lexer\Token` objects. When using `doctrine/lexer` 2, these implement `ArrayAccess` as a way for you to still be able to treat them as arrays in some ways. +- `CachedReader` and `FileCacheReader` have been removed. +- `AnnotationRegistry` method related to registering annotations instead of + using autoloading have been removed. diff --git a/docs/en/annotations.rst b/docs/en/annotations.rst index 2c3c42865..d32b15d12 100644 --- a/docs/en/annotations.rst +++ b/docs/en/annotations.rst @@ -79,23 +79,11 @@ with Doctrine Annotations requires this setup: .. code-block:: php use Doctrine\Common\Annotations\AnnotationReader; - use Doctrine\Common\Annotations\AnnotationRegistry; - - AnnotationRegistry::registerFile("/path/to/doctrine/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php"); - AnnotationRegistry::registerAutoloadNamespace("Symfony\Component\Validator\Constraint", "/path/to/symfony/src"); - AnnotationRegistry::registerAutoloadNamespace("MyProject\Annotations", "/path/to/myproject/src"); $reader = new AnnotationReader(); AnnotationReader::addGlobalIgnoredName('dummy'); -The second block with the annotation registry calls registers all the -three different annotation namespaces that are used. -Doctrine Annotations saves all its annotations in a single file, that is -why ``AnnotationRegistry#registerFile`` is used in contrast to -``AnnotationRegistry#registerAutoloadNamespace`` which creates a PSR-0 -compatible loading mechanism for class to file names. - -In the third block, we create the actual ``AnnotationReader`` instance. +We create the actual ``AnnotationReader`` instance. Note that we also add ``dummy`` to the global list of ignored annotations for which we do not throw exceptions. Setting this is necessary in our example case, otherwise ``@dummy`` would trigger an @@ -165,57 +153,6 @@ class name you can wrap the reader in an ``IndexedReader``: indexed or numeric keys, otherwise your code may experience failures due to caching in a numerical or indexed format. -Registering Annotations -~~~~~~~~~~~~~~~~~~~~~~~ - -As explained in the introduction, Doctrine Annotations uses its own -autoloading mechanism to determine if a given annotation has a -corresponding PHP class that can be autoloaded. For annotation -autoloading you have to configure the -``Doctrine\Common\Annotations\AnnotationRegistry``. There are three -different mechanisms to configure annotation autoloading: - -- Calling ``AnnotationRegistry#registerFile($file)`` to register a file - that contains one or more annotation classes. -- Calling ``AnnotationRegistry#registerNamespace($namespace, $dirs = - null)`` to register that the given namespace contains annotations and - that their base directory is located at the given $dirs or in the - include path if ``NULL`` is passed. The given directories should *NOT* - be the directory where classes of the namespace are in, but the base - directory of the root namespace. The AnnotationRegistry uses a - namespace to directory separator approach to resolve the correct path. -- Calling ``AnnotationRegistry#registerLoader($callable)`` to register - an autoloader callback. The callback accepts the class as first and - only parameter and has to return ``true`` if the corresponding file - was found and included. - -.. note:: - - Loaders have to fail silently, if a class is not found even if it - matches for example the namespace prefix of that loader. Never is a - loader to throw a warning or exception if the loading failed - otherwise parsing doc block annotations will become a huge pain. - -A sample loader callback could look like: - -.. code-block:: php - - use Doctrine\Common\Annotations\AnnotationRegistry; - use Symfony\Component\ClassLoader\UniversalClassLoader; - - AnnotationRegistry::registerLoader(function($class) { - $file = str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php"; - - if (file_exists("/my/base/path/" . $file)) { - // file_exists() makes sure that the loader fails silently - require "/my/base/path/" . $file; - } - }); - - $loader = new UniversalClassLoader(); - AnnotationRegistry::registerLoader(array($loader, "loadClass")); - - Ignoring missing exceptions ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/en/custom.rst b/docs/en/custom.rst index e8f79af71..300516625 100644 --- a/docs/en/custom.rst +++ b/docs/en/custom.rst @@ -2,7 +2,7 @@ Custom Annotation Classes ========================= If you want to define your own annotations, you just have to group them -in a namespace and register this namespace in the ``AnnotationRegistry``. +in a namespace. Annotation classes have to contain a class-level docblock with the text ``@Annotation``: @@ -58,10 +58,10 @@ Optional: Constructors with Named Parameters Starting with Annotations v1.11 a new annotation instantiation strategy is available that aims at compatibility of Annotation classes with the PHP 8 -attribute feature. You need to declare a constructor with regular parameter +attribute feature. You need to declare a constructor with regular parameter names that match the named arguments in the annotation syntax. -To enable this feature, you can tag your annotation class with +To enable this feature, you can tag your annotation class with ``@NamedArgumentConstructor`` (available from v1.12) or implement the ``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation`` interface (available from v1.11 and deprecated as of v1.12). @@ -75,8 +75,8 @@ Usage with the ``@NamedArgumentConstructor`` tag namespace MyCompany\Annotations; - /** - * @Annotation + /** + * @Annotation * @NamedArgumentConstructor */ class Bar implements NamedArgumentConstructorAnnotation @@ -99,8 +99,8 @@ you can simplify this to: namespace MyCompany\Annotations; - /** - * @Annotation + /** + * @Annotation * @NamedArgumentConstructor */ class Bar implements NamedArgumentConstructorAnnotation @@ -109,7 +109,7 @@ you can simplify this to: } -Usage with the +Usage with the ``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation`` interface (v1.11, deprecated as of v1.12): .. code-block:: php diff --git a/docs/en/index.rst b/docs/en/index.rst index 7caffb50a..a6e338333 100644 --- a/docs/en/index.rst +++ b/docs/en/index.rst @@ -73,10 +73,6 @@ annotations of a class. A common one is .. code-block:: php use Doctrine\Common\Annotations\AnnotationReader; - use Doctrine\Common\Annotations\AnnotationRegistry; - - // Deprecated and will be removed in 2.0 but currently needed - AnnotationRegistry::registerLoader('class_exists'); $reflectionClass = new ReflectionClass(Foo::class); $property = $reflectionClass->getProperty('bar'); @@ -89,10 +85,6 @@ annotations of a class. A common one is echo $myAnnotation->myProperty; // result: "value" -Note that ``AnnotationRegistry::registerLoader('class_exists')`` only works -if you already have an autoloader configured (i.e. composer autoloader). -Otherwise, :ref:`please take a look to the other annotation autoload mechanisms `. - A reader has multiple methods to access the annotations of a class or function. diff --git a/lib/Doctrine/Common/Annotations/AnnotationRegistry.php b/lib/Doctrine/Common/Annotations/AnnotationRegistry.php index 259d497dd..290e60aba 100644 --- a/lib/Doctrine/Common/Annotations/AnnotationRegistry.php +++ b/lib/Doctrine/Common/Annotations/AnnotationRegistry.php @@ -3,37 +3,10 @@ namespace Doctrine\Common\Annotations; use function array_key_exists; -use function array_merge; use function class_exists; -use function in_array; -use function is_file; -use function str_replace; -use function stream_resolve_include_path; -use function strpos; - -use const DIRECTORY_SEPARATOR; final class AnnotationRegistry { - /** - * A map of namespaces to use for autoloading purposes based on a PSR-0 convention. - * - * Contains the namespace as key and an array of directories as value. If the value is NULL - * the include path is used for checking for the corresponding file. - * - * This autoloading mechanism does not utilize the PHP autoloading but implements autoloading on its own. - * - * @var string[][]|string[]|null[] - */ - private static $autoloadNamespaces = []; - - /** - * A map of autoloader callables. - * - * @var callable[] - */ - private static $loaders = []; - /** * An array of classes which cannot be found * @@ -41,93 +14,9 @@ final class AnnotationRegistry */ private static $failedToAutoload = []; - /** - * Whenever registerFile() was used. Disables use of standard autoloader. - * - * @var bool - */ - private static $registerFileUsed = false; - public static function reset(): void { - self::$autoloadNamespaces = []; - self::$loaders = []; - self::$failedToAutoload = []; - self::$registerFileUsed = false; - } - - /** - * Registers file. - * - * @deprecated This method is deprecated and will be removed in - * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. - */ - public static function registerFile(string $file): void - { - self::$registerFileUsed = true; - - require_once $file; - } - - /** - * Adds a namespace with one or many directories to look for files or null for the include path. - * - * Loading of this namespaces will be done with a PSR-0 namespace loading algorithm. - * - * @deprecated This method is deprecated and will be removed in - * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. - * - * @phpstan-param string|list|null $dirs - */ - public static function registerAutoloadNamespace(string $namespace, $dirs = null): void - { - self::$autoloadNamespaces[$namespace] = $dirs; - } - - /** - * Registers multiple namespaces. - * - * Loading of this namespaces will be done with a PSR-0 namespace loading algorithm. - * - * @deprecated This method is deprecated and will be removed in - * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. - * - * @param string[][]|string[]|null[] $namespaces indexed by namespace name - */ - public static function registerAutoloadNamespaces(array $namespaces): void - { - self::$autoloadNamespaces = array_merge(self::$autoloadNamespaces, $namespaces); - } - - /** - * Registers an autoloading callable for annotations, much like spl_autoload_register(). - * - * NOTE: These class loaders HAVE to be silent when a class was not found! - * IMPORTANT: Loaders have to return true if they loaded a class that could contain the searched annotation class. - * - * @deprecated This method is deprecated and will be removed in - * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. - */ - public static function registerLoader(callable $callable): void - { - // Reset our static cache now that we have a new loader to work with self::$failedToAutoload = []; - self::$loaders[] = $callable; - } - - /** - * Registers an autoloading callable for annotations, if it is not already registered - * - * @deprecated This method is deprecated and will be removed in - * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. - */ - public static function registerUniqueLoader(callable $callable): void - { - if (in_array($callable, self::$loaders, true)) { - return; - } - - self::registerLoader($callable); } /** @@ -143,43 +32,7 @@ public static function loadAnnotationClass(string $class): bool return false; } - foreach (self::$autoloadNamespaces as $namespace => $dirs) { - if (strpos($class, $namespace) !== 0) { - continue; - } - - $file = str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php'; - - if ($dirs === null) { - $path = stream_resolve_include_path($file); - if ($path) { - require $path; - - return true; - } - } else { - foreach ((array) $dirs as $dir) { - if (is_file($dir . DIRECTORY_SEPARATOR . $file)) { - require $dir . DIRECTORY_SEPARATOR . $file; - - return true; - } - } - } - } - - foreach (self::$loaders as $loader) { - if ($loader($class) === true) { - return true; - } - } - - if ( - self::$loaders === [] && - self::$autoloadNamespaces === [] && - self::$registerFileUsed === false && - class_exists($class) - ) { + if (class_exists($class)) { return true; } diff --git a/lib/Doctrine/Common/Annotations/CachedReader.php b/lib/Doctrine/Common/Annotations/CachedReader.php deleted file mode 100644 index 85dbefab5..000000000 --- a/lib/Doctrine/Common/Annotations/CachedReader.php +++ /dev/null @@ -1,266 +0,0 @@ -> */ - private $loadedAnnotations = []; - - /** @var int[] */ - private $loadedFilemtimes = []; - - /** @param bool $debug */ - public function __construct(Reader $reader, Cache $cache, $debug = false) - { - $this->delegate = $reader; - $this->cache = $cache; - $this->debug = (bool) $debug; - } - - /** - * {@inheritDoc} - */ - public function getClassAnnotations(ReflectionClass $class) - { - $cacheKey = $class->getName(); - - if (isset($this->loadedAnnotations[$cacheKey])) { - return $this->loadedAnnotations[$cacheKey]; - } - - $annots = $this->fetchFromCache($cacheKey, $class); - if ($annots === false) { - $annots = $this->delegate->getClassAnnotations($class); - $this->saveToCache($cacheKey, $annots); - } - - return $this->loadedAnnotations[$cacheKey] = $annots; - } - - /** - * {@inheritDoc} - */ - public function getClassAnnotation(ReflectionClass $class, $annotationName) - { - foreach ($this->getClassAnnotations($class) as $annot) { - if ($annot instanceof $annotationName) { - return $annot; - } - } - - return null; - } - - /** - * {@inheritDoc} - */ - public function getPropertyAnnotations(ReflectionProperty $property) - { - $class = $property->getDeclaringClass(); - $cacheKey = $class->getName() . '$' . $property->getName(); - - if (isset($this->loadedAnnotations[$cacheKey])) { - return $this->loadedAnnotations[$cacheKey]; - } - - $annots = $this->fetchFromCache($cacheKey, $class); - if ($annots === false) { - $annots = $this->delegate->getPropertyAnnotations($property); - $this->saveToCache($cacheKey, $annots); - } - - return $this->loadedAnnotations[$cacheKey] = $annots; - } - - /** - * {@inheritDoc} - */ - public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) - { - foreach ($this->getPropertyAnnotations($property) as $annot) { - if ($annot instanceof $annotationName) { - return $annot; - } - } - - return null; - } - - /** - * {@inheritDoc} - */ - public function getMethodAnnotations(ReflectionMethod $method) - { - $class = $method->getDeclaringClass(); - $cacheKey = $class->getName() . '#' . $method->getName(); - - if (isset($this->loadedAnnotations[$cacheKey])) { - return $this->loadedAnnotations[$cacheKey]; - } - - $annots = $this->fetchFromCache($cacheKey, $class); - if ($annots === false) { - $annots = $this->delegate->getMethodAnnotations($method); - $this->saveToCache($cacheKey, $annots); - } - - return $this->loadedAnnotations[$cacheKey] = $annots; - } - - /** - * {@inheritDoc} - */ - public function getMethodAnnotation(ReflectionMethod $method, $annotationName) - { - foreach ($this->getMethodAnnotations($method) as $annot) { - if ($annot instanceof $annotationName) { - return $annot; - } - } - - return null; - } - - /** - * Clears loaded annotations. - * - * @return void - */ - public function clearLoadedAnnotations() - { - $this->loadedAnnotations = []; - $this->loadedFilemtimes = []; - } - - /** - * Fetches a value from the cache. - * - * @param string $cacheKey The cache key. - * - * @return mixed The cached value or false when the value is not in cache. - */ - private function fetchFromCache($cacheKey, ReflectionClass $class) - { - $data = $this->cache->fetch($cacheKey); - if ($data !== false) { - if (! $this->debug || $this->isCacheFresh($cacheKey, $class)) { - return $data; - } - } - - return false; - } - - /** - * Saves a value to the cache. - * - * @param string $cacheKey The cache key. - * @param mixed $value The value. - * - * @return void - */ - private function saveToCache($cacheKey, $value) - { - $this->cache->save($cacheKey, $value); - if (! $this->debug) { - return; - } - - $this->cache->save('[C]' . $cacheKey, time()); - } - - /** - * Checks if the cache is fresh. - * - * @param string $cacheKey - * - * @return bool - */ - private function isCacheFresh($cacheKey, ReflectionClass $class) - { - $lastModification = $this->getLastModification($class); - if ($lastModification === 0) { - return true; - } - - return $this->cache->fetch('[C]' . $cacheKey) >= $lastModification; - } - - /** - * Returns the time the class was last modified, testing traits and parents - */ - private function getLastModification(ReflectionClass $class): int - { - $filename = $class->getFileName(); - - if (isset($this->loadedFilemtimes[$filename])) { - return $this->loadedFilemtimes[$filename]; - } - - $parent = $class->getParentClass(); - - $lastModification = max(array_merge( - [$filename ? filemtime($filename) : 0], - array_map(function (ReflectionClass $reflectionTrait): int { - return $this->getTraitLastModificationTime($reflectionTrait); - }, $class->getTraits()), - array_map(function (ReflectionClass $class): int { - return $this->getLastModification($class); - }, $class->getInterfaces()), - $parent ? [$this->getLastModification($parent)] : [] - )); - - assert($lastModification !== false); - - return $this->loadedFilemtimes[$filename] = $lastModification; - } - - private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int - { - $fileName = $reflectionTrait->getFileName(); - - if (isset($this->loadedFilemtimes[$fileName])) { - return $this->loadedFilemtimes[$fileName]; - } - - $lastModificationTime = max(array_merge( - [$fileName ? filemtime($fileName) : 0], - array_map(function (ReflectionClass $reflectionTrait): int { - return $this->getTraitLastModificationTime($reflectionTrait); - }, $reflectionTrait->getTraits()) - )); - - assert($lastModificationTime !== false); - - return $this->loadedFilemtimes[$fileName] = $lastModificationTime; - } -} diff --git a/lib/Doctrine/Common/Annotations/FileCacheReader.php b/lib/Doctrine/Common/Annotations/FileCacheReader.php deleted file mode 100644 index 6c6c22c3a..000000000 --- a/lib/Doctrine/Common/Annotations/FileCacheReader.php +++ /dev/null @@ -1,315 +0,0 @@ -> */ - private $loadedAnnotations = []; - - /** @var array */ - private $classNameHashes = []; - - /** @var int */ - private $umask; - - /** - * @param string $cacheDir - * @param bool $debug - * @param int $umask - * - * @throws InvalidArgumentException - */ - public function __construct(Reader $reader, $cacheDir, $debug = false, $umask = 0002) - { - if (! is_int($umask)) { - throw new InvalidArgumentException(sprintf( - 'The parameter umask must be an integer, was: %s', - gettype($umask) - )); - } - - $this->reader = $reader; - $this->umask = $umask; - - if (! is_dir($cacheDir) && ! @mkdir($cacheDir, 0777 & (~$this->umask), true)) { - throw new InvalidArgumentException(sprintf( - 'The directory "%s" does not exist and could not be created.', - $cacheDir - )); - } - - $this->dir = rtrim($cacheDir, '\\/'); - $this->debug = $debug; - } - - /** - * {@inheritDoc} - */ - public function getClassAnnotations(ReflectionClass $class) - { - if (! isset($this->classNameHashes[$class->name])) { - $this->classNameHashes[$class->name] = sha1($class->name); - } - - $key = $this->classNameHashes[$class->name]; - - if (isset($this->loadedAnnotations[$key])) { - return $this->loadedAnnotations[$key]; - } - - $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; - if (! is_file($path)) { - $annot = $this->reader->getClassAnnotations($class); - $this->saveCacheFile($path, $annot); - - return $this->loadedAnnotations[$key] = $annot; - } - - $filename = $class->getFilename(); - if ( - $this->debug - && $filename !== false - && filemtime($path) < filemtime($filename) - ) { - @unlink($path); - - $annot = $this->reader->getClassAnnotations($class); - $this->saveCacheFile($path, $annot); - - return $this->loadedAnnotations[$key] = $annot; - } - - return $this->loadedAnnotations[$key] = include $path; - } - - /** - * {@inheritDoc} - */ - public function getPropertyAnnotations(ReflectionProperty $property) - { - $class = $property->getDeclaringClass(); - if (! isset($this->classNameHashes[$class->name])) { - $this->classNameHashes[$class->name] = sha1($class->name); - } - - $key = $this->classNameHashes[$class->name] . '$' . $property->getName(); - - if (isset($this->loadedAnnotations[$key])) { - return $this->loadedAnnotations[$key]; - } - - $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; - if (! is_file($path)) { - $annot = $this->reader->getPropertyAnnotations($property); - $this->saveCacheFile($path, $annot); - - return $this->loadedAnnotations[$key] = $annot; - } - - $filename = $class->getFilename(); - if ( - $this->debug - && $filename !== false - && filemtime($path) < filemtime($filename) - ) { - @unlink($path); - - $annot = $this->reader->getPropertyAnnotations($property); - $this->saveCacheFile($path, $annot); - - return $this->loadedAnnotations[$key] = $annot; - } - - return $this->loadedAnnotations[$key] = include $path; - } - - /** - * {@inheritDoc} - */ - public function getMethodAnnotations(ReflectionMethod $method) - { - $class = $method->getDeclaringClass(); - if (! isset($this->classNameHashes[$class->name])) { - $this->classNameHashes[$class->name] = sha1($class->name); - } - - $key = $this->classNameHashes[$class->name] . '#' . $method->getName(); - - if (isset($this->loadedAnnotations[$key])) { - return $this->loadedAnnotations[$key]; - } - - $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; - if (! is_file($path)) { - $annot = $this->reader->getMethodAnnotations($method); - $this->saveCacheFile($path, $annot); - - return $this->loadedAnnotations[$key] = $annot; - } - - $filename = $class->getFilename(); - if ( - $this->debug - && $filename !== false - && filemtime($path) < filemtime($filename) - ) { - @unlink($path); - - $annot = $this->reader->getMethodAnnotations($method); - $this->saveCacheFile($path, $annot); - - return $this->loadedAnnotations[$key] = $annot; - } - - return $this->loadedAnnotations[$key] = include $path; - } - - /** - * Saves the cache file. - * - * @param string $path - * @param mixed $data - * - * @return void - */ - private function saveCacheFile($path, $data) - { - if (! is_writable($this->dir)) { - throw new InvalidArgumentException(sprintf( - <<<'EXCEPTION' -The directory "%s" is not writable. Both the webserver and the console user need access. -You can manage access rights for multiple users with "chmod +a". -If your system does not support this, check out the acl package., -EXCEPTION - , - $this->dir - )); - } - - $tempfile = tempnam($this->dir, uniqid('', true)); - - if ($tempfile === false) { - throw new RuntimeException(sprintf('Unable to create tempfile in directory: %s', $this->dir)); - } - - @chmod($tempfile, 0666 & (~$this->umask)); - - $written = file_put_contents( - $tempfile, - 'umask)); - - if (rename($tempfile, $path) === false) { - @unlink($tempfile); - - throw new RuntimeException(sprintf('Unable to rename %s to %s', $tempfile, $path)); - } - } - - /** - * {@inheritDoc} - */ - public function getClassAnnotation(ReflectionClass $class, $annotationName) - { - $annotations = $this->getClassAnnotations($class); - - foreach ($annotations as $annotation) { - if ($annotation instanceof $annotationName) { - return $annotation; - } - } - - return null; - } - - /** - * {@inheritDoc} - */ - public function getMethodAnnotation(ReflectionMethod $method, $annotationName) - { - $annotations = $this->getMethodAnnotations($method); - - foreach ($annotations as $annotation) { - if ($annotation instanceof $annotationName) { - return $annotation; - } - } - - return null; - } - - /** - * {@inheritDoc} - */ - public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) - { - $annotations = $this->getPropertyAnnotations($property); - - foreach ($annotations as $annotation) { - if ($annotation instanceof $annotationName) { - return $annotation; - } - } - - return null; - } - - /** - * Clears loaded annotations. - * - * @return void - */ - public function clearLoadedAnnotations() - { - $this->loadedAnnotations = []; - } -} diff --git a/tests/Doctrine/Tests/Common/Annotations/AnnotationRegistryTest.php b/tests/Doctrine/Tests/Common/Annotations/AnnotationRegistryTest.php index 5fe6c733d..2a9bf27ea 100644 --- a/tests/Doctrine/Tests/Common/Annotations/AnnotationRegistryTest.php +++ b/tests/Doctrine/Tests/Common/Annotations/AnnotationRegistryTest.php @@ -4,202 +4,10 @@ use Doctrine\Common\Annotations\AnnotationRegistry; use Doctrine\Tests\Common\Annotations\Fixtures\Annotation\CanBeAutoLoaded; -use Doctrine\Tests\Common\Annotations\Fixtures\Annotation\LoadedUsingRegisterFile; -use Doctrine\Tests\Common\Annotations\Fixtures\Annotation\ShouldNeverBeLoaded; use PHPUnit\Framework\TestCase; -use ReflectionProperty; -use TypeError; - -use function random_int; class AnnotationRegistryTest extends TestCase { - /** @var string */ - protected $class = AnnotationRegistry::class; - - /** - * @runInSeparateProcess - */ - public function testReset(): void - { - $data = ['foo' => 'bar']; - - $this->setStaticField($this->class, 'autoloadNamespaces', $data); - $this->setStaticField($this->class, 'loaders', $data); - - self::assertSame($data, $this->getStaticField($this->class, 'autoloadNamespaces')); - self::assertSame($data, $this->getStaticField($this->class, 'loaders')); - - AnnotationRegistry::reset(); - - self::assertEmpty($this->getStaticField($this->class, 'autoloadNamespaces')); - self::assertEmpty($this->getStaticField($this->class, 'loaders')); - } - - /** - * @runInSeparateProcess - */ - public function testRegisterAutoloadNamespaces(): void - { - $this->setStaticField($this->class, 'autoloadNamespaces', ['foo' => 'bar']); - - AnnotationRegistry::registerAutoloadNamespaces(['test' => 'bar']); - self::assertSame(['foo' => 'bar', 'test' => 'bar'], $this->getStaticField($this->class, 'autoloadNamespaces')); - } - - /** - * @runInSeparateProcess - */ - public function testRegisterLoaderNoCallable(): void - { - $this->expectException(TypeError::class); - - AnnotationRegistry::registerLoader('test' . random_int(10, 10000)); - } - - /** - * @param mixed[] $value - */ - protected function setStaticField(string $class, string $field, array $value): void - { - $reflection = new ReflectionProperty($class, $field); - - $reflection->setAccessible(true); - $reflection->setValue(null, $value); - } - - /** - * @return mixed - */ - protected function getStaticField(string $class, string $field) - { - $reflection = new ReflectionProperty($class, $field); - - $reflection->setAccessible(true); - - return $reflection->getValue(); - } - - /** - * @runInSeparateProcess - */ - public function testStopCallingLoadersIfClassIsNotFound(): void - { - AnnotationRegistry::reset(); - $i = 0; - $autoLoader = static function () use (&$i): bool { - $i += 1; - - return false; - }; - AnnotationRegistry::registerLoader($autoLoader); - AnnotationRegistry::loadAnnotationClass('unloadableClass'); - AnnotationRegistry::loadAnnotationClass('unloadableClass'); - AnnotationRegistry::loadAnnotationClass('unloadableClass'); - self::assertSame(1, $i, 'Autoloader should only be called once'); - } - - /** - * @runInSeparateProcess - */ - public function testStopCallingLoadersAfterClassIsFound(): void - { - $className = 'autoloadedClass' . random_int(10, 100000); - AnnotationRegistry::reset(); - $i = 0; - $autoLoader = static function () use (&$i, $className): bool { - eval('class ' . $className . ' {}'); - $i += 1; - - return true; - }; - AnnotationRegistry::registerLoader($autoLoader); - AnnotationRegistry::loadAnnotationClass($className); - AnnotationRegistry::loadAnnotationClass($className); - AnnotationRegistry::loadAnnotationClass($className); - self::assertSame(1, $i, 'Autoloader should only be called once'); - } - - /** - * @runInSeparateProcess - */ - public function testAddingANewLoaderClearsTheCache(): void - { - $failures = 0; - $failingLoader = static function () use (&$failures): bool { - $failures += 1; - - return false; - }; - - AnnotationRegistry::reset(); - AnnotationRegistry::registerLoader($failingLoader); - - self::assertSame(0, $failures); - - AnnotationRegistry::loadAnnotationClass('unloadableClass'); - - self::assertSame(1, $failures); - - AnnotationRegistry::loadAnnotationClass('unloadableClass'); - - self::assertSame(1, $failures); - - AnnotationRegistry::registerLoader(static function (): bool { - return false; - }); - AnnotationRegistry::loadAnnotationClass('unloadableClass'); - - self::assertSame(2, $failures); - } - - /** - * @runInSeparateProcess - */ - public function testResetClearsRegisteredAutoloaderFailures(): void - { - $failures = 0; - $failingLoader = static function () use (&$failures): bool { - $failures += 1; - - return false; - }; - - AnnotationRegistry::reset(); - AnnotationRegistry::registerLoader($failingLoader); - - self::assertSame(0, $failures); - - AnnotationRegistry::loadAnnotationClass('unloadableClass'); - - self::assertSame(1, $failures); - - AnnotationRegistry::loadAnnotationClass('unloadableClass'); - - self::assertSame(1, $failures); - - AnnotationRegistry::reset(); - AnnotationRegistry::registerLoader($failingLoader); - AnnotationRegistry::loadAnnotationClass('unloadableClass'); - - self::assertSame(2, $failures); - } - - /** - * @runInSeparateProcess - */ - public function testRegisterLoaderIfNotExistsOnlyRegisteresSameLoaderOnce(): void - { - $className = 'autoloadedClassThatDoesNotExist'; - AnnotationRegistry::reset(); - $autoLoader = self::createPartialMock(Autoloader::class, ['__invoke']); - $autoLoader->expects($this->once())->method('__invoke'); - AnnotationRegistry::registerUniqueLoader($autoLoader); - AnnotationRegistry::registerUniqueLoader($autoLoader); - AnnotationRegistry::loadAnnotationClass($className); - AnnotationRegistry::loadAnnotationClass($className); - } - /** * @runInSeparateProcess */ @@ -209,23 +17,4 @@ public function testClassExistsFallback(): void self::assertTrue(AnnotationRegistry::loadAnnotationClass(CanBeAutoLoaded::class)); } - - /** - * @runInSeparateProcess - */ - public function testClassExistsFallbackNotUsedWhenRegisterFileUsed(): void - { - AnnotationRegistry::reset(); - AnnotationRegistry::registerFile(__DIR__ . '/Fixtures/Annotation/LoadedUsingRegisterFile.php'); - - self::assertTrue(AnnotationRegistry::loadAnnotationClass(LoadedUsingRegisterFile::class)); - self::assertFalse(AnnotationRegistry::loadAnnotationClass(ShouldNeverBeLoaded::class)); - } -} - -class Autoloader -{ - public function __invoke(): void - { - } } diff --git a/tests/Doctrine/Tests/Common/Annotations/CachedReaderTest.php b/tests/Doctrine/Tests/Common/Annotations/CachedReaderTest.php deleted file mode 100644 index 63537e0d4..000000000 --- a/tests/Doctrine/Tests/Common/Annotations/CachedReaderTest.php +++ /dev/null @@ -1,292 +0,0 @@ -markTestSkipped('Cannot test deprecated cached reader without doctrine/cache 1.x'); - } - - parent::setup(); - } - - public function testIgnoresStaleCache(): void - { - $cache = time() - 10; - touch(__DIR__ . '/Fixtures/Controller.php', $cache + 10); - - $this->doTestCacheStale(Fixtures\Controller::class, $cache); - } - - /** - * @group 62 - */ - public function testIgnoresStaleCacheWithParentClass(): void - { - $cache = time() - 10; - touch(__DIR__ . '/Fixtures/ControllerWithParentClass.php', $cache - 10); - touch(__DIR__ . '/Fixtures/AbstractController.php', $cache + 10); - - $this->doTestCacheStale(Fixtures\ControllerWithParentClass::class, $cache); - } - - /** - * @group 62 - */ - public function testIgnoresStaleCacheWithTraits(): void - { - $cache = time() - 10; - touch(__DIR__ . '/Fixtures/ControllerWithTrait.php', $cache - 10); - touch(__DIR__ . '/Fixtures/Traits/SecretRouteTrait.php', $cache + 10); - - $this->doTestCacheStale(Fixtures\ControllerWithTrait::class, $cache); - } - - /** - * @group 62 - */ - public function testIgnoresStaleCacheWithTraitsThatUseOtherTraits(): void - { - $cache = time() - 10; - - touch(__DIR__ . '/Fixtures/ClassThatUsesTraitThatUsesAnotherTrait.php', $cache - 10); - touch(__DIR__ . '/Fixtures/Traits/EmptyTrait.php', $cache + 10); - - $this->doTestCacheStale( - Fixtures\ClassThatUsesTraitThatUsesAnotherTrait::class, - $cache - ); - } - - /** - * @group 62 - */ - public function testIgnoresStaleCacheWithInterfacesThatExtendOtherInterfaces(): void - { - $cache = time() - 10; - - touch(__DIR__ . '/Fixtures/InterfaceThatExtendsAnInterface.php', $cache - 10); - touch(__DIR__ . '/Fixtures/EmptyInterface.php', $cache + 10); - - $this->doTestCacheStale( - Fixtures\InterfaceThatExtendsAnInterface::class, - $cache - ); - } - - /** - * @group 62 - * @group 105 - */ - public function testUsesFreshCacheWithTraitsThatUseOtherTraits(): void - { - $cacheTime = time(); - - touch(__DIR__ . '/Fixtures/ClassThatUsesTraitThatUsesAnotherTrait.php', $cacheTime - 10); - touch(__DIR__ . '/Fixtures/Traits/EmptyTrait.php', $cacheTime - 10); - - $this->doTestCacheFresh( - 'Doctrine\Tests\Common\Annotations\Fixtures\ClassThatUsesTraitThatUsesAnotherTrait', - $cacheTime - ); - } - - /** - * @group 62 - */ - public function testPurgeLoadedAnnotations(): void - { - $cache = time() - 10; - - touch(__DIR__ . '/Fixtures/ClassThatUsesTraitThatUsesAnotherTrait.php', $cache - 10); - touch(__DIR__ . '/Fixtures/Traits/EmptyTrait.php', $cache + 10); - - $reader = $this->doTestCacheStale( - Fixtures\ClassThatUsesTraitThatUsesAnotherTrait::class, - $cache - ); - - $classReader = new ReflectionClass(CachedReader::class); - - $loadedAnnotationsProperty = $classReader->getProperty('loadedAnnotations'); - $loadedAnnotationsProperty->setAccessible(true); - $this->assertCount(1, $loadedAnnotationsProperty->getValue($reader)); - - $loadedFilemtimesProperty = $classReader->getProperty('loadedFilemtimes'); - $loadedFilemtimesProperty->setAccessible(true); - $this->assertCount(3, $loadedFilemtimesProperty->getValue($reader)); - - $reader->clearLoadedAnnotations(); - - $this->assertCount(0, $loadedAnnotationsProperty->getValue($reader)); - $this->assertCount(0, $loadedFilemtimesProperty->getValue($reader)); - } - - /** - * As there is a cache on loadedAnnotations, we need to test two different - * method's annotations of the same file - * - * We test four things - * 1. we load the file (and its filemtime) for method1 annotation with fresh cache - * 2. we load the file for method2 with stale cache => but still no save, because seen as fresh - * 3. we purge loaded annotations and filemtime - * 4. same as 2, but this time without filemtime cache, so file seen as stale and new cache is saved - * - * @group 62 - * @group 105 - */ - public function testAvoidCallingFilemtimeTooMuch(): void - { - $this->markTestSkipped('Skipped until further investigation'); - - $className = ClassThatUsesTraitThatUsesAnotherTraitWithMethods::class; - $cacheKey = $className; - $cacheTime = time() - 10; - - $cacheKeyMethod1 = $cacheKey . '#method1'; - $cacheKeyMethod2 = $cacheKey . '#method2'; - - $route1 = new Route(); - $route1->pattern = '/someprefix'; - $route2 = new Route(); - $route2->pattern = '/someotherprefix'; - - $cache = $this->createMock('Doctrine\Common\Cache\Cache'); - assert($cache instanceof Cache && $cache instanceof MockObject); - - $cache - ->expects($this->exactly(6)) - ->method('fetch') - ->withConsecutive( - // first pass => cache ok for method 1 - // we load annotations AND filemtimes for this file - [$this->equalTo($cacheKeyMethod1)], - [$this->equalTo('[C]' . $cacheKeyMethod1)], - // second pass => cache ok for method 2 - // filemtime is seen as fresh even if it's not - [$this->equalTo($cacheKeyMethod2)], - [$this->equalTo('[C]' . $cacheKeyMethod2)], - // third pass => cache stale for method 2 - // filemtime is seen as not fresh => we save - [$this->equalTo($cacheKeyMethod2)], - [$this->equalTo('[C]' . $cacheKeyMethod2)] - ) - ->willReturnOnConsecutiveCalls( - [$route1], // Result was cached, but there was an annotation; - $cacheTime, - [$route2], // Result was cached, but there was an annotation; - $cacheTime, - [$route2], // Result was cached, but there was an annotation; - $cacheTime - ); - - $cache - ->expects($this->exactly(2)) - ->method('save') - ->withConsecutive( - [$this->equalTo($cacheKeyMethod2)], - [$this->equalTo('[C]' . $cacheKeyMethod2)] - ); - - $reader = new CachedReader(new AnnotationReader(), $cache, true); - - touch(__DIR__ . '/Fixtures/ClassThatUsesTraitThatUsesAnotherTraitWithMethods.php', $cacheTime - 20); - touch(__DIR__ . '/Fixtures/Traits/EmptyTrait.php', $cacheTime - 20); - $this->assertEquals([$route1], $reader->getMethodAnnotations(new ReflectionMethod($className, 'method1'))); - - // only filemtime changes, but not cleared => no change - touch(__DIR__ . '/Fixtures/ClassThatUsesTraitThatUsesAnotherTrait.php', $cacheTime + 5); - touch(__DIR__ . '/Fixtures/Traits/EmptyTrait.php', $cacheTime + 5); - $this->assertEquals([$route2], $reader->getMethodAnnotations(new ReflectionMethod($className, 'method2'))); - - $reader->clearLoadedAnnotations(); - $this->assertEquals([$route2], $reader->getMethodAnnotations(new ReflectionMethod($className, 'method2'))); - } - - protected function doTestCacheStale(string $className, int $lastCacheModification): CachedReader - { - $cacheKey = $className; - - $cache = $this->createMock(Cache::class); - $cache - ->expects($this->exactly(2)) - ->method('fetch') - ->withConsecutive( - [$this->equalTo($cacheKey)], - [$this->equalTo('[C]' . $cacheKey)] - ) - ->willReturnOnConsecutiveCalls( - [], // Result was cached, but there was no annotation - $lastCacheModification - ); - $cache - ->expects($this->exactly(2)) - ->method('save') - ->withConsecutive( - [$this->equalTo($cacheKey)], - [$this->equalTo('[C]' . $cacheKey)] - ); - - $reader = new CachedReader(new AnnotationReader(), $cache, true); - $route = new Route(); - $route->pattern = '/someprefix'; - - self::assertEquals([$route], $reader->getClassAnnotations(new ReflectionClass($className))); - - return $reader; - } - - protected function doTestCacheFresh(string $className, int $lastCacheModification): void - { - $cacheKey = $className; - $route = new Route(); - $route->pattern = '/someprefix'; - - $cache = $this->createMock('Doctrine\Common\Cache\Cache'); - $cache - ->expects($this->exactly(2)) - ->method('fetch') - ->withConsecutive( - [$this->equalTo($cacheKey)], - [$this->equalTo('[C]' . $cacheKey)] - ) - ->willReturnOnConsecutiveCalls( - [$route], - $lastCacheModification - ); - $cache->expects(self::never())->method('save'); - - $reader = new CachedReader(new AnnotationReader(), $cache, true); - - $this->assertEquals([$route], $reader->getClassAnnotations(new ReflectionClass($className))); - } - - protected function getReader(): Reader - { - $this->cache = new ArrayCache(); - - return new CachedReader(new AnnotationReader(), $this->cache); - } -} diff --git a/tests/Doctrine/Tests/Common/Annotations/DocParserTest.php b/tests/Doctrine/Tests/Common/Annotations/DocParserTest.php index 297d7e90c..97196c7b3 100644 --- a/tests/Doctrine/Tests/Common/Annotations/DocParserTest.php +++ b/tests/Doctrine/Tests/Common/Annotations/DocParserTest.php @@ -6,7 +6,6 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; use Doctrine\Common\Annotations\Annotation\Target; use Doctrine\Common\Annotations\AnnotationException; -use Doctrine\Common\Annotations\AnnotationRegistry; use Doctrine\Common\Annotations\DocParser; use Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation; use Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll; @@ -1315,11 +1314,6 @@ class_exists('Doctrine\Tests\Common\Annotations\Fixture\Annotation\Autoload', fa $parser = new DocParser(); - AnnotationRegistry::registerAutoloadNamespace( - 'Doctrine\Tests\Common\Annotations\Fixtures\Annotation', - __DIR__ . '/../../../../' - ); - $parser->setImports([ 'autoload' => Fixtures\Annotation\Autoload::class, ]); diff --git a/tests/Doctrine/Tests/Common/Annotations/FileCacheReaderTest.php b/tests/Doctrine/Tests/Common/Annotations/FileCacheReaderTest.php deleted file mode 100644 index a92f245a0..000000000 --- a/tests/Doctrine/Tests/Common/Annotations/FileCacheReaderTest.php +++ /dev/null @@ -1,56 +0,0 @@ -cacheDir = sys_get_temp_dir() . '/annotations_' . uniqid('', true); - @mkdir($this->cacheDir); - - return new FileCacheReader(new AnnotationReader(), $this->cacheDir); - } - - public function tearDown(): void - { - foreach (glob($this->cacheDir . '/*.php') as $file) { - unlink($file); - } - - rmdir($this->cacheDir); - } - - /** - * @group DCOM-81 - */ - public function testAttemptToCreateAnnotationCacheDir(): void - { - $this->cacheDir = sys_get_temp_dir() . '/not_existed_dir_' . uniqid('', true); - - if (method_exists($this, 'assertDirectoryDoesNotExist')) { - self::assertDirectoryDoesNotExist($this->cacheDir); - } else { - self::assertDirectoryNotExists($this->cacheDir); - } - - new FileCacheReader(new AnnotationReader(), $this->cacheDir); - - self::assertDirectoryExists($this->cacheDir); - } -} diff --git a/tests/Doctrine/Tests/TestInit.php b/tests/Doctrine/Tests/TestInit.php index 27b04a1b6..f704c23f5 100644 --- a/tests/Doctrine/Tests/TestInit.php +++ b/tests/Doctrine/Tests/TestInit.php @@ -1,7 +1,5 @@