diff --git a/UPGRADE-1.2.md b/UPGRADE-1.2.md deleted file mode 100644 index 0c0115d0..00000000 --- a/UPGRADE-1.2.md +++ /dev/null @@ -1,6 +0,0 @@ -UPGRADE FROM 1.x to 1.2 -======================= - -* Deprecated `ObjectManager::merge()`. Please handle merge operations in your application instead. -* Deprecated `ObjectManager::detach()`. Please use `ObjectManager::clear()` instead. -* Deprecated `PersistentObject` class. Please implement this functionality directly in your application if you want ActiveRecord style functionality. diff --git a/UPGRADE-2.2.md b/UPGRADE-2.2.md deleted file mode 100644 index 9b06aba4..00000000 --- a/UPGRADE-2.2.md +++ /dev/null @@ -1,11 +0,0 @@ -UPGRADE FROM 2.1 to 2.2 -======================= - -* Deprecated using doctrine/cache for metadata caching. The `setCacheDriver` and - `getCacheDriver` methods in `Doctrine\Persistence\Mapping\AbstractMetadata` - have been deprecated. Please use `getCache` and `setCache` with a PSR-6 - implementation instead. Note that even after switching to PSR-6, - `getCacheDriver` will return a cache instance that wraps the PSR-6 cache. - Note that if you use a custom implementation of doctrine/cache, the library - may not be able to provide a forward compatibility layer. The cache - implementation MUST extend the `Doctrine\Common\Cache\CacheProvider` class. diff --git a/UPGRADE-2.3.md b/UPGRADE-2.3.md deleted file mode 100644 index 55916563..00000000 --- a/UPGRADE-2.3.md +++ /dev/null @@ -1,4 +0,0 @@ -UPGRADE FROM 2.2 to 2.3 -======================= - -* Deprecated using short namespace alias syntax in favor of ::class syntax. diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 00000000..7dea60fe --- /dev/null +++ b/UPGRADE.md @@ -0,0 +1,59 @@ +Note about upgrading: Doctrine uses static and runtime mechanisms to raise +awareness about deprecated code. + +- Use of `@deprecated` docblock that is detected by IDEs (like PHPStorm) or + Static Analysis tools (like Psalm, phpstan) +- Use of our low-overhead runtime deprecation API, details: + https://github.com/doctrine/deprecations/ + +# Upgrade to 2.4 + +## Deprecated `AnnotationDriver` + +Since attributes were introduced in PHP 8.0, annotations are deprecated. Use +`ColocatedMappingDriver` instead. This will involve implementing +`isTransient()` as well as `__construct()` and `getReader()` to +retain backward compatibility. + +# Upgrade to 2.3 + +## Deprecated using short namespace alias syntax in favor of `::class` syntax. + +Before: + +```php +$objectManager->find('MyPackage:MyClass', $id); +$objectManager->createQuery('SELECT u FROM MyPackage:MyClass'); +``` + +After: + +```php +$objectManager->find(MyClass::class, $id); +$objectManager->createQuery('SELECT u FROM '. MyClass::class); +``` + +# Upgrade to 2.2 + +## Deprecated `doctrine/cache` usage for metadata caching + +The `setCacheDriver` and `getCacheDriver` methods in +`Doctrine\Persistence\Mapping\AbstractMetadata` have been deprecated. Please +use `getCache` and `setCache` with a PSR-6 implementation instead. Note that +even after switching to PSR-6, `getCacheDriver` will return a cache instance +that wraps the PSR-6 cache. Note that if you use a custom implementation of +doctrine/cache, the library may not be able to provide a forward compatibility +layer. The cache implementation MUST extend the +`Doctrine\Common\Cache\CacheProvider` class. + +# Upgrade to 1.2 + +## Deprecated `ObjectManager::merge()` and `ObjectManager::detach()` + +Please handle merge operations in your application, and use +`ObjectManager::clear()` instead. + +## Deprecated `PersistentObject` + +Please implement this functionality directly in your application if you want +ActiveRecord style functionality. diff --git a/docs/en/reference/index.rst b/docs/en/reference/index.rst index 1dc70064..e291a3ff 100644 --- a/docs/en/reference/index.rst +++ b/docs/en/reference/index.rst @@ -159,7 +159,7 @@ Mapping Driver In order to load ``ClassMetadata`` instances you can use the ``Doctrine\Persistence\Mapping\Driver\MappingDriver`` interface. This is the interface that does the core loading of mapping information from wherever they are stored. -That may be in files, annotations, yaml, xml, etc. +That may be in files, attributes, yaml, xml, etc. .. code-block:: php @@ -174,8 +174,8 @@ That may be in files, annotations, yaml, xml, etc. public function isTransient($className); } -The Doctrine Persistence project offers a few base implementations that make it easy to implement your own XML, -Annotations or YAML drivers. +The Doctrine Persistence project offers a few base implementations that +make it easy to implement your own XML, Attributes or YAML drivers. FileDriver ---------- @@ -233,45 +233,9 @@ AnnotationDriver .. note:: - This driver requires the ``doctrine/annotations`` project. You can install it with composer. + This driver requires the ``doctrine/annotations`` project and is + deprecated because of that. - .. code-block:: php - - composer require doctrine/annotations - -The AnnotationDriver reads the mapping metadata from docblock annotations. - -.. code-block:: php - - final class MyAnnotationDriver extends AnnotationDriver - { - public function loadMetadataForClass($className, ClassMetadata $metadata) - { - /** @var ClassMetadata $class */ - $reflClass = $class->getReflectionClass(); - - $classAnnotations = $this->reader->getClassAnnotations($reflClass); - - // Use the reader to read annotations from your classes to then populate the $metadata instance. - } - } - -Now you can use it like the following: - -.. code-block:: php - - use App\Model\User; - use Doctrine\Annotations\AnnotationReader; - use Doctrine\Persistence\Mapping\ClassMetadata; - - $annotationReader = new AnnotationReader(); - - $annotationDriver = new AnnotationDriver($annotationReader, '/path/to/classes/with/annotations'); - - $classMetadata = new ClassMetadata(); - - // looks for a PHP file at /path/to/classes/with/annotations/App/Model/User.php - $annotationDriver->loadMetadataForClass(User::class, $classMetadata); PHPDriver --------- diff --git a/lib/Doctrine/Persistence/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/Persistence/Mapping/Driver/AnnotationDriver.php index e14b492a..9116960b 100644 --- a/lib/Doctrine/Persistence/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/Persistence/Mapping/Driver/AnnotationDriver.php @@ -3,32 +3,19 @@ namespace Doctrine\Persistence\Mapping\Driver; use Doctrine\Common\Annotations\Reader; -use Doctrine\Persistence\Mapping\MappingException; -use FilesystemIterator; -use RecursiveDirectoryIterator; -use RecursiveIteratorIterator; -use RecursiveRegexIterator; use ReflectionClass; -use RegexIterator; -use function array_merge; -use function array_unique; -use function assert; use function get_class; -use function get_declared_classes; -use function in_array; -use function is_dir; -use function preg_match; -use function preg_quote; -use function realpath; -use function str_replace; -use function strpos; /** * The AnnotationDriver reads the mapping metadata from docblock annotations. + * + * @deprecated extend ColocatedMappingDriver directly instead. */ abstract class AnnotationDriver implements MappingDriver { + use ColocatedMappingDriver; + /** * The annotation reader. * @@ -36,35 +23,6 @@ abstract class AnnotationDriver implements MappingDriver */ protected $reader; - /** - * The paths where to look for mapping files. - * - * @var string[] - */ - protected $paths = []; - - /** - * The paths excluded from path where to look for mapping files. - * - * @var string[] - */ - protected $excludePaths = []; - - /** - * The file extension of mapping documents. - * - * @var string - */ - protected $fileExtension = '.php'; - - /** - * Cache for AnnotationDriver#getAllClassNames(). - * - * @var string[]|null - * @psalm-var list|null - */ - protected $classNames; - /** * Name of the entity annotations as keys. * @@ -82,57 +40,10 @@ abstract class AnnotationDriver implements MappingDriver public function __construct($reader, $paths = null) { $this->reader = $reader; - if (! $paths) { - return; - } $this->addPaths((array) $paths); } - /** - * Appends lookup paths to metadata driver. - * - * @param string[] $paths - * - * @return void - */ - public function addPaths(array $paths) - { - $this->paths = array_unique(array_merge($this->paths, $paths)); - } - - /** - * Retrieves the defined metadata lookup paths. - * - * @return string[] - */ - public function getPaths() - { - return $this->paths; - } - - /** - * Append exclude lookup paths to metadata driver. - * - * @param string[] $paths - * - * @return void - */ - public function addExcludePaths(array $paths) - { - $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths)); - } - - /** - * Retrieve the defined metadata lookup exclude paths. - * - * @return string[] - */ - public function getExcludePaths() - { - return $this->excludePaths; - } - /** * Retrieve the current annotation reader * @@ -144,34 +55,6 @@ public function getReader() } /** - * Gets the file extension used to look for mapping files under. - * - * @return string - */ - public function getFileExtension() - { - return $this->fileExtension; - } - - /** - * Sets 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; - } - - /** - * Returns 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 an annotation - * from the {@see AnnotationDriver::entityAnnotationClasses}. - * * {@inheritDoc} */ public function isTransient($className) @@ -186,75 +69,4 @@ public function isTransient($className) return true; } - - /** - * {@inheritDoc} - */ - public function getAllClassNames() - { - if ($this->classNames !== null) { - return $this->classNames; - } - - if (! $this->paths) { - throw MappingException::pathRequired(); - } - - $classes = []; - $includedFiles = []; - - foreach ($this->paths as $path) { - if (! is_dir($path)) { - throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); - } - - $iterator = new RegexIterator( - new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS), - RecursiveIteratorIterator::LEAVES_ONLY - ), - '/^.+' . preg_quote($this->fileExtension) . '$/i', - RecursiveRegexIterator::GET_MATCH - ); - - foreach ($iterator as $file) { - $sourceFile = $file[0]; - - if (preg_match('(^phar:)i', $sourceFile) === 0) { - $sourceFile = realpath($sourceFile); - } - - foreach ($this->excludePaths as $excludePath) { - $realExcludePath = realpath($excludePath); - assert($realExcludePath !== false); - $exclude = str_replace('\\', '/', $realExcludePath); - $current = str_replace('\\', '/', $sourceFile); - - if (strpos($current, $exclude) !== false) { - continue 2; - } - } - - 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)) { - continue; - } - - $classes[] = $className; - } - - $this->classNames = $classes; - - return $classes; - } } diff --git a/lib/Doctrine/Persistence/Mapping/Driver/ColocatedMappingDriver.php b/lib/Doctrine/Persistence/Mapping/Driver/ColocatedMappingDriver.php new file mode 100644 index 00000000..5b0e406e --- /dev/null +++ b/lib/Doctrine/Persistence/Mapping/Driver/ColocatedMappingDriver.php @@ -0,0 +1,207 @@ +|null + */ + protected $classNames; + + /** + * Appends lookup paths to metadata driver. + * + * @param string[] $paths + * + * @return void + */ + public function addPaths(array $paths) + { + $this->paths = array_unique(array_merge($this->paths, $paths)); + } + + /** + * Retrieves the defined metadata lookup paths. + * + * @return string[] + */ + public function getPaths() + { + return $this->paths; + } + + /** + * Append exclude lookup paths to metadata driver. + * + * @param string[] $paths + * + * @return void + */ + public function addExcludePaths(array $paths) + { + $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths)); + } + + /** + * Retrieve the defined metadata lookup exclude paths. + * + * @return string[] + */ + public function getExcludePaths() + { + return $this->excludePaths; + } + + /** + * Gets the file extension used to look for mapping files under. + * + * @return string + */ + public function getFileExtension() + { + return $this->fileExtension; + } + + /** + * Sets the file extension used to look for mapping files under. + * + * @return void + */ + public function setFileExtension(string $fileExtension) + { + $this->fileExtension = $fileExtension; + } + + /** + * Returns whether the class with the specified name is transient. Only non-transient + * classes, that is entities and mapped superclasses, should have their metadata loaded. + * + * @param string $className + * @psalm-param class-string $className + * + * @return bool + */ + abstract public function isTransient($className); + + /** + * Gets the names of all mapped classes known to this driver. + * + * @return string[] The names of all mapped classes known to this driver. + * @psalm-return list + */ + public function getAllClassNames() + { + if ($this->classNames !== null) { + return $this->classNames; + } + + if (! $this->paths) { + throw MappingException::pathRequiredForDriver(static::class); + } + + $classes = []; + $includedFiles = []; + + foreach ($this->paths as $path) { + if (! is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new RegexIterator( + new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS), + RecursiveIteratorIterator::LEAVES_ONLY + ), + '/^.+' . preg_quote($this->fileExtension) . '$/i', + RecursiveRegexIterator::GET_MATCH + ); + + foreach ($iterator as $file) { + $sourceFile = $file[0]; + + if (preg_match('(^phar:)i', $sourceFile) === 0) { + $sourceFile = realpath($sourceFile); + } + + foreach ($this->excludePaths as $excludePath) { + $realExcludePath = realpath($excludePath); + assert($realExcludePath !== false); + $exclude = str_replace('\\', '/', $realExcludePath); + $current = str_replace('\\', '/', $sourceFile); + + if (strpos($current, $exclude) !== false) { + continue 2; + } + } + + 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)) { + continue; + } + + $classes[] = $className; + } + + $this->classNames = $classes; + + return $classes; + } +} diff --git a/lib/Doctrine/Persistence/Mapping/Driver/StaticPHPDriver.php b/lib/Doctrine/Persistence/Mapping/Driver/StaticPHPDriver.php index 844d9e94..be4d5b80 100644 --- a/lib/Doctrine/Persistence/Mapping/Driver/StaticPHPDriver.php +++ b/lib/Doctrine/Persistence/Mapping/Driver/StaticPHPDriver.php @@ -68,7 +68,8 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) /** * {@inheritDoc} * - * @todo Same code exists in AnnotationDriver, should we re-use it somehow or not worry about it? + * @todo Same code exists in ColocatedMappingDriver, should we re-use it + * somehow or not worry about it? */ public function getAllClassNames() { @@ -77,7 +78,7 @@ public function getAllClassNames() } if (! $this->paths) { - throw MappingException::pathRequired(); + throw MappingException::pathRequiredForDriver(static::class); } $classes = []; diff --git a/lib/Doctrine/Persistence/Mapping/MappingException.php b/lib/Doctrine/Persistence/Mapping/MappingException.php index 88845596..675e3f51 100644 --- a/lib/Doctrine/Persistence/Mapping/MappingException.php +++ b/lib/Doctrine/Persistence/Mapping/MappingException.php @@ -36,6 +36,17 @@ public static function pathRequired() 'in the AnnotationDriver to retrieve all class names.'); } + /** + * @param class-string $driverClassName + */ + public static function pathRequiredForDriver(string $driverClassName): self + { + return new self(sprintf( + 'Specifying the paths to your entities is required when using %s to retrieve all class names.', + $driverClassName + )); + } + /** * @param string|null $path * diff --git a/tests/Doctrine/Tests/Persistence/Mapping/AnnotationDriverTest.php b/tests/Doctrine/Tests/Persistence/Mapping/AnnotationDriverTest.php index 3a94c4be..514266b3 100644 --- a/tests/Doctrine/Tests/Persistence/Mapping/AnnotationDriverTest.php +++ b/tests/Doctrine/Tests/Persistence/Mapping/AnnotationDriverTest.php @@ -6,32 +6,13 @@ use Doctrine\Entity; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\Mapping\Driver\AnnotationDriver; -use Doctrine\TestClass; -use Generator; -use PHPUnit\Framework\TestCase; +use Doctrine\Persistence\Mapping\Driver\MappingDriver; -class AnnotationDriverTest extends TestCase +class AnnotationDriverTest extends ColocatedMappingDriverTest { - /** - * @dataProvider pathProvider - */ - public function testGetAllClassNames(string $path): void - { - $reader = new AnnotationReader(); - $driver = new SimpleAnnotationDriver($reader, $path); - - $classes = $driver->getAllClassNames(); - - self::assertSame([TestClass::class], $classes); - } - - /** - * @return Generator - */ - public function pathProvider(): Generator + protected function createDriver(string $path): MappingDriver { - yield 'straigthforward path' => [__DIR__ . '/_files/annotation']; - yield 'winding path' => [__DIR__ . '/../Mapping/_files/annotation']; + return new SimpleAnnotationDriver(new AnnotationReader(), $path); } } diff --git a/tests/Doctrine/Tests/Persistence/Mapping/ColocatedMappingDriverTest.php b/tests/Doctrine/Tests/Persistence/Mapping/ColocatedMappingDriverTest.php new file mode 100644 index 00000000..f1e5494c --- /dev/null +++ b/tests/Doctrine/Tests/Persistence/Mapping/ColocatedMappingDriverTest.php @@ -0,0 +1,67 @@ +createDriver($path); + + $classes = $driver->getAllClassNames(); + + self::assertSame([TestClass::class], $classes); + } + + /** + * @return Generator + */ + public function pathProvider(): Generator + { + yield 'straigthforward path' => [__DIR__ . '/_files/colocated']; + yield 'winding path' => [__DIR__ . '/../Mapping/_files/colocated']; + } + + protected function createDriver(string $path): MappingDriver + { + return new MyDriver($path); + } +} + +final class MyDriver implements MappingDriver +{ + use ColocatedMappingDriver; + + /** + * @param string ...$paths One or multiple paths where mapping classes can be found. + */ + public function __construct(string ...$paths) + { + $this->addPaths($paths); + } + + /** + * {@inheritDoc} + */ + public function loadMetadataForClass($className, ClassMetadata $metadata): void + { + } + + /** + * {@inheritDoc} + */ + public function isTransient($className): bool + { + return $className !== TestClass::class; + } +} diff --git a/tests/Doctrine/Tests/Persistence/Mapping/_files/annotation/TestClass.php b/tests/Doctrine/Tests/Persistence/Mapping/_files/colocated/TestClass.php similarity index 100% rename from tests/Doctrine/Tests/Persistence/Mapping/_files/annotation/TestClass.php rename to tests/Doctrine/Tests/Persistence/Mapping/_files/colocated/TestClass.php