├── src ├── MergeableInterface.php ├── Driver │ ├── FileLocatorInterface.php │ ├── DriverInterface.php │ ├── TraceableFileLocatorInterface.php │ ├── AdvancedDriverInterface.php │ ├── AdvancedFileLocatorInterface.php │ ├── LazyLoadingDriver.php │ ├── AbstractFileDriver.php │ ├── DriverChain.php │ └── FileLocator.php ├── NullMetadata.php ├── Cache │ ├── ClearableCacheInterface.php │ ├── CacheInterface.php │ ├── DoctrineCacheAdapter.php │ ├── PsrCacheAdapter.php │ └── FileCache.php ├── AdvancedMetadataFactoryInterface.php ├── SerializationHelper.php ├── MergeableClassMetadata.php ├── MetadataFactoryInterface.php ├── PropertyMetadata.php ├── ClassHierarchyMetadata.php ├── MethodMetadata.php ├── ClassMetadata.php └── MetadataFactory.php ├── LICENSE ├── composer.json ├── CONTRIBUTING.md ├── UPGRADING.md ├── README.md └── CHANGELOG.md /src/MergeableInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class NullMetadata extends ClassMetadata 13 | { 14 | } 15 | -------------------------------------------------------------------------------- /src/Cache/ClearableCacheInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface ClearableCacheInterface 11 | { 12 | /** 13 | * Clear all classes metadata from the cache. 14 | */ 15 | public function clear(): bool; 16 | } 17 | -------------------------------------------------------------------------------- /src/Driver/TraceableFileLocatorInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface AdvancedDriverInterface extends DriverInterface 13 | { 14 | /** 15 | * Gets all the metadata class names known to this driver. 16 | * 17 | * @return string[] 18 | */ 19 | public function getAllClassNames(): array; 20 | } 21 | -------------------------------------------------------------------------------- /src/Driver/AdvancedFileLocatorInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface AdvancedFileLocatorInterface extends FileLocatorInterface 13 | { 14 | /** 15 | * Finds all possible metadata files.* 16 | * 17 | * @return string[] 18 | */ 19 | public function findAllClasses(string $extension): array; 20 | } 21 | -------------------------------------------------------------------------------- /src/Cache/CacheInterface.php: -------------------------------------------------------------------------------- 1 | 11 | * @author Jordan Stout 12 | */ 13 | interface AdvancedMetadataFactoryInterface extends MetadataFactoryInterface 14 | { 15 | /** 16 | * Gets all the possible classes. 17 | * 18 | * @return string[] 19 | * 20 | * @throws \RuntimeException When driver does not an advanced driver. 21 | */ 22 | public function getAllClassNames(): array; 23 | } 24 | -------------------------------------------------------------------------------- /src/SerializationHelper.php: -------------------------------------------------------------------------------- 1 | serializeToArray()); 17 | } 18 | 19 | /** 20 | * @deprecated Use unserializeFromArray 21 | * 22 | * @param string $str 23 | * 24 | * @return void 25 | */ 26 | public function unserialize($str) 27 | { 28 | $this->unserializeFromArray(unserialize($str)); 29 | } 30 | 31 | public function __serialize(): array 32 | { 33 | return [$this->serialize()]; 34 | } 35 | 36 | public function __unserialize(array $data): void 37 | { 38 | $this->unserialize($data[0]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/MergeableClassMetadata.php: -------------------------------------------------------------------------------- 1 | name = $object->name; 16 | $this->methodMetadata = array_merge($this->methodMetadata, $object->methodMetadata); 17 | $this->propertyMetadata = array_merge($this->propertyMetadata, $object->propertyMetadata); 18 | $this->fileResources = array_merge($this->fileResources, $object->fileResources); 19 | 20 | if ($object->createdAt < $this->createdAt) { 21 | $this->createdAt = $object->createdAt; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/MetadataFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface MetadataFactoryInterface 13 | { 14 | /** 15 | * Returns the gathered metadata for the given class name. 16 | * 17 | * If the drivers return instances of MergeableClassMetadata, these will be 18 | * merged prior to returning. Otherwise, all metadata for the inheritance 19 | * hierarchy will be returned as ClassHierarchyMetadata unmerged. 20 | * 21 | * If no metadata is available, null is returned. 22 | * 23 | * @return ClassHierarchyMetadata|MergeableClassMetadata|null 24 | * 25 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint 26 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.UselessReturnAnnotation 27 | */ 28 | public function getMetadataForClass(string $className); 29 | } 30 | -------------------------------------------------------------------------------- /src/PropertyMetadata.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class PropertyMetadata implements \Serializable 16 | { 17 | use SerializationHelper; 18 | 19 | /** 20 | * @var string 21 | */ 22 | public $class; 23 | 24 | /** 25 | * @var string 26 | */ 27 | public $name; 28 | 29 | public function __construct(string $class, string $name) 30 | { 31 | $this->class = $class; 32 | $this->name = $name; 33 | } 34 | 35 | protected function serializeToArray(): array 36 | { 37 | return [ 38 | $this->class, 39 | $this->name, 40 | ]; 41 | } 42 | 43 | protected function unserializeFromArray(array $data): void 44 | { 45 | [$this->class, $this->name] = $data; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ClassHierarchyMetadata.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class ClassHierarchyMetadata 13 | { 14 | /** 15 | * @var ClassMetadata[] 16 | */ 17 | public $classMetadata = []; 18 | 19 | public function addClassMetadata(ClassMetadata $metadata): void 20 | { 21 | $this->classMetadata[$metadata->name] = $metadata; 22 | } 23 | 24 | public function getRootClassMetadata(): ?ClassMetadata 25 | { 26 | return reset($this->classMetadata); 27 | } 28 | 29 | public function getOutsideClassMetadata(): ?ClassMetadata 30 | { 31 | return end($this->classMetadata); 32 | } 33 | 34 | public function isFresh(int $timestamp): bool 35 | { 36 | foreach ($this->classMetadata as $metadata) { 37 | if (!$metadata->isFresh($timestamp)) { 38 | return false; 39 | } 40 | } 41 | 42 | return true; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Johannes M. Schmitt 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/Driver/LazyLoadingDriver.php: -------------------------------------------------------------------------------- 1 | container = $container; 33 | $this->realDriverId = $realDriverId; 34 | } 35 | 36 | public function loadMetadataForClass(\ReflectionClass $class): ?ClassMetadata 37 | { 38 | return $this->container->get($this->realDriverId)->loadMetadataForClass($class); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jms/metadata", 3 | "description": "Class/method/property metadata management in PHP", 4 | "keywords": ["annotations","metadata","yaml","xml"], 5 | "type": "library", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Johannes M. Schmitt", 10 | "email": "schmittjoh@gmail.com" 11 | }, 12 | { 13 | "name": "Asmir Mustafic", 14 | "email": "goetas@gmail.com" 15 | } 16 | ], 17 | "require": { 18 | "php": "^7.2|^8.0" 19 | }, 20 | "require-dev" : { 21 | "doctrine/cache" : "^1.0|^2.0", 22 | "doctrine/coding-standard": "^8.0", 23 | "phpunit/phpunit": "^8.5.42|^9.6.23", 24 | "psr/container": "^1.0|^2.0", 25 | "symfony/cache" : "^3.1|^4.0|^5.0|^6.0|^7.0|^8.0", 26 | "symfony/dependency-injection" : "^3.1|^4.0|^5.0|^6.0|^7.0|^8.0", 27 | "mikey179/vfsstream": "^1.6.7" 28 | }, 29 | "autoload": { 30 | "psr-4": { "Metadata\\": "src/" } 31 | }, 32 | "autoload-dev": { 33 | "psr-4": { "Metadata\\Tests\\": "tests/" } 34 | }, 35 | "extra": { 36 | "branch-alias": { 37 | "dev-master": "2.x-dev" 38 | } 39 | }, 40 | "config": { 41 | "allow-plugins": { 42 | "dealerdirect/phpcodesniffer-composer-installer": true 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Cache/DoctrineCacheAdapter.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class DoctrineCacheAdapter implements CacheInterface, ClearableCacheInterface 16 | { 17 | /** 18 | * @var string 19 | */ 20 | private $prefix; 21 | /** 22 | * @var Cache 23 | */ 24 | private $cache; 25 | 26 | public function __construct(string $prefix, Cache $cache) 27 | { 28 | $this->prefix = $prefix; 29 | $this->cache = $cache; 30 | } 31 | 32 | public function load(string $class): ?ClassMetadata 33 | { 34 | $cache = $this->cache->fetch($this->prefix . $class); 35 | 36 | return false === $cache ? null : $cache; 37 | } 38 | 39 | public function put(ClassMetadata $metadata): void 40 | { 41 | $this->cache->save($this->prefix . $metadata->name, $metadata); 42 | } 43 | 44 | public function evict(string $class): void 45 | { 46 | $this->cache->delete($this->prefix . $class); 47 | } 48 | 49 | public function clear(): bool 50 | { 51 | if (method_exists($this->cache, 'deleteAll')) { // or $this->cache instanceof ClearableCache 52 | return call_user_func([$this->cache, 'deleteAll']); 53 | } 54 | 55 | return false; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Driver/AbstractFileDriver.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | abstract class AbstractFileDriver implements AdvancedDriverInterface 15 | { 16 | /** 17 | * @var FileLocatorInterface|FileLocator 18 | */ 19 | private $locator; 20 | 21 | public function __construct(FileLocatorInterface $locator) 22 | { 23 | $this->locator = $locator; 24 | } 25 | 26 | public function loadMetadataForClass(\ReflectionClass $class): ?ClassMetadata 27 | { 28 | if (null === $path = $this->locator->findFileForClass($class, $this->getExtension())) { 29 | return null; 30 | } 31 | 32 | return $this->loadMetadataFromFile($class, $path); 33 | } 34 | 35 | /** 36 | * {@inheritDoc} 37 | */ 38 | public function getAllClassNames(): array 39 | { 40 | if (!$this->locator instanceof AdvancedFileLocatorInterface) { 41 | throw new \RuntimeException(sprintf('Locator "%s" must be an instance of "AdvancedFileLocatorInterface".', get_class($this->locator))); 42 | } 43 | 44 | return $this->locator->findAllClasses($this->getExtension()); 45 | } 46 | 47 | /** 48 | * Parses the content of the file, and converts it to the desired metadata. 49 | */ 50 | abstract protected function loadMetadataFromFile(\ReflectionClass $class, string $file): ?ClassMetadata; 51 | 52 | /** 53 | * Returns the extension of the file. 54 | */ 55 | abstract protected function getExtension(): string; 56 | } 57 | -------------------------------------------------------------------------------- /src/Driver/DriverChain.php: -------------------------------------------------------------------------------- 1 | drivers = $drivers; 22 | } 23 | 24 | public function addDriver(DriverInterface $driver): void 25 | { 26 | $this->drivers[] = $driver; 27 | } 28 | 29 | public function loadMetadataForClass(\ReflectionClass $class): ?ClassMetadata 30 | { 31 | foreach ($this->drivers as $driver) { 32 | if (null !== $metadata = $driver->loadMetadataForClass($class)) { 33 | return $metadata; 34 | } 35 | } 36 | 37 | return null; 38 | } 39 | 40 | /** 41 | * {@inheritDoc} 42 | */ 43 | public function getAllClassNames(): array 44 | { 45 | $classes = []; 46 | foreach ($this->drivers as $driver) { 47 | if (!$driver instanceof AdvancedDriverInterface) { 48 | throw new \RuntimeException( 49 | sprintf( 50 | 'Driver "%s" must be an instance of "AdvancedDriverInterface" to use ' . 51 | '"DriverChain::getAllClassNames()".', 52 | get_class($driver) 53 | ) 54 | ); 55 | } 56 | 57 | $driverClasses = $driver->getAllClassNames(); 58 | if (!empty($driverClasses)) { 59 | $classes = array_merge($classes, $driverClasses); 60 | } 61 | } 62 | 63 | return $classes; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute 2 | 3 | Thank you for contributing! 4 | 5 | Before we can merge your Pull-Request here are some guidelines that you need to follow. 6 | These guidelines exist not to annoy you, but to keep the code base clean, unified and future proof. 7 | 8 | ## Coding Standard 9 | 10 | This project uses [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) to enforce coding standards. 11 | The coding standard rules are defined in the **phpcs.xml.dist** file (part of this repository). 12 | The project follows a relaxed version of the Doctrine Coding standards v4. 13 | 14 | Your Pull-Request must be compliant with the said standard. 15 | To check your code you can run `vendor/bin/phpcs`. This command will give you a list of violations in your code (if any). 16 | The most common errors can be automatically fixed just by running `vendor/bin/phpcbf`. 17 | 18 | ## Unit-Tests 19 | 20 | Please try to add a test for your pull-request. This project uses [PHPUnit](https://phpunit.de/) as testing framework. 21 | 22 | You can run the unit-tests by calling `vendor/bin/phpunit`. 23 | 24 | New features without tests can't be merged. 25 | 26 | ## CI 27 | 28 | We automatically run your pull request through [Travis CI](https://www.travis-ci.org) 29 | and [Scrutinizer CI](https://scrutinizer-ci.com/). 30 | If you break the tests, we cannot merge your code, 31 | so please make sure that your code is working before opening up a Pull-Request. 32 | 33 | ## Issues and Bugs 34 | 35 | To create a new issue, you can use the GitHub issue tracking system. 36 | Please try to avoid opening support-related tickets. For support related questions please use more appropriate 37 | channels as Q&A platforms (such as Stackoverflow), Forums, Local PHP user groups. 38 | 39 | ## Getting merged 40 | 41 | Please allow us time to review your pull requests. 42 | We will give our best to review everything as fast as possible, but cannot always live up to our own expectations. 43 | 44 | Thank you very much again for your contribution! 45 | -------------------------------------------------------------------------------- /src/Cache/PsrCacheAdapter.php: -------------------------------------------------------------------------------- 1 | prefix = $prefix; 30 | $this->pool = $pool; 31 | } 32 | 33 | public function load(string $class): ?ClassMetadata 34 | { 35 | $this->lastItem = $this->pool->getItem($this->sanitizeCacheKey($this->prefix . $class)); 36 | 37 | return $this->lastItem->get(); 38 | } 39 | 40 | public function put(ClassMetadata $metadata): void 41 | { 42 | $key = $this->sanitizeCacheKey($this->prefix . $metadata->name); 43 | 44 | if (null === $this->lastItem || $this->lastItem->getKey() !== $key) { 45 | $this->lastItem = $this->pool->getItem($key); 46 | } 47 | 48 | $this->pool->save($this->lastItem->set($metadata)); 49 | } 50 | 51 | public function evict(string $class): void 52 | { 53 | $this->pool->deleteItem($this->sanitizeCacheKey($this->prefix . $class)); 54 | } 55 | 56 | public function clear(): bool 57 | { 58 | return $this->pool->clear(); 59 | } 60 | 61 | /** 62 | * If anonymous class is to be cached, it contains invalid path characters that need to be removed/replaced 63 | * Example of anonymous class name: class@anonymous\x00/app/src/Controller/DefaultController.php0x7f82a7e026ec 64 | */ 65 | private function sanitizeCacheKey(string $key): string 66 | { 67 | return str_replace(['\\', "\0", '@', '/', '$', '{', '}', ':'], '-', $key); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | From 2.2.3 to 2.3.0 2 | ==================== 3 | 4 | - The `\Serializable` PHP interface is deprecated, the methods of this interface will be removed in 3.0. 5 | This change is done to allow the use of the new `__serialize` and `__unserialize` PHP's strategy. 6 | 7 | If you were extending the metadata classes, your custom serialization methods were looking probably as something as this: 8 | 9 | ```php 10 | class MyMetadata extends PropertyMetadata 11 | { 12 | // ... more code 13 | 14 | public function serialize() 15 | { 16 | $data = parent::serialize(); 17 | 18 | return \serialize([$data, $this->customMetadataProp]); 19 | } 20 | 21 | public function unserialize($str) 22 | { 23 | list($str, $this->customMetadataProp) = \unserialize($str); 24 | 25 | parent::unserialize($str); 26 | } 27 | } 28 | ``` 29 | 30 | After this change, your code should look like this: 31 | 32 | ```php 33 | class MyMetadata extends PropertyMetadata 34 | { 35 | // ... more code 36 | 37 | protected function serializeToArray(): array 38 | { 39 | $data = parent::serializeToArray(); 40 | 41 | return [$data, $this->customMetadataProp]; 42 | } 43 | 44 | protected function unserializeFromArray(array $data): void 45 | { 46 | list($data, $this->customMetadataProp) = $data; 47 | 48 | parent::unserializeFromArray($data); 49 | } 50 | } 51 | ``` 52 | 53 | From 1.7.0 to 2.0.0 54 | ==================== 55 | 56 | - Type-hinting everywhere where allowed by PHP 7.2 and strict types are used now 57 | - `Metadata\Cache\CacheInterface` changed, methods have different names and signature; all the classes implementing 58 | that interface have been modified accordingly 59 | - `getValue` and `setValue` methods have been removed from `Metadata\PropertyMetadata`, getting/setting properties is not 60 | responsibility of this library anymore 61 | - the `$reflection` property has been removed from `Metadata\PropertyMetadata`; 62 | metadata information do not require (and do not offer) reflection anymore 63 | -------------------------------------------------------------------------------- /src/MethodMetadata.php: -------------------------------------------------------------------------------- 1 | 14 | * @property $reflection 15 | */ 16 | class MethodMetadata implements \Serializable 17 | { 18 | use SerializationHelper; 19 | 20 | /** 21 | * @var string 22 | */ 23 | public $class; 24 | 25 | /** 26 | * @var string 27 | */ 28 | public $name; 29 | 30 | /** 31 | * @var \ReflectionMethod 32 | */ 33 | private $reflection; 34 | 35 | public function __construct(string $class, string $name) 36 | { 37 | $this->class = $class; 38 | $this->name = $name; 39 | } 40 | 41 | /** 42 | * @param mixed[] $args 43 | * 44 | * @return mixed 45 | */ 46 | public function invoke(object $obj, array $args = []) 47 | { 48 | return $this->getReflection()->invokeArgs($obj, $args); 49 | } 50 | 51 | /** 52 | * @return mixed 53 | */ 54 | public function __get(string $propertyName) 55 | { 56 | if ('reflection' === $propertyName) { 57 | return $this->getReflection(); 58 | } 59 | 60 | return $this->$propertyName; 61 | } 62 | 63 | /** 64 | * @param mixed $value 65 | */ 66 | public function __set(string $propertyName, $value): void 67 | { 68 | $this->$propertyName = $value; 69 | } 70 | 71 | private function getReflection(): \ReflectionMethod 72 | { 73 | if (null === $this->reflection) { 74 | $this->reflection = new \ReflectionMethod($this->class, $this->name); 75 | if (\PHP_VERSION_ID < 80100) { 76 | $this->reflection->setAccessible(true); 77 | } 78 | } 79 | 80 | return $this->reflection; 81 | } 82 | 83 | protected function serializeToArray(): array 84 | { 85 | return [$this->class, $this->name]; 86 | } 87 | 88 | protected function unserializeFromArray(array $data): void 89 | { 90 | [$this->class, $this->name] = $data; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Metadata is a library for class/method/property metadata management in PHP 2 | ========================================================================== 3 | 4 | | [Master (2.x)][Master] | [1.x][1.x] | 5 | |:----------------:|:----------:| 6 | | [![Build status][Master image]][Master] | [![Build status][1.x image]][1.x] | 7 | | [![Coverage Status][Master coverage image]][Master coverage] | [![Coverage Status][1.x coverage image]][1.x coverage] | 8 | 9 | 10 | Overview 11 | -------- 12 | 13 | This library provides some commonly needed base classes for managing metadata 14 | for classes, methods and properties. The metadata can come from many different 15 | sources (annotations, YAML/XML/PHP configuration files). 16 | 17 | The metadata classes are used to abstract away that source and provide a common 18 | interface for all of them. 19 | 20 | Usage 21 | ----- 22 | 23 | The library provides three classes that you can extend to add your application 24 | specific properties, and flags: ``ClassMetadata``, ``MethodMetadata``, and 25 | ``PropertyMetadata`` 26 | 27 | After you have added, your properties in sub-classes, you also need to add 28 | ``DriverInterface`` implementations which know how to populate these classes 29 | from the different metadata sources. 30 | 31 | Finally, you can use the ``MetadataFactory`` to retrieve the metadata:: 32 | 33 | ```php 34 | getMetadataForClass('MyNamespace\MyObject'); 44 | ``` 45 | 46 | [Master image]: https://img.shields.io/travis/schmittjoh/metadata/master.svg?style=flat-square 47 | [Master]: https://travis-ci.org/schmittjoh/metadata 48 | [Master coverage image]: https://img.shields.io/scrutinizer/coverage/g/schmittjoh/metadata/master.svg?style=flat-square 49 | [Master coverage]: https://scrutinizer-ci.com/g/schmittjoh/metadata/?branch=master 50 | 51 | [1.x image]: https://img.shields.io/travis/schmittjoh/metadata/1.x.svg?style=flat-square 52 | [1.x]: https://github.com/schmittjoh/metadata/tree/1.x 53 | [1.x coverage image]: https://img.shields.io/scrutinizer/coverage/g/schmittjoh/metadata/1.x.svg?style=flat-square 54 | [1.x coverage]: https://scrutinizer-ci.com/g/schmittjoh/metadata/?branch=1.x 55 | 56 | -------------------------------------------------------------------------------- /src/Driver/FileLocator.php: -------------------------------------------------------------------------------- 1 | dirs = $dirs; 20 | } 21 | 22 | /** 23 | * @return array 24 | */ 25 | public function getPossibleFilesForClass(\ReflectionClass $class, string $extension): array 26 | { 27 | $possibleFiles = []; 28 | foreach ($this->dirs as $prefix => $dir) { 29 | if ('' !== $prefix && 0 !== strpos($class->getNamespaceName(), $prefix)) { 30 | continue; 31 | } 32 | 33 | $len = '' === $prefix ? 0 : strlen($prefix) + 1; 34 | $path = $dir . '/' . str_replace('\\', '.', substr($class->name, $len)) . '.' . $extension; 35 | $existsPath = file_exists($path); 36 | $possibleFiles[$path] = $existsPath; 37 | if ($existsPath) { 38 | return $possibleFiles; 39 | } 40 | } 41 | 42 | return $possibleFiles; 43 | } 44 | 45 | public function findFileForClass(\ReflectionClass $class, string $extension): ?string 46 | { 47 | foreach ($this->getPossibleFilesForClass($class, $extension) as $path => $existsPath) { 48 | if ($existsPath) { 49 | return $path; 50 | } 51 | } 52 | 53 | return null; 54 | } 55 | 56 | /** 57 | * {@inheritDoc} 58 | */ 59 | public function findAllClasses(string $extension): array 60 | { 61 | $classes = []; 62 | foreach ($this->dirs as $prefix => $dir) { 63 | /** @var \RecursiveIteratorIterator|\SplFileInfo[] $iterator */ 64 | $iterator = new \RecursiveIteratorIterator( 65 | new \RecursiveDirectoryIterator($dir), 66 | \RecursiveIteratorIterator::LEAVES_ONLY 67 | ); 68 | $nsPrefix = '' !== $prefix ? $prefix . '\\' : ''; 69 | foreach ($iterator as $file) { 70 | if (($fileName = $file->getBasename('.' . $extension)) === $file->getBasename()) { 71 | continue; 72 | } 73 | 74 | $classes[] = $nsPrefix . str_replace('.', '\\', $fileName); 75 | } 76 | } 77 | 78 | return $classes; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/ClassMetadata.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class ClassMetadata implements \Serializable 16 | { 17 | use SerializationHelper; 18 | 19 | /** 20 | * @var string 21 | */ 22 | public $name; 23 | 24 | /** 25 | * @var MethodMetadata[] 26 | */ 27 | public $methodMetadata = []; 28 | 29 | /** 30 | * @var PropertyMetadata[] 31 | */ 32 | public $propertyMetadata = []; 33 | 34 | /** 35 | * @var string[] 36 | */ 37 | public $fileResources = []; 38 | 39 | /** 40 | * @var int 41 | */ 42 | public $createdAt; 43 | 44 | public function __construct(string $name) 45 | { 46 | $this->name = $name; 47 | $this->createdAt = time(); 48 | } 49 | 50 | public function addMethodMetadata(MethodMetadata $metadata): void 51 | { 52 | $this->methodMetadata[$metadata->name] = $metadata; 53 | } 54 | 55 | public function addPropertyMetadata(PropertyMetadata $metadata): void 56 | { 57 | $this->propertyMetadata[$metadata->name] = $metadata; 58 | } 59 | 60 | public function isFresh(?int $timestamp = null): bool 61 | { 62 | if (null === $timestamp) { 63 | $timestamp = $this->createdAt; 64 | } 65 | 66 | foreach ($this->fileResources as $filepath) { 67 | if (!file_exists($filepath)) { 68 | return false; 69 | } 70 | 71 | if ($timestamp < filemtime($filepath)) { 72 | return false; 73 | } 74 | } 75 | 76 | return true; 77 | } 78 | 79 | protected function serializeToArray(): array 80 | { 81 | return [ 82 | $this->name, 83 | $this->methodMetadata, 84 | $this->propertyMetadata, 85 | $this->fileResources, 86 | $this->createdAt, 87 | ]; 88 | } 89 | 90 | protected function unserializeFromArray(array $data): void 91 | { 92 | [ 93 | $this->name, 94 | $this->methodMetadata, 95 | $this->propertyMetadata, 96 | $this->fileResources, 97 | $this->createdAt, 98 | ] = $data; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Cache/FileCache.php: -------------------------------------------------------------------------------- 1 | dir = rtrim($dir, '\\/'); 23 | } 24 | 25 | public function load(string $class): ?ClassMetadata 26 | { 27 | $path = $this->getCachePath($class); 28 | if (!is_readable($path)) { 29 | return null; 30 | } 31 | 32 | try { 33 | $metadata = include $path; 34 | if ($metadata instanceof ClassMetadata) { 35 | return $metadata; 36 | } 37 | 38 | // if the file does not return anything, the return value is integer `1`. 39 | } catch (\Error $e) { 40 | // ignore corrupted cache 41 | } 42 | 43 | return null; 44 | } 45 | 46 | public function put(ClassMetadata $metadata): void 47 | { 48 | if (!is_writable($this->dir)) { 49 | throw new \InvalidArgumentException(sprintf('The directory "%s" is not writable.', $this->dir)); 50 | } 51 | 52 | $path = $this->getCachePath($metadata->name); 53 | if (!is_writable(dirname($path))) { 54 | throw new \RuntimeException(sprintf('Cache file "%s" is not writable.', $path)); 55 | } 56 | 57 | $tmpFile = tempnam($this->dir, 'metadata-cache'); 58 | if (false === $tmpFile) { 59 | $this->evict($metadata->name); 60 | 61 | return; 62 | } 63 | 64 | $data = 'evict($metadata->name); 71 | 72 | return; 73 | } 74 | 75 | // Let's not break filesystems which do not support chmod. 76 | @chmod($tmpFile, 0666 & ~umask()); 77 | 78 | $this->renameFile($tmpFile, $path); 79 | } 80 | 81 | /** 82 | * Renames a file with fallback for windows 83 | */ 84 | private function renameFile(string $source, string $target): void 85 | { 86 | if (false === @rename($source, $target)) { 87 | if (defined('PHP_WINDOWS_VERSION_BUILD')) { 88 | if (false === copy($source, $target)) { 89 | throw new \RuntimeException(sprintf('(WIN) Could not write new cache file to %s.', $target)); 90 | } 91 | 92 | if (false === unlink($source)) { 93 | throw new \RuntimeException(sprintf('(WIN) Could not delete temp cache file to %s.', $source)); 94 | } 95 | } else { 96 | throw new \RuntimeException(sprintf('Could not write new cache file to %s.', $target)); 97 | } 98 | } 99 | } 100 | 101 | public function evict(string $class): void 102 | { 103 | $path = $this->getCachePath($class); 104 | if (file_exists($path)) { 105 | @unlink($path); 106 | } 107 | } 108 | 109 | public function clear(): bool 110 | { 111 | $result = true; 112 | $files = glob($this->dir . '/*.cache.php'); 113 | foreach ($files as $file) { 114 | if (is_file($file)) { 115 | $result = $result && @unlink($file); 116 | } 117 | } 118 | 119 | return $result; 120 | } 121 | 122 | /** 123 | * This function computes the cache file path. 124 | * 125 | * If anonymous class is to be cached, it contains invalid path characters that need to be removed/replaced 126 | * Example of anonymous class name: class@anonymous\x00/app/src/Controller/DefaultController.php0x7f82a7e026ec 127 | */ 128 | private function getCachePath(string $key): string 129 | { 130 | $fileName = str_replace(['\\', "\0", '@', '/', '$', '{', '}', ':'], '-', $key); 131 | 132 | return $this->dir . '/' . $fileName . '.cache.php'; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/MetadataFactory.php: -------------------------------------------------------------------------------- 1 | driver = $driver; 51 | $this->hierarchyMetadataClass = $hierarchyMetadataClass; 52 | $this->debug = $debug; 53 | } 54 | 55 | public function setIncludeInterfaces(bool $include): void 56 | { 57 | $this->includeInterfaces = $include; 58 | } 59 | 60 | public function setCache(CacheInterface $cache): void 61 | { 62 | $this->cache = $cache; 63 | } 64 | 65 | /** 66 | * {@inheritDoc} 67 | */ 68 | public function getMetadataForClass(string $className) 69 | { 70 | if (isset($this->loadedMetadata[$className])) { 71 | return $this->filterNullMetadata($this->loadedMetadata[$className]); 72 | } 73 | 74 | $metadata = null; 75 | foreach ($this->getClassHierarchy($className) as $class) { 76 | if (isset($this->loadedClassMetadata[$name = $class->getName()])) { 77 | if (null !== $classMetadata = $this->filterNullMetadata($this->loadedClassMetadata[$name])) { 78 | $this->addClassMetadata($metadata, $classMetadata); 79 | } 80 | 81 | continue; 82 | } 83 | 84 | // check the cache 85 | if (null !== $this->cache) { 86 | if (($classMetadata = $this->cache->load($class->getName())) instanceof NullMetadata) { 87 | $this->loadedClassMetadata[$name] = $classMetadata; 88 | continue; 89 | } 90 | 91 | if (null !== $classMetadata) { 92 | if (!$classMetadata instanceof ClassMetadata) { 93 | throw new \LogicException(sprintf( 94 | 'The cache must return instances of ClassMetadata for class %s, but got %s.', 95 | $className, 96 | var_export($classMetadata, true) 97 | )); 98 | } 99 | 100 | if ($this->debug && !$classMetadata->isFresh()) { 101 | $this->cache->evict($classMetadata->name); 102 | } else { 103 | $this->loadedClassMetadata[$name] = $classMetadata; 104 | $this->addClassMetadata($metadata, $classMetadata); 105 | continue; 106 | } 107 | } 108 | } 109 | 110 | // load from source 111 | if (null !== $classMetadata = $this->driver->loadMetadataForClass($class)) { 112 | $this->loadedClassMetadata[$name] = $classMetadata; 113 | $this->addClassMetadata($metadata, $classMetadata); 114 | 115 | if (null !== $this->cache) { 116 | $this->cache->put($classMetadata); 117 | } 118 | 119 | continue; 120 | } 121 | 122 | if (null !== $this->cache && !$this->debug) { 123 | $this->cache->put(new NullMetadata($class->getName())); 124 | } 125 | } 126 | 127 | if (null === $metadata) { 128 | $metadata = new NullMetadata($className); 129 | } 130 | 131 | return $this->filterNullMetadata($this->loadedMetadata[$className] = $metadata); 132 | } 133 | 134 | /** 135 | * {@inheritDoc} 136 | */ 137 | public function getAllClassNames(): array 138 | { 139 | if (!$this->driver instanceof AdvancedDriverInterface) { 140 | throw new \RuntimeException( 141 | sprintf('Driver "%s" must be an instance of "AdvancedDriverInterface".', get_class($this->driver)) 142 | ); 143 | } 144 | 145 | return $this->driver->getAllClassNames(); 146 | } 147 | 148 | /** 149 | * @param MergeableInterface|ClassHierarchyMetadata $metadata 150 | */ 151 | private function addClassMetadata(&$metadata, ClassMetadata $toAdd): void 152 | { 153 | if ($toAdd instanceof MergeableInterface) { 154 | if (null === $metadata) { 155 | $metadata = clone $toAdd; 156 | } else { 157 | $metadata->merge($toAdd); 158 | } 159 | } else { 160 | if (null === $metadata) { 161 | $class = $this->hierarchyMetadataClass; 162 | $metadata = new $class(); 163 | } 164 | 165 | $metadata->addClassMetadata($toAdd); 166 | } 167 | } 168 | 169 | /** 170 | * @return \ReflectionClass[] 171 | */ 172 | private function getClassHierarchy(string $class): array 173 | { 174 | $classes = []; 175 | $refl = new \ReflectionClass($class); 176 | 177 | do { 178 | $classes[] = $refl; 179 | $refl = $refl->getParentClass(); 180 | } while (false !== $refl); 181 | 182 | $classes = array_reverse($classes, false); 183 | 184 | if (!$this->includeInterfaces) { 185 | return $classes; 186 | } 187 | 188 | $addedInterfaces = []; 189 | $newHierarchy = []; 190 | 191 | foreach ($classes as $class) { 192 | foreach ($class->getInterfaces() as $interface) { 193 | if (isset($addedInterfaces[$interface->getName()])) { 194 | continue; 195 | } 196 | 197 | $addedInterfaces[$interface->getName()] = true; 198 | 199 | $newHierarchy[] = $interface; 200 | } 201 | 202 | $newHierarchy[] = $class; 203 | } 204 | 205 | return $newHierarchy; 206 | } 207 | 208 | /** 209 | * @param ClassMetadata|ClassHierarchyMetadata|MergeableInterface $metadata 210 | * 211 | * @return ClassMetadata|ClassHierarchyMetadata|MergeableInterface 212 | */ 213 | private function filterNullMetadata($metadata = null) 214 | { 215 | return !$metadata instanceof NullMetadata ? $metadata : null; 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.3.0](https://github.com/schmittjoh/metadata/tree/2.3.0) (2020-06-06) 4 | 5 | **Merged pull requests:** 6 | 7 | - Allow PSR-11 container [\#92](https://github.com/schmittjoh/metadata/pull/92) ([goetas](https://github.com/goetas)) 8 | 9 | ## [2.2.0](https://github.com/schmittjoh/metadata/tree/2.2.0) (2020-05-31) 10 | 11 | **Merged pull requests:** 12 | 13 | - Add cache support for anonymous classes \(\#79\) [\#90](https://github.com/schmittjoh/metadata/pull/90) ([goetas](https://github.com/goetas)) 14 | - Merge 1.x [\#91](https://github.com/schmittjoh/metadata/pull/91) ([goetas](https://github.com/goetas)) 15 | - add .gitattributes [\#86](https://github.com/schmittjoh/metadata/pull/86) ([Tobion](https://github.com/Tobion)) 16 | - Test enhancement [\#84](https://github.com/schmittjoh/metadata/pull/84) ([peter279k](https://github.com/peter279k)) 17 | - Improve cache error message [\#80](https://github.com/schmittjoh/metadata/pull/80) ([thePanz](https://github.com/thePanz)) 18 | 19 | ## [2.1.0](https://github.com/schmittjoh/metadata/tree/2.1.0) (2019-09-17) 20 | 21 | **Merged pull requests:** 22 | 23 | - make sure we only use the cache if write was successful [\#82](https://github.com/schmittjoh/metadata/pull/82) ([dbu](https://github.com/dbu)) 24 | - Handle errors when restoring from cache [\#81](https://github.com/schmittjoh/metadata/pull/81) ([dbu](https://github.com/dbu)) 25 | - Do not create MethodMetadata::$reflection on constructor/unserialize,… [\#78](https://github.com/schmittjoh/metadata/pull/78) ([ThomasNunninger](https://github.com/ThomasNunninger)) 26 | 27 | ## [2.0.0](https://github.com/schmittjoh/metadata/tree/2.0.0) (2018-11-09) 28 | 29 | No changes from **2.0.0-RC1** 30 | 31 | ## [1.7.0](https://github.com/schmittjoh/metadata/tree/1.7.0) (2018-10-26) 32 | **Merged pull requests:** 33 | 34 | - Allow Read-only Cache [\#74](https://github.com/schmittjoh/metadata/pull/74) ([goetas](https://github.com/goetas)) 35 | 36 | ## [2.0.0-RC1](https://github.com/schmittjoh/metadata/tree/2.0.0-RC1) (2018-10-17) 37 | **Merged pull requests:** 38 | 39 | - Moved to psr-4 [\#73](https://github.com/schmittjoh/metadata/pull/73) ([samnela](https://github.com/samnela)) 40 | 41 | ## [2.0.0-beta1](https://github.com/schmittjoh/metadata/tree/2.0.0-beta1) (2018-09-12) 42 | **Closed issues:** 43 | 44 | - Read-Only Filesystem Support [\#71](https://github.com/schmittjoh/metadata/issues/71) 45 | - Change license to MIT [\#68](https://github.com/schmittjoh/metadata/issues/68) 46 | - Composer.lock is out of date [\#55](https://github.com/schmittjoh/metadata/issues/55) 47 | - consider changing chmod to @chmod [\#50](https://github.com/schmittjoh/metadata/issues/50) 48 | - Big performance hit when upgrading from 1.4.2 to 1.5.0 [\#44](https://github.com/schmittjoh/metadata/issues/44) 49 | - metadata name not present leads to exception [\#39](https://github.com/schmittjoh/metadata/issues/39) 50 | 51 | **Merged pull requests:** 52 | 53 | - Allow Read-only Cache [\#72](https://github.com/schmittjoh/metadata/pull/72) ([pdugas](https://github.com/pdugas)) 54 | - Code style [\#70](https://github.com/schmittjoh/metadata/pull/70) ([goetas](https://github.com/goetas)) 55 | - Change license to MIT [\#69](https://github.com/schmittjoh/metadata/pull/69) ([goetas](https://github.com/goetas)) 56 | - simplified class metadata [\#67](https://github.com/schmittjoh/metadata/pull/67) ([goetas](https://github.com/goetas)) 57 | - Fix an exception message [\#65](https://github.com/schmittjoh/metadata/pull/65) ([hason](https://github.com/hason)) 58 | - Actualized version constant. [\#64](https://github.com/schmittjoh/metadata/pull/64) ([Aliance](https://github.com/Aliance)) 59 | 60 | ## [1.6.0](https://github.com/schmittjoh/metadata/tree/1.6.0) (2016-12-05) 61 | **Closed issues:** 62 | 63 | - Consider switching to the MIT/BSD license or a dual license otherwise [\#58](https://github.com/schmittjoh/metadata/issues/58) 64 | - Unexpected return value [\#52](https://github.com/schmittjoh/metadata/issues/52) 65 | - Why 0666 mode for cache file [\#48](https://github.com/schmittjoh/metadata/issues/48) 66 | - Tons of I/O operations caused by NullMetadata [\#45](https://github.com/schmittjoh/metadata/issues/45) 67 | 68 | **Merged pull requests:** 69 | 70 | - Add PsrCacheAdapter [\#63](https://github.com/schmittjoh/metadata/pull/63) ([nicolas-grekas](https://github.com/nicolas-grekas)) 71 | - 50 suspress chmod warning [\#53](https://github.com/schmittjoh/metadata/pull/53) ([gusdecool](https://github.com/gusdecool)) 72 | - Adaption for complying with SPDX identifiers [\#51](https://github.com/schmittjoh/metadata/pull/51) ([valioDOTch](https://github.com/valioDOTch)) 73 | 74 | ## [1.5.1](https://github.com/schmittjoh/metadata/tree/1.5.1) (2014-07-12) 75 | **Merged pull requests:** 76 | 77 | - Added more PHP versions and HHVM [\#47](https://github.com/schmittjoh/metadata/pull/47) ([Nyholm](https://github.com/Nyholm)) 78 | - Fix NullMetadata performance issue [\#46](https://github.com/schmittjoh/metadata/pull/46) ([adrienbrault](https://github.com/adrienbrault)) 79 | - Fixed logic bug. [\#41](https://github.com/schmittjoh/metadata/pull/41) ([flip111](https://github.com/flip111)) 80 | - Update FileCache.php added fallback option when rename fails on windows [\#40](https://github.com/schmittjoh/metadata/pull/40) ([flip111](https://github.com/flip111)) 81 | 82 | ## [1.5.0](https://github.com/schmittjoh/metadata/tree/1.5.0) (2013-11-05) 83 | **Closed issues:** 84 | 85 | - Branch alias [\#38](https://github.com/schmittjoh/metadata/issues/38) 86 | 87 | **Merged pull requests:** 88 | 89 | - Don't make MetadataFactory final [\#37](https://github.com/schmittjoh/metadata/pull/37) ([bakura10](https://github.com/bakura10)) 90 | - Cache when there is no metadata for a class [\#36](https://github.com/schmittjoh/metadata/pull/36) ([adrienbrault](https://github.com/adrienbrault)) 91 | - Allow to add drivers to a driver chain [\#35](https://github.com/schmittjoh/metadata/pull/35) ([bakura10](https://github.com/bakura10)) 92 | 93 | ## [1.4.2](https://github.com/schmittjoh/metadata/tree/1.4.2) (2013-09-13) 94 | **Closed issues:** 95 | 96 | - Update changelog [\#33](https://github.com/schmittjoh/metadata/issues/33) 97 | - Error in Symfony2's production environment \(only\) caused with version \>= 1.4.0 [\#32](https://github.com/schmittjoh/metadata/issues/32) 98 | 99 | **Merged pull requests:** 100 | 101 | - Set cache files to be world readable [\#34](https://github.com/schmittjoh/metadata/pull/34) ([tommygnr](https://github.com/tommygnr)) 102 | 103 | ## [1.4.1](https://github.com/schmittjoh/metadata/tree/1.4.1) (2013-08-27) 104 | ## [1.4.0](https://github.com/schmittjoh/metadata/tree/1.4.0) (2013-08-25) 105 | ## [1.3.0](https://github.com/schmittjoh/metadata/tree/1.3.0) (2013-01-22) 106 | **Closed issues:** 107 | 108 | - Ability to eager-load possible metadata [\#19](https://github.com/schmittjoh/metadata/issues/19) 109 | 110 | **Merged pull requests:** 111 | 112 | - misc cleanup [\#23](https://github.com/schmittjoh/metadata/pull/23) ([vicb](https://github.com/vicb)) 113 | - \[Cache\] Remove a race condition [\#22](https://github.com/schmittjoh/metadata/pull/22) ([vicb](https://github.com/vicb)) 114 | - Added configs for ci services [\#21](https://github.com/schmittjoh/metadata/pull/21) ([j](https://github.com/j)) 115 | - Advanced metadata implementation. [\#20](https://github.com/schmittjoh/metadata/pull/20) ([j](https://github.com/j)) 116 | - Remove incorrect docblocks [\#18](https://github.com/schmittjoh/metadata/pull/18) ([adrienbrault](https://github.com/adrienbrault)) 117 | 118 | ## [1.2.0-RC](https://github.com/schmittjoh/metadata/tree/1.2.0-RC) (2012-08-21) 119 | **Closed issues:** 120 | 121 | - install version 1.0.0 with composer [\#9](https://github.com/schmittjoh/metadata/issues/9) 122 | - create version/tag 1.1.1 [\#3](https://github.com/schmittjoh/metadata/issues/3) 123 | 124 | **Merged pull requests:** 125 | 126 | - Added the branch alias and changed the constraint on common [\#8](https://github.com/schmittjoh/metadata/pull/8) ([stof](https://github.com/stof)) 127 | - Add trait test [\#6](https://github.com/schmittjoh/metadata/pull/6) ([Seldaek](https://github.com/Seldaek)) 128 | - Fix locating files for classes without namespace [\#5](https://github.com/schmittjoh/metadata/pull/5) ([Seldaek](https://github.com/Seldaek)) 129 | - Add ApcCache [\#4](https://github.com/schmittjoh/metadata/pull/4) ([henrikbjorn](https://github.com/henrikbjorn)) 130 | 131 | ## [1.1.1](https://github.com/schmittjoh/metadata/tree/1.1.1) (2012-01-02) 132 | **Closed issues:** 133 | 134 | - More documentation requested [\#1](https://github.com/schmittjoh/metadata/issues/1) 135 | 136 | **Merged pull requests:** 137 | 138 | - Add composer.json [\#2](https://github.com/schmittjoh/metadata/pull/2) ([Seldaek](https://github.com/Seldaek)) 139 | 140 | ## [1.1.0](https://github.com/schmittjoh/metadata/tree/1.1.0) (2011-10-04) 141 | ## [1.0.0](https://github.com/schmittjoh/metadata/tree/1.0.0) (2011-07-09) 142 | 143 | 144 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* 145 | --------------------------------------------------------------------------------