├── LICENSE ├── README.md ├── composer.json └── src └── ProxyManager ├── Autoloader ├── Autoloader.php └── AutoloaderInterface.php ├── Configuration.php ├── Exception ├── DisabledMethodException.php ├── ExceptionInterface.php ├── FileNotWritableException.php ├── InvalidProxiedClassException.php ├── InvalidProxyDirectoryException.php └── UnsupportedProxiedClassException.php ├── Factory ├── AbstractBaseFactory.php ├── AccessInterceptorScopeLocalizerFactory.php ├── AccessInterceptorValueHolderFactory.php ├── LazyLoadingGhostFactory.php ├── LazyLoadingValueHolderFactory.php ├── NullObjectFactory.php ├── RemoteObject │ ├── Adapter │ │ ├── BaseAdapter.php │ │ ├── JsonRpc.php │ │ ├── Soap.php │ │ └── XmlRpc.php │ └── AdapterInterface.php └── RemoteObjectFactory.php ├── FileLocator ├── FileLocator.php └── FileLocatorInterface.php ├── Generator ├── ClassGenerator.php ├── MagicMethodGenerator.php ├── MethodGenerator.php ├── Util │ ├── ClassGeneratorUtils.php │ ├── IdentifierSuffixer.php │ ├── ProxiedMethodReturnExpression.php │ └── UniqueIdentifierGenerator.php └── ValueGenerator.php ├── GeneratorStrategy ├── BaseGeneratorStrategy.php ├── EvaluatingGeneratorStrategy.php ├── FileWriterGeneratorStrategy.php └── GeneratorStrategyInterface.php ├── Inflector ├── ClassNameInflector.php ├── ClassNameInflectorInterface.php └── Util │ ├── ParameterEncoder.php │ └── ParameterHasher.php ├── Proxy ├── AccessInterceptorInterface.php ├── AccessInterceptorValueHolderInterface.php ├── Exception │ └── RemoteObjectException.php ├── FallbackValueHolderInterface.php ├── GhostObjectInterface.php ├── LazyLoadingInterface.php ├── NullObjectInterface.php ├── ProxyInterface.php ├── RemoteObjectInterface.php ├── SmartReferenceInterface.php ├── ValueHolderInterface.php └── VirtualProxyInterface.php ├── ProxyGenerator ├── AccessInterceptor │ ├── MethodGenerator │ │ ├── MagicWakeup.php │ │ ├── SetMethodPrefixInterceptor.php │ │ └── SetMethodSuffixInterceptor.php │ └── PropertyGenerator │ │ ├── MethodPrefixInterceptors.php │ │ └── MethodSuffixInterceptors.php ├── AccessInterceptorScopeLocalizer │ └── MethodGenerator │ │ ├── BindProxyProperties.php │ │ ├── InterceptedMethod.php │ │ ├── MagicClone.php │ │ ├── MagicGet.php │ │ ├── MagicIsset.php │ │ ├── MagicSet.php │ │ ├── MagicSleep.php │ │ ├── MagicUnset.php │ │ ├── StaticProxyConstructor.php │ │ └── Util │ │ └── InterceptorGenerator.php ├── AccessInterceptorScopeLocalizerGenerator.php ├── AccessInterceptorValueHolder │ └── MethodGenerator │ │ ├── InterceptedMethod.php │ │ ├── MagicClone.php │ │ ├── MagicGet.php │ │ ├── MagicIsset.php │ │ ├── MagicSet.php │ │ ├── MagicUnset.php │ │ ├── StaticProxyConstructor.php │ │ └── Util │ │ └── InterceptorGenerator.php ├── AccessInterceptorValueHolderGenerator.php ├── Assertion │ └── CanProxyAssertion.php ├── LazyLoading │ └── MethodGenerator │ │ └── StaticProxyConstructor.php ├── LazyLoadingGhost │ ├── MethodGenerator │ │ ├── CallInitializer.php │ │ ├── GetProxyInitializer.php │ │ ├── InitializeProxy.php │ │ ├── IsProxyInitialized.php │ │ ├── MagicClone.php │ │ ├── MagicGet.php │ │ ├── MagicIsset.php │ │ ├── MagicSet.php │ │ ├── MagicSleep.php │ │ ├── MagicUnset.php │ │ ├── SetProxyInitializer.php │ │ └── SkipDestructor.php │ └── PropertyGenerator │ │ ├── InitializationTracker.php │ │ ├── InitializerProperty.php │ │ ├── PrivatePropertiesMap.php │ │ └── ProtectedPropertiesMap.php ├── LazyLoadingGhostGenerator.php ├── LazyLoadingValueHolder │ ├── MethodGenerator │ │ ├── GetProxyInitializer.php │ │ ├── InitializeProxy.php │ │ ├── IsProxyInitialized.php │ │ ├── LazyLoadingMethodInterceptor.php │ │ ├── MagicClone.php │ │ ├── MagicGet.php │ │ ├── MagicIsset.php │ │ ├── MagicSet.php │ │ ├── MagicSleep.php │ │ ├── MagicUnset.php │ │ ├── SetProxyInitializer.php │ │ └── SkipDestructor.php │ └── PropertyGenerator │ │ ├── InitializerProperty.php │ │ └── ValueHolderProperty.php ├── LazyLoadingValueHolderGenerator.php ├── NullObject │ └── MethodGenerator │ │ ├── NullObjectMethodInterceptor.php │ │ └── StaticProxyConstructor.php ├── NullObjectGenerator.php ├── PropertyGenerator │ └── PublicPropertiesMap.php ├── ProxyGeneratorInterface.php ├── RemoteObject │ ├── MethodGenerator │ │ ├── MagicGet.php │ │ ├── MagicIsset.php │ │ ├── MagicSet.php │ │ ├── MagicUnset.php │ │ ├── RemoteObjectMethod.php │ │ └── StaticProxyConstructor.php │ └── PropertyGenerator │ │ └── AdapterProperty.php ├── RemoteObjectGenerator.php ├── Util │ ├── GetMethodIfExists.php │ ├── Properties.php │ ├── ProxiedMethodsFilter.php │ ├── PublicScopeSimulator.php │ └── UnsetPropertiesGenerator.php └── ValueHolder │ └── MethodGenerator │ ├── Constructor.php │ ├── GetWrappedValueHolderValue.php │ └── MagicSleep.php ├── Signature ├── ClassSignatureGenerator.php ├── ClassSignatureGeneratorInterface.php ├── Exception │ ├── ExceptionInterface.php │ ├── InvalidSignatureException.php │ └── MissingSignatureException.php ├── SignatureChecker.php ├── SignatureCheckerInterface.php ├── SignatureGenerator.php └── SignatureGeneratorInterface.php ├── Stub └── EmptyClassStub.php └── Version.php /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Marco Pivetta 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FriendsOfPHP / Proxy Manager LTS 2 | 3 | This package is a fork of the excellent [`ocramius/proxy-manager`](https://github.com/Ocramius/ProxyManager/) library 4 | that adds long term support for a wider range of PHP versions. 5 | 6 | Unless they're caused by this very fork, please report issues and submit new features to the origin library. 7 | 8 | This fork: 9 | - maintains compatibility with PHP `>=7.1`; 10 | supporting new versions of PHP is considered as a bugfix; 11 | - won't bump the minimum supported version of PHP in a minor release; 12 | - does not depend on Composer 2, thus can be used with Composer 1 if you need more time to migrate; 13 | - uses a versioning policy that is friendly to progressive migrations 14 | while providing the latest improvements from the origin lib. 15 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "friendsofphp/proxy-manager-lts", 3 | "description": "Adding support for a wider range of PHP versions to ocramius/proxy-manager", 4 | "type": "library", 5 | "license": "MIT", 6 | "homepage": "https://github.com/FriendsOfPHP/proxy-manager-lts", 7 | "keywords": [ 8 | "proxy", 9 | "proxy pattern", 10 | "service proxies", 11 | "lazy loading", 12 | "aop" 13 | ], 14 | "authors": [ 15 | { 16 | "name": "Marco Pivetta", 17 | "email": "ocramius@gmail.com", 18 | "homepage": "https://ocramius.github.io/" 19 | }, 20 | { 21 | "name": "Nicolas Grekas", 22 | "email": "p@tchwork.com" 23 | } 24 | ], 25 | "replace": { 26 | "ocramius/proxy-manager": "^2.1" 27 | }, 28 | "require": { 29 | "php": ">=7.1", 30 | "laminas/laminas-code": "~3.4.1|^4.0", 31 | "symfony/filesystem": "^4.4.17|^5.0|^6.0|^7.0" 32 | }, 33 | "conflict": { 34 | "zendframework/zend-stdlib": "<3.2.1", 35 | "laminas/laminas-stdlib": "<3.2.1" 36 | }, 37 | "require-dev": { 38 | "ext-phar": "*", 39 | "symfony/phpunit-bridge": "^5.4|^6.0|^7.0" 40 | }, 41 | "autoload": { 42 | "psr-4": { 43 | "ProxyManager\\": "src/ProxyManager" 44 | } 45 | }, 46 | "autoload-dev": { 47 | "psr-4": { 48 | "ProxyManagerTest\\": "tests/ProxyManagerTest", 49 | "ProxyManagerTestAsset\\": "tests/ProxyManagerTestAsset", 50 | "Laminas\\Server\\": "tests/Stubbed/Laminas/Server" 51 | } 52 | }, 53 | "minimum-stability": "dev", 54 | "extra": { 55 | "thanks": { 56 | "name": "ocramius/proxy-manager", 57 | "url": "https://github.com/Ocramius/ProxyManager" 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/ProxyManager/Autoloader/Autoloader.php: -------------------------------------------------------------------------------- 1 | fileLocator = $fileLocator; 21 | $this->classNameInflector = $classNameInflector; 22 | } 23 | 24 | public function __invoke(string $className): bool 25 | { 26 | if (class_exists($className, false) || ! $this->classNameInflector->isProxyClassName($className)) { 27 | return false; 28 | } 29 | 30 | $file = $this->fileLocator->getProxyFileName($className); 31 | 32 | if (! file_exists($file)) { 33 | return false; 34 | } 35 | 36 | /* @noinspection PhpIncludeInspection */ 37 | /* @noinspection UsingInclusionOnceReturnValueInspection */ 38 | return (bool) require_once $file; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/ProxyManager/Autoloader/AutoloaderInterface.php: -------------------------------------------------------------------------------- 1 | proxyAutoloader = $proxyAutoloader; 42 | } 43 | 44 | public function getProxyAutoloader(): AutoloaderInterface 45 | { 46 | return $this->proxyAutoloader 47 | ?? $this->proxyAutoloader = new Autoloader( 48 | new FileLocator($this->getProxiesTargetDir()), 49 | $this->getClassNameInflector() 50 | ); 51 | } 52 | 53 | public function setProxiesNamespace(string $proxiesNamespace): void 54 | { 55 | $this->proxiesNamespace = $proxiesNamespace; 56 | } 57 | 58 | public function getProxiesNamespace(): string 59 | { 60 | return $this->proxiesNamespace; 61 | } 62 | 63 | public function setProxiesTargetDir(string $proxiesTargetDir): void 64 | { 65 | $this->proxiesTargetDir = $proxiesTargetDir; 66 | } 67 | 68 | public function getProxiesTargetDir(): string 69 | { 70 | return $this->proxiesTargetDir 71 | ?? $this->proxiesTargetDir = sys_get_temp_dir(); 72 | } 73 | 74 | public function setGeneratorStrategy(GeneratorStrategyInterface $generatorStrategy): void 75 | { 76 | $this->generatorStrategy = $generatorStrategy; 77 | } 78 | 79 | public function getGeneratorStrategy(): GeneratorStrategyInterface 80 | { 81 | return $this->generatorStrategy 82 | ?? $this->generatorStrategy = new EvaluatingGeneratorStrategy(); 83 | } 84 | 85 | public function setClassNameInflector(ClassNameInflectorInterface $classNameInflector): void 86 | { 87 | $this->classNameInflector = $classNameInflector; 88 | } 89 | 90 | public function getClassNameInflector(): ClassNameInflectorInterface 91 | { 92 | return $this->classNameInflector 93 | ?? $this->classNameInflector = new ClassNameInflector($this->getProxiesNamespace()); 94 | } 95 | 96 | public function setSignatureGenerator(SignatureGeneratorInterface $signatureGenerator): void 97 | { 98 | $this->signatureGenerator = $signatureGenerator; 99 | } 100 | 101 | public function getSignatureGenerator(): SignatureGeneratorInterface 102 | { 103 | return $this->signatureGenerator 104 | ?? $this->signatureGenerator = new SignatureGenerator(); 105 | } 106 | 107 | public function setSignatureChecker(SignatureCheckerInterface $signatureChecker): void 108 | { 109 | $this->signatureChecker = $signatureChecker; 110 | } 111 | 112 | public function getSignatureChecker(): SignatureCheckerInterface 113 | { 114 | return $this->signatureChecker 115 | ?? $this->signatureChecker = new SignatureChecker($this->getSignatureGenerator()); 116 | } 117 | 118 | public function setClassSignatureGenerator(ClassSignatureGeneratorInterface $classSignatureGenerator): void 119 | { 120 | $this->classSignatureGenerator = $classSignatureGenerator; 121 | } 122 | 123 | public function getClassSignatureGenerator(): ClassSignatureGeneratorInterface 124 | { 125 | return $this->classSignatureGenerator 126 | ?? $this->classSignatureGenerator = new ClassSignatureGenerator($this->getSignatureGenerator()); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/ProxyManager/Exception/DisabledMethodException.php: -------------------------------------------------------------------------------- 1 | getMessage(), 0, $previous); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ProxyManager/Exception/InvalidProxiedClassException.php: -------------------------------------------------------------------------------- 1 | getName())); 24 | } 25 | 26 | public static function finalClassNotSupported(ReflectionClass $reflection): self 27 | { 28 | return new self(sprintf('Provided class "%s" is final and cannot be proxied', $reflection->getName())); 29 | } 30 | 31 | public static function abstractProtectedMethodsNotSupported(ReflectionClass $reflection): self 32 | { 33 | return new self(sprintf( 34 | 'Provided class "%s" has following protected abstract methods, and therefore cannot be proxied:' . "\n%s", 35 | $reflection->getName(), 36 | implode( 37 | "\n", 38 | array_map( 39 | static function (ReflectionMethod $reflectionMethod): string { 40 | return $reflectionMethod->getDeclaringClass()->getName() . '::' . $reflectionMethod->getName(); 41 | }, 42 | array_filter( 43 | $reflection->getMethods(), 44 | static function (ReflectionMethod $method): bool { 45 | return $method->isAbstract() && $method->isProtected(); 46 | } 47 | ) 48 | ) 49 | ) 50 | )); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/ProxyManager/Exception/InvalidProxyDirectoryException.php: -------------------------------------------------------------------------------- 1 | getName(), 27 | $property->getDeclaringClass()->getName() 28 | ) 29 | ); 30 | } 31 | 32 | public static function nonReferenceableLocalizedReflectionProperties( 33 | ReflectionClass $class, 34 | Properties $properties 35 | ): self { 36 | return new self(sprintf( 37 | 'Cannot create references for following properties of class %s: %s', 38 | $class->getName(), 39 | implode(', ', array_map(static function (ReflectionProperty $property): string { 40 | return $property->getName(); 41 | }, $properties->getInstanceProperties())) 42 | )); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/ProxyManager/Factory/AbstractBaseFactory.php: -------------------------------------------------------------------------------- 1 | 34 | * @psalm-var array 35 | */ 36 | private $checkedClasses = []; 37 | 38 | public function __construct(?Configuration $configuration = null) 39 | { 40 | $this->configuration = $configuration ?? new Configuration(); 41 | } 42 | 43 | /** 44 | * Generate a proxy from a class name 45 | * 46 | * @param array $proxyOptions 47 | * @psalm-param class-string $className 48 | * 49 | * @psalm-return class-string 50 | * 51 | * @throws InvalidSignatureException 52 | * @throws MissingSignatureException 53 | * @throws OutOfBoundsException 54 | * 55 | * @psalm-template RealObjectType of object 56 | */ 57 | protected function generateProxy(string $className, array $proxyOptions = []): string 58 | { 59 | $cacheKey = $proxyOptions ? sha1(serialize([$className, $proxyOptions])) : $className; 60 | 61 | if (array_key_exists($cacheKey, $this->checkedClasses)) { 62 | $generatedClassName = $this->checkedClasses[$cacheKey]; 63 | 64 | assert(is_a($generatedClassName, $className, true)); 65 | 66 | return $generatedClassName; 67 | } 68 | 69 | $proxyParameters = [ 70 | 'className' => $className, 71 | 'factory' => static::class, 72 | 'proxyManagerVersion' => Version::getVersion(), 73 | 'proxyOptions' => $proxyOptions, 74 | ]; 75 | $proxyClassName = $this 76 | ->configuration 77 | ->getClassNameInflector() 78 | ->getProxyClassName($className, $proxyParameters); 79 | 80 | if (! class_exists($proxyClassName)) { 81 | $this->generateProxyClass( 82 | $proxyClassName, 83 | $className, 84 | $proxyParameters, 85 | $proxyOptions 86 | ); 87 | } 88 | 89 | $this 90 | ->configuration 91 | ->getSignatureChecker() 92 | ->checkSignature(new ReflectionClass($proxyClassName), $proxyParameters); 93 | 94 | return $this->checkedClasses[$cacheKey] = $proxyClassName; 95 | } 96 | 97 | abstract protected function getGenerator(): ProxyGeneratorInterface; 98 | 99 | /** 100 | * Generates the provided `$proxyClassName` from the given `$className` and `$proxyParameters` 101 | * 102 | * @param array $proxyParameters 103 | * @param array $proxyOptions 104 | * @psalm-param class-string $proxyClassName 105 | * @psalm-param class-string $className 106 | */ 107 | private function generateProxyClass( 108 | string $proxyClassName, 109 | string $className, 110 | array $proxyParameters, 111 | array $proxyOptions = [] 112 | ): void { 113 | $className = $this->configuration->getClassNameInflector()->getUserClassName($className); 114 | $phpClass = new ClassGenerator($proxyClassName); 115 | 116 | /** @psalm-suppress TooManyArguments - generator interface was not updated due to BC compliance */ 117 | $this->getGenerator()->generate(new ReflectionClass($className), $phpClass, $proxyOptions); 118 | 119 | $phpClass = $this->configuration->getClassSignatureGenerator()->addSignature($phpClass, $proxyParameters); 120 | 121 | /** @psalm-suppress TooManyArguments - generator interface was not updated due to BC compliance */ 122 | $this->configuration->getGeneratorStrategy()->generate($phpClass, $proxyOptions); 123 | 124 | $autoloader = $this->configuration->getProxyAutoloader(); 125 | 126 | $autoloader($proxyClassName); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/ProxyManager/Factory/AccessInterceptorScopeLocalizerFactory.php: -------------------------------------------------------------------------------- 1 | generator = new AccessInterceptorScopeLocalizerGenerator(); 30 | } 31 | 32 | /** 33 | * @param object $instance the object to be localized within the access interceptor 34 | * @param array $prefixInterceptors an array (indexed by method name) of interceptor closures to be called 35 | * before method logic is executed 36 | * @param array $suffixInterceptors an array (indexed by method name) of interceptor closures to be called 37 | * after method logic is executed 38 | * @psalm-param RealObjectType $instance 39 | * @psalm-param array=, 41 | * RealObjectType=, 42 | * string=, 43 | * array=, 44 | * bool= 45 | * ) : mixed> $prefixInterceptors 46 | * @psalm-param array=, 48 | * RealObjectType=, 49 | * string=, 50 | * array=, 51 | * mixed=, 52 | * bool= 53 | * ) : mixed> $suffixInterceptors 54 | * 55 | * @psalm-return RealObjectType&AccessInterceptorInterface 56 | * 57 | * @throws InvalidSignatureException 58 | * @throws MissingSignatureException 59 | * @throws OutOfBoundsException 60 | * 61 | * @psalm-template RealObjectType of object 62 | * @psalm-suppress MixedInferredReturnType We ignore type checks here, since `staticProxyConstructor` is not 63 | * interfaced (by design) 64 | */ 65 | public function createProxy( 66 | $instance, 67 | array $prefixInterceptors = [], 68 | array $suffixInterceptors = [] 69 | ): AccessInterceptorInterface { 70 | $proxyClassName = $this->generateProxy(get_class($instance)); 71 | 72 | /** 73 | * We ignore type checks here, since `staticProxyConstructor` is not interfaced (by design) 74 | * 75 | * @psalm-suppress MixedMethodCall 76 | * @psalm-suppress MixedReturnStatement 77 | */ 78 | return $proxyClassName::staticProxyConstructor($instance, $prefixInterceptors, $suffixInterceptors); 79 | } 80 | 81 | protected function getGenerator(): ProxyGeneratorInterface 82 | { 83 | return $this->generator; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/ProxyManager/Factory/AccessInterceptorValueHolderFactory.php: -------------------------------------------------------------------------------- 1 | generator = new AccessInterceptorValueHolderGenerator(); 32 | } 33 | 34 | /** 35 | * @param object $instance the object to be wrapped within the value holder 36 | * @param array $prefixInterceptors an array (indexed by method name) of interceptor closures to be called 37 | * before method logic is executed 38 | * @param array $suffixInterceptors an array (indexed by method name) of interceptor closures to be called 39 | * after method logic is executed 40 | * @psalm-param RealObjectType $instance 41 | * @psalm-param array=, 43 | * RealObjectType=, 44 | * string=, 45 | * array=, 46 | * bool= 47 | * ) : mixed> $prefixInterceptors 48 | * @psalm-param array=, 50 | * RealObjectType=, 51 | * string=, 52 | * array=, 53 | * mixed=, 54 | * bool= 55 | * ) : mixed> $suffixInterceptors 56 | * 57 | * @psalm-return RealObjectType&AccessInterceptorInterface&ValueHolderInterface&AccessInterceptorValueHolderInterface 58 | * 59 | * @throws InvalidSignatureException 60 | * @throws MissingSignatureException 61 | * @throws OutOfBoundsException 62 | * 63 | * @psalm-template RealObjectType of object 64 | * @psalm-suppress MixedInferredReturnType We ignore type checks here, since `staticProxyConstructor` is not 65 | * interfaced (by design) 66 | */ 67 | public function createProxy( 68 | $instance, 69 | array $prefixInterceptors = [], 70 | array $suffixInterceptors = [] 71 | ): AccessInterceptorValueHolderInterface { 72 | $proxyClassName = $this->generateProxy(get_class($instance)); 73 | 74 | /** 75 | * We ignore type checks here, since `staticProxyConstructor` is not interfaced (by design) 76 | * 77 | * @psalm-suppress MixedMethodCall 78 | * @psalm-suppress MixedReturnStatement 79 | */ 80 | return $proxyClassName::staticProxyConstructor($instance, $prefixInterceptors, $suffixInterceptors); 81 | } 82 | 83 | protected function getGenerator(): ProxyGeneratorInterface 84 | { 85 | return $this->generator; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/ProxyManager/Factory/LazyLoadingValueHolderFactory.php: -------------------------------------------------------------------------------- 1 | generator = new LazyLoadingValueHolderGenerator(); 26 | } 27 | 28 | /** 29 | * @param array $proxyOptions 30 | * @psalm-param class-string $className 31 | * @psalm-param Closure( 32 | * RealObjectType|null=, 33 | * RealObjectType&ValueHolderInterface&VirtualProxyInterface=, 34 | * string=, 35 | * array=, 36 | * ?Closure= 37 | * ) : bool $initializer 38 | * @psalm-param array{skipDestructor?: bool, fluentSafe?: bool} $proxyOptions 39 | * 40 | * @psalm-return RealObjectType&ValueHolderInterface&VirtualProxyInterface 41 | * 42 | * @psalm-template RealObjectType of object 43 | * @psalm-suppress MixedInferredReturnType We ignore type checks here, since `staticProxyConstructor` is not 44 | * interfaced (by design) 45 | */ 46 | public function createProxy( 47 | string $className, 48 | Closure $initializer, 49 | array $proxyOptions = [] 50 | ): VirtualProxyInterface { 51 | $proxyClassName = $this->generateProxy($className, $proxyOptions); 52 | 53 | /** 54 | * We ignore type checks here, since `staticProxyConstructor` is not interfaced (by design) 55 | * 56 | * @psalm-suppress MixedMethodCall 57 | * @psalm-suppress MixedReturnStatement 58 | */ 59 | return $proxyClassName::staticProxyConstructor($initializer); 60 | } 61 | 62 | protected function getGenerator(): ProxyGeneratorInterface 63 | { 64 | return $this->generator; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/ProxyManager/Factory/NullObjectFactory.php: -------------------------------------------------------------------------------- 1 | generator = new NullObjectGenerator(); 30 | } 31 | 32 | /** 33 | * @param object|string $instanceOrClassName the object to be wrapped or interface to transform to null object 34 | * @psalm-param RealObjectType|class-string $instanceOrClassName 35 | * 36 | * @psalm-return RealObjectType&NullObjectInterface 37 | * 38 | * @throws InvalidSignatureException 39 | * @throws MissingSignatureException 40 | * @throws OutOfBoundsException 41 | * 42 | * @psalm-template RealObjectType of object 43 | * @psalm-suppress MixedInferredReturnType We ignore type checks here, since `staticProxyConstructor` is not 44 | * interfaced (by design) 45 | */ 46 | public function createProxy($instanceOrClassName): NullObjectInterface 47 | { 48 | $className = is_object($instanceOrClassName) ? get_class($instanceOrClassName) : $instanceOrClassName; 49 | $proxyClassName = $this->generateProxy($className); 50 | 51 | /** 52 | * We ignore type checks here, since `staticProxyConstructor` is not interfaced (by design) 53 | * 54 | * @psalm-suppress MixedMethodCall 55 | * @psalm-suppress MixedReturnStatement 56 | */ 57 | return $proxyClassName::staticProxyConstructor(); 58 | } 59 | 60 | protected function getGenerator(): ProxyGeneratorInterface 61 | { 62 | return $this->generator; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/ProxyManager/Factory/RemoteObject/Adapter/BaseAdapter.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | protected $map = []; 25 | 26 | /** 27 | * Constructor 28 | * 29 | * @param array $map map of service names to their aliases 30 | */ 31 | public function __construct(Client $client, array $map = []) 32 | { 33 | $this->client = $client; 34 | $this->map = $map; 35 | } 36 | 37 | /** 38 | * {@inheritDoc} 39 | */ 40 | public function call(string $wrappedClass, string $method, array $params = []) 41 | { 42 | $serviceName = $this->getServiceName($wrappedClass, $method); 43 | 44 | if (array_key_exists($serviceName, $this->map)) { 45 | $serviceName = $this->map[$serviceName]; 46 | } 47 | 48 | return $this->client->call($serviceName, $params); 49 | } 50 | 51 | /** 52 | * Get the service name will be used by the adapter 53 | */ 54 | abstract protected function getServiceName(string $wrappedClass, string $method): string; 55 | } 56 | -------------------------------------------------------------------------------- /src/ProxyManager/Factory/RemoteObject/Adapter/JsonRpc.php: -------------------------------------------------------------------------------- 1 | $params 16 | * 17 | * Due to BC compliance, we cannot add a native `: mixed` return type declaration here 18 | * 19 | * phpcs:disable SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint 20 | * 21 | * @return mixed 22 | */ 23 | public function call(string $wrappedClass, string $method, array $params = []); 24 | } 25 | -------------------------------------------------------------------------------- /src/ProxyManager/Factory/RemoteObjectFactory.php: -------------------------------------------------------------------------------- 1 | adapter = $adapter; 38 | $this->generator = new RemoteObjectGenerator(); 39 | } 40 | 41 | /** 42 | * @psalm-param RealObjectType|class-string $instanceOrClassName 43 | * 44 | * @psalm-return RealObjectType&RemoteObjectInterface 45 | * 46 | * @throws InvalidSignatureException 47 | * @throws MissingSignatureException 48 | * @throws OutOfBoundsException 49 | * 50 | * @psalm-template RealObjectType of object 51 | * @psalm-suppress MixedInferredReturnType We ignore type checks here, since `staticProxyConstructor` is not 52 | * interfaced (by design) 53 | */ 54 | public function createProxy($instanceOrClassName): RemoteObjectInterface 55 | { 56 | $proxyClassName = $this->generateProxy( 57 | is_object($instanceOrClassName) ? get_class($instanceOrClassName) : $instanceOrClassName 58 | ); 59 | 60 | /** 61 | * We ignore type checks here, since `staticProxyConstructor` is not interfaced (by design) 62 | * 63 | * @psalm-suppress MixedMethodCall 64 | * @psalm-suppress MixedReturnStatement 65 | */ 66 | return $proxyClassName::staticProxyConstructor($this->adapter); 67 | } 68 | 69 | protected function getGenerator(): ProxyGeneratorInterface 70 | { 71 | return $this->generator ?? $this->generator = new RemoteObjectGenerator(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/ProxyManager/FileLocator/FileLocator.php: -------------------------------------------------------------------------------- 1 | proxiesDirectory = $absolutePath; 30 | } 31 | 32 | public function getProxyFileName(string $className): string 33 | { 34 | return $this->proxiesDirectory . DIRECTORY_SEPARATOR . str_replace('\\', '', $className) . '.php'; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/ProxyManager/FileLocator/FileLocatorInterface.php: -------------------------------------------------------------------------------- 1 | setReturnsReference(strtolower($name) === '__get'); 36 | 37 | if (! $originalClass->hasMethod($name)) { 38 | return; 39 | } 40 | 41 | $originalMethod = $originalClass->getMethod($name); 42 | $originalReturnType = $originalMethod->getReturnType(); 43 | 44 | $this->setReturnsReference($originalMethod->returnsReference()); 45 | 46 | if ($originalReturnType instanceof ReflectionNamedType) { 47 | $this->setReturnType(($originalReturnType->allowsNull() && $originalReturnType->getName() !== 'mixed' ? '?' : '') . $originalReturnType->getName()); 48 | } elseif ($originalReturnType instanceof ReflectionUnionType || $originalReturnType instanceof ReflectionIntersectionType) { 49 | $returnType = []; 50 | foreach ($originalReturnType->getTypes() as $type) { 51 | $returnType[] = $type->getName(); 52 | } 53 | 54 | $this->setReturnType(implode($originalReturnType instanceof ReflectionIntersectionType ? '&' : '|', $returnType)); 55 | } elseif ($originalReturnType) { 56 | throw new LogicException('Unexpected ' . get_class($type)); 57 | } 58 | } 59 | 60 | public function setBody($body): LaminasMethodGenerator 61 | { 62 | if ((string) $this->getReturnType() === 'void') { 63 | $body = preg_replace('/return ([^;]++;)/', '\1 return;', $body); 64 | } 65 | 66 | return parent::setBody($body); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/ProxyManager/Generator/Util/ClassGeneratorUtils.php: -------------------------------------------------------------------------------- 1 | getName(); 22 | 23 | if ($originalClass->hasMethod($methodName) && $originalClass->getMethod($methodName)->isFinal()) { 24 | return false; 25 | } 26 | 27 | $classGenerator->addMethodFromGenerator($generatedMethod); 28 | 29 | return true; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ProxyManager/Generator/Util/IdentifierSuffixer.php: -------------------------------------------------------------------------------- 1 | = 2.0.14 60 | : InstalledVersions::getRawData() 61 | )); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/ProxyManager/Generator/Util/ProxiedMethodReturnExpression.php: -------------------------------------------------------------------------------- 1 | getReturnType(); 22 | 23 | if ($originalReturnType instanceof ReflectionNamedType && $originalReturnType->getName() === 'void') { 24 | return $returnedValueExpression . ";\nreturn;"; 25 | } 26 | 27 | if ($originalReturnType instanceof ReflectionNamedType && $originalReturnType->getName() === 'never') { 28 | return $returnedValueExpression . ';'; 29 | } 30 | 31 | return 'return ' . $returnedValueExpression . ';'; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ProxyManager/Generator/Util/UniqueIdentifierGenerator.php: -------------------------------------------------------------------------------- 1 | generate(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/ProxyManager/GeneratorStrategy/EvaluatingGeneratorStrategy.php: -------------------------------------------------------------------------------- 1 | canEval = ! ini_get('suhosin.executor.disable_eval'); 28 | // @codeCoverageIgnoreEnd 29 | } 30 | 31 | /** 32 | * Evaluates the generated code before returning it 33 | * 34 | * {@inheritDoc} 35 | */ 36 | public function generate(ClassGenerator $classGenerator): string 37 | { 38 | $code = $classGenerator->generate(); 39 | 40 | // @codeCoverageIgnoreStart 41 | if (! $this->canEval) { 42 | $fileName = __DIR__ . '/EvaluatingGeneratorStrategy.php.tmp'; 43 | (new Filesystem())->dumpFile($fileName, "fileLocator = $fileLocator; 26 | } 27 | 28 | /** 29 | * Write generated code to disk and return the class code 30 | * 31 | * {@inheritDoc} 32 | * 33 | * @throws FileNotWritableException 34 | */ 35 | public function generate(ClassGenerator $classGenerator): string 36 | { 37 | $generatedCode = $classGenerator->generate(); 38 | $className = (string) $classGenerator->getNamespaceName() . '\\' . $classGenerator->getName(); 39 | $fileName = $this->fileLocator->getProxyFileName($className); 40 | 41 | try { 42 | (new Filesystem())->dumpFile($fileName, "proxyNamespace = $proxyNamespace; 26 | $this->proxyMarker = '\\' . self::PROXY_MARKER . '\\'; 27 | $this->proxyMarkerLength = strlen($this->proxyMarker); 28 | $this->parameterHasher = new ParameterHasher(); 29 | } 30 | 31 | /** 32 | * {@inheritDoc} 33 | * 34 | * @psalm-suppress MoreSpecificReturnType we ignore these issues because classes may not have been loaded yet 35 | */ 36 | public function getUserClassName(string $className): string 37 | { 38 | $className = ltrim($className, '\\'); 39 | $position = strrpos($className, $this->proxyMarker); 40 | 41 | if (! is_int($position)) { 42 | /** @psalm-suppress LessSpecificReturnStatement */ 43 | return $className; 44 | } 45 | 46 | /** @psalm-suppress LessSpecificReturnStatement */ 47 | return substr( 48 | $className, 49 | $this->proxyMarkerLength + $position, 50 | (int) strrpos($className, '\\') - ($position + $this->proxyMarkerLength) 51 | ); 52 | } 53 | 54 | /** 55 | * {@inheritDoc} 56 | * 57 | * @psalm-suppress MoreSpecificReturnType we ignore these issues because classes may not have been loaded yet 58 | */ 59 | public function getProxyClassName(string $className, array $options = []): string 60 | { 61 | /** @psalm-suppress LessSpecificReturnStatement */ 62 | return $this->proxyNamespace 63 | . $this->proxyMarker 64 | . $this->getUserClassName($className) 65 | . '\\Generated' . $this->parameterHasher->hashParameters($options); 66 | } 67 | 68 | public function isProxyClassName(string $className): bool 69 | { 70 | return strrpos($className, $this->proxyMarker) !== false; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/ProxyManager/Inflector/ClassNameInflectorInterface.php: -------------------------------------------------------------------------------- 1 | |class-string> $className 23 | * 24 | * @psalm-return class-string 25 | * 26 | * @psalm-template RealClassName of object 27 | */ 28 | public function getUserClassName(string $className): string; 29 | 30 | /** 31 | * Retrieve the class name of the proxy for the given user-defined class name 32 | * 33 | * @param array $options arbitrary options to be used for the generated class name 34 | * @psalm-param class-string|class-string> $className 35 | * 36 | * @psalm-return class-string 37 | * 38 | * @psalm-template RealClassName of object 39 | */ 40 | public function getProxyClassName(string $className, array $options = []): string; 41 | 42 | /** 43 | * Retrieve whether the provided class name is a proxy 44 | * 45 | * @psalm-param class-string|class-string> $className 46 | * 47 | * @psalm-template RealClassName of object 48 | */ 49 | public function isProxyClassName(string $className): bool; 50 | } 51 | -------------------------------------------------------------------------------- /src/ProxyManager/Inflector/Util/ParameterEncoder.php: -------------------------------------------------------------------------------- 1 | 24 | * $interceptor = function ($proxy, $instance, string $method, array $params, & $returnEarly) {}; 25 | * 26 | * 27 | * @param string $methodName name of the intercepted method 28 | * @param Closure|null $prefixInterceptor interceptor closure or null to unset the currently active interceptor 29 | * @psalm-param null|Closure( 30 | * InterceptedObjectType&AccessInterceptorInterface=, 31 | * InterceptedObjectType=, 32 | * string=, 33 | * array=, 34 | * bool= 35 | * ) : mixed $prefixInterceptor 36 | */ 37 | public function setMethodPrefixInterceptor(string $methodName, ?Closure $prefixInterceptor = null): void; 38 | 39 | /** 40 | * Set or remove the suffix interceptor for a method 41 | * 42 | * @link https://github.com/Ocramius/ProxyManager/blob/master/docs/access-interceptor-value-holder.md 43 | * 44 | * A prefix interceptor should have a signature like following: 45 | * 46 | * 47 | * $interceptor = function ($proxy, $instance, string $method, array $params, $returnValue, & $returnEarly) {}; 48 | * 49 | * 50 | * @param string $methodName name of the intercepted method 51 | * @param Closure|null $suffixInterceptor interceptor closure or null to unset the currently active interceptor 52 | * @psalm-param null|Closure( 53 | * InterceptedObjectType&AccessInterceptorInterface=, 54 | * InterceptedObjectType=, 55 | * string=, 56 | * array=, 57 | * mixed=, 58 | * bool= 59 | * ) : mixed $suffixInterceptor 60 | */ 61 | public function setMethodSuffixInterceptor(string $methodName, ?Closure $suffixInterceptor = null): void; 62 | } 63 | -------------------------------------------------------------------------------- /src/ProxyManager/Proxy/AccessInterceptorValueHolderInterface.php: -------------------------------------------------------------------------------- 1 | =, 26 | * bool= 27 | * ) : mixed $prefixInterceptor 28 | */ 29 | public function setMethodPrefixInterceptor(string $methodName, ?Closure $prefixInterceptor = null): void; 30 | 31 | /** 32 | * {@inheritDoc} 33 | * 34 | * Definitions are duplicated here to allow templated definitions in this child type 35 | * 36 | * @param string $methodName name of the intercepted method 37 | * @param Closure|null $suffixInterceptor interceptor closure or null to unset the currently active interceptor 38 | * @psalm-param null|Closure( 39 | * InterceptedObjectType&AccessInterceptorInterface=, 40 | * InterceptedObjectType=, 41 | * string=, 42 | * array=, 43 | * mixed=, 44 | * bool= 45 | * ) : mixed $suffixInterceptor 46 | */ 47 | public function setMethodSuffixInterceptor(string $methodName, ?Closure $suffixInterceptor = null): void; 48 | 49 | /** 50 | * {@inheritDoc} 51 | * 52 | * Definitions are duplicated here to allow templated definitions in this child type 53 | * 54 | * @psalm-return InterceptedObjectType|null 55 | */ 56 | public function getWrappedValueHolderValue(); 57 | } 58 | -------------------------------------------------------------------------------- /src/ProxyManager/Proxy/Exception/RemoteObjectException.php: -------------------------------------------------------------------------------- 1 | =, 23 | * string=, 24 | * array=, 25 | * ?Closure=, 26 | * array= 27 | * ) : bool $initializer 28 | * 29 | * @psalm-suppress ImplementedParamTypeMismatch Note that the closure signature below is slightly different 30 | * from the one declared in LazyLoadingInterface. 31 | */ 32 | public function setProxyInitializer(?Closure $initializer = null); 33 | 34 | /** 35 | * {@inheritDoc} 36 | * 37 | * Definitions are duplicated here to allow templated definitions in this child type 38 | * 39 | * @psalm-return null|Closure( 40 | * LazilyLoadedObjectType&GhostObjectInterface=, 41 | * string, 42 | * array=, 43 | * ?Closure=, 44 | * array= 45 | * ) : bool 46 | * 47 | * @psalm-suppress ImplementedReturnTypeMismatch Note that the closure signature below is slightly different 48 | * from the one declared in LazyLoadingInterface. 49 | */ 50 | public function getProxyInitializer(): ?Closure; 51 | } 52 | -------------------------------------------------------------------------------- /src/ProxyManager/Proxy/LazyLoadingInterface.php: -------------------------------------------------------------------------------- 1 | 24 | * $initializer = function ( 25 | * & ?object $wrappedObject, 26 | * LazyLoadingInterface $proxy, 27 | * string $calledMethod, 28 | * array $callParameters, 29 | * & ?\Closure $initializer, 30 | * array $propertiesToBeSet = [] // works only on ghost objects 31 | * ) {}; 32 | * 33 | * 34 | * @psalm-param null|Closure( 35 | * LazilyLoadedObjectType|null=, 36 | * LazilyLoadedObjectType&LazyLoadingInterface=, 37 | * string=, 38 | * array=, 39 | * ?Closure=, 40 | * array= 41 | * ) : bool $initializer 42 | * 43 | * Due to BC compliance, we cannot add a native `: void` return type declaration here 44 | * 45 | * phpcs:disable SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint 46 | * 47 | * @return void 48 | */ 49 | public function setProxyInitializer(?Closure $initializer = null); 50 | 51 | /** 52 | * @psalm-return null|Closure( 53 | * LazilyLoadedObjectType|null=, 54 | * LazilyLoadedObjectType&LazyLoadingInterface=, 55 | * string, 56 | * array=, 57 | * ?Closure=, 58 | * array= 59 | * ) : bool 60 | */ 61 | public function getProxyInitializer(): ?Closure; 62 | 63 | /** 64 | * Force initialization of the proxy 65 | * 66 | * @return bool true if the proxy could be initialized 67 | */ 68 | public function initializeProxy(): bool; 69 | 70 | /** 71 | * Retrieves current initialization status of the proxy 72 | */ 73 | public function isProxyInitialized(): bool; 74 | } 75 | -------------------------------------------------------------------------------- /src/ProxyManager/Proxy/NullObjectInterface.php: -------------------------------------------------------------------------------- 1 | setBody(UnsetPropertiesGenerator::generateSnippet( 25 | Properties::fromReflectionClass($originalClass), 26 | 'this' 27 | )); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptor/MethodGenerator/SetMethodPrefixInterceptor.php: -------------------------------------------------------------------------------- 1 | setType('?' . Closure::class); 31 | $interceptor->setDefaultValue(null); 32 | $this->setParameter(new ParameterGenerator('methodName', 'string')); 33 | $this->setParameter($interceptor); 34 | $this->setReturnType('void'); 35 | $this->setBody('$this->' . $prefixInterceptor->getName() . '[$methodName] = $prefixInterceptor;'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptor/MethodGenerator/SetMethodSuffixInterceptor.php: -------------------------------------------------------------------------------- 1 | setType('?' . Closure::class); 31 | $interceptor->setDefaultValue(null); 32 | $this->setParameter(new ParameterGenerator('methodName', 'string')); 33 | $this->setParameter($interceptor); 34 | $this->setReturnType('void'); 35 | $this->setBody('$this->' . $suffixInterceptor->getName() . '[$methodName] = $suffixInterceptor;'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptor/PropertyGenerator/MethodPrefixInterceptors.php: -------------------------------------------------------------------------------- 1 | setDefaultValue([]); 26 | $this->setVisibility(self::VISIBILITY_PRIVATE); 27 | $this->setDocBlock('@var \\Closure[] map of interceptors to be called per-method before execution'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptor/PropertyGenerator/MethodSuffixInterceptors.php: -------------------------------------------------------------------------------- 1 | setDefaultValue([]); 26 | $this->setVisibility(self::VISIBILITY_PRIVATE); 27 | $this->setDocBlock('@var \\Closure[] map of interceptors to be called per-method after execution'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/BindProxyProperties.php: -------------------------------------------------------------------------------- 1 | getName()), 34 | new ParameterGenerator('prefixInterceptors', 'array', []), 35 | new ParameterGenerator('suffixInterceptors', 'array', []), 36 | ], 37 | self::FLAG_PRIVATE, 38 | null, 39 | "@override constructor to setup interceptors\n\n" 40 | . '@param \\' . $originalClass->getName() . " \$localizedObject\n" 41 | . "@param \\Closure[] \$prefixInterceptors method interceptors to be used before method logic\n" 42 | . '@param \\Closure[] $suffixInterceptors method interceptors to be used before method logic' 43 | ); 44 | 45 | $localizedProperties = []; 46 | $properties = Properties::fromReflectionClass($originalClass); 47 | $nonReferenceableProperties = $properties 48 | ->onlyNonReferenceableProperties() 49 | ->onlyInstanceProperties(); 50 | 51 | if (! $nonReferenceableProperties->empty()) { 52 | throw UnsupportedProxiedClassException::nonReferenceableLocalizedReflectionProperties( 53 | $originalClass, 54 | $nonReferenceableProperties 55 | ); 56 | } 57 | 58 | foreach ($properties->getAccessibleProperties() as $property) { 59 | $propertyName = $property->getName(); 60 | 61 | $localizedProperties[] = '$this->' . $propertyName . ' = & $localizedObject->' . $propertyName . ';'; 62 | } 63 | 64 | foreach ($properties->getPrivateProperties() as $property) { 65 | $propertyName = $property->getName(); 66 | 67 | $localizedProperties[] = "\\Closure::bind(function () use (\$localizedObject) {\n " 68 | . '$this->' . $propertyName . ' = & $localizedObject->' . $propertyName . ";\n" 69 | . '}, $this, ' . var_export($property->getDeclaringClass()->getName(), true) 70 | . ')->__invoke();'; 71 | } 72 | 73 | $this->setBody( 74 | ($localizedProperties ? implode("\n\n", $localizedProperties) . "\n\n" : '') 75 | . '$this->' . $prefixInterceptors->getName() . " = \$prefixInterceptors;\n" 76 | . '$this->' . $suffixInterceptors->getName() . ' = $suffixInterceptors;' 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/InterceptedMethod.php: -------------------------------------------------------------------------------- 1 | getParameters() as $parameter) { 32 | $forwardedParams[] = ($parameter->isVariadic() ? '...' : '') . '$' . $parameter->getName(); 33 | } 34 | 35 | $method->setBody(InterceptorGenerator::createInterceptedMethodBody( 36 | '$returnValue = parent::' 37 | . $originalMethod->getName() . '(' . implode(', ', $forwardedParams) . ');', 38 | $method, 39 | $prefixInterceptors, 40 | $suffixInterceptors, 41 | $originalMethod 42 | )); 43 | 44 | return $method; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicClone.php: -------------------------------------------------------------------------------- 1 | setBody(InterceptorGenerator::createInterceptedMethodBody( 31 | $parent ? '$returnValue = parent::__clone();' : '$returnValue = null;', 32 | $this, 33 | $prefixInterceptors, 34 | $suffixInterceptors, 35 | $parent 36 | )); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicGet.php: -------------------------------------------------------------------------------- 1 | setBody(InterceptorGenerator::createInterceptedMethodBody( 47 | $callParent, 48 | $this, 49 | $prefixInterceptors, 50 | $suffixInterceptors, 51 | $parent 52 | )); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicIsset.php: -------------------------------------------------------------------------------- 1 | setBody(InterceptorGenerator::createInterceptedMethodBody( 47 | $callParent, 48 | $this, 49 | $prefixInterceptors, 50 | $suffixInterceptors, 51 | $parent 52 | )); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicSet.php: -------------------------------------------------------------------------------- 1 | setBody(InterceptorGenerator::createInterceptedMethodBody( 51 | $callParent, 52 | $this, 53 | $prefixInterceptors, 54 | $suffixInterceptors, 55 | $parent 56 | )); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicSleep.php: -------------------------------------------------------------------------------- 1 | setBody(InterceptorGenerator::createInterceptedMethodBody( 33 | $callParent, 34 | $this, 35 | $prefixInterceptors, 36 | $suffixInterceptors, 37 | $parent 38 | )); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicUnset.php: -------------------------------------------------------------------------------- 1 | setBody(InterceptorGenerator::createInterceptedMethodBody( 47 | $callParent, 48 | $this, 49 | $prefixInterceptors, 50 | $suffixInterceptors, 51 | $parent 52 | )); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/StaticProxyConstructor.php: -------------------------------------------------------------------------------- 1 | setType($originalClass->getName()); 31 | $prefix->setDefaultValue([]); 32 | $suffix->setDefaultValue([]); 33 | $prefix->setType('array'); 34 | $suffix->setType('array'); 35 | 36 | $this->setParameter($localizedObject); 37 | $this->setParameter($prefix); 38 | $this->setParameter($suffix); 39 | $this->setReturnType($originalClass->getName()); 40 | 41 | $this->setDocBlock( 42 | "Constructor to setup interceptors\n\n" 43 | . '@param \\' . $originalClass->getName() . " \$localizedObject\n" 44 | . "@param \\Closure[] \$prefixInterceptors method interceptors to be used before method logic\n" 45 | . "@param \\Closure[] \$suffixInterceptors method interceptors to be used before method logic\n\n" 46 | . '@return self' 47 | ); 48 | $this->setBody( 49 | 'static $reflection;' . "\n\n" 50 | . '$reflection = $reflection ?? new \ReflectionClass(__CLASS__);' . "\n" 51 | . '$instance = $reflection->newInstanceWithoutConstructor();' . "\n\n" 52 | . '$instance->bindProxyProperties($localizedObject, $prefixInterceptors, $suffixInterceptors);' . "\n\n" 53 | . 'return $instance;' 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/Util/InterceptorGenerator.php: -------------------------------------------------------------------------------- 1 | {{$prefixInterceptorsName}}[{{$name}}])) { 28 | $returnEarly = false; 29 | $prefixReturnValue = $this->{{$prefixInterceptorsName}}[{{$name}}]->__invoke($this, $this, {{$name}}, {{$paramsString}}, $returnEarly); 30 | 31 | if ($returnEarly) { 32 | {{$prefixEarlyReturnExpression}} 33 | } 34 | } 35 | 36 | {{$methodBody}} 37 | 38 | if (isset($this->{{$suffixInterceptorsName}}[{{$name}}])) { 39 | $returnEarly = false; 40 | $suffixReturnValue = $this->{{$suffixInterceptorsName}}[{{$name}}]->__invoke($this, $this, {{$name}}, {{$paramsString}}, $returnValue, $returnEarly); 41 | 42 | if ($returnEarly) { 43 | {{$suffixEarlyReturnExpression}} 44 | } 45 | } 46 | 47 | {{$returnExpression}} 48 | PHP; 49 | 50 | /** 51 | * @param string $methodBody the body of the previously generated code. 52 | * It MUST assign the return value to a variable 53 | * `$returnValue` instead of directly returning 54 | */ 55 | public static function createInterceptedMethodBody( 56 | string $methodBody, 57 | MethodGenerator $method, 58 | PropertyGenerator $prefixInterceptors, 59 | PropertyGenerator $suffixInterceptors, 60 | ?ReflectionMethod $originalMethod 61 | ): string { 62 | $replacements = [ 63 | '{{$name}}' => var_export($method->getName(), true), 64 | '{{$prefixInterceptorsName}}' => $prefixInterceptors->getName(), 65 | '{{$prefixEarlyReturnExpression}}' => ProxiedMethodReturnExpression::generate('$prefixReturnValue', $originalMethod), 66 | '{{$methodBody}}' => $methodBody, 67 | '{{$suffixInterceptorsName}}' => $suffixInterceptors->getName(), 68 | '{{$suffixEarlyReturnExpression}}' => ProxiedMethodReturnExpression::generate('$suffixReturnValue', $originalMethod), 69 | '{{$returnExpression}}' => ProxiedMethodReturnExpression::generate('$returnValue', $originalMethod), 70 | '{{$paramsString}}' => 'array(' . implode(', ', array_map( 71 | static function (ParameterGenerator $parameter): string { 72 | return var_export($parameter->getName(), true) . ' => ' . ($parameter->getPassedByReference() ? '&$' : '$') . $parameter->getName(); 73 | }, 74 | $method->getParameters() 75 | )) 76 | . ')', 77 | ]; 78 | 79 | return str_replace( 80 | array_keys($replacements), 81 | $replacements, 82 | self::TEMPLATE 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/InterceptedMethod.php: -------------------------------------------------------------------------------- 1 | getParameters() as $parameter) { 33 | $forwardedParams[] = ($parameter->isVariadic() ? '...' : '') . '$' . $parameter->getName(); 34 | } 35 | 36 | $method->setBody(InterceptorGenerator::createInterceptedMethodBody( 37 | '$returnValue = $this->' . $valueHolderProperty->getName() . '->' 38 | . $originalMethod->getName() . '(' . implode(', ', $forwardedParams) . ');', 39 | $method, 40 | $valueHolderProperty, 41 | $prefixInterceptors, 42 | $suffixInterceptors, 43 | $originalMethod 44 | )); 45 | 46 | return $method; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicClone.php: -------------------------------------------------------------------------------- 1 | {{$valueHolder}} = clone $this->{{$valueHolder}}; 21 | 22 | foreach ($this->{{$prefix}} as $key => $value) { 23 | $this->{{$prefix}}[$key] = clone $value; 24 | } 25 | 26 | foreach ($this->{{$suffix}} as $key => $value) { 27 | $this->{{$suffix}}[$key] = clone $value; 28 | } 29 | PHP; 30 | 31 | /** 32 | * Constructor 33 | */ 34 | public function __construct( 35 | ReflectionClass $originalClass, 36 | PropertyGenerator $valueHolderProperty, 37 | PropertyGenerator $prefixInterceptors, 38 | PropertyGenerator $suffixInterceptors 39 | ) { 40 | parent::__construct($originalClass, '__clone'); 41 | 42 | $valueHolder = $valueHolderProperty->getName(); 43 | $prefix = $prefixInterceptors->getName(); 44 | $suffix = $suffixInterceptors->getName(); 45 | 46 | $replacements = [ 47 | '{{$valueHolder}}' => $valueHolder, 48 | '{{$prefix}}' => $prefix, 49 | '{{$suffix}}' => $suffix, 50 | ]; 51 | 52 | $this->setBody(str_replace( 53 | array_keys($replacements), 54 | $replacements, 55 | self::TEMPLATE 56 | )); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicGet.php: -------------------------------------------------------------------------------- 1 | getName(); 38 | 39 | $callParent = PublicScopeSimulator::getPublicAccessSimulationCode( 40 | PublicScopeSimulator::OPERATION_GET, 41 | 'name', 42 | null, 43 | $valueHolder, 44 | 'returnValue', 45 | $originalClass 46 | ); 47 | 48 | if (! $publicProperties->isEmpty()) { 49 | $callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n" 50 | . ' $returnValue = & $this->' . $valueHolderName . '->$name;' 51 | . "\n} else {\n " . $callParent . "\n}\n\n"; 52 | } 53 | 54 | $this->setBody(InterceptorGenerator::createInterceptedMethodBody( 55 | $callParent, 56 | $this, 57 | $valueHolder, 58 | $prefixInterceptors, 59 | $suffixInterceptors, 60 | $parent 61 | )); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicIsset.php: -------------------------------------------------------------------------------- 1 | getName(); 38 | 39 | $callParent = PublicScopeSimulator::getPublicAccessSimulationCode( 40 | PublicScopeSimulator::OPERATION_ISSET, 41 | 'name', 42 | null, 43 | $valueHolder, 44 | 'returnValue', 45 | $originalClass 46 | ); 47 | 48 | if (! $publicProperties->isEmpty()) { 49 | $callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n" 50 | . ' $returnValue = isset($this->' . $valueHolderName . '->$name);' 51 | . "\n} else {\n " . $callParent . "\n}\n\n"; 52 | } 53 | 54 | $this->setBody(InterceptorGenerator::createInterceptedMethodBody( 55 | $callParent, 56 | $this, 57 | $valueHolder, 58 | $prefixInterceptors, 59 | $suffixInterceptors, 60 | $parent 61 | )); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicSet.php: -------------------------------------------------------------------------------- 1 | getName(); 42 | 43 | $callParent = PublicScopeSimulator::getPublicAccessSimulationCode( 44 | PublicScopeSimulator::OPERATION_SET, 45 | 'name', 46 | 'value', 47 | $valueHolder, 48 | 'returnValue', 49 | $originalClass 50 | ); 51 | 52 | if (! $publicProperties->isEmpty()) { 53 | $callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n" 54 | . ' $returnValue = ($this->' . $valueHolderName . '->$name = $value);' 55 | . "\n} else {\n " . $callParent . "\n}\n\n"; 56 | } 57 | 58 | $this->setBody(InterceptorGenerator::createInterceptedMethodBody( 59 | $callParent, 60 | $this, 61 | $valueHolder, 62 | $prefixInterceptors, 63 | $suffixInterceptors, 64 | $parent 65 | )); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicUnset.php: -------------------------------------------------------------------------------- 1 | getName(); 38 | 39 | $callParent = PublicScopeSimulator::getPublicAccessSimulationCode( 40 | PublicScopeSimulator::OPERATION_UNSET, 41 | 'name', 42 | null, 43 | $valueHolder, 44 | 'returnValue', 45 | $originalClass 46 | ); 47 | 48 | if (! $publicProperties->isEmpty()) { 49 | $callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n" 50 | . ' unset($this->' . $valueHolderName . '->$name);' 51 | . "\n} else {\n " . $callParent . "\n}\n\n"; 52 | } 53 | 54 | $callParent .= '$returnValue = false;'; 55 | 56 | $this->setBody(InterceptorGenerator::createInterceptedMethodBody( 57 | $callParent, 58 | $this, 59 | $valueHolder, 60 | $prefixInterceptors, 61 | $suffixInterceptors, 62 | $parent 63 | )); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/StaticProxyConstructor.php: -------------------------------------------------------------------------------- 1 | setDefaultValue([]); 37 | $suffix->setDefaultValue([]); 38 | $prefix->setType('array'); 39 | $suffix->setType('array'); 40 | 41 | $this->setParameter(new ParameterGenerator('wrappedObject')); 42 | $this->setParameter($prefix); 43 | $this->setParameter($suffix); 44 | $this->setReturnType($originalClass->getName()); 45 | 46 | $this->setDocBlock( 47 | "Constructor to setup interceptors\n\n" 48 | . '@param \\' . $originalClass->getName() . " \$wrappedObject\n" 49 | . "@param \\Closure[] \$prefixInterceptors method interceptors to be used before method logic\n" 50 | . "@param \\Closure[] \$suffixInterceptors method interceptors to be used before method logic\n\n" 51 | . '@return self' 52 | ); 53 | 54 | $this->setBody( 55 | 'static $reflection;' . "\n\n" 56 | . '$reflection = $reflection ?? new \ReflectionClass(__CLASS__);' . "\n" 57 | . '$instance = $reflection->newInstanceWithoutConstructor();' . "\n\n" 58 | . UnsetPropertiesGenerator::generateSnippet(Properties::fromReflectionClass($originalClass), 'instance') 59 | . '$instance->' . $valueHolder->getName() . " = \$wrappedObject;\n" 60 | . '$instance->' . $prefixInterceptors->getName() . " = \$prefixInterceptors;\n" 61 | . '$instance->' . $suffixInterceptors->getName() . " = \$suffixInterceptors;\n\n" 62 | . 'return $instance;' 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/Util/InterceptorGenerator.php: -------------------------------------------------------------------------------- 1 | {{$prefixInterceptorsName}}[{{$name}}])) { 26 | $returnEarly = false; 27 | $prefixReturnValue = $this->{{$prefixInterceptorsName}}[{{$name}}]->__invoke($this, $this->{{$valueHolderName}}, {{$name}}, {{$paramsString}}, $returnEarly); 28 | 29 | if ($returnEarly) { 30 | {{$returnEarlyPrefixExpression}} 31 | } 32 | } 33 | 34 | {{$methodBody}} 35 | 36 | if (isset($this->{{$suffixInterceptorsName}}[{{$name}}])) { 37 | $returnEarly = false; 38 | $suffixReturnValue = $this->{{$suffixInterceptorsName}}[{{$name}}]->__invoke($this, $this->{{$valueHolderName}}, {{$name}}, {{$paramsString}}, $returnValue, $returnEarly); 39 | 40 | if ($returnEarly) { 41 | {{$returnEarlySuffixExpression}} 42 | } 43 | } 44 | 45 | {{$returnExpression}} 46 | PHP; 47 | 48 | /** 49 | * @param string $methodBody the body of the previously generated code. 50 | * It MUST assign the return value to a variable 51 | * `$returnValue` instead of directly returning 52 | */ 53 | public static function createInterceptedMethodBody( 54 | string $methodBody, 55 | MethodGenerator $method, 56 | PropertyGenerator $valueHolder, 57 | PropertyGenerator $prefixInterceptors, 58 | PropertyGenerator $suffixInterceptors, 59 | ?ReflectionMethod $originalMethod 60 | ): string { 61 | $name = var_export($method->getName(), true); 62 | $valueHolderName = $valueHolder->getName(); 63 | $prefixInterceptorsName = $prefixInterceptors->getName(); 64 | $suffixInterceptorsName = $suffixInterceptors->getName(); 65 | $params = []; 66 | 67 | foreach ($method->getParameters() as $parameter) { 68 | $parameterName = $parameter->getName(); 69 | $symbol = $parameter->getPassedByReference() ? '&$' : '$'; 70 | $params[] = var_export($parameterName, true) . ' => ' . $symbol . $parameterName; 71 | } 72 | 73 | $paramsString = 'array(' . implode(', ', $params) . ')'; 74 | 75 | $replacements = [ 76 | '{{$prefixInterceptorsName}}' => $prefixInterceptorsName, 77 | '{{$name}}' => $name, 78 | '{{$valueHolderName}}' => $valueHolderName, 79 | '{{$paramsString}}' => $paramsString, 80 | '{{$returnEarlyPrefixExpression}}' => ProxiedMethodReturnExpression::generate('$prefixReturnValue', $originalMethod), 81 | '{{$methodBody}}' => $methodBody, 82 | '{{$suffixInterceptorsName}}' => $suffixInterceptorsName, 83 | '{{$returnEarlySuffixExpression}}' => ProxiedMethodReturnExpression::generate('$suffixReturnValue', $originalMethod), 84 | '{{$returnExpression}}' => ProxiedMethodReturnExpression::generate('$returnValue', $originalMethod), 85 | 86 | ]; 87 | 88 | return str_replace(array_keys($replacements), $replacements, self::TEMPLATE); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/Assertion/CanProxyAssertion.php: -------------------------------------------------------------------------------- 1 | isFinal()) { 50 | throw InvalidProxiedClassException::finalClassNotSupported($originalClass); 51 | } 52 | } 53 | 54 | /** 55 | * @throws InvalidProxiedClassException 56 | */ 57 | private static function hasNoAbstractProtectedMethods(ReflectionClass $originalClass): void 58 | { 59 | $protectedAbstract = array_filter( 60 | $originalClass->getMethods(), 61 | static function (ReflectionMethod $method): bool { 62 | return $method->isAbstract() && $method->isProtected(); 63 | } 64 | ); 65 | 66 | if ($protectedAbstract) { 67 | throw InvalidProxiedClassException::abstractProtectedMethodsNotSupported($originalClass); 68 | } 69 | } 70 | 71 | /** 72 | * @throws InvalidProxiedClassException 73 | */ 74 | private static function isNotInterface(ReflectionClass $originalClass): void 75 | { 76 | if ($originalClass->isInterface()) { 77 | throw InvalidProxiedClassException::interfaceNotSupported($originalClass); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoading/MethodGenerator/StaticProxyConstructor.php: -------------------------------------------------------------------------------- 1 | setParameter(new ParameterGenerator('initializer')); 29 | 30 | $this->setDocBlock("Constructor for lazy initialization\n\n@param \\Closure|null \$initializer"); 31 | $this->setBody( 32 | 'static $reflection;' . "\n\n" 33 | . '$reflection = $reflection ?? new \ReflectionClass(__CLASS__);' . "\n" 34 | . '$instance = $reflection->newInstanceWithoutConstructor();' . "\n\n" 35 | . UnsetPropertiesGenerator::generateSnippet($properties, 'instance') 36 | . '$instance->' . $initializerProperty->getName() . ' = $initializer;' . "\n\n" 37 | . 'return $instance;' 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/GetProxyInitializer.php: -------------------------------------------------------------------------------- 1 | setReturnType('?\\Closure'); 26 | $this->setBody('return $this->' . $initializerProperty->getName() . ';'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/InitializeProxy.php: -------------------------------------------------------------------------------- 1 | setReturnType('bool'); 27 | 28 | $this->setBody( 29 | 'return $this->' . $initializerProperty->getName() . ' && $this->' . $callInitializer->getName() 30 | . '(\'initializeProxy\', []);' 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/IsProxyInitialized.php: -------------------------------------------------------------------------------- 1 | setReturnType('bool'); 26 | $this->setBody('return ! $this->' . $initializerProperty->getName() . ';'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicClone.php: -------------------------------------------------------------------------------- 1 | setBody( 28 | '$this->' . $initializerProperty->getName() . ' && $this->' . $callInitializer->getName() 29 | . '(\'__clone\', []);' 30 | . ($originalClass->hasMethod('__clone') ? "\n\nparent::__clone();" : '') 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicIsset.php: -------------------------------------------------------------------------------- 1 | $name); 30 | } 31 | 32 | if (isset(self::$%s[$name])) { 33 | // check protected property access via compatible class 34 | $callers = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2); 35 | $caller = isset($callers[1]) ? $callers[1] : []; 36 | $object = isset($caller['object']) ? $caller['object'] : ''; 37 | $expectedType = self::$%s[$name]; 38 | 39 | if ($object instanceof $expectedType) { 40 | return isset($this->$name); 41 | } 42 | 43 | $class = isset($caller['class']) ? $caller['class'] : ''; 44 | 45 | if ($class === $expectedType || is_subclass_of($class, $expectedType)) { 46 | return isset($this->$name); 47 | } 48 | } else { 49 | // check private property access via same class 50 | $callers = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2); 51 | $caller = isset($callers[1]) ? $callers[1] : []; 52 | $class = isset($caller['class']) ? $caller['class'] : ''; 53 | 54 | static $accessorCache = []; 55 | 56 | if (isset(self::$%s[$name][$class])) { 57 | $cacheKey = $class . '#' . $name; 58 | $accessor = isset($accessorCache[$cacheKey]) 59 | ? $accessorCache[$cacheKey] 60 | : $accessorCache[$cacheKey] = \Closure::bind(static function ($instance) use ($name) { 61 | return isset($instance->$name); 62 | }, null, $class); 63 | 64 | return $accessor($this); 65 | } 66 | 67 | if ('ReflectionProperty' === $class) { 68 | $tmpClass = key(self::$%s[$name]); 69 | $cacheKey = $tmpClass . '#' . $name; 70 | $accessor = isset($accessorCache[$cacheKey]) 71 | ? $accessorCache[$cacheKey] 72 | : $accessorCache[$cacheKey] = \Closure::bind(static function ($instance) use ($name) { 73 | return isset($instance->$name); 74 | }, null, $tmpClass); 75 | 76 | return $accessor($this); 77 | } 78 | } 79 | 80 | %s 81 | PHP; 82 | 83 | /** 84 | * @throws InvalidArgumentException 85 | */ 86 | public function __construct( 87 | ReflectionClass $originalClass, 88 | PropertyGenerator $initializerProperty, 89 | MethodGenerator $callInitializer, 90 | PublicPropertiesMap $publicProperties, 91 | ProtectedPropertiesMap $protectedProperties, 92 | PrivatePropertiesMap $privateProperties 93 | ) { 94 | parent::__construct($originalClass, '__isset', [new ParameterGenerator('name')]); 95 | 96 | $override = $originalClass->hasMethod('__isset'); 97 | 98 | $parentAccess = 'return parent::__isset($name);'; 99 | 100 | if (! $override) { 101 | $parentAccess = PublicScopeSimulator::getPublicAccessSimulationCode( 102 | PublicScopeSimulator::OPERATION_ISSET, 103 | 'name' 104 | ); 105 | } 106 | 107 | $this->setBody(sprintf( 108 | $this->callParentTemplate, 109 | '$this->' . $initializerProperty->getName() . ' && $this->' . $callInitializer->getName() 110 | . '(\'__isset\', array(\'name\' => $name));', 111 | $publicProperties->getName(), 112 | $protectedProperties->getName(), 113 | $protectedProperties->getName(), 114 | $privateProperties->getName(), 115 | $privateProperties->getName(), 116 | $parentAccess 117 | )); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicSleep.php: -------------------------------------------------------------------------------- 1 | setBody( 28 | '$this->' . $initializerProperty->getName() . ' && $this->' . $callInitializer->getName() 29 | . '(\'__sleep\', []);' . "\n\n" 30 | . ($originalClass->hasMethod('__sleep') ? 'return parent::__sleep();' : 'return array_keys((array) $this);') 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicUnset.php: -------------------------------------------------------------------------------- 1 | $name); 30 | 31 | return; 32 | } 33 | 34 | if (isset(self::$%s[$name])) { 35 | // check protected property access via compatible class 36 | $callers = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2); 37 | $caller = isset($callers[1]) ? $callers[1] : []; 38 | $object = isset($caller['object']) ? $caller['object'] : ''; 39 | $expectedType = self::$%s[$name]; 40 | 41 | if ($object instanceof $expectedType) { 42 | unset($this->$name); 43 | 44 | return; 45 | } 46 | 47 | $class = isset($caller['class']) ? $caller['class'] : ''; 48 | 49 | if ($class === $expectedType || is_subclass_of($class, $expectedType) || $class === 'ReflectionProperty') { 50 | unset($this->$name); 51 | 52 | return; 53 | } 54 | } elseif (isset(self::$%s[$name])) { 55 | // check private property access via same class 56 | $callers = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2); 57 | $caller = isset($callers[1]) ? $callers[1] : []; 58 | $class = isset($caller['class']) ? $caller['class'] : ''; 59 | 60 | static $accessorCache = []; 61 | 62 | if (isset(self::$%s[$name][$class])) { 63 | $cacheKey = $class . '#' . $name; 64 | $accessor = isset($accessorCache[$cacheKey]) 65 | ? $accessorCache[$cacheKey] 66 | : $accessorCache[$cacheKey] = \Closure::bind(static function ($instance) use ($name) { 67 | unset($instance->$name); 68 | }, null, $class); 69 | 70 | return $accessor($this); 71 | } 72 | 73 | if ('ReflectionProperty' === $class) { 74 | $tmpClass = key(self::$%s[$name]); 75 | $cacheKey = $tmpClass . '#' . $name; 76 | $accessor = isset($accessorCache[$cacheKey]) 77 | ? $accessorCache[$cacheKey] 78 | : $accessorCache[$cacheKey] = \Closure::bind(static function ($instance) use ($name) { 79 | unset($instance->$name); 80 | }, null, $tmpClass); 81 | 82 | return $accessor($this); 83 | } 84 | } 85 | 86 | %s 87 | PHP; 88 | 89 | /** 90 | * @throws InvalidArgumentException 91 | */ 92 | public function __construct( 93 | ReflectionClass $originalClass, 94 | PropertyGenerator $initializerProperty, 95 | MethodGenerator $callInitializer, 96 | PublicPropertiesMap $publicProperties, 97 | ProtectedPropertiesMap $protectedProperties, 98 | PrivatePropertiesMap $privateProperties 99 | ) { 100 | parent::__construct($originalClass, '__unset', [new ParameterGenerator('name')]); 101 | 102 | $override = $originalClass->hasMethod('__unset'); 103 | 104 | $parentAccess = 'return parent::__unset($name);'; 105 | 106 | if (! $override) { 107 | $parentAccess = PublicScopeSimulator::getPublicAccessSimulationCode( 108 | PublicScopeSimulator::OPERATION_UNSET, 109 | 'name' 110 | ); 111 | } 112 | 113 | $this->setBody(sprintf( 114 | $this->callParentTemplate, 115 | '$this->' . $initializerProperty->getName() . ' && $this->' . $callInitializer->getName() 116 | . '(\'__unset\', array(\'name\' => $name));', 117 | $publicProperties->getName(), 118 | $protectedProperties->getName(), 119 | $protectedProperties->getName(), 120 | $privateProperties->getName(), 121 | $privateProperties->getName(), 122 | $privateProperties->getName(), 123 | $parentAccess 124 | )); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/SetProxyInitializer.php: -------------------------------------------------------------------------------- 1 | setDefaultValue(null)], 26 | self::FLAG_PUBLIC, 27 | '$this->' . $initializerProperty->getName() . ' = $initializer;' 28 | ); 29 | 30 | $this->setReturnType('void'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/SkipDestructor.php: -------------------------------------------------------------------------------- 1 | setBody( 26 | '$this->' . $initializerProperty->getName() . ' || parent::__destruct();' 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingGhost/PropertyGenerator/InitializationTracker.php: -------------------------------------------------------------------------------- 1 | setVisibility(self::VISIBILITY_PRIVATE); 26 | $this->setDocBlock('@var bool tracks initialization status - true while the object is initializing'); 27 | $this->setDefaultValue(false); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingGhost/PropertyGenerator/InitializerProperty.php: -------------------------------------------------------------------------------- 1 | setVisibility(self::VISIBILITY_PRIVATE); 26 | $this->setDocBlock('@var \\Closure|null initializer responsible for generating the wrapped object'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingGhost/PropertyGenerator/PrivatePropertiesMap.php: -------------------------------------------------------------------------------- 1 | */ 20 | private $readOnlyPropertyNames = []; 21 | 22 | /** 23 | * Constructor 24 | * 25 | * @throws InvalidArgumentException 26 | */ 27 | public function __construct(Properties $properties) 28 | { 29 | parent::__construct( 30 | IdentifierSuffixer::getIdentifier('privateProperties') 31 | ); 32 | 33 | $this->setVisibility(self::VISIBILITY_PRIVATE); 34 | $this->setStatic(true); 35 | $this->setDocBlock( 36 | '@var array[][] visibility and default value of defined properties, indexed by property name and class name' 37 | ); 38 | $this->setDefaultValue($this->getMap($properties)); 39 | } 40 | 41 | /** 42 | * @return list 43 | */ 44 | public function getReadOnlyPropertyNames(): array 45 | { 46 | return $this->readOnlyPropertyNames; 47 | } 48 | 49 | /** 50 | * @return array> 51 | */ 52 | private function getMap(Properties $properties): array 53 | { 54 | $map = []; 55 | 56 | foreach ($properties->getInstanceProperties() as $property) { 57 | if (\PHP_VERSION_ID >= 80100 && $property->isReadOnly()) { 58 | $this->readOnlyPropertyNames[] = $property->getName(); 59 | } elseif (! $property->isPrivate()) { 60 | continue; 61 | } 62 | 63 | $map[$property->getName()][$property->getDeclaringClass()->getName()] = true; 64 | } 65 | 66 | return $map; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingGhost/PropertyGenerator/ProtectedPropertiesMap.php: -------------------------------------------------------------------------------- 1 | setVisibility(self::VISIBILITY_PRIVATE); 31 | $this->setStatic(true); 32 | $this->setDocBlock( 33 | '@var string[][] declaring class name of defined protected properties, indexed by property name' 34 | ); 35 | $this->setDefaultValue($this->getMap($properties)); 36 | } 37 | 38 | /** @return string[] */ 39 | private function getMap(Properties $properties): array 40 | { 41 | $map = []; 42 | 43 | foreach ($properties->getProtectedProperties() as $property) { 44 | if (\PHP_VERSION_ID >= 80100 && $property->isReadOnly()) { 45 | continue; 46 | } 47 | 48 | $map[$property->getName()] = $property->getDeclaringClass()->getName(); 49 | } 50 | 51 | return $map; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/GetProxyInitializer.php: -------------------------------------------------------------------------------- 1 | setReturnType('?\\Closure'); 26 | $this->setBody('return $this->' . $initializerProperty->getName() . ';'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/InitializeProxy.php: -------------------------------------------------------------------------------- 1 | setReturnType('bool'); 26 | 27 | $initializer = $initializerProperty->getName(); 28 | $valueHolder = $valueHolderProperty->getName(); 29 | 30 | $this->setBody( 31 | 'return $this->' . $initializer . ' && ($this->' . $initializer 32 | . '->__invoke($' . $valueHolder 33 | . ', $this, \'initializeProxy\', array(), $this->' . $initializer . ') || 1)' 34 | . ' && $this->' . $valueHolder . ' = $' . $valueHolder . ';' 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/IsProxyInitialized.php: -------------------------------------------------------------------------------- 1 | setReturnType('bool'); 26 | $this->setBody('return null !== $this->' . $valueHolderProperty->getName() . ';'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/LazyLoadingMethodInterceptor.php: -------------------------------------------------------------------------------- 1 | getName(); 31 | $valueHolderName = $valueHolderProperty->getName(); 32 | $parameters = $originalMethod->getParameters(); 33 | $methodName = $originalMethod->getName(); 34 | $initializerParams = []; 35 | $forwardedParams = []; 36 | 37 | foreach ($parameters as $parameter) { 38 | $parameterName = $parameter->getName(); 39 | $variadicPrefix = $parameter->isVariadic() ? '...' : ''; 40 | $initializerParams[] = var_export($parameterName, true) . ' => $' . $parameterName; 41 | $forwardedParams[] = $variadicPrefix . '$' . $parameterName; 42 | } 43 | 44 | $method->setBody( 45 | '$this->' . $initializerName 46 | . ' && ($this->' . $initializerName 47 | . '->__invoke($' . $valueHolderName . ', $this, ' . var_export($methodName, true) 48 | . ', array(' . implode(', ', $initializerParams) . '), $this->' . $initializerName . ') || 1)' 49 | . ' && $this->' . $valueHolderName . ' = $' . $valueHolderName . ";\n\n" 50 | . ProxiedMethodReturnExpression::generate( 51 | '$this->' . $valueHolderName . '->' . $methodName . '(' . implode(', ', $forwardedParams) . ')', 52 | $originalMethod 53 | ) 54 | ); 55 | 56 | return $method; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicClone.php: -------------------------------------------------------------------------------- 1 | getName(); 27 | $valueHolder = $valueHolderProperty->getName(); 28 | 29 | $this->setBody( 30 | '$this->' . $initializer . ' && ($this->' . $initializer 31 | . '->__invoke($' . $valueHolder 32 | . ', $this, \'__clone\', array(), $this->' . $initializer . ') || 1)' 33 | . ' && $this->' . $valueHolder . ' = $' . $valueHolder . ';' . "\n\n" 34 | . '$this->' . $valueHolder . ' = clone $this->' . $valueHolder . ';' 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicGet.php: -------------------------------------------------------------------------------- 1 | hasMethod('__get'); 34 | 35 | $initializer = $initializerProperty->getName(); 36 | $valueHolder = $valueHolderProperty->getName(); 37 | $callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n" 38 | . ' return $this->' . $valueHolder . '->$name;' 39 | . "\n}\n\n"; 40 | 41 | if ($hasParent) { 42 | $this->setInitializerBody( 43 | $initializer, 44 | $valueHolder, 45 | $callParent . 'return $this->' . $valueHolder . '->__get($name);' 46 | ); 47 | 48 | return; 49 | } 50 | 51 | $this->setInitializerBody( 52 | $initializer, 53 | $valueHolder, 54 | $callParent . PublicScopeSimulator::getPublicAccessSimulationCode( 55 | PublicScopeSimulator::OPERATION_GET, 56 | 'name', 57 | null, 58 | $valueHolderProperty, 59 | null, 60 | $originalClass 61 | ) 62 | ); 63 | } 64 | 65 | private function setInitializerBody(string $initializer, string $valueHolder, string $callParent): void 66 | { 67 | $this->setBody( 68 | '$this->' . $initializer . ' && ($this->' . $initializer 69 | . '->__invoke($' . $valueHolder . ', $this, \'__get\', [\'name\' => $name], $this->' 70 | . $initializer . ') || 1) && $this->' . $valueHolder . ' = $' . $valueHolder . ';' 71 | . "\n\n" . $callParent 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicIsset.php: -------------------------------------------------------------------------------- 1 | getName(); 34 | $valueHolder = $valueHolderProperty->getName(); 35 | $callParent = ''; 36 | 37 | if (! $publicProperties->isEmpty()) { 38 | $callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n" 39 | . ' return isset($this->' . $valueHolder . '->$name);' 40 | . "\n}\n\n"; 41 | } 42 | 43 | $callParent .= PublicScopeSimulator::getPublicAccessSimulationCode( 44 | PublicScopeSimulator::OPERATION_ISSET, 45 | 'name', 46 | null, 47 | $valueHolderProperty, 48 | null, 49 | $originalClass 50 | ); 51 | 52 | $this->setBody( 53 | '$this->' . $initializer . ' && ($this->' . $initializer 54 | . '->__invoke($' . $valueHolder . ', $this, \'__isset\', array(\'name\' => $name), $this->' 55 | . $initializer . ') || 1) && $this->' . $valueHolder . ' = $' . $valueHolder . ';' . "\n\n" . $callParent 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicSet.php: -------------------------------------------------------------------------------- 1 | hasMethod('__set'); 38 | $initializer = $initializerProperty->getName(); 39 | $valueHolder = $valueHolderProperty->getName(); 40 | $callParent = ''; 41 | 42 | if (! $publicProperties->isEmpty()) { 43 | $callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n" 44 | . ' return ($this->' . $valueHolder . '->$name = $value);' 45 | . "\n}\n\n"; 46 | } 47 | 48 | $callParent .= $hasParent 49 | ? 'return $this->' . $valueHolder . '->__set($name, $value);' 50 | : PublicScopeSimulator::getPublicAccessSimulationCode( 51 | PublicScopeSimulator::OPERATION_SET, 52 | 'name', 53 | 'value', 54 | $valueHolderProperty, 55 | null, 56 | $originalClass 57 | ); 58 | 59 | $this->setBody( 60 | '$this->' . $initializer . ' && ($this->' . $initializer 61 | . '->__invoke($' . $valueHolder . ', $this, ' 62 | . '\'__set\', array(\'name\' => $name, \'value\' => $value), $this->' . $initializer . ') || 1)' 63 | . ' && $this->' . $valueHolder . ' = $' . $valueHolder . ';' 64 | . "\n\n" . $callParent 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicSleep.php: -------------------------------------------------------------------------------- 1 | getName(); 29 | $valueHolder = $valueHolderProperty->getName(); 30 | 31 | $this->setBody( 32 | '$this->' . $initializer . ' && ($this->' . $initializer 33 | . '->__invoke($' . $valueHolder . ', $this, \'__sleep\', array(), $this->' 34 | . $initializer . ') || 1) && $this->' . $valueHolder . ' = $' . $valueHolder . ';' . "\n\n" 35 | . 'return array(' . var_export($valueHolder, true) . ');' 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicUnset.php: -------------------------------------------------------------------------------- 1 | hasMethod('__unset'); 34 | $initializer = $initializerProperty->getName(); 35 | $valueHolder = $valueHolderProperty->getName(); 36 | $callParent = ''; 37 | 38 | if (! $publicProperties->isEmpty()) { 39 | $callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n" 40 | . ' unset($this->' . $valueHolder . '->$name);' . "\n\n return;" 41 | . "\n}\n\n"; 42 | } 43 | 44 | $callParent .= $hasParent 45 | ? 'return $this->' . $valueHolder . '->__unset($name);' 46 | : PublicScopeSimulator::getPublicAccessSimulationCode( 47 | PublicScopeSimulator::OPERATION_UNSET, 48 | 'name', 49 | null, 50 | $valueHolderProperty, 51 | null, 52 | $originalClass 53 | ); 54 | 55 | $this->setBody( 56 | '$this->' . $initializer . ' && ($this->' . $initializer 57 | . '->__invoke($' . $valueHolder . ', $this, \'__unset\', array(\'name\' => $name), $this->' 58 | . $initializer . ') || 1) && $this->' . $valueHolder . ' = $' . $valueHolder . ';' . "\n\n" . $callParent 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/SetProxyInitializer.php: -------------------------------------------------------------------------------- 1 | setType('?' . Closure::class); 31 | $initializerParameter->setDefaultValue(null); 32 | $this->setParameter($initializerParameter); 33 | $this->setBody('$this->' . $initializerProperty->getName() . ' = $initializer;'); 34 | $this->setReturnType('void'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/SkipDestructor.php: -------------------------------------------------------------------------------- 1 | getName(); 26 | $valueHolder = $valueHolderProperty->getName(); 27 | 28 | $this->setBody( 29 | '$this->' . $initializer . ' || $this->' . $valueHolder . '->__destruct();' 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/PropertyGenerator/InitializerProperty.php: -------------------------------------------------------------------------------- 1 | setVisibility(self::VISIBILITY_PRIVATE); 26 | $this->setDocBlock('@var \\Closure|null initializer responsible for generating the wrapped object'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/PropertyGenerator/ValueHolderProperty.php: -------------------------------------------------------------------------------- 1 | setWordWrap(false); 30 | $docBlock->setLongDescription('@var \\' . $type->getName() . '|null wrapped object, if the proxy is initialized'); 31 | $this->setDocBlock($docBlock); 32 | $this->setVisibility(self::VISIBILITY_PRIVATE); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/NullObject/MethodGenerator/NullObjectMethodInterceptor.php: -------------------------------------------------------------------------------- 1 | getReturnType(); 27 | $nullCast = $returnType instanceof ReflectionNamedType && ! $returnType->allowsNull() && in_array($returnType->getName(), ['array', 'float', 'int', 'string'], true) ? '(' . $returnType->getName() . ') ' : ''; 28 | 29 | if ($originalMethod->returnsReference() || $nullCast !== '') { 30 | $reference = IdentifierSuffixer::getIdentifier('ref'); 31 | 32 | $method->setBody('$' . $reference . ' = ' . $nullCast . "null;\nreturn \$" . $reference . ';'); 33 | } 34 | 35 | return $method; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/NullObject/MethodGenerator/StaticProxyConstructor.php: -------------------------------------------------------------------------------- 1 | ' . $publicProperty->getName() . ' = null;'; 35 | }, 36 | Properties::fromReflectionClass($originalClass) 37 | ->onlyNullableProperties() 38 | ->getPublicProperties() 39 | ); 40 | 41 | $this->setReturnType($originalClass->getName()); 42 | $this->setDocBlock('Constructor for null object initialization'); 43 | $this->setBody( 44 | 'static $reflection;' . "\n\n" 45 | . '$reflection = $reflection ?? new \ReflectionClass(__CLASS__);' . "\n" 46 | . '$instance = $reflection->newInstanceWithoutConstructor();' . "\n\n" 47 | . ($nullableProperties ? implode("\n", $nullableProperties) . "\n\n" : '') 48 | . 'return $instance;' 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/NullObjectGenerator.php: -------------------------------------------------------------------------------- 1 | isInterface()) { 39 | $interfaces[] = $originalClass->getName(); 40 | } else { 41 | $classGenerator->setExtendedClass($originalClass->getName()); 42 | } 43 | 44 | $classGenerator->setImplementedInterfaces($interfaces); 45 | 46 | foreach (ProxiedMethodsFilter::getProxiedMethods($originalClass, []) as $method) { 47 | $classGenerator->addMethodFromGenerator( 48 | NullObjectMethodInterceptor::generateMethod( 49 | new MethodReflection($method->getDeclaringClass()->getName(), $method->getName()) 50 | ) 51 | ); 52 | } 53 | 54 | ClassGeneratorUtils::addMethodIfNotFinal( 55 | $originalClass, 56 | $classGenerator, 57 | new StaticProxyConstructor($originalClass) 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/PropertyGenerator/PublicPropertiesMap.php: -------------------------------------------------------------------------------- 1 | */ 18 | private $publicProperties = []; 19 | 20 | /** 21 | * @throws InvalidArgumentException 22 | */ 23 | public function __construct(Properties $properties, bool $skipReadOnlyProperties = false) 24 | { 25 | parent::__construct(IdentifierSuffixer::getIdentifier('publicProperties')); 26 | 27 | foreach ($properties->getPublicProperties() as $publicProperty) { 28 | if ($skipReadOnlyProperties && \PHP_VERSION_ID >= 80100 && $publicProperty->isReadOnly()) { 29 | continue; 30 | } 31 | 32 | $this->publicProperties[$publicProperty->getName()] = true; 33 | } 34 | 35 | $this->setDefaultValue($this->publicProperties); 36 | $this->setVisibility(self::VISIBILITY_PRIVATE); 37 | $this->setStatic(true); 38 | $this->setDocBlock('@var bool[] map of public properties of the parent class'); 39 | } 40 | 41 | public function isEmpty(): bool 42 | { 43 | return ! $this->publicProperties; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/ProxyGeneratorInterface.php: -------------------------------------------------------------------------------- 1 | setDocBlock('@param string $name'); 30 | $this->setBody( 31 | '$return = $this->' . $adapterProperty->getName() . '->call(' . var_export($originalClass->getName(), true) 32 | . ', \'__get\', array($name));' . "\n\n" . 'return $return;' 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicIsset.php: -------------------------------------------------------------------------------- 1 | setDocBlock('@param string $name'); 30 | $this->setBody( 31 | '$return = $this->' . $adapterProperty->getName() . '->call(' . var_export($originalClass->getName(), true) 32 | . ', \'__isset\', array($name));' . "\n\n" 33 | . 'return $return;' 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicSet.php: -------------------------------------------------------------------------------- 1 | setDocBlock('@param string \$name\n@param mixed \$value'); 34 | $this->setBody( 35 | '$return = $this->' . $adapterProperty->getName() . '->call(' . var_export($originalClass->getName(), true) 36 | . ', \'__set\', array($name, $value));' . "\n\n" 37 | . 'return $return;' 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicUnset.php: -------------------------------------------------------------------------------- 1 | setDocBlock('@param string $name'); 30 | $this->setBody( 31 | '$return = $this->' . $adapterProperty->getName() . '->call(' . var_export($originalClass->getName(), true) 32 | . ', \'__unset\', array($name));' . "\n\n" 33 | . 'return $return;' 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/RemoteObjectMethod.php: -------------------------------------------------------------------------------- 1 | ' . $adapterProperty->getName() 41 | . '->call(' . var_export($originalClass->getName(), true) 42 | . ', ' . var_export($originalMethod->getName(), true) . ', $args);' . "\n\n" 43 | . ProxiedMethodReturnExpression::generate('$return', $originalMethod); 44 | 45 | $defaultValues = self::getDefaultValuesForMethod($originalMethod); 46 | 47 | $method->setBody( 48 | strtr( 49 | self::TEMPLATE, 50 | [ 51 | '#PROXIED_RETURN#' => $proxiedReturn, 52 | '#DEFAULT_VALUES#' => $defaultValues, 53 | ] 54 | ) 55 | ); 56 | 57 | return $method; 58 | } 59 | 60 | private static function getDefaultValuesForMethod(MethodReflection $originalMethod): string 61 | { 62 | $defaultValues = ''; 63 | foreach ($originalMethod->getParameters() as $i => $parameter) { 64 | if ($parameter->isOptional() && $parameter->isDefaultValueAvailable()) { 65 | $default = new ValueGenerator($parameter->getDefaultValue(), $parameter); 66 | $defaultValues .= sprintf("\n case %d: \$args[] = %s;", $i, $default->generate()); 67 | continue; 68 | } 69 | 70 | if ($parameter->isVariadic()) { 71 | continue; 72 | } 73 | 74 | $defaultValues .= sprintf("\n case %d: \$args[] = null;", $i); 75 | } 76 | 77 | return $defaultValues; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/StaticProxyConstructor.php: -------------------------------------------------------------------------------- 1 | getName(); 29 | 30 | parent::__construct( 31 | 'staticProxyConstructor', 32 | [new ParameterGenerator($adapterName, AdapterInterface::class)], 33 | MethodGenerator::FLAG_PUBLIC | MethodGenerator::FLAG_STATIC, 34 | null, 35 | 'Constructor for remote object control\n\n' 36 | . '@param \\ProxyManager\\Factory\\RemoteObject\\AdapterInterface \$adapter' 37 | ); 38 | 39 | $body = 'static $reflection;' . "\n\n" 40 | . '$reflection = $reflection ?? new \ReflectionClass(__CLASS__);' . "\n" 41 | . '$instance = $reflection->newInstanceWithoutConstructor();' . "\n\n" 42 | . '$instance->' . $adapterName . ' = $' . $adapterName . ";\n\n" 43 | . UnsetPropertiesGenerator::generateSnippet(Properties::fromReflectionClass($originalClass), 'instance'); 44 | 45 | $this->setBody($body . "\n\nreturn \$instance;"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/RemoteObject/PropertyGenerator/AdapterProperty.php: -------------------------------------------------------------------------------- 1 | setVisibility(self::VISIBILITY_PRIVATE); 27 | $this->setDocBlock('@var \\' . AdapterInterface::class . ' Remote web service adapter'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/RemoteObjectGenerator.php: -------------------------------------------------------------------------------- 1 | isInterface()) { 51 | $interfaces[] = $originalClass->getName(); 52 | } else { 53 | $classGenerator->setExtendedClass($originalClass->getName()); 54 | } 55 | 56 | $classGenerator->setImplementedInterfaces($interfaces); 57 | $classGenerator->addPropertyFromGenerator($adapter = new AdapterProperty()); 58 | 59 | array_map( 60 | static function (MethodGenerator $generatedMethod) use ($originalClass, $classGenerator): void { 61 | ClassGeneratorUtils::addMethodIfNotFinal($originalClass, $classGenerator, $generatedMethod); 62 | }, 63 | array_merge( 64 | array_map( 65 | static function (ReflectionMethod $method) use ($adapter, $originalClass): RemoteObjectMethod { 66 | return RemoteObjectMethod::generateMethod( 67 | new MethodReflection($method->getDeclaringClass()->getName(), $method->getName()), 68 | $adapter, 69 | $originalClass 70 | ); 71 | }, 72 | ProxiedMethodsFilter::getProxiedMethods( 73 | $originalClass, 74 | ['__get', '__set', '__isset', '__unset'] 75 | ) 76 | ), 77 | [ 78 | new StaticProxyConstructor($originalClass, $adapter), 79 | new MagicGet($originalClass, $adapter), 80 | new MagicSet($originalClass, $adapter), 81 | new MagicIsset($originalClass, $adapter), 82 | new MagicUnset($originalClass, $adapter), 83 | ] 84 | ) 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/Util/GetMethodIfExists.php: -------------------------------------------------------------------------------- 1 | hasMethod($method) ? $class->getMethod($method) : null; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/Util/ProxiedMethodsFilter.php: -------------------------------------------------------------------------------- 1 | $excluded methods to be ignored 38 | * 39 | * @return ReflectionMethod[] 40 | */ 41 | public static function getProxiedMethods(ReflectionClass $class, ?array $excluded = null): array 42 | { 43 | return self::doFilter($class, $excluded ?? self::DEFAULT_EXCLUDED); 44 | } 45 | 46 | /** 47 | * @param ReflectionClass $class reflection class from which methods should be extracted 48 | * @param array $excluded methods to be ignored 49 | * 50 | * @return ReflectionMethod[] 51 | */ 52 | public static function getAbstractProxiedMethods(ReflectionClass $class, ?array $excluded = null): array 53 | { 54 | return self::doFilter($class, $excluded ?? self::DEFAULT_EXCLUDED, true); 55 | } 56 | 57 | /** 58 | * @param array $excluded 59 | * 60 | * @return array 61 | */ 62 | private static function doFilter(ReflectionClass $class, array $excluded, bool $requireAbstract = false): array 63 | { 64 | $ignored = array_flip(array_map('strtolower', $excluded)); 65 | 66 | return array_values(array_filter( 67 | $class->getMethods(ReflectionMethod::IS_PUBLIC), 68 | static function (ReflectionMethod $method) use ($ignored, $requireAbstract): bool { 69 | return (! $requireAbstract || $method->isAbstract()) && ! ( 70 | array_key_exists(strtolower($method->getName()), $ignored) 71 | || self::methodCannotBeProxied($method) 72 | ); 73 | } 74 | )); 75 | } 76 | 77 | /** 78 | * Checks whether the method cannot be proxied 79 | */ 80 | private static function methodCannotBeProxied(ReflectionMethod $method): bool 81 | { 82 | return $method->isConstructor() || $method->isFinal() || $method->isStatic(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/Util/UnsetPropertiesGenerator.php: -------------------------------------------------------------------------------- 1 | __invoke($%s); 26 | PHP; 27 | 28 | public static function generateSnippet(Properties $properties, string $instanceName): string 29 | { 30 | $scopedPropertyGroups = []; 31 | $nonScopedProperties = []; 32 | 33 | foreach ($properties->getInstanceProperties() as $propertyInternalName => $property) { 34 | if ($property->isPrivate() || (\PHP_VERSION_ID >= 80100 && $property->isReadOnly())) { 35 | $scopedPropertyGroups[$property->getDeclaringClass()->getName()][$property->getName()] = $property; 36 | } else { 37 | $nonScopedProperties[$propertyInternalName] = $property; 38 | } 39 | } 40 | 41 | return self::generateUnsetNonScopedPropertiesCode($nonScopedProperties, $instanceName) 42 | . self::generateUnsetScopedPropertiesCode($scopedPropertyGroups, $instanceName); 43 | } 44 | 45 | /** @param array $nonScopedProperties */ 46 | private static function generateUnsetNonScopedPropertiesCode(array $nonScopedProperties, string $instanceName): string 47 | { 48 | if (! $nonScopedProperties) { 49 | return ''; 50 | } 51 | 52 | return self::generateUnsetStatement($nonScopedProperties, $instanceName) . "\n\n"; 53 | } 54 | 55 | /** @param array> $scopedPropertyGroups */ 56 | private static function generateUnsetScopedPropertiesCode(array $scopedPropertyGroups, string $instanceName): string 57 | { 58 | if (! $scopedPropertyGroups) { 59 | return ''; 60 | } 61 | 62 | $unsetClosureCalls = []; 63 | 64 | foreach ($scopedPropertyGroups as $scopedProperties) { 65 | $firstProperty = reset($scopedProperties); 66 | assert($firstProperty instanceof ReflectionProperty); 67 | 68 | $unsetClosureCalls[] = self::generateUnsetClassScopedPropertiesBlock( 69 | $firstProperty->getDeclaringClass(), 70 | $scopedProperties, 71 | $instanceName 72 | ); 73 | } 74 | 75 | return implode("\n\n", $unsetClosureCalls) . "\n\n"; 76 | } 77 | 78 | /** @param array $properties */ 79 | private static function generateUnsetClassScopedPropertiesBlock( 80 | ReflectionClass $declaringClass, 81 | array $properties, 82 | string $instanceName 83 | ): string { 84 | $declaringClassName = $declaringClass->getName(); 85 | 86 | return sprintf( 87 | self::CLOSURE_TEMPLATE, 88 | $declaringClassName, 89 | self::generateUnsetStatement($properties, 'instance'), 90 | $instanceName, 91 | var_export($declaringClassName, true), 92 | $instanceName 93 | ); 94 | } 95 | 96 | /** @param array $properties */ 97 | private static function generateUnsetStatement(array $properties, string $instanceName): string 98 | { 99 | return 'unset(' 100 | . implode( 101 | ', ', 102 | array_map( 103 | static function (ReflectionProperty $property) use ($instanceName): string { 104 | return '$' . $instanceName . '->' . $property->getName(); 105 | }, 106 | $properties 107 | ) 108 | ) 109 | . ');'; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/ValueHolder/MethodGenerator/Constructor.php: -------------------------------------------------------------------------------- 1 | setBody( 40 | 'static $reflection;' . "\n\n" 41 | . 'if (! $this->' . $valueHolder->getName() . ') {' . "\n" 42 | . ' $reflection = $reflection ?? new \ReflectionClass(' 43 | . var_export($originalClass->getName(), true) 44 | . ");\n" 45 | . ' $this->' . $valueHolder->getName() . ' = $reflection->newInstanceWithoutConstructor();' . "\n" 46 | . UnsetPropertiesGenerator::generateSnippet(Properties::fromReflectionClass($originalClass), 'this') 47 | . '}' 48 | . ($originalConstructor ? self::generateOriginalConstructorCall($originalConstructor, $valueHolder) : '') 49 | ); 50 | 51 | return $constructor; 52 | } 53 | 54 | private static function generateOriginalConstructorCall( 55 | MethodReflection $originalConstructor, 56 | PropertyGenerator $valueHolder 57 | ): string { 58 | return "\n\n" 59 | . '$this->' . $valueHolder->getName() . '->' . $originalConstructor->getName() . '(' 60 | . implode( 61 | ', ', 62 | array_map( 63 | static function (ParameterReflection $parameter): string { 64 | return ($parameter->isVariadic() ? '...' : '') . '$' . $parameter->getName(); 65 | }, 66 | $originalConstructor->getParameters() 67 | ) 68 | ) 69 | . ');'; 70 | } 71 | 72 | private static function getConstructor(ReflectionClass $class): ?MethodReflection 73 | { 74 | $constructors = array_map( 75 | static function (ReflectionMethod $method): MethodReflection { 76 | return new MethodReflection( 77 | $method->getDeclaringClass()->getName(), 78 | $method->getName() 79 | ); 80 | }, 81 | array_filter( 82 | $class->getMethods(), 83 | static function (ReflectionMethod $method): bool { 84 | return $method->isConstructor(); 85 | } 86 | ) 87 | ); 88 | 89 | return reset($constructors) ?: null; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/ValueHolder/MethodGenerator/GetWrappedValueHolderValue.php: -------------------------------------------------------------------------------- 1 | setBody('return $this->' . $valueHolderProperty->getName() . ';'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/ProxyManager/ProxyGenerator/ValueHolder/MethodGenerator/MagicSleep.php: -------------------------------------------------------------------------------- 1 | setBody('return array(' . var_export($valueHolderProperty->getName(), true) . ');'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/ProxyManager/Signature/ClassSignatureGenerator.php: -------------------------------------------------------------------------------- 1 | signatureGenerator = $signatureGenerator; 21 | } 22 | 23 | /** 24 | * {@inheritDoc} 25 | * 26 | * @throws InvalidArgumentException 27 | */ 28 | public function addSignature(ClassGenerator $classGenerator, array $parameters): ClassGenerator 29 | { 30 | $classGenerator->addPropertyFromGenerator(new PropertyGenerator( 31 | 'signature' . $this->signatureGenerator->generateSignatureKey($parameters), 32 | $this->signatureGenerator->generateSignature($parameters), 33 | PropertyGenerator::FLAG_STATIC | PropertyGenerator::FLAG_PRIVATE 34 | )); 35 | 36 | return $classGenerator; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/ProxyManager/Signature/ClassSignatureGeneratorInterface.php: -------------------------------------------------------------------------------- 1 | $parameters 20 | */ 21 | public function addSignature(ClassGenerator $classGenerator, array $parameters): ClassGenerator; 22 | } 23 | -------------------------------------------------------------------------------- /src/ProxyManager/Signature/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | getName(), 29 | $expected, 30 | count($parameters) 31 | )); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ProxyManager/Signature/Exception/MissingSignatureException.php: -------------------------------------------------------------------------------- 1 | getName(), 24 | $expected, 25 | count($parameters) 26 | )); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/ProxyManager/Signature/SignatureChecker.php: -------------------------------------------------------------------------------- 1 | signatureGenerator = $signatureGenerator; 24 | } 25 | 26 | /** 27 | * {@inheritDoc} 28 | */ 29 | public function checkSignature(ReflectionClass $class, array $parameters): void 30 | { 31 | $propertyName = 'signature' . $this->signatureGenerator->generateSignatureKey($parameters); 32 | $signature = $this->signatureGenerator->generateSignature($parameters); 33 | $defaultProperties = $class->getDefaultProperties(); 34 | 35 | if (! (array_key_exists($propertyName, $defaultProperties) && is_string($defaultProperties[$propertyName]))) { 36 | throw MissingSignatureException::fromMissingSignature($class, $parameters, $signature); 37 | } 38 | 39 | if ($defaultProperties[$propertyName] !== $signature) { 40 | throw InvalidSignatureException::fromInvalidSignature( 41 | $class, 42 | $parameters, 43 | $defaultProperties[$propertyName], 44 | $signature 45 | ); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ProxyManager/Signature/SignatureCheckerInterface.php: -------------------------------------------------------------------------------- 1 | $parameters 20 | * 21 | * @throws InvalidSignatureException 22 | * @throws MissingSignatureException 23 | */ 24 | public function checkSignature(ReflectionClass $class, array $parameters): void; 25 | } 26 | -------------------------------------------------------------------------------- /src/ProxyManager/Signature/SignatureGenerator.php: -------------------------------------------------------------------------------- 1 | parameterEncoder = new ParameterEncoder(); 18 | $this->parameterHasher = new ParameterHasher(); 19 | } 20 | 21 | /** 22 | * {@inheritDoc} 23 | */ 24 | public function generateSignature(array $parameters): string 25 | { 26 | return $this->parameterEncoder->encodeParameters($parameters); 27 | } 28 | 29 | /** 30 | * {@inheritDoc} 31 | */ 32 | public function generateSignatureKey(array $parameters): string 33 | { 34 | return $this->parameterHasher->hashParameters($parameters); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/ProxyManager/Signature/SignatureGeneratorInterface.php: -------------------------------------------------------------------------------- 1 | $parameters 16 | */ 17 | public function generateSignature(array $parameters): string; 18 | 19 | /** 20 | * Generates a signature key to be looked up when verifying generated code validity 21 | * 22 | * @param array $parameters 23 | */ 24 | public function generateSignatureKey(array $parameters): string; 25 | } 26 | -------------------------------------------------------------------------------- /src/ProxyManager/Stub/EmptyClassStub.php: -------------------------------------------------------------------------------- 1 | @, 29 | * where the detected version is what composer could detect. 30 | * 31 | * @throws OutOfBoundsException 32 | * 33 | * @psalm-pure 34 | * 35 | * @psalm-suppress MixedOperand `composer-runtime-api:^2` has rough if no type declarations at all - we'll live with it 36 | * @psalm-suppress ImpureMethodCall `composer-runtime-api:^2` has rough if no type declarations at all - we'll live with it 37 | */ 38 | public static function getVersion(): string 39 | { 40 | if (class_exists(InstalledVersions::class) && InstalledVersions::isInstalled('friendsofphp/proxy-manager-lts')) { 41 | return InstalledVersions::getPrettyVersion('friendsofphp/proxy-manager-lts') 42 | . '@' . InstalledVersions::getReference('friendsofphp/proxy-manager-lts'); 43 | } 44 | 45 | if (class_exists(Versions::class)) { 46 | try { 47 | return Versions::getVersion('friendsofphp/proxy-manager-lts'); 48 | } catch (\OutOfBoundsException $e) { 49 | // no-op 50 | } 51 | } 52 | 53 | return '1@friendsofphp/proxy-manager-lts'; 54 | } 55 | } 56 | --------------------------------------------------------------------------------