Subiendo proyecto completo sin restricciones de git ignore

This commit is contained in:
Jose Sanchez
2023-08-17 11:44:02 -04:00
parent a0d4f5ba3b
commit 20f1c60600
19921 changed files with 2509159 additions and 45 deletions

View File

@@ -0,0 +1,499 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\Persistence\Proxy;
use Psr\Cache\CacheItemPoolInterface;
use ReflectionClass;
use ReflectionException;
use function array_combine;
use function array_keys;
use function array_map;
use function array_reverse;
use function array_unshift;
use function assert;
use function class_exists;
use function ltrim;
use function str_replace;
use function strpos;
use function strrpos;
use function substr;
/**
* The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
* metadata mapping informations of a class which describes how a class should be mapped
* to a relational database.
*
* This class was abstracted from the ORM ClassMetadataFactory.
*
* @template CMTemplate of ClassMetadata
* @template-implements ClassMetadataFactory<CMTemplate>
*/
abstract class AbstractClassMetadataFactory implements ClassMetadataFactory
{
/**
* Salt used by specific Object Manager implementation.
*
* @var string
*/
protected $cacheSalt = '__CLASSMETADATA__';
/** @var CacheItemPoolInterface|null */
private $cache;
/**
* @var array<string, ClassMetadata>
* @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<class-string>
*/
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<int, string>
* @psalm-return list<string>
*/
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<class-string> $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<T> $className
*
* @return ClassMetadata<T>
* @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<Proxy<T>>|class-string<T> $class
*
* @psalm-return class-string<T>
*
* @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<Proxy<T>>|class-string<T> $className
*
* @psalm-return class-string<T>
*
* @template T of object
*/
public function resolveClassName(string $className): string
{
$pos = strrpos($className, '\\' . Proxy::MARKER . '\\');
if ($pos === false) {
/** @psalm-var class-string<T> */
return $className;
}
/** @psalm-var class-string<T> */
return substr($className, $pos + Proxy::MARKER_LENGTH + 2);
}
};
}
}

View File

@@ -0,0 +1,141 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping;
use ReflectionClass;
/**
* Contract for a Doctrine persistence layer ClassMetadata class to implement.
*
* @template-covariant T of object
*/
interface ClassMetadata
{
/**
* Gets the fully-qualified class name of this persistent class.
*
* @return string
* @psalm-return class-string<T>
*/
public function getName();
/**
* Gets the mapped identifier field name.
*
* The returned structure is an array of the identifier field names.
*
* @return array<int, string>
* @psalm-return list<string>
*/
public function getIdentifier();
/**
* Gets the ReflectionClass instance for this mapped class.
*
* @return ReflectionClass<T>
*/
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<int, string>
*/
public function getFieldNames();
/**
* Returns an array of identifier field names numerically indexed.
*
* @return array<int, string>
*/
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<int, string>
*/
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<string, mixed>
*/
public function getIdentifierValues(object $object);
}

View File

@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping;
/**
* Contract for a Doctrine persistence layer ClassMetadata class to implement.
*
* @template T of ClassMetadata
*/
interface ClassMetadataFactory
{
/**
* Forces the factory to load the metadata of all classes known to the underlying
* mapping driver.
*
* @return ClassMetadata[] The ClassMetadata instances of all mapped classes.
* @psalm-return list<T>
*/
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);
}

View File

@@ -0,0 +1,212 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
use Doctrine\Persistence\Mapping\MappingException;
use FilesystemIterator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RecursiveRegexIterator;
use ReflectionClass;
use RegexIterator;
use function array_merge;
use function array_unique;
use function assert;
use function get_declared_classes;
use function in_array;
use function is_dir;
use function preg_match;
use function preg_quote;
use function realpath;
use function str_replace;
use function strpos;
/**
* The ColocatedMappingDriver reads the mapping metadata located near the code.
*/
trait ColocatedMappingDriver
{
/**
* The paths where to look for mapping files.
*
* @var array<int, string>
*/
protected $paths = [];
/**
* The paths excluded from path where to look for mapping files.
*
* @var array<int, string>
*/
protected $excludePaths = [];
/**
* The file extension of mapping documents.
*
* @var string
*/
protected $fileExtension = '.php';
/**
* Cache for getAllClassNames().
*
* @var array<int, string>|null
* @psalm-var list<class-string>|null
*/
protected $classNames;
/**
* Appends lookup paths to metadata driver.
*
* @param array<int, string> $paths
*
* @return void
*/
public function addPaths(array $paths)
{
$this->paths = array_unique(array_merge($this->paths, $paths));
}
/**
* Retrieves the defined metadata lookup paths.
*
* @return array<int, string>
*/
public function getPaths()
{
return $this->paths;
}
/**
* Append exclude lookup paths to metadata driver.
*
* @param string[] $paths
*
* @return void
*/
public function addExcludePaths(array $paths)
{
$this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
}
/**
* Retrieve the defined metadata lookup exclude paths.
*
* @return array<int, string>
*/
public function getExcludePaths()
{
return $this->excludePaths;
}
/**
* Gets the file extension used to look for mapping files under.
*
* @return string
*/
public function getFileExtension()
{
return $this->fileExtension;
}
/**
* Sets the file extension used to look for mapping files under.
*
* @return void
*/
public function setFileExtension(string $fileExtension)
{
$this->fileExtension = $fileExtension;
}
/**
* {@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<class-string>
*/
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;
}
}

