CONTRIBUTING.md 0000644 00000000540 14431224400 0006766 0 ustar 00 # Circular dependency
This package has a development dependency on `doctrine/common`, which has a
regular dependency on this package (`^2.0` at the time of writing).
To be able to use Composer, one has to let it understand that this is version 2
(even when developing on 3.0.x), as follows:
```shell
COMPOSER_ROOT_VERSION=2.0 composer update -v
```
LICENSE 0000644 00000002051 14431224400 0005541 0 ustar 00 Copyright (c) 2006-2015 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
README.md 0000644 00000001166 14431224400 0006021 0 ustar 00 # Doctrine Persistence
[![Build Status](https://travis-ci.org/doctrine/persistence.svg)](https://travis-ci.org/doctrine/persistence)
[![Code Coverage](https://codecov.io/gh/doctrine/persistence/branch/2.1.x/graph/badge.svg)](https://codecov.io/gh/doctrine/persistence/branch/2.1.x)
The Doctrine Persistence project is a library that provides common abstractions for object mapper persistence.
## More resources:
* [Website](https://www.doctrine-project.org/)
* [Documentation](https://www.doctrine-project.org/projects/doctrine-persistence/en/latest/index.html)
* [Downloads](https://github.com/doctrine/persistence/releases)
UPGRADE.md 0000644 00000011755 14431224400 0006160 0 ustar 00 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 3.1
## Added method `Proxy::__setInitialized()`
Classes implementing `Doctrine\Persistence\Proxy` should implement the new
method. This method will be added to the interface in 4.0.
## Deprecated `RuntimePublicReflectionProperty`
Use `RuntimeReflectionProperty` instead.
# Upgrade to 3.0
## Removed `OnClearEventArgs::clearsAllEntities()` and `OnClearEventArgs::getEntityClass()`
These methods only make sense when partially clearing the object manager, which
is no longer possible.
The second argument of the constructor of `OnClearEventArgs` is removed as well.
## BC Break: removed `ObjectManagerAware`
Implement active record style functionality directly in your application, by
using a `postLoad` event.
## BC Break: removed `AnnotationDriver`
Use `ColocatedMappingDriver` instead.
## BC Break: Removed `MappingException::pathRequired()`
Use `MappingException::pathRequiredForDriver()` instead.
## BC Break: removed `LifecycleEventArgs::getEntity()`
Use `LifecycleEventArgs::getObject()` instead.
## BC Break: removed support for short namespace aliases
- `AbstractClassMetadataFactory::getFqcnFromAlias()` is removed.
- `ClassMetadataFactory` methods now require their `$className` argument to be an
actual FQCN.
## BC Break: removed `ObjectManager::merge()`
`ObjectManagerDecorator::merge()` is removed without replacement.
## BC Break: removed support for `doctrine/cache`
Removed support for using doctrine/cache for metadata caching. The
`setCacheDriver` and `getCacheDriver` methods have been removed from
`Doctrine\Persistence\Mapping\AbstractMetadata`. Please use `getCache` and
`setCache` with a PSR-6 implementation instead.
## BC Break: changed signatures
`$objectName` has been dropped from the signature of `ObjectManager::clear()`.
```diff
- public function clear($objectName = null)
+ public function clear(): void
```
Also, native parameter type declarations have been added on all public APIs.
Native return type declarations have not been added so that it is possible to
implement types compatible with both 2.x and 3.x.
## BC Break: Removed `PersistentObject`
Please implement this functionality directly in your application if you want
ActiveRecord style functionality.
# Upgrade to 2.5
## Deprecated `OnClearEventArgs::clearsAllEntities()` and `OnClearEventArgs::getEntityClass()`
These methods only make sense when partially clearing the object manager, which
is deprecated.
Passing a second argument to the constructor of `OnClearEventArgs` is
deprecated as well.
## Deprecated `ObjectManagerAware`
Along with deprecating `PersistentObject`, deprecating `ObjectManagerAware`
means deprecating support for active record, which already came with a word of
warning. Please implement this directly in your application with a `postLoad`
event if you need active record style functionality.
## Deprecated `MappingException::pathRequired()`
`MappingException::pathRequiredForDriver()` should be used instead.
# Upgrade to 2.4
## Deprecated `AnnotationDriver`
Since attributes were introduced in PHP 8.0, annotations are deprecated.
`AnnotationDriver` is an abstract class that is used when implementing concrete
annotation drivers in dependent packages. It is deprecated in favor of using
`ColocatedMappingDriver` to implement both annotation and attribute based
drivers. 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.
composer.json 0000644 00000003717 14431224400 0007270 0 ustar 00 {
"name": "doctrine/persistence",
"type": "library",
"description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.",
"keywords": [
"persistence",
"object",
"mapper",
"orm",
"odm"
],
"homepage": "https://www.doctrine-project.org/projects/persistence.html",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"},
{"name": "Marco Pivetta", "email": "ocramius@gmail.com"}
],
"require": {
"php": "^7.2 || ^8.0",
"doctrine/event-manager": "^1 || ^2",
"psr/cache": "^1.0 || ^2.0 || ^3.0"
},
"require-dev": {
"composer/package-versions-deprecated": "^1.11",
"phpstan/phpstan": "1.9.4",
"phpstan/phpstan-phpunit": "^1",
"phpstan/phpstan-strict-rules": "^1.1",
"doctrine/coding-standard": "^11",
"doctrine/common": "^3.0",
"phpunit/phpunit": "^8.5 || ^9.5",
"symfony/cache": "^4.4 || ^5.4 || ^6.0",
"vimeo/psalm": "4.30.0 || 5.3.0"
},
"conflict": {
"doctrine/common": "<2.10"
},
"autoload": {
"psr-4": {
"Doctrine\\Persistence\\": "src/Persistence"
}
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Tests\\": "tests",
"Doctrine\\Tests_PHP74\\": "tests_php74",
"Doctrine\\Tests_PHP81\\": "tests_php81"
}
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true,
"composer/package-versions-deprecated": true
}
}
}
psalm-baseline.xml 0000644 00000000625 14431224400 0010157 0 ustar 00
getName$parentClass === false
psalm.phpstub 0000644 00000000623 14431224400 0007262 0 ustar 00 return
*/
function ltrim(string $string, string $characters = " \t\n\r\0\x0B") : string {}
src/Persistence/AbstractManagerRegistry.php 0000644 00000014062 14431224400 0015114 0 ustar 00 */
private $connections;
/** @var array */
private $managers;
/** @var string */
private $defaultConnection;
/** @var string */
private $defaultManager;
/**
* @var string
* @psalm-var class-string
*/
private $proxyInterfaceName;
/**
* @param array $connections
* @param array $managers
* @psalm-param class-string $proxyInterfaceName
*/
public function __construct(
string $name,
array $connections,
array $managers,
string $defaultConnection,
string $defaultManager,
string $proxyInterfaceName
) {
$this->name = $name;
$this->connections = $connections;
$this->managers = $managers;
$this->defaultConnection = $defaultConnection;
$this->defaultManager = $defaultManager;
$this->proxyInterfaceName = $proxyInterfaceName;
}
/**
* Fetches/creates the given services.
*
* A service in this context is connection or a manager instance.
*
* @param string $name The name of the service.
*
* @return ObjectManager The instance of the given service.
*/
abstract protected function getService(string $name);
/**
* Resets the given services.
*
* A service in this context is connection or a manager instance.
*
* @param string $name The name of the service.
*
* @return void
*/
abstract protected function resetService(string $name);
/**
* Gets the name of the registry.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function getConnection(?string $name = null)
{
if ($name === null) {
$name = $this->defaultConnection;
}
if (! isset($this->connections[$name])) {
throw new InvalidArgumentException(
sprintf('Doctrine %s Connection named "%s" does not exist.', $this->name, $name)
);
}
return $this->getService($this->connections[$name]);
}
/**
* {@inheritdoc}
*/
public function getConnectionNames()
{
return $this->connections;
}
/**
* {@inheritdoc}
*/
public function getConnections()
{
$connections = [];
foreach ($this->connections as $name => $id) {
$connections[$name] = $this->getService($id);
}
return $connections;
}
/**
* {@inheritdoc}
*/
public function getDefaultConnectionName()
{
return $this->defaultConnection;
}
/**
* {@inheritdoc}
*/
public function getDefaultManagerName()
{
return $this->defaultManager;
}
/**
* {@inheritdoc}
*
* @throws InvalidArgumentException
*/
public function getManager(?string $name = null)
{
if ($name === null) {
$name = $this->defaultManager;
}
if (! isset($this->managers[$name])) {
throw new InvalidArgumentException(
sprintf('Doctrine %s Manager named "%s" does not exist.', $this->name, $name)
);
}
return $this->getService($this->managers[$name]);
}
/**
* {@inheritDoc}
*/
public function getManagerForClass(string $class)
{
$proxyClass = new ReflectionClass($class);
if ($proxyClass->isAnonymous()) {
return null;
}
if ($proxyClass->implementsInterface($this->proxyInterfaceName)) {
$parentClass = $proxyClass->getParentClass();
if ($parentClass === false) {
return null;
}
$class = $parentClass->getName();
}
foreach ($this->managers as $id) {
$manager = $this->getService($id);
if (! $manager->getMetadataFactory()->isTransient($class)) {
return $manager;
}
}
return null;
}
/**
* {@inheritdoc}
*/
public function getManagerNames()
{
return $this->managers;
}
/**
* {@inheritdoc}
*/
public function getManagers()
{
$managers = [];
foreach ($this->managers as $name => $id) {
$manager = $this->getService($id);
$managers[$name] = $manager;
}
return $managers;
}
/**
* {@inheritdoc}
*/
public function getRepository(
string $persistentObject,
?string $persistentManagerName = null
) {
return $this
->selectManager($persistentObject, $persistentManagerName)
->getRepository($persistentObject);
}
/**
* {@inheritdoc}
*/
public function resetManager(?string $name = null)
{
if ($name === null) {
$name = $this->defaultManager;
}
if (! isset($this->managers[$name])) {
throw new InvalidArgumentException(sprintf('Doctrine %s Manager named "%s" does not exist.', $this->name, $name));
}
// force the creation of a new document manager
// if the current one is closed
$this->resetService($this->managers[$name]);
return $this->getManager($name);
}
/** @psalm-param class-string $persistentObject */
private function selectManager(
string $persistentObject,
?string $persistentManagerName = null
): ObjectManager {
if ($persistentManagerName !== null) {
return $this->getManager($persistentManagerName);
}
return $this->getManagerForClass($persistentObject) ?? $this->getManager();
}
}
src/Persistence/ConnectionRegistry.php 0000644 00000001663 14431224400 0014160 0 ustar 00 An array of Connection instances.
*/
public function getConnections();
/**
* Gets all connection names.
*
* @return array An array of connection names.
*/
public function getConnectionNames();
}
src/Persistence/Event/LifecycleEventArgs.php 0000644 00000002136 14431224400 0015123 0 ustar 00 object = $object;
$this->objectManager = $objectManager;
}
/**
* Retrieves the associated object.
*
* @return object
*/
public function getObject()
{
return $this->object;
}
/**
* Retrieves the associated ObjectManager.
*
* @return ObjectManager
* @psalm-return TObjectManager
*/
public function getObjectManager()
{
return $this->objectManager;
}
}
src/Persistence/Event/LoadClassMetadataEventArgs.php 0000644 00000002535 14431224400 0016535 0 ustar 00
* @template-covariant TObjectManager of ObjectManager
*/
class LoadClassMetadataEventArgs extends EventArgs
{
/**
* @var ClassMetadata
* @psalm-var TClassMetadata
*/
private $classMetadata;
/**
* @var ObjectManager
* @psalm-var TObjectManager
*/
private $objectManager;
/**
* @psalm-param TClassMetadata $classMetadata
* @psalm-param TObjectManager $objectManager
*/
public function __construct(ClassMetadata $classMetadata, ObjectManager $objectManager)
{
$this->classMetadata = $classMetadata;
$this->objectManager = $objectManager;
}
/**
* Retrieves the associated ClassMetadata.
*
* @return ClassMetadata
* @psalm-return TClassMetadata
*/
public function getClassMetadata()
{
return $this->classMetadata;
}
/**
* Retrieves the associated ObjectManager.
*
* @return TObjectManager
*/
public function getObjectManager()
{
return $this->objectManager;
}
}
src/Persistence/Event/ManagerEventArgs.php 0000644 00000001452 14431224400 0014576 0 ustar 00 objectManager = $objectManager;
}
/**
* Retrieves the associated ObjectManager.
*
* @return ObjectManager
* @psalm-return TObjectManager
*/
public function getObjectManager()
{
return $this->objectManager;
}
}
src/Persistence/Event/OnClearEventArgs.php 0000644 00000001564 14431224400 0014553 0 ustar 00 objectManager = $objectManager;
}
/**
* Retrieves the associated ObjectManager.
*
* @return ObjectManager
* @psalm-return TObjectManager
*/
public function getObjectManager()
{
return $this->objectManager;
}
}
src/Persistence/Event/PreUpdateEventArgs.php 0000644 00000004754 14431224400 0015125 0 ustar 00
*/
class PreUpdateEventArgs extends LifecycleEventArgs
{
/** @var array> */
private $entityChangeSet;
/**
* @param array> $changeSet
* @psalm-param TObjectManager $objectManager
*/
public function __construct(object $entity, ObjectManager $objectManager, array &$changeSet)
{
parent::__construct($entity, $objectManager);
$this->entityChangeSet = &$changeSet;
}
/**
* Retrieves the entity changeset.
*
* @return array>
*/
public function getEntityChangeSet()
{
return $this->entityChangeSet;
}
/**
* Checks if field has a changeset.
*
* @return bool
*/
public function hasChangedField(string $field)
{
return isset($this->entityChangeSet[$field]);
}
/**
* Gets the old value of the changeset of the changed field.
*
* @return mixed
*/
public function getOldValue(string $field)
{
$this->assertValidField($field);
return $this->entityChangeSet[$field][0];
}
/**
* Gets the new value of the changeset of the changed field.
*
* @return mixed
*/
public function getNewValue(string $field)
{
$this->assertValidField($field);
return $this->entityChangeSet[$field][1];
}
/**
* Sets the new value of this field.
*
* @param mixed $value
*
* @return void
*/
public function setNewValue(string $field, $value)
{
$this->assertValidField($field);
$this->entityChangeSet[$field][1] = $value;
}
/**
* Asserts the field exists in changeset.
*
* @return void
*
* @throws InvalidArgumentException
*/
private function assertValidField(string $field)
{
if (! isset($this->entityChangeSet[$field])) {
throw new InvalidArgumentException(sprintf(
'Field "%s" is not a valid field of the entity "%s" in PreUpdateEventArgs.',
$field,
get_class($this->getObject())
));
}
}
}
src/Persistence/ManagerRegistry.php 0000644 00000005334 14431224400 0013432 0 ustar 00 An array of ObjectManager instances
*/
public function getManagers();
/**
* Resets a named object manager.
*
* This method is useful when an object manager has been closed
* because of a rollbacked transaction AND when you think that
* it makes sense to get a new one to replace the closed one.
*
* Be warned that you will get a brand new object manager as
* the existing one is not useable anymore. This means that any
* other object with a dependency on this object manager will
* hold an obsolete reference. You can inject the registry instead
* to avoid this problem.
*
* @param string|null $name The object manager name (null for the default one).
*
* @return ObjectManager
*/
public function resetManager(?string $name = null);
/**
* Gets all object manager names and associated service IDs. A service ID
* is a string that allows to obtain an object manager, typically from a
* PSR-11 container.
*
* @return array An array with object manager names as keys,
* and service IDs as values.
*/
public function getManagerNames();
/**
* Gets the ObjectRepository for a persistent object.
*
* @param string $persistentObject The name of the persistent object.
* @param string|null $persistentManagerName The object manager name (null for the default one).
* @psalm-param class-string $persistentObject
*
* @return ObjectRepository
* @psalm-return ObjectRepository
*
* @template T of object
*/
public function getRepository(
string $persistentObject,
?string $persistentManagerName = null
);
/**
* Gets the object manager associated with a given class.
*
* @param class-string $class A persistent object class name.
*
* @return ObjectManager|null
*/
public function getManagerForClass(string $class);
}
src/Persistence/Mapping/AbstractClassMetadataFactory.php 0000644 00000034631 14431224400 0017446 0 ustar 00
*/
abstract class AbstractClassMetadataFactory implements ClassMetadataFactory
{
/**
* Salt used by specific Object Manager implementation.
*
* @var string
*/
protected $cacheSalt = '__CLASSMETADATA__';
/** @var CacheItemPoolInterface|null */
private $cache;
/**
* @var array
* @psalm-var CMTemplate[]
*/
private $loadedMetadata = [];
/** @var bool */
protected $initialized = false;
/** @var ReflectionService|null */
private $reflectionService = null;
/** @var ProxyClassNameResolver|null */
private $proxyClassNameResolver = null;
public function setCache(CacheItemPoolInterface $cache): void
{
$this->cache = $cache;
}
final protected function getCache(): ?CacheItemPoolInterface
{
return $this->cache;
}
/**
* Returns an array of all the loaded metadata currently in memory.
*
* @return ClassMetadata[]
* @psalm-return CMTemplate[]
*/
public function getLoadedMetadata()
{
return $this->loadedMetadata;
}
/**
* {@inheritDoc}
*/
public function getAllMetadata()
{
if (! $this->initialized) {
$this->initialize();
}
$driver = $this->getDriver();
$metadata = [];
foreach ($driver->getAllClassNames() as $className) {
$metadata[] = $this->getMetadataFor($className);
}
return $metadata;
}
public function setProxyClassNameResolver(ProxyClassNameResolver $resolver): void
{
$this->proxyClassNameResolver = $resolver;
}
/**
* Lazy initialization of this stuff, especially the metadata driver,
* since these are not needed at all when a metadata cache is active.
*
* @return void
*/
abstract protected function initialize();
/**
* Returns the mapping driver implementation.
*
* @return MappingDriver
*/
abstract protected function getDriver();
/**
* Wakes up reflection after ClassMetadata gets unserialized from cache.
*
* @psalm-param CMTemplate $class
*
* @return void
*/
abstract protected function wakeupReflection(
ClassMetadata $class,
ReflectionService $reflService
);
/**
* Initializes Reflection after ClassMetadata was constructed.
*
* @psalm-param CMTemplate $class
*
* @return void
*/
abstract protected function initializeReflection(
ClassMetadata $class,
ReflectionService $reflService
);
/**
* Checks whether the class metadata is an entity.
*
* This method should return false for mapped superclasses or embedded classes.
*
* @psalm-param CMTemplate $class
*
* @return bool
*/
abstract protected function isEntity(ClassMetadata $class);
/**
* Removes the prepended backslash of a class string to conform with how php outputs class names
*
* @psalm-param class-string $className
*
* @psalm-return class-string
*/
private function normalizeClassName(string $className): string
{
return ltrim($className, '\\');
}
/**
* {@inheritDoc}
*
* @throws ReflectionException
* @throws MappingException
*/
public function getMetadataFor(string $className)
{
$className = $this->normalizeClassName($className);
if (isset($this->loadedMetadata[$className])) {
return $this->loadedMetadata[$className];
}
if (class_exists($className, false) && (new ReflectionClass($className))->isAnonymous()) {
throw MappingException::classIsAnonymous($className);
}
if (! class_exists($className, false) && strpos($className, ':') !== false) {
throw MappingException::nonExistingClass($className);
}
$realClassName = $this->getRealClass($className);
if (isset($this->loadedMetadata[$realClassName])) {
// We do not have the alias name in the map, include it
return $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
}
try {
if ($this->cache !== null) {
$cached = $this->cache->getItem($this->getCacheKey($realClassName))->get();
if ($cached instanceof ClassMetadata) {
/** @psalm-var CMTemplate $cached */
$this->loadedMetadata[$realClassName] = $cached;
$this->wakeupReflection($cached, $this->getReflectionService());
} else {
$loadedMetadata = $this->loadMetadata($realClassName);
$classNames = array_combine(
array_map([$this, 'getCacheKey'], $loadedMetadata),
$loadedMetadata
);
foreach ($this->cache->getItems(array_keys($classNames)) as $item) {
if (! isset($classNames[$item->getKey()])) {
continue;
}
$item->set($this->loadedMetadata[$classNames[$item->getKey()]]);
$this->cache->saveDeferred($item);
}
$this->cache->commit();
}
} else {
$this->loadMetadata($realClassName);
}
} catch (MappingException $loadingException) {
$fallbackMetadataResponse = $this->onNotFoundMetadata($realClassName);
if ($fallbackMetadataResponse === null) {
throw $loadingException;
}
$this->loadedMetadata[$realClassName] = $fallbackMetadataResponse;
}
if ($className !== $realClassName) {
// We do not have the alias name in the map, include it
$this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
}
return $this->loadedMetadata[$className];
}
/**
* {@inheritDoc}
*/
public function hasMetadataFor(string $className)
{
$className = $this->normalizeClassName($className);
return isset($this->loadedMetadata[$className]);
}
/**
* Sets the metadata descriptor for a specific class.
*
* NOTE: This is only useful in very special cases, like when generating proxy classes.
*
* @psalm-param class-string $className
* @psalm-param CMTemplate $class
*
* @return void
*/
public function setMetadataFor(string $className, ClassMetadata $class)
{
$this->loadedMetadata[$this->normalizeClassName($className)] = $class;
}
/**
* Gets an array of parent classes for the given entity class.
*
* @psalm-param class-string $name
*
* @return string[]
* @psalm-return list
*/
protected function getParentClasses(string $name)
{
// Collect parent classes, ignoring transient (not-mapped) classes.
$parentClasses = [];
foreach (array_reverse($this->getReflectionService()->getParentClasses($name)) as $parentClass) {
if ($this->getDriver()->isTransient($parentClass)) {
continue;
}
$parentClasses[] = $parentClass;
}
return $parentClasses;
}
/**
* Loads the metadata of the class in question and all it's ancestors whose metadata
* is still not loaded.
*
* Important: The class $name does not necessarily exist at this point here.
* Scenarios in a code-generation setup might have access to XML/YAML
* Mapping files without the actual PHP code existing here. That is why the
* {@see \Doctrine\Persistence\Mapping\ReflectionService} interface
* should be used for reflection.
*
* @param string $name The name of the class for which the metadata should get loaded.
* @psalm-param class-string $name
*
* @return array
* @psalm-return list
*/
protected function loadMetadata(string $name)
{
if (! $this->initialized) {
$this->initialize();
}
$loaded = [];
$parentClasses = $this->getParentClasses($name);
$parentClasses[] = $name;
// Move down the hierarchy of parent classes, starting from the topmost class
$parent = null;
$rootEntityFound = false;
$visited = [];
$reflService = $this->getReflectionService();
foreach ($parentClasses as $className) {
if (isset($this->loadedMetadata[$className])) {
$parent = $this->loadedMetadata[$className];
if ($this->isEntity($parent)) {
$rootEntityFound = true;
array_unshift($visited, $className);
}
continue;
}
$class = $this->newClassMetadataInstance($className);
$this->initializeReflection($class, $reflService);
$this->doLoadMetadata($class, $parent, $rootEntityFound, $visited);
$this->loadedMetadata[$className] = $class;
$parent = $class;
if ($this->isEntity($class)) {
$rootEntityFound = true;
array_unshift($visited, $className);
}
$this->wakeupReflection($class, $reflService);
$loaded[] = $className;
}
return $loaded;
}
/**
* Provides a fallback hook for loading metadata when loading failed due to reflection/mapping exceptions
*
* Override this method to implement a fallback strategy for failed metadata loading
*
* @return ClassMetadata|null
* @psalm-return CMTemplate|null
*/
protected function onNotFoundMetadata(string $className)
{
return null;
}
/**
* Actually loads the metadata from the underlying metadata.
*
* @param bool $rootEntityFound True when there is another entity (non-mapped superclass) class above the current class in the PHP class hierarchy.
* @param list $nonSuperclassParents All parent class names that are not marked as mapped superclasses, with the direct parent class being the first and the root entity class the last element.
* @psalm-param CMTemplate $class
* @psalm-param CMTemplate|null $parent
*
* @return void
*/
abstract protected function doLoadMetadata(
ClassMetadata $class,
?ClassMetadata $parent,
bool $rootEntityFound,
array $nonSuperclassParents
);
/**
* Creates a new ClassMetadata instance for the given class name.
*
* @psalm-param class-string $className
*
* @return ClassMetadata
* @psalm-return CMTemplate
*
* @template T of object
*/
abstract protected function newClassMetadataInstance(string $className);
/**
* {@inheritDoc}
*/
public function isTransient(string $className)
{
if (! $this->initialized) {
$this->initialize();
}
if (class_exists($className, false) && (new ReflectionClass($className))->isAnonymous()) {
return false;
}
if (! class_exists($className, false) && strpos($className, ':') !== false) {
throw MappingException::nonExistingClass($className);
}
/** @psalm-var class-string $className */
return $this->getDriver()->isTransient($className);
}
/**
* Sets the reflectionService.
*
* @return void
*/
public function setReflectionService(ReflectionService $reflectionService)
{
$this->reflectionService = $reflectionService;
}
/**
* Gets the reflection service associated with this metadata factory.
*
* @return ReflectionService
*/
public function getReflectionService()
{
if ($this->reflectionService === null) {
$this->reflectionService = new RuntimeReflectionService();
}
return $this->reflectionService;
}
protected function getCacheKey(string $realClassName): string
{
return str_replace('\\', '__', $realClassName) . $this->cacheSalt;
}
/**
* Gets the real class name of a class name that could be a proxy.
*
* @psalm-param class-string>|class-string $class
*
* @psalm-return class-string
*
* @template T of object
*/
private function getRealClass(string $class): string
{
if ($this->proxyClassNameResolver === null) {
$this->createDefaultProxyClassNameResolver();
}
assert($this->proxyClassNameResolver !== null);
return $this->proxyClassNameResolver->resolveClassName($class);
}
private function createDefaultProxyClassNameResolver(): void
{
$this->proxyClassNameResolver = new class implements ProxyClassNameResolver {
/**
* @psalm-param class-string>|class-string $className
*
* @psalm-return class-string
*
* @template T of object
*/
public function resolveClassName(string $className): string
{
$pos = strrpos($className, '\\' . Proxy::MARKER . '\\');
if ($pos === false) {
/** @psalm-var class-string */
return $className;
}
/** @psalm-var class-string */
return substr($className, $pos + Proxy::MARKER_LENGTH + 2);
}
};
}
}
src/Persistence/Mapping/ClassMetadata.php 0000644 00000007065 14431224400 0014433 0 ustar 00
*/
public function getName();
/**
* Gets the mapped identifier field name.
*
* The returned structure is an array of the identifier field names.
*
* @return array
* @psalm-return list
*/
public function getIdentifier();
/**
* Gets the ReflectionClass instance for this mapped class.
*
* @return ReflectionClass
*/
public function getReflectionClass();
/**
* Checks if the given field name is a mapped identifier for this class.
*
* @return bool
*/
public function isIdentifier(string $fieldName);
/**
* Checks if the given field is a mapped property for this class.
*
* @return bool
*/
public function hasField(string $fieldName);
/**
* Checks if the given field is a mapped association for this class.
*
* @return bool
*/
public function hasAssociation(string $fieldName);
/**
* Checks if the given field is a mapped single valued association for this class.
*
* @return bool
*/
public function isSingleValuedAssociation(string $fieldName);
/**
* Checks if the given field is a mapped collection valued association for this class.
*
* @return bool
*/
public function isCollectionValuedAssociation(string $fieldName);
/**
* A numerically indexed list of field names of this persistent class.
*
* This array includes identifier fields if present on this class.
*
* @return array
*/
public function getFieldNames();
/**
* Returns an array of identifier field names numerically indexed.
*
* @return array
*/
public function getIdentifierFieldNames();
/**
* Returns a numerically indexed list of association names of this persistent class.
*
* This array includes identifier associations if present on this class.
*
* @return array
*/
public function getAssociationNames();
/**
* Returns a type name of this field.
*
* This type names can be implementation specific but should at least include the php types:
* integer, string, boolean, float/double, datetime.
*
* @return string|null
*/
public function getTypeOfField(string $fieldName);
/**
* Returns the target class name of the given association.
*
* @return string|null
* @psalm-return class-string|null
*/
public function getAssociationTargetClass(string $assocName);
/**
* Checks if the association is the inverse side of a bidirectional association.
*
* @return bool
*/
public function isAssociationInverseSide(string $assocName);
/**
* Returns the target field of the owning side of the association.
*
* @return string
*/
public function getAssociationMappedByTargetField(string $assocName);
/**
* Returns the identifier of this object as an array with field name as key.
*
* Has to return an empty array if no identifier isset.
*
* @return array
*/
public function getIdentifierValues(object $object);
}
src/Persistence/Mapping/ClassMetadataFactory.php 0000644 00000003160 14431224400 0015753 0 ustar 00
*/
public function getAllMetadata();
/**
* Gets the class metadata descriptor for a class.
*
* @param class-string $className The name of the class.
*
* @return ClassMetadata
* @psalm-return T
*/
public function getMetadataFor(string $className);
/**
* Checks whether the factory has the metadata for a class loaded already.
*
* @param class-string $className
*
* @return bool TRUE if the metadata of the class in question is already loaded, FALSE otherwise.
*/
public function hasMetadataFor(string $className);
/**
* Sets the metadata descriptor for a specific class.
*
* @param class-string $className
* @psalm-param T $class
*
* @return void
*/
public function setMetadataFor(string $className, ClassMetadata $class);
/**
* Returns whether the class with the specified name should have its metadata loaded.
* This is only the case if it is either mapped directly or as a MappedSuperclass.
*
* @psalm-param class-string $className
*
* @return bool
*/
public function isTransient(string $className);
}
src/Persistence/Mapping/Driver/ColocatedMappingDriver.php 0000644 00000012362 14431224400 0017541 0 ustar 00
*/
protected $paths = [];
/**
* The paths excluded from path where to look for mapping files.
*
* @var array
*/
protected $excludePaths = [];
/**
* The file extension of mapping documents.
*
* @var string
*/
protected $fileExtension = '.php';
/**
* Cache for getAllClassNames().
*
* @var array|null
* @psalm-var list|null
*/
protected $classNames;
/**
* Appends lookup paths to metadata driver.
*
* @param array $paths
*
* @return void
*/
public function addPaths(array $paths)
{
$this->paths = array_unique(array_merge($this->paths, $paths));
}
/**
* Retrieves the defined metadata lookup paths.
*
* @return array
*/
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 array
*/
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;
}
/**
* {@inheritDoc}
*
* 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.
*
* @psalm-param class-string $className
*
* @return bool
*/
abstract public function isTransient(string $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, true) || $this->isTransient($className)) {
continue;
}
$classes[] = $className;
}
$this->classNames = $classes;
return $classes;
}
}
src/Persistence/Mapping/Driver/DefaultFileLocator.php 0000644 00000011045 14431224400 0016661 0 ustar 00
*/
protected $paths = [];
/**
* The file extension of mapping documents.
*
* @var string|null
*/
protected $fileExtension;
/**
* Initializes a new FileDriver that looks in the given path(s) for mapping
* documents and operates in the specified operating mode.
*
* @param string|array $paths One or multiple paths where mapping documents
* can be found.
* @param string|null $fileExtension The file extension of mapping documents,
* usually prefixed with a dot.
*/
public function __construct($paths, ?string $fileExtension = null)
{
$this->addPaths((array) $paths);
$this->fileExtension = $fileExtension;
}
/**
* Appends lookup paths to metadata driver.
*
* @param array $paths
*
* @return void
*/
public function addPaths(array $paths)
{
$this->paths = array_unique(array_merge($this->paths, $paths));
}
/**
* Retrieves the defined metadata lookup paths.
*
* @return array
*/
public function getPaths()
{
return $this->paths;
}
/**
* Gets the file extension used to look for mapping files under.
*
* @return string|null
*/
public function getFileExtension()
{
return $this->fileExtension;
}
/**
* Sets the file extension used to look for mapping files under.
*
* @param string|null $fileExtension The file extension to set.
*
* @return void
*/
public function setFileExtension(?string $fileExtension)
{
$this->fileExtension = $fileExtension;
}
/**
* {@inheritDoc}
*/
public function findMappingFile(string $className)
{
$fileName = str_replace('\\', '.', $className) . $this->fileExtension;
// Check whether file exists
foreach ($this->paths as $path) {
if (is_file($path . DIRECTORY_SEPARATOR . $fileName)) {
return $path . DIRECTORY_SEPARATOR . $fileName;
}
}
throw MappingException::mappingFileNotFound($className, $fileName);
}
/**
* {@inheritDoc}
*/
public function getAllClassNames(string $globalBasename)
{
if ($this->paths === []) {
return [];
}
$classes = [];
foreach ($this->paths as $path) {
if (! is_dir($path)) {
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($iterator as $file) {
$fileName = $file->getBasename($this->fileExtension);
if ($fileName === $file->getBasename() || $fileName === $globalBasename) {
continue;
}
// NOTE: All files found here means classes are not transient!
assert(is_string($fileName));
/** @psalm-var class-string */
$class = str_replace('.', '\\', $fileName);
$classes[] = $class;
}
}
return $classes;
}
/**
* {@inheritDoc}
*/
public function fileExists(string $className)
{
$fileName = str_replace('\\', '.', $className) . $this->fileExtension;
// Check whether file exists
foreach ($this->paths as $path) {
if (is_file($path . DIRECTORY_SEPARATOR . $fileName)) {
return true;
}
}
return false;
}
}
src/Persistence/Mapping/Driver/FileDriver.php 0000644 00000013400 14431224400 0015201 0 ustar 00 >|null
*/
protected $classCache;
/** @var string */
protected $globalBasename = '';
/**
* Initializes a new FileDriver that looks in the given path(s) for mapping
* documents and operates in the specified operating mode.
*
* @param string|array|FileLocator $locator A FileLocator or one/multiple paths
* where mapping documents can be found.
*/
public function __construct($locator, ?string $fileExtension = null)
{
if ($locator instanceof FileLocator) {
$this->locator = $locator;
} else {
$this->locator = new DefaultFileLocator((array) $locator, $fileExtension);
}
}
/**
* Sets the global basename.
*
* @return void
*/
public function setGlobalBasename(string $file)
{
$this->globalBasename = $file;
}
/**
* Retrieves the global basename.
*
* @return string|null
*/
public function getGlobalBasename()
{
return $this->globalBasename;
}
/**
* Gets the element of schema meta data for the class from the mapping file.
* This will lazily load the mapping file if it is not loaded yet.
*
* @psalm-param class-string $className
*
* @return ClassMetadata The element of schema meta data.
* @psalm-return ClassMetadata