LICENSE000064400000002051151742726200005555 0ustar00Copyright (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.md000064400000001547151742726200006040 0ustar00# Doctrine Persistence [![GitHub Actions][GA 3.4 image]][GA 3.4] [![Code Coverage][Coverage 3.4 image]][CodeCov 3.4] 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) [Coverage 3.4 image]: https://codecov.io/gh/doctrine/persistence/branch/3.4.x/graph/badge.svg [CodeCov 3.4]: https://codecov.io/gh/doctrine/persistence/branch/3.4.x [GA 3.4 image]: https://github.com/doctrine/persistence/actions/workflows/continuous-integration.yml/badge.svg?branch=3.4.x [GA 3.4]: https://github.com/doctrine/persistence/actions/workflows/continuous-integration.yml?branch=3.4.x UPGRADE.md000064400000014416151742726200006171 0ustar00Note 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 4.0 ## BC Break: Removed `StaticReflectionService` The class `Doctrine\Persistence\Mapping\StaticReflectionService` is removed without replacement. ## BC Break: Narrowed `ReflectionService::getClass()` return type The return type of `ReflectionService::getClass()` has been narrowed so that `null` is no longer a valid return value. ## BC Break: Added `ObjectManager::isUninitializedObject()` Classes implementing `Doctrine\Persistence\ObjectManager` must implement this new method. ## BC Break: Added type declarations The code base is now fully typed, meaning properties, parameters and return type declarations have been added to all types. ## BC Break: Dropped support for Common proxies Proxy objects implementing the `Doctrine\Common\Proxy\Proxy` interface are not supported anymore. Implement `Doctrine\Persistence\Proxy` instead. ## BC Break: Removed deprecated ReflectionProperty overrides Deprecated classes have been removed: - `Doctrine\Persistence\Reflection\RuntimePublicReflectionProperty` - `Doctrine\Persistence\Reflection\TypedNoDefaultRuntimePublicReflectionProperty` # Upgrade to 3.4 ## Deprecated `StaticReflectionService` The class `Doctrine\Persistence\Mapping\StaticReflectionService` is deprecated without replacement. # Upgrade to 3.3 ## Added method `ObjectManager::isUninitializedObject()` Classes implementing `Doctrine\Persistence\ObjectManager` should implement the new method. This method will be added to the interface in 4.0. # Upgrade to 3.1 ## 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.json000064400000003326151742726200007300 0ustar00{ "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": "^8.1", "doctrine/event-manager": "^1 || ^2", "psr/cache": "^1.0 || ^2.0 || ^3.0" }, "require-dev": { "phpstan/phpstan": "1.12.7", "phpstan/phpstan-phpunit": "^1", "phpstan/phpstan-strict-rules": "^1.1", "doctrine/coding-standard": "^12", "phpunit/phpunit": "^9.6", "symfony/cache": "^4.4 || ^5.4 || ^6.0 || ^7.0" }, "conflict": { "doctrine/common": "<2.10" }, "autoload": { "psr-4": { "Doctrine\\Persistence\\": "src/Persistence" } }, "autoload-dev": { "psr-4": { "Doctrine\\Tests\\": "tests" } }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true, "composer/package-versions-deprecated": true } } } src/Persistence/AbstractManagerRegistry.php000064400000013047151742726200015132 0ustar00 $connections * @param array $managers * @phpstan-param class-string $proxyInterfaceName */ public function __construct( private readonly string $name, private array $connections, private array $managers, private readonly string $defaultConnection, private readonly string $defaultManager, private readonly string $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 object The instance of the given service. */ abstract protected function getService(string $name): object; /** * Resets the given services. * * A service in this context is connection or a manager instance. * * @param string $name The name of the service. */ abstract protected function resetService(string $name): void; /** Gets the name of the registry. */ public function getName(): string { return $this->name; } public function getConnection(string|null $name = null): object { 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(): array { return $this->connections; } /** * {@inheritDoc} */ public function getConnections(): array { $connections = []; foreach ($this->connections as $name => $id) { $connections[$name] = $this->getService($id); } return $connections; } public function getDefaultConnectionName(): string { return $this->defaultConnection; } public function getDefaultManagerName(): string { return $this->defaultManager; } /** * {@inheritDoc} * * @throws InvalidArgumentException */ public function getManager(string|null $name = null): ObjectManager { 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), ); } $service = $this->getService($this->managers[$name]); assert($service instanceof ObjectManager); return $service; } public function getManagerForClass(string $class): ObjectManager|null { $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); assert($manager instanceof ObjectManager); if (! $manager->getMetadataFactory()->isTransient($class)) { return $manager; } } return null; } /** * {@inheritDoc} */ public function getManagerNames(): array { return $this->managers; } /** * {@inheritDoc} */ public function getManagers(): array { $managers = []; foreach ($this->managers as $name => $id) { $manager = $this->getService($id); assert($manager instanceof ObjectManager); $managers[$name] = $manager; } return $managers; } public function getRepository( string $persistentObject, string|null $persistentManagerName = null, ): ObjectRepository { return $this ->selectManager($persistentObject, $persistentManagerName) ->getRepository($persistentObject); } public function resetManager(string|null $name = null): ObjectManager { 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); } /** @phpstan-param class-string $persistentObject */ private function selectManager( string $persistentObject, string|null $persistentManagerName = null, ): ObjectManager { if ($persistentManagerName !== null) { return $this->getManager($persistentManagerName); } return $this->getManagerForClass($persistentObject) ?? $this->getManager(); } } src/Persistence/ConnectionRegistry.php000064400000001670151742726200014172 0ustar00 An array of Connection instances. */ public function getConnections(): array; /** * Gets all connection names. * * @return array An array of connection names. */ public function getConnectionNames(): array; } src/Persistence/Event/LifecycleEventArgs.php000064400000001574151742726200015144 0ustar00object; } /** * Retrieves the associated ObjectManager. * * @phpstan-return TObjectManager */ public function getObjectManager(): ObjectManager { return $this->objectManager; } } src/Persistence/Event/LoadClassMetadataEventArgs.php000064400000002163151742726200016546 0ustar00 * @template-covariant TObjectManager of ObjectManager */ class LoadClassMetadataEventArgs extends EventArgs { /** * @phpstan-param TClassMetadata $classMetadata * @phpstan-param TObjectManager $objectManager */ public function __construct( private readonly ClassMetadata $classMetadata, private readonly ObjectManager $objectManager, ) { } /** * Retrieves the associated ClassMetadata. * * @phpstan-return TClassMetadata */ public function getClassMetadata(): ClassMetadata { return $this->classMetadata; } /** * Retrieves the associated ObjectManager. * * @phpstan-return TObjectManager */ public function getObjectManager(): ObjectManager { return $this->objectManager; } } src/Persistence/Event/ManagerEventArgs.php000064400000001245151742726200014612 0ustar00objectManager; } } src/Persistence/Event/OnClearEventArgs.php000064400000001344151742726200014563 0ustar00objectManager; } } src/Persistence/Event/PreUpdateEventArgs.php000064400000004453151742726200015135 0ustar00 */ class PreUpdateEventArgs extends LifecycleEventArgs { /** @var array> */ private array $entityChangeSet; /** * @param array> $changeSet * @phpstan-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(): array { return $this->entityChangeSet; } /** Checks if field has a changeset. */ public function hasChangedField(string $field): bool { return isset($this->entityChangeSet[$field]); } /** Gets the old value of the changeset of the changed field. */ public function getOldValue(string $field): mixed { $this->assertValidField($field); return $this->entityChangeSet[$field][0]; } /** Gets the new value of the changeset of the changed field. */ public function getNewValue(string $field): mixed { $this->assertValidField($field); return $this->entityChangeSet[$field][1]; } /** Sets the new value of this field. */ public function setNewValue(string $field, mixed $value): void { $this->assertValidField($field); $this->entityChangeSet[$field][1] = $value; } /** * Asserts the field exists in changeset. * * @throws InvalidArgumentException */ private function assertValidField(string $field): void { if (! isset($this->entityChangeSet[$field])) { throw new InvalidArgumentException(sprintf( 'Field "%s" is not a valid field of the entity "%s" in PreUpdateEventArgs.', $field, $this->getObject()::class, )); } } } src/Persistence/ManagerRegistry.php000064400000005266151742726200013452 0ustar00 An array of ObjectManager instances */ public function getManagers(): array; /** * 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). */ public function resetManager(string|null $name = null): ObjectManager; /** * 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(): array; /** * 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). * @phpstan-param class-string $persistentObject * * @phpstan-return ObjectRepository * * @template T of object */ public function getRepository( string $persistentObject, string|null $persistentManagerName = null, ): ObjectRepository; /** * Gets the object manager associated with a given class. * * @param class-string $class A persistent object class name. */ public function getManagerForClass(string $class): ObjectManager|null; } src/Persistence/Mapping/AbstractClassMetadataFactory.php000064400000034223151742726200017457 0ustar00 */ abstract class AbstractClassMetadataFactory implements ClassMetadataFactory { /** Salt used by specific Object Manager implementation. */ protected string $cacheSalt = '__CLASSMETADATA__'; private CacheItemPoolInterface|null $cache = null; /** * @var array * @phpstan-var CMTemplate[] */ private array $loadedMetadata = []; protected bool $initialized = false; private ReflectionService|null $reflectionService = null; private ProxyClassNameResolver|null $proxyClassNameResolver = null; public function setCache(CacheItemPoolInterface $cache): void { $this->cache = $cache; } final protected function getCache(): CacheItemPoolInterface|null { return $this->cache; } /** * Returns an array of all the loaded metadata currently in memory. * * @return ClassMetadata[] * @phpstan-return CMTemplate[] */ public function getLoadedMetadata(): array { return $this->loadedMetadata; } /** * {@inheritDoc} */ public function getAllMetadata(): array { 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. */ abstract protected function initialize(): void; /** Returns the mapping driver implementation. */ abstract protected function getDriver(): MappingDriver; /** * Wakes up reflection after ClassMetadata gets unserialized from cache. * * @phpstan-param CMTemplate $class */ abstract protected function wakeupReflection( ClassMetadata $class, ReflectionService $reflService, ): void; /** * Initializes Reflection after ClassMetadata was constructed. * * @phpstan-param CMTemplate $class */ abstract protected function initializeReflection( ClassMetadata $class, ReflectionService $reflService, ): void; /** * Checks whether the class metadata is an entity. * * This method should return false for mapped superclasses or embedded classes. * * @phpstan-param CMTemplate $class */ abstract protected function isEntity(ClassMetadata $class): bool; /** * Removes the prepended backslash of a class string to conform with how php outputs class names * * @phpstan-param class-string $className * * @phpstan-return class-string */ private function normalizeClassName(string $className): string { return ltrim($className, '\\'); } /** * {@inheritDoc} * * @throws ReflectionException * @throws MappingException */ public function getMetadataFor(string $className): ClassMetadata { $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) && str_contains($className, ':')) { 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) { /** @phpstan-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]; } public function hasMetadataFor(string $className): bool { $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. * * @phpstan-param class-string $className * @phpstan-param CMTemplate $class */ public function setMetadataFor(string $className, ClassMetadata $class): void { $this->loadedMetadata[$this->normalizeClassName($className)] = $class; } /** * Gets an array of parent classes for the given entity class. * * @phpstan-param class-string $name * * @return string[] * @phpstan-return list */ protected function getParentClasses(string $name): array { // 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. * @phpstan-param class-string $name * * @return array * @phpstan-return list */ protected function loadMetadata(string $name): array { 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 * * @phpstan-return CMTemplate|null */ protected function onNotFoundMetadata(string $className): ClassMetadata|null { 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. * @phpstan-param CMTemplate $class * @phpstan-param CMTemplate|null $parent */ abstract protected function doLoadMetadata( ClassMetadata $class, ClassMetadata|null $parent, bool $rootEntityFound, array $nonSuperclassParents, ): void; /** * Creates a new ClassMetadata instance for the given class name. * * @phpstan-param class-string $className * * @return ClassMetadata * @phpstan-return CMTemplate * * @template T of object */ abstract protected function newClassMetadataInstance(string $className): ClassMetadata; public function isTransient(string $className): bool { if (! $this->initialized) { $this->initialize(); } if (class_exists($className, false) && (new ReflectionClass($className))->isAnonymous()) { return false; } if (! class_exists($className, false) && str_contains($className, ':')) { throw MappingException::nonExistingClass($className); } /** @phpstan-var class-string $className */ return $this->getDriver()->isTransient($className); } /** Sets the reflectionService. */ public function setReflectionService(ReflectionService $reflectionService): void { $this->reflectionService = $reflectionService; } /** Gets the reflection service associated with this metadata factory. */ public function getReflectionService(): ReflectionService { 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. * * @phpstan-param class-string>|class-string $class * * @phpstan-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 { /** * @phpstan-param class-string>|class-string $className * * @phpstan-return class-string * * @template T of object */ public function resolveClassName(string $className): string { $pos = strrpos($className, '\\' . Proxy::MARKER . '\\'); if ($pos === false) { /** @phpstan-var class-string */ return $className; } /** @phpstan-var class-string */ return substr($className, $pos + Proxy::MARKER_LENGTH + 2); } }; } } src/Persistence/Mapping/ClassMetadata.php000064400000006527151742726200014451 0ustar00 */ public function getName(): string; /** * Gets the mapped identifier field name. * * The returned structure is an array of the identifier field names. * * @return array * @phpstan-return list */ public function getIdentifier(): array; /** * Gets the ReflectionClass instance for this mapped class. * * @return ReflectionClass */ public function getReflectionClass(): ReflectionClass; /** Checks if the given field name is a mapped identifier for this class. */ public function isIdentifier(string $fieldName): bool; /** Checks if the given field is a mapped property for this class. */ public function hasField(string $fieldName): bool; /** Checks if the given field is a mapped association for this class. */ public function hasAssociation(string $fieldName): bool; /** Checks if the given field is a mapped single valued association for this class. */ public function isSingleValuedAssociation(string $fieldName): bool; /** Checks if the given field is a mapped collection valued association for this class. */ public function isCollectionValuedAssociation(string $fieldName): bool; /** * 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(): array; /** * Returns an array of identifier field names numerically indexed. * * @return array */ public function getIdentifierFieldNames(): array; /** * 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(): array; /** * 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. */ public function getTypeOfField(string $fieldName): string|null; /** * Returns the target class name of the given association. * * @phpstan-return class-string|null */ public function getAssociationTargetClass(string $assocName): string|null; /** Checks if the association is the inverse side of a bidirectional association. */ public function isAssociationInverseSide(string $assocName): bool; /** Returns the target field of the owning side of the association. */ public function getAssociationMappedByTargetField(string $assocName): string; /** * 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): array; } src/Persistence/Mapping/ClassMetadataFactory.php000064400000003115151742726200015767 0ustar00 */ public function getAllMetadata(): array; /** * Gets the class metadata descriptor for a class. * * @param class-string $className The name of the class. * * @phpstan-return T */ public function getMetadataFor(string $className): ClassMetadata; /** * 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): bool; /** * Sets the metadata descriptor for a specific class. * * @param class-string $className * @phpstan-param T $class */ public function setMetadataFor(string $className, ClassMetadata $class): void; /** * 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. * * @phpstan-param class-string $className */ public function isTransient(string $className): bool; } src/Persistence/Mapping/Driver/ColocatedMappingDriver.php000064400000012217151742726200017554 0ustar00 */ protected array $paths = []; /** * The paths excluded from path where to look for mapping files. * * @var array */ protected array $excludePaths = []; /** The file extension of mapping documents. */ protected string $fileExtension = '.php'; /** * Cache for getAllClassNames(). * * @var array|null * @phpstan-var list|null */ protected array|null $classNames = null; /** * Appends lookup paths to metadata driver. * * @param array $paths */ public function addPaths(array $paths): void { $this->paths = array_unique(array_merge($this->paths, $paths)); } /** * Retrieves the defined metadata lookup paths. * * @return array */ public function getPaths(): array { return $this->paths; } /** * Append exclude lookup paths to metadata driver. * * @param string[] $paths */ public function addExcludePaths(array $paths): void { $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths)); } /** * Retrieve the defined metadata lookup exclude paths. * * @return array */ public function getExcludePaths(): array { return $this->excludePaths; } /** Gets the file extension used to look for mapping files under. */ public function getFileExtension(): string { return $this->fileExtension; } /** Sets the file extension used to look for mapping files under. */ public function setFileExtension(string $fileExtension): void { $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. * * @phpstan-param class-string $className */ abstract public function isTransient(string $className): bool; /** * Gets the names of all mapped classes known to this driver. * * @return string[] The names of all mapped classes known to this driver. * @phpstan-return list */ public function getAllClassNames(): array { 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 (str_contains($current, $exclude)) { 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.php000064400000010614151742726200016676 0ustar00 */ protected array $paths = []; /** The file extension of mapping documents. */ protected string|null $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(string|array $paths, string|null $fileExtension = null) { $this->addPaths((array) $paths); $this->fileExtension = $fileExtension; } /** * Appends lookup paths to metadata driver. * * @param array $paths */ public function addPaths(array $paths): void { $this->paths = array_unique([...$this->paths, ...$paths]); } /** * Retrieves the defined metadata lookup paths. * * @return array */ public function getPaths(): array { return $this->paths; } /** Gets the file extension used to look for mapping files under. */ public function getFileExtension(): string|null { return $this->fileExtension; } /** * Sets the file extension used to look for mapping files under. * * @param string|null $fileExtension The file extension to set. */ public function setFileExtension(string|null $fileExtension): void { $this->fileExtension = $fileExtension; } public function findMappingFile(string $className): string { $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): array { 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)); /** @phpstan-var class-string */ $class = str_replace('.', '\\', $fileName); $classes[] = $class; } } return $classes; } public function fileExists(string $className): bool { $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.php000064400000012634151742726200015225 0ustar00|null */ protected array|null $classCache = null; protected string $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(string|array|FileLocator $locator, string|null $fileExtension = null) { if ($locator instanceof FileLocator) { $this->locator = $locator; } else { $this->locator = new DefaultFileLocator((array) $locator, $fileExtension); } } /** Sets the global basename. */ public function setGlobalBasename(string $file): void { $this->globalBasename = $file; } /** Retrieves the global basename. */ public function getGlobalBasename(): string { 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. * * @phpstan-param class-string $className * * @return T The element of schema meta data. * * @throws MappingException */ public function getElement(string $className): mixed { if ($this->classCache === null) { $this->initialize(); } if (isset($this->classCache[$className])) { return $this->classCache[$className]; } $result = $this->loadMappingFile($this->locator->findMappingFile($className)); if (! isset($result[$className])) { throw MappingException::invalidMappingFile( $className, str_replace('\\', '.', $className) . $this->locator->getFileExtension(), ); } $this->classCache[$className] = $result[$className]; return $result[$className]; } public function isTransient(string $className): bool { if ($this->classCache === null) { $this->initialize(); } if (isset($this->classCache[$className])) { return false; } return ! $this->locator->fileExists($className); } /** * {@inheritDoc} */ public function getAllClassNames(): array { if ($this->classCache === null) { $this->initialize(); } if ($this->classCache === []) { return $this->locator->getAllClassNames($this->globalBasename); } /** @phpstan-var array> $classCache */ $classCache = $this->classCache; /** @var list $keys */ $keys = array_keys($classCache); return array_values(array_unique([...$keys, ...$this->locator->getAllClassNames($this->globalBasename)])); } /** * Loads a mapping file with the given name and returns a map * from class/entity names to their corresponding file driver elements. * * @param string $file The mapping file to load. * * @return mixed[] * @phpstan-return array */ abstract protected function loadMappingFile(string $file): array; /** * Initializes the class cache from all the global files. * * Using this feature adds a substantial performance hit to file drivers as * more metadata has to be loaded into memory than might actually be * necessary. This may not be relevant to scenarios where caching of * metadata is in place, however hits very hard in scenarios where no * caching is used. */ protected function initialize(): void { $this->classCache = []; if ($this->globalBasename === '') { return; } foreach ($this->locator->getPaths() as $path) { $file = $path . '/' . $this->globalBasename . $this->locator->getFileExtension(); if (! is_file($file)) { continue; } $this->classCache = [...$this->classCache, ...$this->loadMappingFile($file)]; } } /** Retrieves the locator used to discover mapping files by className. */ public function getLocator(): FileLocator { return $this->locator; } /** Sets the locator used to discover mapping files by className. */ public function setLocator(FileLocator $locator): void { $this->locator = $locator; } } src/Persistence/Mapping/Driver/FileLocator.php000064400000002263151742726200015372 0ustar00 * @phpstan-return list */ public function getAllClassNames(string $globalBasename): array; /** Checks if a file can be found for this class name. */ public function fileExists(string $className): bool; /** * Gets all the paths that this file locator looks for mapping files. * * @return array */ public function getPaths(): array; /** Gets the file extension that mapping files are suffixed with. */ public function getFileExtension(): string|null; } src/Persistence/Mapping/Driver/MappingDriver.php000064400000002115151742726200015732 0ustar00 $className * @phpstan-param ClassMetadata $metadata * * @template T of object */ public function loadMetadataForClass(string $className, ClassMetadata $metadata): void; /** * Gets the names of all mapped classes known to this driver. * * @return array The names of all mapped classes known to this driver. * @phpstan-return list */ public function getAllClassNames(): array; /** * Returns whether the class with the specified name should have its metadata loaded. * This is only the case if it is either mapped as an Entity or a MappedSuperclass. * * @phpstan-param class-string $className */ public function isTransient(string $className): bool; } src/Persistence/Mapping/Driver/MappingDriverChain.php000064400000006142151742726200016701 0ustar00 */ private array $drivers = []; /** Gets the default driver. */ public function getDefaultDriver(): MappingDriver|null { return $this->defaultDriver; } /** Set the default driver. */ public function setDefaultDriver(MappingDriver $driver): void { $this->defaultDriver = $driver; } /** Adds a nested driver. */ public function addDriver(MappingDriver $nestedDriver, string $namespace): void { $this->drivers[$namespace] = $nestedDriver; } /** * Gets the array of nested drivers. * * @return array $drivers */ public function getDrivers(): array { return $this->drivers; } public function loadMetadataForClass(string $className, ClassMetadata $metadata): void { foreach ($this->drivers as $namespace => $driver) { if (str_starts_with($className, $namespace)) { $driver->loadMetadataForClass($className, $metadata); return; } } if ($this->defaultDriver !== null) { $this->defaultDriver->loadMetadataForClass($className, $metadata); return; } throw MappingException::classNotFoundInNamespaces($className, array_keys($this->drivers)); } /** * {@inheritDoc} */ public function getAllClassNames(): array { $classNames = []; $driverClasses = []; foreach ($this->drivers as $namespace => $driver) { $oid = spl_object_hash($driver); if (! isset($driverClasses[$oid])) { $driverClasses[$oid] = $driver->getAllClassNames(); } foreach ($driverClasses[$oid] as $className) { if (! str_starts_with($className, $namespace)) { continue; } $classNames[$className] = true; } } if ($this->defaultDriver !== null) { foreach ($this->defaultDriver->getAllClassNames() as $className) { $classNames[$className] = true; } } return array_keys($classNames); } public function isTransient(string $className): bool { foreach ($this->drivers as $namespace => $driver) { if (str_starts_with($className, $namespace)) { return $driver->isTransient($className); } } if ($this->defaultDriver !== null) { return $this->defaultDriver->isTransient($className); } return true; } } src/Persistence/Mapping/Driver/PHPDriver.php000064400000002046151742726200014771 0ustar00> */ class PHPDriver extends FileDriver { /** @phpstan-var ClassMetadata */ protected ClassMetadata $metadata; /** @param string|array|FileLocator $locator */ public function __construct(string|array|FileLocator $locator) { parent::__construct($locator, '.php'); } public function loadMetadataForClass(string $className, ClassMetadata $metadata): void { $this->metadata = $metadata; $this->loadMappingFile($this->locator->findMappingFile($className)); } /** * {@inheritDoc} */ protected function loadMappingFile(string $file): array { $metadata = $this->metadata; include $file; return [$metadata->getName() => $metadata]; } } src/Persistence/Mapping/Driver/StaticPHPDriver.php000064400000006142151742726200016142 0ustar00 */ private array $paths = []; /** * Map of all class names. * * @var array * @phpstan-var list */ private array|null $classNames = null; /** @param array|string $paths */ public function __construct(array|string $paths) { $this->addPaths((array) $paths); } /** @param array $paths */ public function addPaths(array $paths): void { $this->paths = array_unique([...$this->paths, ...$paths]); } public function loadMetadataForClass(string $className, ClassMetadata $metadata): void { $className::loadMetadata($metadata); } /** * {@inheritDoc} * * @todo Same code exists in ColocatedMappingDriver, should we re-use it * somehow or not worry about it? */ public function getAllClassNames(): array { 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 RecursiveIteratorIterator( new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::LEAVES_ONLY, ); foreach ($iterator as $file) { if ($file->getBasename('.php') === $file->getBasename()) { continue; } $sourceFile = realpath($file->getPathName()); require_once $sourceFile; $includedFiles[] = $sourceFile; } } $declared = get_declared_classes(); foreach ($declared as $className) { $rc = new ReflectionClass($className); $sourceFile = $rc->getFileName(); if (! in_array($sourceFile, $includedFiles, true) || $this->isTransient($className)) { continue; } $classes[] = $className; } $this->classNames = $classes; return $classes; } public function isTransient(string $className): bool { return ! method_exists($className, 'loadMetadata'); } } src/Persistence/Mapping/Driver/SymfonyFileLocator.php000064400000015700151742726200016757 0ustar00 */ protected array $paths = []; /** * A map of mapping directory path to namespace prefix used to expand class shortnames. * * @var array */ protected array $prefixes = []; /** File extension that is searched for. */ protected string|null $fileExtension; /** * Represents PHP namespace delimiters when looking for files */ private readonly string $nsSeparator; /** * @param array $prefixes * @param string $nsSeparator String which would be used when converting FQCN * to filename and vice versa. Should not be empty */ public function __construct( array $prefixes, string $fileExtension = '', string $nsSeparator = '.', ) { $this->addNamespacePrefixes($prefixes); $this->fileExtension = $fileExtension; if ($nsSeparator === '') { throw new InvalidArgumentException('Namespace separator should not be empty'); } $this->nsSeparator = $nsSeparator; } /** * Adds Namespace Prefixes. * * @param array $prefixes */ public function addNamespacePrefixes(array $prefixes): void { $this->prefixes = [...$this->prefixes, ...$prefixes]; $this->paths = [...$this->paths, ...array_keys($prefixes)]; } /** * Gets Namespace Prefixes. * * @return string[] */ public function getNamespacePrefixes(): array { return $this->prefixes; } /** * {@inheritDoc} */ public function getPaths(): array { return $this->paths; } public function getFileExtension(): string|null { return $this->fileExtension; } /** * Sets the file extension used to look for mapping files under. * * @param string $fileExtension The file extension to set. */ public function setFileExtension(string $fileExtension): void { $this->fileExtension = $fileExtension; } public function fileExists(string $className): bool { $defaultFileName = str_replace('\\', $this->nsSeparator, $className) . $this->fileExtension; foreach ($this->paths as $path) { if (! isset($this->prefixes[$path])) { // global namespace class if (is_file($path . DIRECTORY_SEPARATOR . $defaultFileName)) { return true; } continue; } $prefix = $this->prefixes[$path]; if (! str_starts_with($className, $prefix . '\\')) { continue; } $filename = $path . '/' . strtr(substr($className, strlen($prefix) + 1), '\\', $this->nsSeparator) . $this->fileExtension; if (is_file($filename)) { return true; } } return false; } /** * {@inheritDoc} */ public function getAllClassNames(string|null $globalBasename = null): array { 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! if (isset($this->prefixes[$path])) { // Calculate namespace suffix for given prefix as a relative path from basepath to file path $nsSuffix = strtr( substr($this->realpath($file->getPath()), strlen($this->realpath($path))), $this->nsSeparator, '\\', ); /** @phpstan-var class-string */ $class = $this->prefixes[$path] . str_replace(DIRECTORY_SEPARATOR, '\\', $nsSuffix) . '\\' . str_replace($this->nsSeparator, '\\', $fileName); } else { /** @phpstan-var class-string */ $class = str_replace($this->nsSeparator, '\\', $fileName); } $classes[] = $class; } } return $classes; } public function findMappingFile(string $className): string { $defaultFileName = str_replace('\\', $this->nsSeparator, $className) . $this->fileExtension; foreach ($this->paths as $path) { if (! isset($this->prefixes[$path])) { if (is_file($path . DIRECTORY_SEPARATOR . $defaultFileName)) { return $path . DIRECTORY_SEPARATOR . $defaultFileName; } continue; } $prefix = $this->prefixes[$path]; if (! str_starts_with($className, $prefix . '\\')) { continue; } $filename = $path . '/' . strtr(substr($className, strlen($prefix) + 1), '\\', $this->nsSeparator) . $this->fileExtension; if (is_file($filename)) { return $filename; } } $pos = strrpos($className, '\\'); assert(is_int($pos)); throw MappingException::mappingFileNotFound( $className, substr($className, $pos + 1) . $this->fileExtension, ); } private function realpath(string $path): string { $realpath = realpath($path); if ($realpath === false) { throw new RuntimeException(sprintf('Could not get realpath for %s', $path)); } return $realpath; } } src/Persistence/Mapping/MappingException.php000064400000004311151742726200015202 0ustar00 $namespaces */ public static function classNotFoundInNamespaces( string $className, array $namespaces, ): self { return new self(sprintf( "The class '%s' was not found in the chain configured namespaces %s", $className, implode(', ', $namespaces), )); } /** @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, )); } public static function fileMappingDriversRequireConfiguredDirectoryPath( string|null $path = null, ): self { if ($path !== null) { $path = '[' . $path . ']'; } return new self(sprintf( 'File mapping drivers must have a valid directory path, ' . 'however the given path %s seems to be incorrect!', (string) $path, )); } public static function mappingFileNotFound(string $entityName, string $fileName): self { return new self(sprintf( "No mapping file found named '%s' for class '%s'.", $fileName, $entityName, )); } public static function invalidMappingFile(string $entityName, string $fileName): self { return new self(sprintf( "Invalid mapping file '%s' for class '%s'.", $fileName, $entityName, )); } public static function nonExistingClass(string $className): self { return new self(sprintf("Class '%s' does not exist", $className)); } /** @param class-string $className */ public static function classIsAnonymous(string $className): self { return new self(sprintf('Class "%s" is anonymous', $className)); } } src/Persistence/Mapping/ProxyClassNameResolver.php000064400000000573151742726200016370 0ustar00>|class-string $className * * @phpstan-return class-string * * @template T of object */ public function resolveClassName(string $className): string; } src/Persistence/Mapping/ReflectionService.php000064400000003122151742726200015342 0ustar00 $class * * @phpstan-return ReflectionClass * * @template T of object */ public function getClass(string $class): ReflectionClass; /** * Returns an accessible property (setAccessible(true)) or null. * * @phpstan-param class-string $class */ public function getAccessibleProperty(string $class, string $property): ReflectionProperty|null; /** * Checks if the class have a public method with the given name. * * @phpstan-param class-string $class */ public function hasPublicMethod(string $class, string $method): bool; } src/Persistence/Mapping/RuntimeReflectionService.php000064400000004731151742726200016715 0ustar00supportsTypedPropertiesWorkaround = version_compare(phpversion(), '7.4.0') >= 0; } /** * {@inheritDoc} */ public function getParentClasses(string $class): array { if (! class_exists($class)) { throw MappingException::nonExistingClass($class); } $parents = class_parents($class); assert($parents !== false); return $parents; } public function getClassShortName(string $class): string { $reflectionClass = new ReflectionClass($class); return $reflectionClass->getShortName(); } public function getClassNamespace(string $class): string { $reflectionClass = new ReflectionClass($class); return $reflectionClass->getNamespaceName(); } /** * @phpstan-param class-string $class * * @phpstan-return ReflectionClass * * @template T of object */ public function getClass(string $class): ReflectionClass { return new ReflectionClass($class); } public function getAccessibleProperty(string $class, string $property): RuntimeReflectionProperty { $reflectionProperty = new RuntimeReflectionProperty($class, $property); if ($this->supportsTypedPropertiesWorkaround && ! array_key_exists($property, $this->getClass($class)->getDefaultProperties())) { $reflectionProperty = new TypedNoDefaultReflectionProperty($class, $property); } $reflectionProperty->setAccessible(true); return $reflectionProperty; } public function hasPublicMethod(string $class, string $method): bool { try { $reflectionMethod = new ReflectionMethod($class, $method); } catch (ReflectionException) { return false; } return $reflectionMethod->isPublic(); } } src/Persistence/NotifyPropertyChanged.php000064400000001161151742726200014624 0ustar00find($id). * * @param string $className The class name of the object to find. * @param mixed $id The identity of the object to find. * @phpstan-param class-string $className * * @return object|null The found object. * @phpstan-return T|null * * @template T of object */ public function find(string $className, mixed $id): object|null; /** * Tells the ObjectManager to make an instance managed and persistent. * * The object will be entered into the database as a result of the flush operation. * * NOTE: The persist operation always considers objects that are not yet known to * this ObjectManager as NEW. Do not pass detached objects to the persist operation. * * @param object $object The instance to make managed and persistent. */ public function persist(object $object): void; /** * Removes an object instance. * * A removed object will be removed from the database as a result of the flush operation. * * @param object $object The object instance to remove. */ public function remove(object $object): void; /** * Clears the ObjectManager. All objects that are currently managed * by this ObjectManager become detached. */ public function clear(): void; /** * Detaches an object from the ObjectManager, causing a managed object to * become detached. Unflushed changes made to the object if any * (including removal of the object), will not be synchronized to the database. * Objects which previously referenced the detached object will continue to * reference it. * * @param object $object The object to detach. */ public function detach(object $object): void; /** * Refreshes the persistent state of an object from the database, * overriding any local changes that have not yet been persisted. * * @param object $object The object to refresh. */ public function refresh(object $object): void; /** * Flushes all changes to objects that have been queued up to now to the database. * This effectively synchronizes the in-memory state of managed objects with the * database. */ public function flush(): void; /** * Gets the repository for a class. * * @phpstan-param class-string $className * * @phpstan-return ObjectRepository * * @template T of object */ public function getRepository(string $className): ObjectRepository; /** * Returns the ClassMetadata descriptor for a class. * * The class name must be the fully-qualified class name without a leading backslash * (as it is returned by get_class($obj)). * * @phpstan-param class-string $className * * @phpstan-return ClassMetadata * * @template T of object */ public function getClassMetadata(string $className): ClassMetadata; /** * Gets the metadata factory used to gather the metadata of classes. * * @phpstan-return ClassMetadataFactory> */ public function getMetadataFactory(): ClassMetadataFactory; /** * Helper method to initialize a lazy loading proxy or persistent collection. * * This method is a no-op for other objects. */ public function initializeObject(object $obj): void; /** Helper method to check whether a lazy loading proxy or persistent collection has been initialized. */ public function isUninitializedObject(mixed $value): bool; /** * Checks if the object is part of the current UnitOfWork and therefore managed. */ public function contains(object $object): bool; } src/Persistence/ObjectManagerDecorator.php000064400000003744151742726200014712 0ustar00wrapped->find($className, $id); } public function persist(object $object): void { $this->wrapped->persist($object); } public function remove(object $object): void { $this->wrapped->remove($object); } public function clear(): void { $this->wrapped->clear(); } public function detach(object $object): void { $this->wrapped->detach($object); } public function refresh(object $object): void { $this->wrapped->refresh($object); } public function flush(): void { $this->wrapped->flush(); } public function getRepository(string $className): ObjectRepository { return $this->wrapped->getRepository($className); } public function getClassMetadata(string $className): ClassMetadata { return $this->wrapped->getClassMetadata($className); } /** @phpstan-return ClassMetadataFactory> */ public function getMetadataFactory(): ClassMetadataFactory { return $this->wrapped->getMetadataFactory(); } public function initializeObject(object $obj): void { $this->wrapped->initializeObject($obj); } public function isUninitializedObject(mixed $value): bool { return $this->wrapped->isUninitializedObject($value); } public function contains(object $object): bool { return $this->wrapped->contains($object); } } src/Persistence/ObjectRepository.php000064400000003532151742726200013647 0ustar00 The objects. * @phpstan-return T[] */ public function findAll(): array; /** * Finds objects by a set of criteria. * * Optionally sorting and limiting details can be passed. An implementation may throw * an UnexpectedValueException if certain values of the sorting or limiting details are * not supported. * * @param array $criteria * @param array|null $orderBy * @phpstan-param array|null $orderBy * * @return array The objects. * @phpstan-return T[] * * @throws UnexpectedValueException */ public function findBy( array $criteria, array|null $orderBy = null, int|null $limit = null, int|null $offset = null, ): array; /** * Finds a single object by a set of criteria. * * @param array $criteria The criteria. * * @return object|null The object. * @phpstan-return T|null */ public function findOneBy(array $criteria): object|null; /** * Returns the class name of the object managed by the repository. * * @phpstan-return class-string */ public function getClassName(): string; } src/Persistence/PropertyChangedListener.php000064400000001312151742726200015137 0ustar00 $enumType */ public function __construct(private readonly ReflectionProperty $originalReflectionProperty, private readonly string $enumType) { } public function getDeclaringClass(): ReflectionClass { return $this->originalReflectionProperty->getDeclaringClass(); } public function getName(): string { return $this->originalReflectionProperty->getName(); } public function getType(): ReflectionType|null { return $this->originalReflectionProperty->getType(); } /** * {@inheritDoc} */ public function getAttributes(string|null $name = null, int $flags = 0): array { return $this->originalReflectionProperty->getAttributes($name, $flags); } /** * {@inheritDoc} * * Converts enum instance to its value. * * @param object|null $object * * @return int|string|int[]|string[]|null */ public function getValue($object = null): int|string|array|null { if ($object === null) { return null; } $enum = $this->originalReflectionProperty->getValue($object); if ($enum === null) { return null; } return $this->fromEnum($enum); } /** * Converts enum value to enum instance. * * @param object|null $object */ public function setValue(mixed $object, mixed $value = null): void { if ($value !== null) { $value = $this->toEnum($value); } $this->originalReflectionProperty->setValue($object, $value); } /** * @param BackedEnum|BackedEnum[] $enum * * @return ($enum is BackedEnum ? (string|int) : (string[]|int[])) */ private function fromEnum(BackedEnum|array $enum) { if (is_array($enum)) { return array_map(static fn (BackedEnum $enum) => $enum->value, $enum); } return $enum->value; } /** * @param int|string|int[]|string[]|BackedEnum|BackedEnum[] $value * * @return ($value is int|string|BackedEnum ? BackedEnum : BackedEnum[]) */ private function toEnum(int|string|array|BackedEnum $value) { if ($value instanceof BackedEnum) { return $value; } if (is_array($value)) { $v = reset($value); if ($v instanceof BackedEnum) { return $value; } return array_map([$this->enumType, 'from'], $value); } return $this->enumType::from($value); } public function getModifiers(): int { return $this->originalReflectionProperty->getModifiers(); } public function getDocComment(): string|false { return $this->originalReflectionProperty->getDocComment(); } public function isPrivate(): bool { return $this->originalReflectionProperty->isPrivate(); } } src/Persistence/Reflection/RuntimeReflectionProperty.php000064400000002705151742726200017637 0ustar00key = $this->isPrivate() ? "\0" . ltrim($class, '\\') . "\0" . $name : ($this->isProtected() ? "\0*\0" . $name : $name); } public function getValue(object|null $object = null): mixed { if ($object === null) { return parent::getValue($object); } return ((array) $object)[$this->key] ?? null; } /** * {@inheritDoc} * * @param object|null $object */ public function setValue(mixed $object, mixed $value = null): void { if (! ($object instanceof Proxy && ! $object->__isInitialized())) { parent::setValue($object, $value); return; } if (! method_exists($object, '__setInitialized')) { return; } $object->__setInitialized(true); parent::setValue($object, $value); $object->__setInitialized(false); } } src/Persistence/Reflection/TypedNoDefaultReflectionProperty.php000064400000003222151742726200021076 0ustar00isInitialized($object) ? parent::getValue($object) : null; } /** * {@inheritDoc} * * Works around the problem with setting typed no default properties to * NULL which is not supported, instead unset() to uninitialize. * * @link https://github.com/doctrine/orm/issues/7999 * * @param object|null $object */ public function setValue(mixed $object, mixed $value = null): void { if ($value === null && $this->hasType() && ! $this->getType()->allowsNull()) { $propertyName = $this->getName(); $unsetter = function () use ($propertyName): void { unset($this->$propertyName); }; $unsetter = $unsetter->bindTo($object, $this->getDeclaringClass()->getName()); assert($unsetter instanceof Closure); $unsetter(); return; } parent::setValue($object, $value); } }