View File

@@ -0,0 +1,175 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
use Doctrine\Persistence\Mapping\MappingException;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use function array_merge;
use function array_unique;
use function assert;
use function is_dir;
use function is_file;
use function is_string;
use function str_replace;
use const DIRECTORY_SEPARATOR;
/**
* Locates the file that contains the metadata information for a given class name.
*
* This behavior is independent of the actual content of the file. It just detects
* the file which is responsible for the given class name.
*/
class DefaultFileLocator implements FileLocator
{
/**
* The paths where to look for mapping files.
*
* @var array<int, string>
*/
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<int, string> $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<int, string> $paths
*
* @return void
*/
public function addPaths(array $paths)
{
$this->paths = array_unique(array_merge($this->paths, $paths));
}
/**
* Retrieves the defined metadata lookup paths.
*
* @return array<int, string>
*/
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;
}
}

View File

@@ -0,0 +1,212 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\MappingException;
use function array_keys;
use function array_merge;
use function array_unique;
use function array_values;
use function is_file;
use function str_replace;
/**
* Base driver for file-based metadata drivers.
*
* A file driver operates in a mode where it loads the mapping files of individual
* classes on demand. This requires the user to adhere to the convention of 1 mapping
* file per class and the file names of the mapping files must correspond to the full
* class name, including namespace, with the namespace delimiters '\', replaced by dots '.'.
*/
abstract class FileDriver implements MappingDriver
{
/** @var FileLocator */
protected $locator;
/**
* @var ClassMetadata[]|null
* @psalm-var array<class-string, ClassMetadata<object>>|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<int, string>|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<object>
*
* @throws MappingException
*/
public function getElement(string $className)
{
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];
}
/**
* {@inheritDoc}
*/
public function isTransient(string $className)
{
if ($this->classCache === null) {
$this->initialize();
}
if (isset($this->classCache[$className])) {
return false;
}
return ! $this->locator->fileExists($className);
}
/**
* {@inheritDoc}
*/
public function getAllClassNames()
{
if ($this->classCache === null) {
$this->initialize();
}
if ($this->classCache === []) {
return $this->locator->getAllClassNames($this->globalBasename);
}
/** @psalm-var array<class-string, ClassMetadata<object>> $classCache */
$classCache = $this->classCache;
/** @var list<class-string> $keys */
$keys = array_keys($classCache);
return array_values(array_unique(array_merge(
$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 ClassMetadata[]
* @psalm-return array<class-string, ClassMetadata<object>>
*/
abstract protected function loadMappingFile(string $file);
/**
* 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.
*
* @return void
*/
protected function initialize()
{
$this->classCache = [];
if ($this->globalBasename === null) {
return;
}
foreach ($this->locator->getPaths() as $path) {
$file = $path . '/' . $this->globalBasename . $this->locator->getFileExtension();
if (! is_file($file)) {
continue;
}
$this->classCache = array_merge(
$this->classCache,
$this->loadMappingFile($file)
);
}
}
/**
* Retrieves the locator used to discover mapping files by className.
*
* @return FileLocator
*/
public function getLocator()
{
return $this->locator;
}
/**
* Sets the locator used to discover mapping files by className.
*
* @return void
*/
public function setLocator(FileLocator $locator)
{
$this->locator = $locator;
}
}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
/**
* Locates the file that contains the metadata information for a given class name.
*
* This behavior is independent of the actual content of the file. It just detects
* the file which is responsible for the given class name.
*/
interface FileLocator
{
/**
* Locates mapping file for the given class name.
*
* @return string
*/
public function findMappingFile(string $className);
/**
* Gets all class names that are found with this file locator.
*
* @param string $globalBasename Passed to allow excluding the basename.
*
* @return array<int, string>
* @psalm-return list<class-string>
*/
public function getAllClassNames(string $globalBasename);
/**
* Checks if a file can be found for this class name.
*
* @return bool
*/
public function fileExists(string $className);
/**
* Gets all the paths that this file locator looks for mapping files.
*
* @return array<int, string>
*/
public function getPaths();
/**
* Gets the file extension that mapping files are suffixed with.
*
* @return string|null
*/
public function getFileExtension();
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
use Doctrine\Persistence\Mapping\ClassMetadata;
/**
* Contract for metadata drivers.
*/
interface MappingDriver
{
/**
* Loads the metadata for the specified class into the provided container.
*
* @psalm-param class-string<T> $className
* @psalm-param ClassMetadata<T> $metadata
*
* @return void
*
* @template T of object
*/
public function loadMetadataForClass(string $className, ClassMetadata $metadata);
/**
* Gets the names of all mapped classes known to this driver.
*
* @return array<int, string> The names of all mapped classes known to this driver.
* @psalm-return list<class-string>
*/
public function getAllClassNames();
/**
* 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.
*
* @psalm-param class-string $className
*
* @return bool
*/
public function isTransient(string $className);
}

View File

@@ -0,0 +1,142 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\MappingException;
use function array_keys;
use function spl_object_hash;
use function strpos;
/**
* The DriverChain allows you to add multiple other mapping drivers for
* certain namespaces.
*/
class MappingDriverChain implements MappingDriver
{
/**
* The default driver.
*
* @var MappingDriver|null
*/
private $defaultDriver;
/** @var array<string, MappingDriver> */
private $drivers = [];
/**
* Gets the default driver.
*
* @return MappingDriver|null
*/
public function getDefaultDriver()
{
return $this->defaultDriver;
}
/**
* Set the default driver.
*
* @return void
*/
public function setDefaultDriver(MappingDriver $driver)
{
$this->defaultDriver = $driver;
}
/**
* Adds a nested driver.
*
* @return void
*/
public function addDriver(MappingDriver $nestedDriver, string $namespace)
{
$this->drivers[$namespace] = $nestedDriver;
}
/**
* Gets the array of nested drivers.
*
* @return array<string, MappingDriver> $drivers
*/
public function getDrivers()
{
return $this->drivers;
}
/**
* {@inheritDoc}
*/
public function loadMetadataForClass(string $className, ClassMetadata $metadata)
{
foreach ($this->drivers as $namespace => $driver) {
if (strpos($className, $namespace) === 0) {
$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()
{
$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 (strpos($className, $namespace) !== 0) {
continue;
}
$classNames[$className] = true;
}
}
if ($this->defaultDriver !== null) {
foreach ($this->defaultDriver->getAllClassNames() as $className) {
$classNames[$className] = true;
}
}
return array_keys($classNames);
}
/**
* {@inheritDoc}
*/
public function isTransient(string $className)
{
foreach ($this->drivers as $namespace => $driver) {
if (strpos($className, $namespace) === 0) {
return $driver->isTransient($className);
}
}
if ($this->defaultDriver !== null) {
return $this->defaultDriver->isTransient($className);
}
return true;
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
use Doctrine\Persistence\Mapping\ClassMetadata;
/**
* The PHPDriver includes php files which just populate ClassMetadataInfo
* instances with plain PHP code.
*/
class PHPDriver extends FileDriver
{
/**
* @var ClassMetadata
* @psalm-var ClassMetadata<object>
*/
protected $metadata;
/** @param string|array<int, string>|FileLocator $locator */
public function __construct($locator)
{
parent::__construct($locator, '.php');
}
/**
* {@inheritDoc}
*/
public function loadMetadataForClass(string $className, ClassMetadata $metadata)
{
$this->metadata = $metadata;
$this->loadMappingFile($this->locator->findMappingFile($className));
}
/**
* {@inheritDoc}
*/
protected function loadMappingFile(string $file)
{
$metadata = $this->metadata;
include $file;
return [$metadata->getName() => $metadata];
}
}

View File

@@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\MappingException;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use ReflectionClass;
use function array_merge;
use function array_unique;
use function get_declared_classes;
use function in_array;
use function is_dir;
use function method_exists;
use function realpath;
/**
* The StaticPHPDriver calls a static loadMetadata() method on your entity
* classes where you can manually populate the ClassMetadata instance.
*/
class StaticPHPDriver implements MappingDriver
{
/**
* Paths of entity directories.
*
* @var array<int, string>
*/
private $paths = [];
/**
* Map of all class names.
*
* @var array<int, string>
* @psalm-var list<class-string>
*/
private $classNames;
/** @param array<int, string>|string $paths */
public function __construct($paths)
{
$this->addPaths((array) $paths);
}
/**
* @param array<int, string> $paths
*
* @return void
*/
public function addPaths(array $paths)
{
$this->paths = array_unique(array_merge($this->paths, $paths));
}
/**
* {@inheritdoc}
*/
public function loadMetadataForClass(string $className, ClassMetadata $metadata)
{
$className::loadMetadata($metadata);
}
/**
* {@inheritDoc}
*
* @todo Same code exists in ColocatedMappingDriver, should we re-use it
* somehow or not worry about it?
*/
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 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;
}
/**
* {@inheritdoc}
*/
public function isTransient(string $className)
{
return ! method_exists($className, 'loadMetadata');
}
}

View File

@@ -0,0 +1,265 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
use Doctrine\Persistence\Mapping\MappingException;
use InvalidArgumentException;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RuntimeException;
use function array_keys;
use function array_merge;
use function assert;
use function is_dir;
use function is_file;
use function is_int;
use function realpath;
use function sprintf;
use function str_replace;
use function strlen;
use function strpos;
use function strrpos;
use function strtr;
use function substr;
use const DIRECTORY_SEPARATOR;
/**
* The Symfony File Locator makes a simplifying assumptions compared
* to the DefaultFileLocator. By assuming paths only contain entities of a certain
* namespace the mapping files consists of the short classname only.
*/
class SymfonyFileLocator implements FileLocator
{
/**
* The paths where to look for mapping files.
*
* @var array<int, string>
*/
protected $paths = [];
/**
* A map of mapping directory path to namespace prefix used to expand class shortnames.
*
* @var array<string, string>
*/
protected $prefixes = [];
/**
* File extension that is searched for.
*
* @var string|null
*/
protected $fileExtension;
/**
* Represents PHP namespace delimiters when looking for files
*
* @var string
*/
private $nsSeparator;
/**
* @param array<string, string> $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<string, string> $prefixes
*
* @return void
*/
public function addNamespacePrefixes(array $prefixes)
{
$this->prefixes = array_merge($this->prefixes, $prefixes);
$this->paths = array_merge($this->paths, array_keys($prefixes));
}
/**
* Gets Namespace Prefixes.
*
* @return string[]
*/
public function getNamespacePrefixes()
{
return $this->prefixes;
}
/**
* {@inheritDoc}
*/
public function getPaths()
{
return $this->paths;
}
/**
* {@inheritDoc}
*/
public function getFileExtension()
{
return $this->fileExtension;
}
/**
* Sets the file extension used to look for mapping files under.
*
* @param string $fileExtension The file extension to set.
*
* @return void
*/
public function setFileExtension(string $fileExtension)
{
$this->fileExtension = $fileExtension;
}
/**
* {@inheritDoc}
*/
public function fileExists(string $className)
{
$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 (strpos($className, $prefix . '\\') !== 0) {
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 $globalBasename = null)
{
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,
'\\'
);
/** @psalm-var class-string */
$class = $this->prefixes[$path] . str_replace(DIRECTORY_SEPARATOR, '\\', $nsSuffix) . '\\' . str_replace($this->nsSeparator, '\\', $fileName);
} else {
/** @psalm-var class-string */
$class = str_replace($this->nsSeparator, '\\', $fileName);
}
$classes[] = $class;
}
}
return $classes;
}
/**
* {@inheritDoc}
*/
public function findMappingFile(string $className)
{
$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 (strpos($className, $prefix . '\\') !== 0) {
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;
}
}

View File

@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping;
use Exception;
use function implode;
use function sprintf;
/**
* A MappingException indicates that something is wrong with the mapping setup.
*/
class MappingException extends Exception
{
/**
* @param array<int, string> $namespaces
*
* @return self
*/
public static function classNotFoundInNamespaces(
string $className,
array $namespaces
) {
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
));
}
/** @return self */
public static function fileMappingDriversRequireConfiguredDirectoryPath(
?string $path = null
) {
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
));
}
/** @return self */
public static function mappingFileNotFound(string $entityName, string $fileName)
{
return new self(sprintf(
"No mapping file found named '%s' for class '%s'.",
$fileName,
$entityName
));
}
/** @return self */
public static function invalidMappingFile(string $entityName, string $fileName)
{
return new self(sprintf(
"Invalid mapping file '%s' for class '%s'.",
$fileName,
$entityName
));
}
/** @return self */
public static function nonExistingClass(string $className)
{
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));
}
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping;
use Doctrine\Persistence\Proxy;
interface ProxyClassNameResolver
{
/**
* @psalm-param class-string<Proxy<T>>|class-string<T> $className
*
* @psalm-return class-string<T>
*
* @template T of object
*/
public function resolveClassName(string $className): string;
}

View File

@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping;
use ReflectionClass;
use ReflectionProperty;
/**
* Very simple reflection service abstraction.
*
* This is required inside metadata layers that may require either
* static or runtime reflection.
*/
interface ReflectionService
{
/**
* Returns an array of the parent classes (not interfaces) for the given class.
*
* @psalm-param class-string $class
*
* @return string[]
* @psalm-return class-string[]
*
* @throws MappingException
*/
public function getParentClasses(string $class);
/**
* Returns the shortname of a class.
*
* @psalm-param class-string $class
*
* @return string
*/
public function getClassShortName(string $class);
/**
* @psalm-param class-string $class
*
* @return string
*/
public function getClassNamespace(string $class);
/**
* Returns a reflection class instance or null.
*
* @psalm-param class-string<T> $class
*
* @return ReflectionClass|null
* @psalm-return ReflectionClass<T>|null
*
* @template T of object
*/
public function getClass(string $class);
/**
* Returns an accessible property (setAccessible(true)) or null.
*
* @psalm-param class-string $class
*
* @return ReflectionProperty|null
*/
public function getAccessibleProperty(string $class, string $property);
/**
* Checks if the class have a public method with the given name.
*
* @psalm-param class-string $class
*
* @return bool
*/
public function hasPublicMethod(string $class, string $method);
}

View File

@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping;
use Doctrine\Persistence\Reflection\RuntimeReflectionProperty;
use Doctrine\Persistence\Reflection\TypedNoDefaultReflectionProperty;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
use function array_key_exists;
use function assert;
use function class_exists;
use function class_parents;
use function phpversion;
use function version_compare;
/**
* PHP Runtime Reflection Service.
*/
class RuntimeReflectionService implements ReflectionService
{
/** @var bool */
private $supportsTypedPropertiesWorkaround;
public function __construct()
{
$this->supportsTypedPropertiesWorkaround = version_compare(phpversion(), '7.4.0') >= 0;
}
/**
* {@inheritDoc}
*/
public function getParentClasses(string $class)
{
if (! class_exists($class)) {
throw MappingException::nonExistingClass($class);
}
$parents = class_parents($class);
assert($parents !== false);
return $parents;
}
/**
* {@inheritDoc}
*/
public function getClassShortName(string $class)
{
$reflectionClass = new ReflectionClass($class);
return $reflectionClass->getShortName();
}
/**
* {@inheritDoc}
*/
public function getClassNamespace(string $class)
{
$reflectionClass = new ReflectionClass($class);
return $reflectionClass->getNamespaceName();
}
/**
* @psalm-param class-string<T> $class
*
* @return ReflectionClass
* @psalm-return ReflectionClass<T>
*
* @template T of object
*/
public function getClass(string $class)
{
return new ReflectionClass($class);
}
/**
* {@inheritDoc}
*/
public function getAccessibleProperty(string $class, string $property)
{
$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;
}
/**
* {@inheritDoc}
*/
public function hasPublicMethod(string $class, string $method)
{
try {
$reflectionMethod = new ReflectionMethod($class, $method);
} catch (ReflectionException $e) {
return false;
}
return $reflectionMethod->isPublic();
}
}

View File

@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping;
use function strpos;
use function strrev;
use function strrpos;
use function substr;
/**
* PHP Runtime Reflection Service.
*/
class StaticReflectionService implements ReflectionService
{
/**
* {@inheritDoc}
*/
public function getParentClasses(string $class)
{
return [];
}
/**
* {@inheritDoc}
*/
public function getClassShortName(string $class)
{
$nsSeparatorLastPosition = strrpos($class, '\\');
if ($nsSeparatorLastPosition !== false) {
$class = substr($class, $nsSeparatorLastPosition + 1);
}
return $class;
}
/**
* {@inheritDoc}
*/
public function getClassNamespace(string $class)
{
$namespace = '';
if (strpos($class, '\\') !== false) {
$namespace = strrev(substr(strrev($class), (int) strpos(strrev($class), '\\') + 1));
}
return $namespace;
}
/**
* {@inheritDoc}
*
* @return null
*/
public function getClass(string $class)
{
return null;
}
/**
* {@inheritDoc}
*/
public function getAccessibleProperty(string $class, string $property)
{
return null;
}
/**
* {@inheritDoc}
*/
public function hasPublicMethod(string $class, string $method)
{
return true;
}
}