Subida del módulo y tema de PrestaShop

This commit is contained in:
Kaloyan
2026-04-09 18:31:51 +02:00
parent 12c253296f
commit 16b3ff9424
39262 changed files with 7418797 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
Copyright (c) 2013 Marco Pivetta
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Autoloader;
use ProxyManager\FileLocator\FileLocatorInterface;
use ProxyManager\Inflector\ClassNameInflectorInterface;
use function class_exists;
use function file_exists;
class Autoloader implements AutoloaderInterface
{
protected $fileLocator;
protected $classNameInflector;
public function __construct(FileLocatorInterface $fileLocator, ClassNameInflectorInterface $classNameInflector)
{
$this->fileLocator = $fileLocator;
$this->classNameInflector = $classNameInflector;
}
public function __invoke(string $className): bool
{
if (class_exists($className, false) || ! $this->classNameInflector->isProxyClassName($className)) {
return false;
}
$file = $this->fileLocator->getProxyFileName($className);
if (! file_exists($file)) {
return false;
}
/* @noinspection PhpIncludeInspection */
/* @noinspection UsingInclusionOnceReturnValueInspection */
return (bool) require_once $file;
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Autoloader;
/**
* Basic autoloader utilities required to work with proxy files
*/
interface AutoloaderInterface
{
/**
* Callback to allow the object to be handled as autoloader - tries to autoload the given class name
*
* @psalm-param class-string $className
*/
public function __invoke(string $className): bool;
}

View File

@@ -0,0 +1,128 @@
<?php
declare(strict_types=1);
namespace ProxyManager;
use ProxyManager\Autoloader\Autoloader;
use ProxyManager\Autoloader\AutoloaderInterface;
use ProxyManager\FileLocator\FileLocator;
use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
use ProxyManager\GeneratorStrategy\GeneratorStrategyInterface;
use ProxyManager\Inflector\ClassNameInflector;
use ProxyManager\Inflector\ClassNameInflectorInterface;
use ProxyManager\Signature\ClassSignatureGenerator;
use ProxyManager\Signature\ClassSignatureGeneratorInterface;
use ProxyManager\Signature\SignatureChecker;
use ProxyManager\Signature\SignatureCheckerInterface;
use ProxyManager\Signature\SignatureGenerator;
use ProxyManager\Signature\SignatureGeneratorInterface;
use function sys_get_temp_dir;
/**
* Base configuration class for the proxy manager - serves as micro disposable DIC/facade
*/
class Configuration
{
public const DEFAULT_PROXY_NAMESPACE = 'ProxyManagerGeneratedProxy';
protected $proxiesTargetDir;
protected $proxiesNamespace = self::DEFAULT_PROXY_NAMESPACE;
protected $generatorStrategy;
protected $proxyAutoloader;
protected $classNameInflector;
protected $signatureGenerator;
protected $signatureChecker;
protected $classSignatureGenerator;
public function setProxyAutoloader(AutoloaderInterface $proxyAutoloader): void
{
$this->proxyAutoloader = $proxyAutoloader;
}
public function getProxyAutoloader(): AutoloaderInterface
{
return $this->proxyAutoloader
?? $this->proxyAutoloader = new Autoloader(
new FileLocator($this->getProxiesTargetDir()),
$this->getClassNameInflector()
);
}
public function setProxiesNamespace(string $proxiesNamespace): void
{
$this->proxiesNamespace = $proxiesNamespace;
}
public function getProxiesNamespace(): string
{
return $this->proxiesNamespace;
}
public function setProxiesTargetDir(string $proxiesTargetDir): void
{
$this->proxiesTargetDir = $proxiesTargetDir;
}
public function getProxiesTargetDir(): string
{
return $this->proxiesTargetDir
?? $this->proxiesTargetDir = sys_get_temp_dir();
}
public function setGeneratorStrategy(GeneratorStrategyInterface $generatorStrategy): void
{
$this->generatorStrategy = $generatorStrategy;
}
public function getGeneratorStrategy(): GeneratorStrategyInterface
{
return $this->generatorStrategy
?? $this->generatorStrategy = new EvaluatingGeneratorStrategy();
}
public function setClassNameInflector(ClassNameInflectorInterface $classNameInflector): void
{
$this->classNameInflector = $classNameInflector;
}
public function getClassNameInflector(): ClassNameInflectorInterface
{
return $this->classNameInflector
?? $this->classNameInflector = new ClassNameInflector($this->getProxiesNamespace());
}
public function setSignatureGenerator(SignatureGeneratorInterface $signatureGenerator): void
{
$this->signatureGenerator = $signatureGenerator;
}
public function getSignatureGenerator(): SignatureGeneratorInterface
{
return $this->signatureGenerator
?? $this->signatureGenerator = new SignatureGenerator();
}
public function setSignatureChecker(SignatureCheckerInterface $signatureChecker): void
{
$this->signatureChecker = $signatureChecker;
}
public function getSignatureChecker(): SignatureCheckerInterface
{
return $this->signatureChecker
?? $this->signatureChecker = new SignatureChecker($this->getSignatureGenerator());
}
public function setClassSignatureGenerator(ClassSignatureGeneratorInterface $classSignatureGenerator): void
{
$this->classSignatureGenerator = $classSignatureGenerator;
}
public function getClassSignatureGenerator(): ClassSignatureGeneratorInterface
{
return $this->classSignatureGenerator
?? $this->classSignatureGenerator = new ClassSignatureGenerator($this->getSignatureGenerator());
}
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Exception;
use BadMethodCallException;
use function sprintf;
/**
* Exception for forcefully disabled methods
*/
class DisabledMethodException extends BadMethodCallException implements ExceptionInterface
{
public const NAME = self::class;
public static function disabledMethod(string $method): self
{
return new self(sprintf('Method "%s" is forcefully disabled', $method));
}
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Exception;
use Throwable;
/**
* Base exception class for the proxy manager
*/
interface ExceptionInterface extends Throwable
{
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Exception;
use Symfony\Component\Filesystem\Exception\IOException;
use Throwable;
use UnexpectedValueException;
use function sprintf;
/**
* Exception for non writable files
*/
class FileNotWritableException extends UnexpectedValueException implements ExceptionInterface
{
/**
* @deprecated
*/
public static function fromInvalidMoveOperation(string $fromPath, string $toPath): self
{
return new self(sprintf(
'Could not move file "%s" to location "%s": '
. 'either the source file is not readable, or the destination is not writable',
$fromPath,
$toPath
));
}
/**
* @deprecated
*/
public static function fromNotWritableDirectory(string $directory): self
{
return new self(sprintf(
'Could not create temp file in directory "%s" '
. 'either the directory does not exist, or it is not writable',
$directory
));
}
public static function fromPrevious(Throwable $previous): self
{
return new self($previous->getMessage(), 0, $previous);
}
}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Exception;
use InvalidArgumentException;
use ReflectionClass;
use ReflectionMethod;
use function array_filter;
use function array_map;
use function implode;
use function sprintf;
/**
* Exception for invalid proxied classes
*/
class InvalidProxiedClassException extends InvalidArgumentException implements ExceptionInterface
{
public static function interfaceNotSupported(ReflectionClass $reflection): self
{
return new self(sprintf('Provided interface "%s" cannot be proxied', $reflection->getName()));
}
public static function finalClassNotSupported(ReflectionClass $reflection): self
{
return new self(sprintf('Provided class "%s" is final and cannot be proxied', $reflection->getName()));
}
public static function abstractProtectedMethodsNotSupported(ReflectionClass $reflection): self
{
return new self(sprintf(
'Provided class "%s" has following protected abstract methods, and therefore cannot be proxied:' . "\n%s",
$reflection->getName(),
implode(
"\n",
array_map(
static function (ReflectionMethod $reflectionMethod): string {
return $reflectionMethod->getDeclaringClass()->getName() . '::' . $reflectionMethod->getName();
},
array_filter(
$reflection->getMethods(),
static function (ReflectionMethod $method): bool {
return $method->isAbstract() && $method->isProtected();
}
)
)
)
));
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Exception;
use InvalidArgumentException;
use function sprintf;
/**
* Exception for invalid directories
*/
class InvalidProxyDirectoryException extends InvalidArgumentException implements ExceptionInterface
{
public static function proxyDirectoryNotFound(string $directory): self
{
return new self(sprintf('Provided directory "%s" does not exist', $directory));
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Exception;
use LogicException;
use ProxyManager\ProxyGenerator\Util\Properties;
use ReflectionClass;
use ReflectionProperty;
use function array_map;
use function implode;
use function sprintf;
/**
* Exception for invalid proxied classes
*/
class UnsupportedProxiedClassException extends LogicException implements ExceptionInterface
{
public static function unsupportedLocalizedReflectionProperty(ReflectionProperty $property): self
{
return new self(
sprintf(
'Provided reflection property "%s" of class "%s" is private and cannot be localized in PHP 5.3',
$property->getName(),
$property->getDeclaringClass()->getName()
)
);
}
public static function nonReferenceableLocalizedReflectionProperties(
ReflectionClass $class,
Properties $properties
): self {
return new self(sprintf(
'Cannot create references for following properties of class %s: %s',
$class->getName(),
implode(', ', array_map(static function (ReflectionProperty $property): string {
return $property->getName();
}, $properties->getInstanceProperties()))
));
}
}

View File

@@ -0,0 +1,128 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Factory;
use OutOfBoundsException;
use ProxyManager\Configuration;
use ProxyManager\Generator\ClassGenerator;
use ProxyManager\ProxyGenerator\ProxyGeneratorInterface;
use ProxyManager\Signature\Exception\InvalidSignatureException;
use ProxyManager\Signature\Exception\MissingSignatureException;
use ProxyManager\Version;
use ReflectionClass;
use function array_key_exists;
use function assert;
use function class_exists;
use function is_a;
use function serialize;
use function sha1;
/**
* Base factory common logic
*/
abstract class AbstractBaseFactory
{
protected $configuration;
/**
* Cached checked class names
*
* @var array<string, string>
* @psalm-var array<string, class-string>
*/
private $checkedClasses = [];
public function __construct(?Configuration $configuration = null)
{
$this->configuration = $configuration ?? new Configuration();
}
/**
* Generate a proxy from a class name
*
* @param array<string, mixed> $proxyOptions
* @psalm-param class-string<RealObjectType> $className
*
* @psalm-return class-string<RealObjectType>
*
* @throws InvalidSignatureException
* @throws MissingSignatureException
* @throws OutOfBoundsException
*
* @psalm-template RealObjectType of object
*/
protected function generateProxy(string $className, array $proxyOptions = []): string
{
$cacheKey = $proxyOptions ? sha1(serialize([$className, $proxyOptions])) : $className;
if (array_key_exists($cacheKey, $this->checkedClasses)) {
$generatedClassName = $this->checkedClasses[$cacheKey];
assert(is_a($generatedClassName, $className, true));
return $generatedClassName;
}
$proxyParameters = [
'className' => $className,
'factory' => static::class,
'proxyManagerVersion' => Version::getVersion(),
'proxyOptions' => $proxyOptions,
];
$proxyClassName = $this
->configuration
->getClassNameInflector()
->getProxyClassName($className, $proxyParameters);
if (! class_exists($proxyClassName)) {
$this->generateProxyClass(
$proxyClassName,
$className,
$proxyParameters,
$proxyOptions
);
}
$this
->configuration
->getSignatureChecker()
->checkSignature(new ReflectionClass($proxyClassName), $proxyParameters);
return $this->checkedClasses[$cacheKey] = $proxyClassName;
}
abstract protected function getGenerator(): ProxyGeneratorInterface;
/**
* Generates the provided `$proxyClassName` from the given `$className` and `$proxyParameters`
*
* @param array<string, mixed> $proxyParameters
* @param array<string, mixed> $proxyOptions
* @psalm-param class-string $proxyClassName
* @psalm-param class-string $className
*/
private function generateProxyClass(
string $proxyClassName,
string $className,
array $proxyParameters,
array $proxyOptions = []
): void {
$className = $this->configuration->getClassNameInflector()->getUserClassName($className);
$phpClass = new ClassGenerator($proxyClassName);
/** @psalm-suppress TooManyArguments - generator interface was not updated due to BC compliance */
$this->getGenerator()->generate(new ReflectionClass($className), $phpClass, $proxyOptions);
$phpClass = $this->configuration->getClassSignatureGenerator()->addSignature($phpClass, $proxyParameters);
/** @psalm-suppress TooManyArguments - generator interface was not updated due to BC compliance */
$this->configuration->getGeneratorStrategy()->generate($phpClass, $proxyOptions);
$autoloader = $this->configuration->getProxyAutoloader();
$autoloader($proxyClassName);
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Factory;
use Closure;
use OutOfBoundsException;
use ProxyManager\Configuration;
use ProxyManager\Proxy\AccessInterceptorInterface;
use ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizerGenerator;
use ProxyManager\ProxyGenerator\ProxyGeneratorInterface;
use ProxyManager\Signature\Exception\InvalidSignatureException;
use ProxyManager\Signature\Exception\MissingSignatureException;
use function get_class;
/**
* Factory responsible of producing proxy objects
*/
class AccessInterceptorScopeLocalizerFactory extends AbstractBaseFactory
{
private $generator;
public function __construct(?Configuration $configuration = null)
{
parent::__construct($configuration);
$this->generator = new AccessInterceptorScopeLocalizerGenerator();
}
/**
* @param object $instance the object to be localized within the access interceptor
* @param array<string, Closure> $prefixInterceptors an array (indexed by method name) of interceptor closures to be called
* before method logic is executed
* @param array<string, Closure> $suffixInterceptors an array (indexed by method name) of interceptor closures to be called
* after method logic is executed
* @psalm-param RealObjectType $instance
* @psalm-param array<string, Closure(
* RealObjectType&AccessInterceptorInterface<RealObjectType>=,
* RealObjectType=,
* string=,
* array<string, mixed>=,
* bool=
* ) : mixed> $prefixInterceptors
* @psalm-param array<string, Closure(
* RealObjectType&AccessInterceptorInterface<RealObjectType>=,
* RealObjectType=,
* string=,
* array<string, mixed>=,
* mixed=,
* bool=
* ) : mixed> $suffixInterceptors
*
* @psalm-return RealObjectType&AccessInterceptorInterface<RealObjectType>
*
* @throws InvalidSignatureException
* @throws MissingSignatureException
* @throws OutOfBoundsException
*
* @psalm-template RealObjectType of object
* @psalm-suppress MixedInferredReturnType We ignore type checks here, since `staticProxyConstructor` is not
* interfaced (by design)
*/
public function createProxy(
$instance,
array $prefixInterceptors = [],
array $suffixInterceptors = []
): AccessInterceptorInterface {
$proxyClassName = $this->generateProxy(get_class($instance));
/**
* We ignore type checks here, since `staticProxyConstructor` is not interfaced (by design)
*
* @psalm-suppress MixedMethodCall
* @psalm-suppress MixedReturnStatement
*/
return $proxyClassName::staticProxyConstructor($instance, $prefixInterceptors, $suffixInterceptors);
}
protected function getGenerator(): ProxyGeneratorInterface
{
return $this->generator;
}
}

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Factory;
use Closure;
use OutOfBoundsException;
use ProxyManager\Configuration;
use ProxyManager\Proxy\AccessInterceptorInterface;
use ProxyManager\Proxy\AccessInterceptorValueHolderInterface;
use ProxyManager\Proxy\ValueHolderInterface;
use ProxyManager\ProxyGenerator\AccessInterceptorValueHolderGenerator;
use ProxyManager\ProxyGenerator\ProxyGeneratorInterface;
use ProxyManager\Signature\Exception\InvalidSignatureException;
use ProxyManager\Signature\Exception\MissingSignatureException;
use function get_class;
/**
* Factory responsible of producing proxy objects
*/
class AccessInterceptorValueHolderFactory extends AbstractBaseFactory
{
private $generator;
public function __construct(?Configuration $configuration = null)
{
parent::__construct($configuration);
$this->generator = new AccessInterceptorValueHolderGenerator();
}
/**
* @param object $instance the object to be wrapped within the value holder
* @param array<string, Closure> $prefixInterceptors an array (indexed by method name) of interceptor closures to be called
* before method logic is executed
* @param array<string, Closure> $suffixInterceptors an array (indexed by method name) of interceptor closures to be called
* after method logic is executed
* @psalm-param RealObjectType $instance
* @psalm-param array<string, callable(
* RealObjectType&AccessInterceptorInterface<RealObjectType>=,
* RealObjectType=,
* string=,
* array<string, mixed>=,
* bool=
* ) : mixed> $prefixInterceptors
* @psalm-param array<string, callable(
* RealObjectType&AccessInterceptorInterface<RealObjectType>=,
* RealObjectType=,
* string=,
* array<string, mixed>=,
* mixed=,
* bool=
* ) : mixed> $suffixInterceptors
*
* @psalm-return RealObjectType&AccessInterceptorInterface<RealObjectType>&ValueHolderInterface<RealObjectType>&AccessInterceptorValueHolderInterface<RealObjectType>
*
* @throws InvalidSignatureException
* @throws MissingSignatureException
* @throws OutOfBoundsException
*
* @psalm-template RealObjectType of object
* @psalm-suppress MixedInferredReturnType We ignore type checks here, since `staticProxyConstructor` is not
* interfaced (by design)
*/
public function createProxy(
$instance,
array $prefixInterceptors = [],
array $suffixInterceptors = []
): AccessInterceptorValueHolderInterface {
$proxyClassName = $this->generateProxy(get_class($instance));
/**
* We ignore type checks here, since `staticProxyConstructor` is not interfaced (by design)
*
* @psalm-suppress MixedMethodCall
* @psalm-suppress MixedReturnStatement
*/
return $proxyClassName::staticProxyConstructor($instance, $prefixInterceptors, $suffixInterceptors);
}
protected function getGenerator(): ProxyGeneratorInterface
{
return $this->generator;
}
}

View File

@@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Factory;
use Closure;
use OutOfBoundsException;
use ProxyManager\Configuration;
use ProxyManager\Proxy\GhostObjectInterface;
use ProxyManager\ProxyGenerator\LazyLoadingGhostGenerator;
use ProxyManager\ProxyGenerator\ProxyGeneratorInterface;
use ProxyManager\Signature\Exception\InvalidSignatureException;
use ProxyManager\Signature\Exception\MissingSignatureException;
/**
* Factory responsible of producing ghost instances
*/
class LazyLoadingGhostFactory extends AbstractBaseFactory
{
private $generator;
public function __construct(?Configuration $configuration = null)
{
parent::__construct($configuration);
$this->generator = new LazyLoadingGhostGenerator();
}
protected function getGenerator(): ProxyGeneratorInterface
{
return $this->generator;
}
/**
* Creates a new lazy proxy instance of the given class with
* the given initializer
*
* Please refer to the following documentation when using this method:
*
* @link https://github.com/Ocramius/ProxyManager/blob/master/docs/lazy-loading-ghost-object.md
*
* @param string $className name of the class to be proxied
* @param Closure $initializer initializer to be passed to the proxy. The initializer closure should have following
* signature:
*
* <code>
* $initializer = function (
* GhostObjectInterface $proxy,
* string $method,
* array $parameters,
* & $initializer,
* array $properties
* ) {};
* </code>
*
* Where:
* - $proxy is the proxy instance on which the initializer is acting
* - $method is the name of the method that triggered the lazy initialization
* - $parameters are the parameters that were passed to $method
* - $initializer by-ref initializer - should be assigned null in the initializer body
* - $properties a by-ref map of the properties of the object, indexed by PHP
* internal property name. Assign values to it to initialize the
* object state
* @param mixed[] $proxyOptions a set of options to be used when generating the proxy. Currently supports only
* key "skippedProperties", which allows to skip lazy-loading of some properties.
* "skippedProperties" is a string[], containing a list of properties referenced
* via PHP's internal property name (i.e. "\0ClassName\0propertyName")
* @psalm-param class-string<RealObjectType> $className
* @psalm-param Closure(
* RealObjectType&GhostObjectInterface<RealObjectType>=,
* string=,
* array<string, mixed>=,
* ?Closure=,
* array<string, mixed>=
* ) : bool $initializer
* @psalm-param array{skippedProperties?: array<int, string>} $proxyOptions
*
* @psalm-return RealObjectType&GhostObjectInterface<RealObjectType>
*
* @throws MissingSignatureException
* @throws InvalidSignatureException
* @throws OutOfBoundsException
*
* @psalm-template RealObjectType as object
* @psalm-suppress MixedInferredReturnType We ignore type checks here, since `staticProxyConstructor` is not
* interfaced (by design)
*/
public function createProxy(
string $className,
Closure $initializer,
array $proxyOptions = []
): GhostObjectInterface {
$proxyClassName = $this->generateProxy($className, $proxyOptions);
/**
* We ignore type checks here, since `staticProxyConstructor` is not interfaced (by design)
*
* @psalm-suppress MixedMethodCall
* @psalm-suppress MixedReturnStatement
*/
return $proxyClassName::staticProxyConstructor($initializer);
}
}

View File

@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Factory;
use Closure;
use ProxyManager\Configuration;
use ProxyManager\Proxy\ValueHolderInterface;
use ProxyManager\Proxy\VirtualProxyInterface;
use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator;
use ProxyManager\ProxyGenerator\ProxyGeneratorInterface;
/**
* Factory responsible of producing virtual proxy instances
*/
class LazyLoadingValueHolderFactory extends AbstractBaseFactory
{
private $generator;
public function __construct(?Configuration $configuration = null)
{
parent::__construct($configuration);
$this->generator = new LazyLoadingValueHolderGenerator();
}
/**
* @param array<string, mixed> $proxyOptions
* @psalm-param class-string<RealObjectType> $className
* @psalm-param Closure(
* RealObjectType|null=,
* RealObjectType&ValueHolderInterface<RealObjectType>&VirtualProxyInterface=,
* string=,
* array<string, mixed>=,
* ?Closure=
* ) : bool $initializer
* @psalm-param array{skipDestructor?: bool, fluentSafe?: bool} $proxyOptions
*
* @psalm-return RealObjectType&ValueHolderInterface<RealObjectType>&VirtualProxyInterface
*
* @psalm-template RealObjectType of object
* @psalm-suppress MixedInferredReturnType We ignore type checks here, since `staticProxyConstructor` is not
* interfaced (by design)
*/
public function createProxy(
string $className,
Closure $initializer,
array $proxyOptions = []
): VirtualProxyInterface {
$proxyClassName = $this->generateProxy($className, $proxyOptions);
/**
* We ignore type checks here, since `staticProxyConstructor` is not interfaced (by design)
*
* @psalm-suppress MixedMethodCall
* @psalm-suppress MixedReturnStatement
*/
return $proxyClassName::staticProxyConstructor($initializer);
}
protected function getGenerator(): ProxyGeneratorInterface
{
return $this->generator;
}
}

View File

@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Factory;
use OutOfBoundsException;
use ProxyManager\Configuration;
use ProxyManager\Proxy\NullObjectInterface;
use ProxyManager\ProxyGenerator\NullObjectGenerator;
use ProxyManager\ProxyGenerator\ProxyGeneratorInterface;
use ProxyManager\Signature\Exception\InvalidSignatureException;
use ProxyManager\Signature\Exception\MissingSignatureException;
use function get_class;
use function is_object;
/**
* Factory responsible of producing proxy objects
*/
class NullObjectFactory extends AbstractBaseFactory
{
private $generator;
public function __construct(?Configuration $configuration = null)
{
parent::__construct($configuration);
$this->generator = new NullObjectGenerator();
}
/**
* @param object|string $instanceOrClassName the object to be wrapped or interface to transform to null object
* @psalm-param RealObjectType|class-string<RealObjectType> $instanceOrClassName
*
* @psalm-return RealObjectType&NullObjectInterface
*
* @throws InvalidSignatureException
* @throws MissingSignatureException
* @throws OutOfBoundsException
*
* @psalm-template RealObjectType of object
* @psalm-suppress MixedInferredReturnType We ignore type checks here, since `staticProxyConstructor` is not
* interfaced (by design)
*/
public function createProxy($instanceOrClassName): NullObjectInterface
{
$className = is_object($instanceOrClassName) ? get_class($instanceOrClassName) : $instanceOrClassName;
$proxyClassName = $this->generateProxy($className);
/**
* We ignore type checks here, since `staticProxyConstructor` is not interfaced (by design)
*
* @psalm-suppress MixedMethodCall
* @psalm-suppress MixedReturnStatement
*/
return $proxyClassName::staticProxyConstructor();
}
protected function getGenerator(): ProxyGeneratorInterface
{
return $this->generator;
}
}

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Factory\RemoteObject\Adapter;
use Laminas\Server\Client;
use ProxyManager\Factory\RemoteObject\AdapterInterface;
use function array_key_exists;
/**
* Remote Object base adapter
*/
abstract class BaseAdapter implements AdapterInterface
{
protected $client;
/**
* Service name mapping
*
* @var array<string, string>
*/
protected $map = [];
/**
* Constructor
*
* @param array<string, string> $map map of service names to their aliases
*/
public function __construct(Client $client, array $map = [])
{
$this->client = $client;
$this->map = $map;
}
/**
* {@inheritDoc}
*/
public function call(string $wrappedClass, string $method, array $params = [])
{
$serviceName = $this->getServiceName($wrappedClass, $method);
if (array_key_exists($serviceName, $this->map)) {
$serviceName = $this->map[$serviceName];
}
return $this->client->call($serviceName, $params);
}
/**
* Get the service name will be used by the adapter
*/
abstract protected function getServiceName(string $wrappedClass, string $method): string;
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Factory\RemoteObject\Adapter;
/**
* Remote Object JSON RPC adapter
*/
class JsonRpc extends BaseAdapter
{
protected function getServiceName(string $wrappedClass, string $method): string
{
return $wrappedClass . '.' . $method;
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Factory\RemoteObject\Adapter;
/**
* Remote Object SOAP adapter
*/
class Soap extends BaseAdapter
{
protected function getServiceName(string $wrappedClass, string $method): string
{
return $method;
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Factory\RemoteObject\Adapter;
/**
* Remote Object XML RPC adapter
*/
class XmlRpc extends BaseAdapter
{
protected function getServiceName(string $wrappedClass, string $method): string
{
return $wrappedClass . '.' . $method;
}
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Factory\RemoteObject;
/**
* Remote Object adapter interface
*/
interface AdapterInterface
{
/**
* Call remote object
*
* @param array<int, mixed> $params
*
* Due to BC compliance, we cannot add a native `: mixed` return type declaration here
*
* phpcs:disable SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint
*
* @return mixed
*/
public function call(string $wrappedClass, string $method, array $params = []);
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Factory;
use OutOfBoundsException;
use ProxyManager\Configuration;
use ProxyManager\Factory\RemoteObject\AdapterInterface;
use ProxyManager\Proxy\RemoteObjectInterface;
use ProxyManager\ProxyGenerator\ProxyGeneratorInterface;
use ProxyManager\ProxyGenerator\RemoteObjectGenerator;
use ProxyManager\Signature\Exception\InvalidSignatureException;
use ProxyManager\Signature\Exception\MissingSignatureException;
use function get_class;
use function is_object;
/**
* Factory responsible of producing remote proxy objects
*/
class RemoteObjectFactory extends AbstractBaseFactory
{
protected $adapter;
private $generator;
/**
* {@inheritDoc}
*
* @param AdapterInterface $adapter
* @param Configuration $configuration
*/
public function __construct(AdapterInterface $adapter, ?Configuration $configuration = null)
{
parent::__construct($configuration);
$this->adapter = $adapter;
$this->generator = new RemoteObjectGenerator();
}
/**
* @psalm-param RealObjectType|class-string<RealObjectType> $instanceOrClassName
*
* @psalm-return RealObjectType&RemoteObjectInterface
*
* @throws InvalidSignatureException
* @throws MissingSignatureException
* @throws OutOfBoundsException
*
* @psalm-template RealObjectType of object
* @psalm-suppress MixedInferredReturnType We ignore type checks here, since `staticProxyConstructor` is not
* interfaced (by design)
*/
public function createProxy($instanceOrClassName): RemoteObjectInterface
{
$proxyClassName = $this->generateProxy(
is_object($instanceOrClassName) ? get_class($instanceOrClassName) : $instanceOrClassName
);
/**
* We ignore type checks here, since `staticProxyConstructor` is not interfaced (by design)
*
* @psalm-suppress MixedMethodCall
* @psalm-suppress MixedReturnStatement
*/
return $proxyClassName::staticProxyConstructor($this->adapter);
}
protected function getGenerator(): ProxyGeneratorInterface
{
return $this->generator ?? $this->generator = new RemoteObjectGenerator();
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace ProxyManager\FileLocator;
use ProxyManager\Exception\InvalidProxyDirectoryException;
use function realpath;
use function str_replace;
use const DIRECTORY_SEPARATOR;
class FileLocator implements FileLocatorInterface
{
protected $proxiesDirectory;
/**
* @throws InvalidProxyDirectoryException
*/
public function __construct(string $proxiesDirectory)
{
$absolutePath = realpath($proxiesDirectory);
if ($absolutePath === false) {
throw InvalidProxyDirectoryException::proxyDirectoryNotFound($proxiesDirectory);
}
$this->proxiesDirectory = $absolutePath;
}
public function getProxyFileName(string $className): string
{
return $this->proxiesDirectory . DIRECTORY_SEPARATOR . str_replace('\\', '', $className) . '.php';
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace ProxyManager\FileLocator;
/**
* Basic autoloader utilities required to work with proxy files
*/
interface FileLocatorInterface
{
/**
* Retrieves the file name for the given proxy
*/
public function getProxyFileName(string $className): string;
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Generator;
use Laminas\Code\Generator\ClassGenerator as LaminasClassGenerator;
/**
* Class generator that ensures that interfaces/classes that are implemented/extended are FQCNs
*
* @internal do not use this in your code: it is only here for internal use
* @deprecated this class was in use due to parent implementation not receiving prompt bugfixes, but
* `laminas/laminas-code` is actively maintained and receives quick release iterations.
*/
class ClassGenerator extends LaminasClassGenerator
{
}

View File

@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Generator;
use Laminas\Code\Generator\MethodGenerator as LaminasMethodGenerator;
use Laminas\Code\Generator\ParameterGenerator;
use LogicException;
use ReflectionClass;
use ReflectionIntersectionType;
use ReflectionNamedType;
use ReflectionUnionType;
use function get_class;
use function implode;
use function strtolower;
/**
* Method generator for magic methods
*/
class MagicMethodGenerator extends MethodGenerator
{
/**
* @param ParameterGenerator[]|array[]|string[] $parameters
*/
public function __construct(ReflectionClass $originalClass, string $name, array $parameters = [])
{
parent::__construct(
$name,
$parameters,
self::FLAG_PUBLIC
);
$this->setReturnsReference(strtolower($name) === '__get');
if (! $originalClass->hasMethod($name)) {
return;
}
$originalMethod = $originalClass->getMethod($name);
$originalReturnType = $originalMethod->getReturnType();
$this->setReturnsReference($originalMethod->returnsReference());
if ($originalReturnType instanceof ReflectionNamedType) {
$this->setReturnType(($originalReturnType->allowsNull() && $originalReturnType->getName() !== 'mixed' ? '?' : '') . $originalReturnType->getName());
} elseif ($originalReturnType instanceof ReflectionUnionType || $originalReturnType instanceof ReflectionIntersectionType) {
$returnType = [];
foreach ($originalReturnType->getTypes() as $type) {
$returnType[] = $type->getName();
}
$this->setReturnType(implode($originalReturnType instanceof ReflectionIntersectionType ? '&' : '|', $returnType));
} elseif ($originalReturnType) {
throw new LogicException('Unexpected ' . get_class($type));
}
}
public function setBody($body): LaminasMethodGenerator
{
if ((string) $this->getReturnType() === 'void') {
$body = preg_replace('/return ([^;]++;)/', '\1 return;', $body);
}
return parent::setBody($body);
}
}

View File

@@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Generator;
use Laminas\Code\Generator\DocBlockGenerator;
use Laminas\Code\Generator\MethodGenerator as LaminasMethodGenerator;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Reflection\MethodReflection;
use ReflectionException;
use ReflectionMethod;
use ReflectionParameter;
/**
* Method generator that fixes minor quirks in ZF2's method generator
*/
class MethodGenerator extends LaminasMethodGenerator
{
protected $hasTentativeReturnType = false;
/**
* @return static
*/
public static function fromReflectionWithoutBodyAndDocBlock(MethodReflection $reflectionMethod): self
{
/** @var static $method */
$method = static::copyMethodSignature($reflectionMethod);
$method->setInterface(false);
$method->setBody('');
if (\PHP_VERSION_ID < 80100) {
return $method;
}
/** @var callable(ReflectionMethod) : ReflectionMethod $getPrototype */
$getPrototype = \Closure::fromCallable([new ReflectionMethod(ReflectionMethod::class, 'getPrototype'), 'invoke']);
while (true) {
if ($reflectionMethod->hasTentativeReturnType()) {
$method->hasTentativeReturnType = true;
break;
}
if ($reflectionMethod->isAbstract()) {
break;
}
try {
$reflectionMethod = $getPrototype($reflectionMethod);
} catch (ReflectionException $e) {
break;
}
}
return $method;
}
public static function copyMethodSignature(MethodReflection $reflectionMethod): parent
{
$method = parent::copyMethodSignature($reflectionMethod);
foreach ($reflectionMethod->getParameters() as $reflectionParameter) {
$parameter = ParameterGenerator::fromReflection($reflectionParameter);
$default = $parameter->getDefaultValue();
if ($default !== null) {
$parameter->setDefaultValue(new ValueGenerator($default, $reflectionParameter));
$type = $parameter->getType();
if ($default->getValue() === null && strpos($type ?? '?', '?') !== 0 && strpos($type, '|') === false && $type !== 'mixed') {
$parameter->setType('?' . $type);
}
}
$method->setParameter($parameter);
}
return $method;
}
public function getDocBlock(): ?DocBlockGenerator
{
$docBlock = parent::getDocBlock();
if (! $this->hasTentativeReturnType) {
return $docBlock;
}
if ($docBlock === null) {
return new class ($this->getIndentation()) extends DocBlockGenerator {
public function __construct(string $indentation)
{
$this->setIndentation($indentation);
}
public function generate(): string
{
return $this->getIndentation() . '#[\ReturnTypeWillChange]' . self::LINE_FEED;
}
};
}
return new class ($docBlock) extends DocBlockGenerator {
public function __construct(DocBlockGenerator $docBlock)
{
$this->setShortDescription($docBlock->getShortDescription());
$this->setLongDescription($docBlock->getLongDescription());
$this->setTags($docBlock->getTags());
$this->setWordWrap($docBlock->getWordWrap());
$this->setSourceDirty($docBlock->isSourceDirty());
$this->setIndentation($docBlock->getIndentation());
$this->setSourceContent($docBlock->getSourceContent());
}
public function generate(): string
{
return parent::generate() . $this->getIndentation() . '#[\ReturnTypeWillChange]' . self::LINE_FEED;
}
};
}
/**
* {@inheritDoc} override needed to specify type in more detail
*/
public function getSourceContent(): ?string
{
return parent::getSourceContent();
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Generator\Util;
use Laminas\Code\Generator\ClassGenerator;
use Laminas\Code\Generator\MethodGenerator;
use ReflectionClass;
/**
* Util class to help to generate code
*/
final class ClassGeneratorUtils
{
public static function addMethodIfNotFinal(
ReflectionClass $originalClass,
ClassGenerator $classGenerator,
MethodGenerator $generatedMethod
): bool {
$methodName = $generatedMethod->getName();
if ($originalClass->hasMethod($methodName) && $originalClass->getMethod($methodName)->isFinal()) {
return false;
}
$classGenerator->addMethodFromGenerator($generatedMethod);
return true;
}
}

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Generator\Util;
use Composer\InstalledVersions;
use function class_exists;
use function method_exists;
use function preg_match;
use function serialize;
use function sha1;
use function substr;
/**
* Utility class capable of generating
* valid class/property/method identifiers
* with a deterministic attached suffix,
* in order to prevent property name collisions
* and tampering from userland
*/
abstract class IdentifierSuffixer
{
public const VALID_IDENTIFIER_FORMAT = '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]+$/';
public const DEFAULT_IDENTIFIER = 'g';
final private function __construct()
{
}
/**
* Generates a valid unique identifier from the given name,
* with a suffix attached to it
*/
public static function getIdentifier(string $name): string
{
/** @var string|null $salt */
static $salt;
$salt = $salt ?? self::loadBaseHashSalt();
$suffix = substr(sha1($name . $salt), 0, 5);
if (! preg_match(self::VALID_IDENTIFIER_FORMAT, $name)) {
return self::DEFAULT_IDENTIFIER . $suffix;
}
return $name . $suffix;
}
private static function loadBaseHashSalt(): string
{
if (! class_exists(InstalledVersions::class)) {
return self::class;
}
return sha1(serialize(
method_exists(InstalledVersions::class, 'getAllRawData')
? InstalledVersions::getAllRawData() // Composer >= 2.0.14
: InstalledVersions::getRawData()
));
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Generator\Util;
use ReflectionMethod;
use ReflectionNamedType;
/**
* Utility class to generate return expressions in method, given a method signature.
*
* This is required since return expressions may be forbidden by the method signature (void).
*/
final class ProxiedMethodReturnExpression
{
public static function generate(string $returnedValueExpression, ?ReflectionMethod $originalMethod): string
{
$originalReturnType = $originalMethod === null
? null
: $originalMethod->getReturnType();
if ($originalReturnType instanceof ReflectionNamedType && $originalReturnType->getName() === 'void') {
return $returnedValueExpression . ";\nreturn;";
}
if ($originalReturnType instanceof ReflectionNamedType && $originalReturnType->getName() === 'never') {
return $returnedValueExpression . ';';
}
return 'return ' . $returnedValueExpression . ';';
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Generator\Util;
use function preg_match;
use function str_replace;
use function uniqid;
/**
* Utility class capable of generating unique
* valid class/property/method identifiers
*/
abstract class UniqueIdentifierGenerator
{
public const VALID_IDENTIFIER_FORMAT = '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]+$/';
public const DEFAULT_IDENTIFIER = 'g';
/**
* Generates a valid unique identifier from the given name
*
* @psalm-return class-string
*
* @psalm-suppress MoreSpecificReturnType
*/
public static function getIdentifier(string $name): string
{
/**
* @psalm-suppress LessSpecificReturnStatement
*/
return str_replace(
'.',
'',
uniqid(
preg_match(self::VALID_IDENTIFIER_FORMAT, $name)
? $name
: self::DEFAULT_IDENTIFIER,
true
)
);
}
}

View File

@@ -0,0 +1,125 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Generator;
use Laminas\Code\Generator\Exception\RuntimeException;
use Laminas\Code\Generator\ValueGenerator as LaminasValueGenerator;
use ReflectionParameter;
use function explode;
use function implode;
use function in_array;
use function preg_replace;
use function preg_split;
use function rtrim;
use function substr;
use function var_export;
use const PREG_SPLIT_DELIM_CAPTURE;
use const PREG_SPLIT_NO_EMPTY;
/**
* @internal do not use this in your code: it is only here for internal use
*/
class ValueGenerator extends LaminasValueGenerator
{
private $reflection;
public function __construct($value, ?ReflectionParameter $reflection = null)
{
if ($value instanceof LaminasValueGenerator) {
$this->value = $value->value;
$this->type = $value->type;
$this->arrayDepth = $value->arrayDepth;
$this->outputMode = $value->outputMode;
$this->allowedTypes = $value->allowedTypes;
$this->constants = $value->constants;
$this->isSourceDirty = $value->isSourceDirty;
$this->indentation = $value->indentation;
$this->sourceContent = $value->sourceContent;
} else {
parent::__construct($value, parent::TYPE_AUTO, parent::OUTPUT_SINGLE_LINE);
}
$this->reflection = $reflection;
}
public function generate(): string
{
try {
return parent::generate();
} catch (RuntimeException $e) {
if ($this->reflection) {
$value = self::exportDefault($this->reflection);
} else {
$value = var_export($this->value, true);
if (\PHP_VERSION_ID < 80200) {
return self::fixVarExport($value);
}
}
return $value;
}
}
private static function fixVarExport(string $value): string
{
$parts = preg_split('{(\'(?:[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')}', $value, -1, PREG_SPLIT_DELIM_CAPTURE);
foreach ($parts as $i => &$part) {
if ($part === '' || $i % 2 !== 0) {
continue;
}
$part = preg_replace('/(?(DEFINE)(?<V>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))(?<!\\\\)(?&V)(?:\\\\(?&V))*+::/', '\\\\$0', $part);
}
return implode('', $parts);
}
private static function exportDefault(\ReflectionParameter $param): string
{
$default = rtrim(substr(explode('$'.$param->name.' = ', (string) $param, 2)[1] ?? '', 0, -2));
if (in_array($default, ['<default>', 'NULL'], true)) {
return 'null';
}
if (str_ends_with($default, "...'") && preg_match("/^'(?:[^'\\\\]*+(?:\\\\.)*+)*+'$/", $default)) {
return var_export($param->getDefaultValue(), true);
}
$regexp = "/(\"(?:[^\"\\\\]*+(?:\\\\.)*+)*+\"|'(?:[^'\\\\]*+(?:\\\\.)*+)*+')/";
$parts = preg_split($regexp, $default, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
$regexp = '/([\[\( ]|^)([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z0-9_\x7f-\xff]++)*+)(?!: )/';
$callback = (false !== strpbrk($default, "\\:('") && $class = $param->getDeclaringClass())
? function ($m) use ($class) {
switch ($m[2]) {
case 'new': case 'false': case 'true': case 'null': return $m[1].$m[2];
case 'NULL': return $m[1].'null';
case 'self': return $m[1].'\\'.$class->name;
case 'namespace\\parent':
case 'parent': $m[1].(($parent = $class->getParentClass()) ? '\\'.$parent->name : 'parent');
default: return $m[1].'\\'.$m[2];
}
}
: function ($m) {
switch ($m[2]) {
case 'new': case 'false': case 'true': case 'null': return $m[1].$m[2];
case 'NULL': return $m[1].'null';
default: return $m[1].'\\'.$m[2];
}
};
return implode('', array_map(function ($part) use ($regexp, $callback) {
switch ($part[0]) {
case '"': return $part; // for internal classes only
case "'": return false !== strpbrk($part, "\\\0\r\n") ? '"'.substr(str_replace(['$', "\0", "\r", "\n"], ['\$', '\0', '\r', '\n'], $part), 1, -1).'"' : $part;
default: return preg_replace_callback($regexp, $callback, $part);
}
}, $parts));
}
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace ProxyManager\GeneratorStrategy;
use Laminas\Code\Generator\ClassGenerator;
/**
* Generator strategy that generates the class body
*/
class BaseGeneratorStrategy implements GeneratorStrategyInterface
{
/**
* {@inheritDoc}
*
* @psalm-suppress MixedInferredReturnType upstream has no declared type
*/
public function generate(ClassGenerator $classGenerator): string
{
/** @psalm-suppress MixedReturnStatement upstream has no declared type */
return $classGenerator->generate();
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace ProxyManager\GeneratorStrategy;
use Laminas\Code\Generator\ClassGenerator;
use Symfony\Component\Filesystem\Filesystem;
use function ini_get;
use function unlink;
/**
* Generator strategy that produces the code and evaluates it at runtime
*/
class EvaluatingGeneratorStrategy implements GeneratorStrategyInterface
{
/** @var bool flag indicating whether {@see eval} can be used */
private $canEval = true;
/**
* Constructor
*/
public function __construct()
{
// @codeCoverageIgnoreStart
$this->canEval = ! ini_get('suhosin.executor.disable_eval');
// @codeCoverageIgnoreEnd
}
/**
* Evaluates the generated code before returning it
*
* {@inheritDoc}
*/
public function generate(ClassGenerator $classGenerator): string
{
$code = $classGenerator->generate();
// @codeCoverageIgnoreStart
if (! $this->canEval) {
$fileName = __DIR__ . '/EvaluatingGeneratorStrategy.php.tmp';
(new Filesystem())->dumpFile($fileName, "<?php\n" . $code);
/* @noinspection PhpIncludeInspection */
require $fileName;
unlink($fileName);
return $code;
}
// @codeCoverageIgnoreEnd
eval($code);
return $code;
}
}

View File

@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace ProxyManager\GeneratorStrategy;
use Laminas\Code\Generator\ClassGenerator;
use ProxyManager\Exception\FileNotWritableException;
use ProxyManager\FileLocator\FileLocatorInterface;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
/**
* Generator strategy that writes the generated classes to disk while generating them
*
* {@inheritDoc}
*/
class FileWriterGeneratorStrategy implements GeneratorStrategyInterface
{
protected $fileLocator;
private $emptyErrorHandler;
public function __construct(FileLocatorInterface $fileLocator)
{
$this->fileLocator = $fileLocator;
}
/**
* Write generated code to disk and return the class code
*
* {@inheritDoc}
*
* @throws FileNotWritableException
*/
public function generate(ClassGenerator $classGenerator): string
{
$generatedCode = $classGenerator->generate();
$className = (string) $classGenerator->getNamespaceName() . '\\' . $classGenerator->getName();
$fileName = $this->fileLocator->getProxyFileName($className);
try {
(new Filesystem())->dumpFile($fileName, "<?php\n\n" . $generatedCode);
return $generatedCode;
} catch (IOException $e) {
throw FileNotWritableException::fromPrevious($e);
}
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace ProxyManager\GeneratorStrategy;
use Laminas\Code\Generator\ClassGenerator;
class_exists(\Zend\Code\Generator\ClassGenerator::class);
/**
* Generator strategy interface - defines basic behavior of class generators
*/
interface GeneratorStrategyInterface
{
/**
* Generate the provided class
*/
public function generate(ClassGenerator $classGenerator): string;
}

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Inflector;
use ProxyManager\Inflector\Util\ParameterHasher;
use function is_int;
use function ltrim;
use function strlen;
use function strrpos;
use function substr;
final class ClassNameInflector implements ClassNameInflectorInterface
{
private $proxyNamespace;
/** @var int @TODO annotation still needed for phpstan to understand this */
private $proxyMarkerLength;
private $proxyMarker;
private $parameterHasher;
public function __construct(string $proxyNamespace)
{
$this->proxyNamespace = $proxyNamespace;
$this->proxyMarker = '\\' . self::PROXY_MARKER . '\\';
$this->proxyMarkerLength = strlen($this->proxyMarker);
$this->parameterHasher = new ParameterHasher();
}
/**
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificReturnType we ignore these issues because classes may not have been loaded yet
*/
public function getUserClassName(string $className): string
{
$className = ltrim($className, '\\');
$position = strrpos($className, $this->proxyMarker);
if (! is_int($position)) {
/** @psalm-suppress LessSpecificReturnStatement */
return $className;
}
/** @psalm-suppress LessSpecificReturnStatement */
return substr(
$className,
$this->proxyMarkerLength + $position,
(int) strrpos($className, '\\') - ($position + $this->proxyMarkerLength)
);
}
/**
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificReturnType we ignore these issues because classes may not have been loaded yet
*/
public function getProxyClassName(string $className, array $options = []): string
{
/** @psalm-suppress LessSpecificReturnStatement */
return $this->proxyNamespace
. $this->proxyMarker
. $this->getUserClassName($className)
. '\\Generated' . $this->parameterHasher->hashParameters($options);
}
public function isProxyClassName(string $className): bool
{
return strrpos($className, $this->proxyMarker) !== false;
}
}

View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Inflector;
use ProxyManager\Proxy\ProxyInterface;
/**
* Interface for a proxy- to user-class and user- to proxy-class name inflector
*/
interface ClassNameInflectorInterface
{
/**
* Marker for proxy classes - classes containing this marker are considered proxies
*/
public const PROXY_MARKER = '__PM__';
/**
* Retrieve the class name of a user-defined class
*
* @psalm-param class-string<RealClassName>|class-string<ProxyInterface<RealClassName>> $className
*
* @psalm-return class-string<RealClassName>
*
* @psalm-template RealClassName of object
*/
public function getUserClassName(string $className): string;
/**
* Retrieve the class name of the proxy for the given user-defined class name
*
* @param array<string, mixed> $options arbitrary options to be used for the generated class name
* @psalm-param class-string<RealClassName>|class-string<ProxyInterface<RealClassName>> $className
*
* @psalm-return class-string<RealClassName&ProxyInterface>
*
* @psalm-template RealClassName of object
*/
public function getProxyClassName(string $className, array $options = []): string;
/**
* Retrieve whether the provided class name is a proxy
*
* @psalm-param class-string<RealClassName>|class-string<ProxyInterface<RealClassName>> $className
*
* @psalm-template RealClassName of object
*/
public function isProxyClassName(string $className): bool;
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Inflector\Util;
use function base64_encode;
use function serialize;
/**
* Encodes parameters into a class-name safe string
*/
class ParameterEncoder
{
/**
* Converts the given parameters into a set of characters that are safe to
* use in a class name
*
* @param mixed[] $parameters
*/
public function encodeParameters(array $parameters): string
{
return base64_encode(serialize($parameters));
}
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Inflector\Util;
use function md5;
use function serialize;
/**
* Converts given parameters into a likely unique hash
*/
class ParameterHasher
{
/**
* Converts the given parameters into a likely-unique hash
*
* @param mixed[] $parameters
*/
public function hashParameters(array $parameters): string
{
return md5(serialize($parameters));
}
}

View File

@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Proxy;
use Closure;
/**
* Access interceptor object marker
*
* @psalm-template InterceptedObjectType of object
*/
interface AccessInterceptorInterface extends ProxyInterface
{
/**
* Set or remove the prefix interceptor for a method
*
* @link https://github.com/Ocramius/ProxyManager/blob/master/docs/access-interceptor-value-holder.md
*
* A prefix interceptor should have a signature like following:
*
* <code>
* $interceptor = function ($proxy, $instance, string $method, array $params, & $returnEarly) {};
* </code>
*
* @param string $methodName name of the intercepted method
* @param Closure|null $prefixInterceptor interceptor closure or null to unset the currently active interceptor
* @psalm-param null|Closure(
* InterceptedObjectType&AccessInterceptorInterface=,
* InterceptedObjectType=,
* string=,
* array<string, mixed>=,
* bool=
* ) : mixed $prefixInterceptor
*/
public function setMethodPrefixInterceptor(string $methodName, ?Closure $prefixInterceptor = null): void;
/**
* Set or remove the suffix interceptor for a method
*
* @link https://github.com/Ocramius/ProxyManager/blob/master/docs/access-interceptor-value-holder.md
*
* A prefix interceptor should have a signature like following:
*
* <code>
* $interceptor = function ($proxy, $instance, string $method, array $params, $returnValue, & $returnEarly) {};
* </code>
*
* @param string $methodName name of the intercepted method
* @param Closure|null $suffixInterceptor interceptor closure or null to unset the currently active interceptor
* @psalm-param null|Closure(
* InterceptedObjectType&AccessInterceptorInterface=,
* InterceptedObjectType=,
* string=,
* array<string, mixed>=,
* mixed=,
* bool=
* ) : mixed $suffixInterceptor
*/
public function setMethodSuffixInterceptor(string $methodName, ?Closure $suffixInterceptor = null): void;
}

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Proxy;
use Closure;
/**
* Aggregates AccessInterceptor and ValueHolderInterface, mostly for return type hinting
*
* @psalm-template InterceptedObjectType of object
*/
interface AccessInterceptorValueHolderInterface extends AccessInterceptorInterface, ValueHolderInterface
{
/**
* {@inheritDoc}
*
* Definitions are duplicated here to allow templated definitions in this child type
*
* @psalm-param null|Closure(
* InterceptedObjectType&AccessInterceptorInterface=,
* InterceptedObjectType=,
* string=,
* array<string, mixed>=,
* bool=
* ) : mixed $prefixInterceptor
*/
public function setMethodPrefixInterceptor(string $methodName, ?Closure $prefixInterceptor = null): void;
/**
* {@inheritDoc}
*
* Definitions are duplicated here to allow templated definitions in this child type
*
* @param string $methodName name of the intercepted method
* @param Closure|null $suffixInterceptor interceptor closure or null to unset the currently active interceptor
* @psalm-param null|Closure(
* InterceptedObjectType&AccessInterceptorInterface=,
* InterceptedObjectType=,
* string=,
* array<string, mixed>=,
* mixed=,
* bool=
* ) : mixed $suffixInterceptor
*/
public function setMethodSuffixInterceptor(string $methodName, ?Closure $suffixInterceptor = null): void;
/**
* {@inheritDoc}
*
* Definitions are duplicated here to allow templated definitions in this child type
*
* @psalm-return InterceptedObjectType|null
*/
public function getWrappedValueHolderValue();
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Proxy\Exception;
use RuntimeException;
/**
* Remote object exception
*
* @deprecated this exception is not in use anymore, and should not be relied upon
*/
class RemoteObjectException extends RuntimeException
{
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Proxy;
/**
* Fallback value holder object marker
*
* @deprecated this interface is not in use anymore, and should not be relied upon
*/
interface FallbackValueHolderInterface extends ProxyInterface
{
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Proxy;
use Closure;
/**
* Ghost object marker
*
* @psalm-template LazilyLoadedObjectType of object
*/
interface GhostObjectInterface extends LazyLoadingInterface
{
/**
* {@inheritDoc}
*
* Definitions are duplicated here to allow templated definitions in this child type.
*
* @psalm-param null|Closure(
* LazilyLoadedObjectType&GhostObjectInterface<LazilyLoadedObjectType>=,
* string=,
* array<string, mixed>=,
* ?Closure=,
* array<string, mixed>=
* ) : bool $initializer
*
* @psalm-suppress ImplementedParamTypeMismatch Note that the closure signature below is slightly different
* from the one declared in LazyLoadingInterface.
*/
public function setProxyInitializer(?Closure $initializer = null);
/**
* {@inheritDoc}
*
* Definitions are duplicated here to allow templated definitions in this child type
*
* @psalm-return null|Closure(
* LazilyLoadedObjectType&GhostObjectInterface<LazilyLoadedObjectType>=,
* string,
* array<string, mixed>=,
* ?Closure=,
* array<string, mixed>=
* ) : bool
*
* @psalm-suppress ImplementedReturnTypeMismatch Note that the closure signature below is slightly different
* from the one declared in LazyLoadingInterface.
*/
public function getProxyInitializer(): ?Closure;
}

View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Proxy;
use Closure;
/**
* Lazy loading object identifier
*
* @psalm-template LazilyLoadedObjectType of object
*/
interface LazyLoadingInterface extends ProxyInterface
{
/**
* Set or unset the initializer for the proxy instance
*
* @link https://github.com/Ocramius/ProxyManager/blob/master/docs/lazy-loading-value-holder.md#lazy-initialization
*
* An initializer should have a signature like following:
*
* <code>
* $initializer = function (
* & ?object $wrappedObject,
* LazyLoadingInterface $proxy,
* string $calledMethod,
* array $callParameters,
* & ?\Closure $initializer,
* array $propertiesToBeSet = [] // works only on ghost objects
* ) {};
* </code>
*
* @psalm-param null|Closure(
* LazilyLoadedObjectType|null=,
* LazilyLoadedObjectType&LazyLoadingInterface<LazilyLoadedObjectType>=,
* string=,
* array<string, mixed>=,
* ?Closure=,
* array<string, mixed>=
* ) : bool $initializer
*
* Due to BC compliance, we cannot add a native `: void` return type declaration here
*
* phpcs:disable SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint
*
* @return void
*/
public function setProxyInitializer(?Closure $initializer = null);
/**
* @psalm-return null|Closure(
* LazilyLoadedObjectType|null=,
* LazilyLoadedObjectType&LazyLoadingInterface<LazilyLoadedObjectType>=,
* string,
* array<string, mixed>=,
* ?Closure=,
* array<string, mixed>=
* ) : bool
*/
public function getProxyInitializer(): ?Closure;
/**
* Force initialization of the proxy
*
* @return bool true if the proxy could be initialized
*/
public function initializeProxy(): bool;
/**
* Retrieves current initialization status of the proxy
*/
public function isProxyInitialized(): bool;
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Proxy;
/**
* Null object marker
*/
interface NullObjectInterface extends ProxyInterface
{
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Proxy;
/**
* Base proxy marker
*
* @psalm-template DecoratedClassName of object
*/
interface ProxyInterface
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Proxy;
/**
* Remote object marker
*/
interface RemoteObjectInterface extends ProxyInterface
{
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Proxy;
/**
* Smart reference object marker
*
* @deprecated this interface is not in use anymore, and should not be relied upon
*/
interface SmartReferenceInterface extends ProxyInterface
{
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Proxy;
/**
* Value holder marker
*
* @psalm-template WrappedValueHolderType of object
*/
interface ValueHolderInterface extends ProxyInterface
{
/**
* @return object|null the wrapped value
* @psalm-return WrappedValueHolderType|null
*/
public function getWrappedValueHolderValue();
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace ProxyManager\Proxy;
/**
* Virtual Proxy - a lazy initializing object wrapping around the proxied subject
*/
interface VirtualProxyInterface extends LazyLoadingInterface, ValueHolderInterface
{
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptor\MethodGenerator;
use ProxyManager\Generator\MagicMethodGenerator;
use ProxyManager\ProxyGenerator\Util\Properties;
use ProxyManager\ProxyGenerator\Util\UnsetPropertiesGenerator;
use ReflectionClass;
/**
* Magic `__wakeup` for lazy loading value holder objects
*/
class MagicWakeup extends MagicMethodGenerator
{
/**
* Constructor
*/
public function __construct(ReflectionClass $originalClass)
{
parent::__construct($originalClass, '__wakeup');
$this->setBody(UnsetPropertiesGenerator::generateSnippet(
Properties::fromReflectionClass($originalClass),
'this'
));
}
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptor\MethodGenerator;
use Closure;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MethodGenerator;
/**
* Implementation for {@see \ProxyManager\Proxy\AccessInterceptorInterface::setMethodPrefixInterceptor}
* for access interceptor objects
*/
class SetMethodPrefixInterceptor extends MethodGenerator
{
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct(PropertyGenerator $prefixInterceptor)
{
parent::__construct('setMethodPrefixInterceptor');
$interceptor = new ParameterGenerator('prefixInterceptor');
$interceptor->setType('?' . Closure::class);
$interceptor->setDefaultValue(null);
$this->setParameter(new ParameterGenerator('methodName', 'string'));
$this->setParameter($interceptor);
$this->setReturnType('void');
$this->setBody('$this->' . $prefixInterceptor->getName() . '[$methodName] = $prefixInterceptor;');
}
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptor\MethodGenerator;
use Closure;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MethodGenerator;
/**
* Implementation for {@see \ProxyManager\Proxy\AccessInterceptorInterface::setMethodSuffixInterceptor}
* for access interceptor objects
*/
class SetMethodSuffixInterceptor extends MethodGenerator
{
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct(PropertyGenerator $suffixInterceptor)
{
parent::__construct('setMethodSuffixInterceptor');
$interceptor = new ParameterGenerator('suffixInterceptor');
$interceptor->setType('?' . Closure::class);
$interceptor->setDefaultValue(null);
$this->setParameter(new ParameterGenerator('methodName', 'string'));
$this->setParameter($interceptor);
$this->setReturnType('void');
$this->setBody('$this->' . $suffixInterceptor->getName() . '[$methodName] = $suffixInterceptor;');
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptor\PropertyGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\Util\IdentifierSuffixer;
/**
* Property that contains the interceptor for operations to be executed before method execution
*/
class MethodPrefixInterceptors extends PropertyGenerator
{
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct()
{
parent::__construct(IdentifierSuffixer::getIdentifier('methodPrefixInterceptors'));
$this->setDefaultValue([]);
$this->setVisibility(self::VISIBILITY_PRIVATE);
$this->setDocBlock('@var \\Closure[] map of interceptors to be called per-method before execution');
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptor\PropertyGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\Util\IdentifierSuffixer;
/**
* Property that contains the interceptor for operations to be executed after method execution
*/
class MethodSuffixInterceptors extends PropertyGenerator
{
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct()
{
parent::__construct(IdentifierSuffixer::getIdentifier('methodSuffixInterceptors'));
$this->setDefaultValue([]);
$this->setVisibility(self::VISIBILITY_PRIVATE);
$this->setDocBlock('@var \\Closure[] map of interceptors to be called per-method after execution');
}
}

View File

@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Exception\UnsupportedProxiedClassException;
use ProxyManager\Generator\MethodGenerator;
use ProxyManager\ProxyGenerator\Util\Properties;
use ReflectionClass;
use function implode;
use function var_export;
/**
* The `bindProxyProperties` method implementation for access interceptor scope localizers
*/
class BindProxyProperties extends MethodGenerator
{
/**
* Constructor
*/
public function __construct(
ReflectionClass $originalClass,
PropertyGenerator $prefixInterceptors,
PropertyGenerator $suffixInterceptors
) {
parent::__construct(
'bindProxyProperties',
[
new ParameterGenerator('localizedObject', $originalClass->getName()),
new ParameterGenerator('prefixInterceptors', 'array', []),
new ParameterGenerator('suffixInterceptors', 'array', []),
],
self::FLAG_PRIVATE,
null,
"@override constructor to setup interceptors\n\n"
. '@param \\' . $originalClass->getName() . " \$localizedObject\n"
. "@param \\Closure[] \$prefixInterceptors method interceptors to be used before method logic\n"
. '@param \\Closure[] $suffixInterceptors method interceptors to be used before method logic'
);
$localizedProperties = [];
$properties = Properties::fromReflectionClass($originalClass);
$nonReferenceableProperties = $properties
->onlyNonReferenceableProperties()
->onlyInstanceProperties();
if (! $nonReferenceableProperties->empty()) {
throw UnsupportedProxiedClassException::nonReferenceableLocalizedReflectionProperties(
$originalClass,
$nonReferenceableProperties
);
}
foreach ($properties->getAccessibleProperties() as $property) {
$propertyName = $property->getName();
$localizedProperties[] = '$this->' . $propertyName . ' = & $localizedObject->' . $propertyName . ';';
}
foreach ($properties->getPrivateProperties() as $property) {
$propertyName = $property->getName();
$localizedProperties[] = "\\Closure::bind(function () use (\$localizedObject) {\n "
. '$this->' . $propertyName . ' = & $localizedObject->' . $propertyName . ";\n"
. '}, $this, ' . var_export($property->getDeclaringClass()->getName(), true)
. ')->__invoke();';
}
$this->setBody(
($localizedProperties ? implode("\n\n", $localizedProperties) . "\n\n" : '')
. '$this->' . $prefixInterceptors->getName() . " = \$prefixInterceptors;\n"
. '$this->' . $suffixInterceptors->getName() . ' = $suffixInterceptors;'
);
}
}

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\PropertyGenerator;
use Laminas\Code\Reflection\MethodReflection;
use ProxyManager\Generator\MethodGenerator;
use ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\Util\InterceptorGenerator;
use function implode;
/**
* Method with additional pre- and post- interceptor logic in the body
*/
class InterceptedMethod extends MethodGenerator
{
/**
* @throws InvalidArgumentException
*/
public static function generateMethod(
MethodReflection $originalMethod,
PropertyGenerator $prefixInterceptors,
PropertyGenerator $suffixInterceptors
): self {
$method = static::fromReflectionWithoutBodyAndDocBlock($originalMethod);
$forwardedParams = [];
foreach ($originalMethod->getParameters() as $parameter) {
$forwardedParams[] = ($parameter->isVariadic() ? '...' : '') . '$' . $parameter->getName();
}
$method->setBody(InterceptorGenerator::createInterceptedMethodBody(
'$returnValue = parent::'
. $originalMethod->getName() . '(' . implode(', ', $forwardedParams) . ');',
$method,
$prefixInterceptors,
$suffixInterceptors,
$originalMethod
));
return $method;
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MagicMethodGenerator;
use ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\Util\InterceptorGenerator;
use ProxyManager\ProxyGenerator\Util\GetMethodIfExists;
use ReflectionClass;
/**
* Magic `__clone` for lazy loading ghost objects
*/
class MagicClone extends MagicMethodGenerator
{
/**
* Constructor
*/
public function __construct(
ReflectionClass $originalClass,
PropertyGenerator $prefixInterceptors,
PropertyGenerator $suffixInterceptors
) {
parent::__construct($originalClass, '__clone');
$parent = GetMethodIfExists::get($originalClass, '__clone');
$this->setBody(InterceptorGenerator::createInterceptedMethodBody(
$parent ? '$returnValue = parent::__clone();' : '$returnValue = null;',
$this,
$prefixInterceptors,
$suffixInterceptors,
$parent
));
}
}

View File

@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator;
use InvalidArgumentException;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MagicMethodGenerator;
use ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\Util\InterceptorGenerator;
use ProxyManager\ProxyGenerator\Util\GetMethodIfExists;
use ProxyManager\ProxyGenerator\Util\PublicScopeSimulator;
use ReflectionClass;
/**
* Magic `__get` for lazy loading ghost objects
*/
class MagicGet extends MagicMethodGenerator
{
/**
* @throws InvalidArgumentException
*/
public function __construct(
ReflectionClass $originalClass,
PropertyGenerator $prefixInterceptors,
PropertyGenerator $suffixInterceptors
) {
parent::__construct($originalClass, '__get', [new ParameterGenerator('name')]);
$parent = GetMethodIfExists::get($originalClass, '__get');
$callParent = '$returnValue = & parent::__get($name);';
if (! $parent) {
$callParent = PublicScopeSimulator::getPublicAccessSimulationCode(
PublicScopeSimulator::OPERATION_GET,
'name',
null,
null,
'returnValue',
$originalClass
);
}
$this->setBody(InterceptorGenerator::createInterceptedMethodBody(
$callParent,
$this,
$prefixInterceptors,
$suffixInterceptors,
$parent
));
}
}

View File

@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator;
use InvalidArgumentException;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MagicMethodGenerator;
use ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\Util\InterceptorGenerator;
use ProxyManager\ProxyGenerator\Util\GetMethodIfExists;
use ProxyManager\ProxyGenerator\Util\PublicScopeSimulator;
use ReflectionClass;
/**
* Magic `__isset` method for lazy loading ghost objects
*/
class MagicIsset extends MagicMethodGenerator
{
/**
* @throws InvalidArgumentException
*/
public function __construct(
ReflectionClass $originalClass,
PropertyGenerator $prefixInterceptors,
PropertyGenerator $suffixInterceptors
) {
parent::__construct($originalClass, '__isset', [new ParameterGenerator('name')]);
$parent = GetMethodIfExists::get($originalClass, '__isset');
$callParent = '$returnValue = & parent::__isset($name);';
if (! $parent) {
$callParent = PublicScopeSimulator::getPublicAccessSimulationCode(
PublicScopeSimulator::OPERATION_ISSET,
'name',
null,
null,
'returnValue',
$originalClass
);
}
$this->setBody(InterceptorGenerator::createInterceptedMethodBody(
$callParent,
$this,
$prefixInterceptors,
$suffixInterceptors,
$parent
));
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator;
use InvalidArgumentException;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MagicMethodGenerator;
use ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\Util\InterceptorGenerator;
use ProxyManager\ProxyGenerator\Util\GetMethodIfExists;
use ProxyManager\ProxyGenerator\Util\PublicScopeSimulator;
use ReflectionClass;
/**
* Magic `__set` for lazy loading ghost objects
*/
class MagicSet extends MagicMethodGenerator
{
/**
* @throws InvalidArgumentException
*/
public function __construct(
ReflectionClass $originalClass,
PropertyGenerator $prefixInterceptors,
PropertyGenerator $suffixInterceptors
) {
parent::__construct(
$originalClass,
'__set',
[new ParameterGenerator('name'), new ParameterGenerator('value')]
);
$parent = GetMethodIfExists::get($originalClass, '__set');
$callParent = '$returnValue = & parent::__set($name, $value);';
if (! $parent) {
$callParent = PublicScopeSimulator::getPublicAccessSimulationCode(
PublicScopeSimulator::OPERATION_SET,
'name',
'value',
null,
'returnValue',
$originalClass
);
}
$this->setBody(InterceptorGenerator::createInterceptedMethodBody(
$callParent,
$this,
$prefixInterceptors,
$suffixInterceptors,
$parent
));
}
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MagicMethodGenerator;
use ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\Util\InterceptorGenerator;
use ProxyManager\ProxyGenerator\Util\GetMethodIfExists;
use ReflectionClass;
/**
* Magic `__sleep` for lazy loading ghost objects
*/
class MagicSleep extends MagicMethodGenerator
{
/**
* Constructor
*/
public function __construct(
ReflectionClass $originalClass,
PropertyGenerator $prefixInterceptors,
PropertyGenerator $suffixInterceptors
) {
parent::__construct($originalClass, '__sleep');
$parent = GetMethodIfExists::get($originalClass, '__sleep');
$callParent = $parent ? '$returnValue = & parent::__sleep();' : '$returnValue = array_keys((array) $this);';
$this->setBody(InterceptorGenerator::createInterceptedMethodBody(
$callParent,
$this,
$prefixInterceptors,
$suffixInterceptors,
$parent
));
}
}

View File

@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator;
use InvalidArgumentException;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MagicMethodGenerator;
use ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\Util\InterceptorGenerator;
use ProxyManager\ProxyGenerator\Util\GetMethodIfExists;
use ProxyManager\ProxyGenerator\Util\PublicScopeSimulator;
use ReflectionClass;
/**
* Magic `__unset` method for lazy loading ghost objects
*/
class MagicUnset extends MagicMethodGenerator
{
/**
* @throws InvalidArgumentException
*/
public function __construct(
ReflectionClass $originalClass,
PropertyGenerator $prefixInterceptors,
PropertyGenerator $suffixInterceptors
) {
parent::__construct($originalClass, '__unset', [new ParameterGenerator('name')]);
$parent = GetMethodIfExists::get($originalClass, '__unset');
$callParent = '$returnValue = & parent::__unset($name);';
if (! $parent) {
$callParent = PublicScopeSimulator::getPublicAccessSimulationCode(
PublicScopeSimulator::OPERATION_UNSET,
'name',
null,
null,
'returnValue',
$originalClass
);
}
$this->setBody(InterceptorGenerator::createInterceptedMethodBody(
$callParent,
$this,
$prefixInterceptors,
$suffixInterceptors,
$parent
));
}
}

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\ParameterGenerator;
use ProxyManager\Generator\MethodGenerator;
use ReflectionClass;
/**
* The `staticProxyConstructor` implementation for an access interceptor scope localizer proxy
*/
class StaticProxyConstructor extends MethodGenerator
{
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct(ReflectionClass $originalClass)
{
parent::__construct('staticProxyConstructor', [], self::FLAG_PUBLIC | self::FLAG_STATIC);
$localizedObject = new ParameterGenerator('localizedObject');
$prefix = new ParameterGenerator('prefixInterceptors');
$suffix = new ParameterGenerator('suffixInterceptors');
$localizedObject->setType($originalClass->getName());
$prefix->setDefaultValue([]);
$suffix->setDefaultValue([]);
$prefix->setType('array');
$suffix->setType('array');
$this->setParameter($localizedObject);
$this->setParameter($prefix);
$this->setParameter($suffix);
$this->setReturnType($originalClass->getName());
$this->setDocBlock(
"Constructor to setup interceptors\n\n"
. '@param \\' . $originalClass->getName() . " \$localizedObject\n"
. "@param \\Closure[] \$prefixInterceptors method interceptors to be used before method logic\n"
. "@param \\Closure[] \$suffixInterceptors method interceptors to be used before method logic\n\n"
. '@return self'
);
$this->setBody(
'static $reflection;' . "\n\n"
. '$reflection = $reflection ?? new \ReflectionClass(__CLASS__);' . "\n"
. '$instance = $reflection->newInstanceWithoutConstructor();' . "\n\n"
. '$instance->bindProxyProperties($localizedObject, $prefixInterceptors, $suffixInterceptors);' . "\n\n"
. 'return $instance;'
);
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\Util;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MethodGenerator;
use ProxyManager\Generator\Util\ProxiedMethodReturnExpression;
use ReflectionMethod;
use function array_keys;
use function array_map;
use function implode;
use function str_replace;
use function var_export;
/**
* Utility to create pre- and post- method interceptors around a given method body
*
* @private - this class is just here as a small utility for this component, don't use it in your own code
*/
class InterceptorGenerator
{
private const TEMPLATE = <<<'PHP'
if (isset($this->{{$prefixInterceptorsName}}[{{$name}}])) {
$returnEarly = false;
$prefixReturnValue = $this->{{$prefixInterceptorsName}}[{{$name}}]->__invoke($this, $this, {{$name}}, {{$paramsString}}, $returnEarly);
if ($returnEarly) {
{{$prefixEarlyReturnExpression}}
}
}
{{$methodBody}}
if (isset($this->{{$suffixInterceptorsName}}[{{$name}}])) {
$returnEarly = false;
$suffixReturnValue = $this->{{$suffixInterceptorsName}}[{{$name}}]->__invoke($this, $this, {{$name}}, {{$paramsString}}, $returnValue, $returnEarly);
if ($returnEarly) {
{{$suffixEarlyReturnExpression}}
}
}
{{$returnExpression}}
PHP;
/**
* @param string $methodBody the body of the previously generated code.
* It MUST assign the return value to a variable
* `$returnValue` instead of directly returning
*/
public static function createInterceptedMethodBody(
string $methodBody,
MethodGenerator $method,
PropertyGenerator $prefixInterceptors,
PropertyGenerator $suffixInterceptors,
?ReflectionMethod $originalMethod
): string {
$replacements = [
'{{$name}}' => var_export($method->getName(), true),
'{{$prefixInterceptorsName}}' => $prefixInterceptors->getName(),
'{{$prefixEarlyReturnExpression}}' => ProxiedMethodReturnExpression::generate('$prefixReturnValue', $originalMethod),
'{{$methodBody}}' => $methodBody,
'{{$suffixInterceptorsName}}' => $suffixInterceptors->getName(),
'{{$suffixEarlyReturnExpression}}' => ProxiedMethodReturnExpression::generate('$suffixReturnValue', $originalMethod),
'{{$returnExpression}}' => ProxiedMethodReturnExpression::generate('$returnValue', $originalMethod),
'{{$paramsString}}' => 'array(' . implode(', ', array_map(
static function (ParameterGenerator $parameter): string {
return var_export($parameter->getName(), true) . ' => ' . ($parameter->getPassedByReference() ? '&$' : '$') . $parameter->getName();
},
$method->getParameters()
))
. ')',
];
return str_replace(
array_keys($replacements),
$replacements,
self::TEMPLATE
);
}
}

View File

@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator;
use InvalidArgumentException;
use Laminas\Code\Generator\ClassGenerator;
use Laminas\Code\Generator\MethodGenerator;
use Laminas\Code\Reflection\MethodReflection;
use ProxyManager\Exception\InvalidProxiedClassException;
use ProxyManager\Generator\Util\ClassGeneratorUtils;
use ProxyManager\Proxy\AccessInterceptorInterface;
use ProxyManager\ProxyGenerator\AccessInterceptor\MethodGenerator\SetMethodPrefixInterceptor;
use ProxyManager\ProxyGenerator\AccessInterceptor\MethodGenerator\SetMethodSuffixInterceptor;
use ProxyManager\ProxyGenerator\AccessInterceptor\PropertyGenerator\MethodPrefixInterceptors;
use ProxyManager\ProxyGenerator\AccessInterceptor\PropertyGenerator\MethodSuffixInterceptors;
use ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\BindProxyProperties;
use ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\InterceptedMethod;
use ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\MagicClone;
use ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\MagicGet;
use ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\MagicIsset;
use ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\MagicSet;
use ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\MagicSleep;
use ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\MagicUnset;
use ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\StaticProxyConstructor;
use ProxyManager\ProxyGenerator\Assertion\CanProxyAssertion;
use ProxyManager\ProxyGenerator\Util\ProxiedMethodsFilter;
use ReflectionClass;
use ReflectionMethod;
use function array_map;
use function array_merge;
/**
* Generator for proxies implementing {@see \ProxyManager\Proxy\ValueHolderInterface}
* and localizing scope of the proxied object at instantiation
*
* {@inheritDoc}
*/
class AccessInterceptorScopeLocalizerGenerator implements ProxyGeneratorInterface
{
/**
* {@inheritDoc}
*
* @return void
*
* @throws InvalidArgumentException
* @throws InvalidProxiedClassException
*/
public function generate(ReflectionClass $originalClass, ClassGenerator $classGenerator)
{
CanProxyAssertion::assertClassCanBeProxied($originalClass, false);
$classGenerator->setExtendedClass($originalClass->getName());
$classGenerator->setImplementedInterfaces([AccessInterceptorInterface::class]);
$classGenerator->addPropertyFromGenerator($prefixInterceptors = new MethodPrefixInterceptors());
$classGenerator->addPropertyFromGenerator($suffixInterceptors = new MethodSuffixInterceptors());
array_map(
static function (MethodGenerator $generatedMethod) use ($originalClass, $classGenerator): void {
ClassGeneratorUtils::addMethodIfNotFinal($originalClass, $classGenerator, $generatedMethod);
},
array_merge(
array_map(
$this->buildMethodInterceptor($prefixInterceptors, $suffixInterceptors),
ProxiedMethodsFilter::getProxiedMethods(
$originalClass,
['__get', '__set', '__isset', '__unset', '__clone', '__sleep']
)
),
[
new StaticProxyConstructor($originalClass),
new BindProxyProperties($originalClass, $prefixInterceptors, $suffixInterceptors),
new SetMethodPrefixInterceptor($prefixInterceptors),
new SetMethodSuffixInterceptor($suffixInterceptors),
new MagicGet($originalClass, $prefixInterceptors, $suffixInterceptors),
new MagicSet($originalClass, $prefixInterceptors, $suffixInterceptors),
new MagicIsset($originalClass, $prefixInterceptors, $suffixInterceptors),
new MagicUnset($originalClass, $prefixInterceptors, $suffixInterceptors),
new MagicSleep($originalClass, $prefixInterceptors, $suffixInterceptors),
new MagicClone($originalClass, $prefixInterceptors, $suffixInterceptors),
]
)
);
}
private function buildMethodInterceptor(
MethodPrefixInterceptors $prefixInterceptors,
MethodSuffixInterceptors $suffixInterceptors
): callable {
return static function (ReflectionMethod $method) use ($prefixInterceptors, $suffixInterceptors): InterceptedMethod {
return InterceptedMethod::generateMethod(
new MethodReflection($method->getDeclaringClass()->getName(), $method->getName()),
$prefixInterceptors,
$suffixInterceptors
);
};
}
}

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\PropertyGenerator;
use Laminas\Code\Reflection\MethodReflection;
use ProxyManager\Generator\MethodGenerator;
use ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\Util\InterceptorGenerator;
use function implode;
/**
* Method with additional pre- and post- interceptor logic in the body
*/
class InterceptedMethod extends MethodGenerator
{
/**
* @throws InvalidArgumentException
*/
public static function generateMethod(
MethodReflection $originalMethod,
PropertyGenerator $valueHolderProperty,
PropertyGenerator $prefixInterceptors,
PropertyGenerator $suffixInterceptors
): self {
$method = static::fromReflectionWithoutBodyAndDocBlock($originalMethod);
$forwardedParams = [];
foreach ($originalMethod->getParameters() as $parameter) {
$forwardedParams[] = ($parameter->isVariadic() ? '...' : '') . '$' . $parameter->getName();
}
$method->setBody(InterceptorGenerator::createInterceptedMethodBody(
'$returnValue = $this->' . $valueHolderProperty->getName() . '->'
. $originalMethod->getName() . '(' . implode(', ', $forwardedParams) . ');',
$method,
$valueHolderProperty,
$prefixInterceptors,
$suffixInterceptors,
$originalMethod
));
return $method;
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MagicMethodGenerator;
use ReflectionClass;
use function array_keys;
use function str_replace;
/**
* Magic `__clone` for lazy loading value holder objects
*/
class MagicClone extends MagicMethodGenerator
{
private const TEMPLATE = <<<'PHP'
$this->{{$valueHolder}} = clone $this->{{$valueHolder}};
foreach ($this->{{$prefix}} as $key => $value) {
$this->{{$prefix}}[$key] = clone $value;
}
foreach ($this->{{$suffix}} as $key => $value) {
$this->{{$suffix}}[$key] = clone $value;
}
PHP;
/**
* Constructor
*/
public function __construct(
ReflectionClass $originalClass,
PropertyGenerator $valueHolderProperty,
PropertyGenerator $prefixInterceptors,
PropertyGenerator $suffixInterceptors
) {
parent::__construct($originalClass, '__clone');
$valueHolder = $valueHolderProperty->getName();
$prefix = $prefixInterceptors->getName();
$suffix = $suffixInterceptors->getName();
$replacements = [
'{{$valueHolder}}' => $valueHolder,
'{{$prefix}}' => $prefix,
'{{$suffix}}' => $suffix,
];
$this->setBody(str_replace(
array_keys($replacements),
$replacements,
self::TEMPLATE
));
}
}

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator;
use InvalidArgumentException;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MagicMethodGenerator;
use ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\Util\InterceptorGenerator;
use ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesMap;
use ProxyManager\ProxyGenerator\Util\GetMethodIfExists;
use ProxyManager\ProxyGenerator\Util\PublicScopeSimulator;
use ReflectionClass;
/**
* Magic `__get` for method interceptor value holder objects
*/
class MagicGet extends MagicMethodGenerator
{
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct(
ReflectionClass $originalClass,
PropertyGenerator $valueHolder,
PropertyGenerator $prefixInterceptors,
PropertyGenerator $suffixInterceptors,
PublicPropertiesMap $publicProperties
) {
parent::__construct($originalClass, '__get', [new ParameterGenerator('name')]);
$parent = GetMethodIfExists::get($originalClass, '__get');
$valueHolderName = $valueHolder->getName();
$callParent = PublicScopeSimulator::getPublicAccessSimulationCode(
PublicScopeSimulator::OPERATION_GET,
'name',
null,
$valueHolder,
'returnValue',
$originalClass
);
if (! $publicProperties->isEmpty()) {
$callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n"
. ' $returnValue = & $this->' . $valueHolderName . '->$name;'
. "\n} else {\n " . $callParent . "\n}\n\n";
}
$this->setBody(InterceptorGenerator::createInterceptedMethodBody(
$callParent,
$this,
$valueHolder,
$prefixInterceptors,
$suffixInterceptors,
$parent
));
}
}

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator;
use InvalidArgumentException;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MagicMethodGenerator;
use ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\Util\InterceptorGenerator;
use ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesMap;
use ProxyManager\ProxyGenerator\Util\GetMethodIfExists;
use ProxyManager\ProxyGenerator\Util\PublicScopeSimulator;
use ReflectionClass;
/**
* Magic `__isset` for method interceptor value holder objects
*/
class MagicIsset extends MagicMethodGenerator
{
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct(
ReflectionClass $originalClass,
PropertyGenerator $valueHolder,
PropertyGenerator $prefixInterceptors,
PropertyGenerator $suffixInterceptors,
PublicPropertiesMap $publicProperties
) {
parent::__construct($originalClass, '__isset', [new ParameterGenerator('name')]);
$parent = GetMethodIfExists::get($originalClass, '__isset');
$valueHolderName = $valueHolder->getName();
$callParent = PublicScopeSimulator::getPublicAccessSimulationCode(
PublicScopeSimulator::OPERATION_ISSET,
'name',
null,
$valueHolder,
'returnValue',
$originalClass
);
if (! $publicProperties->isEmpty()) {
$callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n"
. ' $returnValue = isset($this->' . $valueHolderName . '->$name);'
. "\n} else {\n " . $callParent . "\n}\n\n";
}
$this->setBody(InterceptorGenerator::createInterceptedMethodBody(
$callParent,
$this,
$valueHolder,
$prefixInterceptors,
$suffixInterceptors,
$parent
));
}
}

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator;
use InvalidArgumentException;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MagicMethodGenerator;
use ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\Util\InterceptorGenerator;
use ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesMap;
use ProxyManager\ProxyGenerator\Util\GetMethodIfExists;
use ProxyManager\ProxyGenerator\Util\PublicScopeSimulator;
use ReflectionClass;
/**
* Magic `__set` for method interceptor value holder objects
*/
class MagicSet extends MagicMethodGenerator
{
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct(
ReflectionClass $originalClass,
PropertyGenerator $valueHolder,
PropertyGenerator $prefixInterceptors,
PropertyGenerator $suffixInterceptors,
PublicPropertiesMap $publicProperties
) {
parent::__construct(
$originalClass,
'__set',
[new ParameterGenerator('name'), new ParameterGenerator('value')]
);
$parent = GetMethodIfExists::get($originalClass, '__set');
$valueHolderName = $valueHolder->getName();
$callParent = PublicScopeSimulator::getPublicAccessSimulationCode(
PublicScopeSimulator::OPERATION_SET,
'name',
'value',
$valueHolder,
'returnValue',
$originalClass
);
if (! $publicProperties->isEmpty()) {
$callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n"
. ' $returnValue = ($this->' . $valueHolderName . '->$name = $value);'
. "\n} else {\n " . $callParent . "\n}\n\n";
}
$this->setBody(InterceptorGenerator::createInterceptedMethodBody(
$callParent,
$this,
$valueHolder,
$prefixInterceptors,
$suffixInterceptors,
$parent
));
}
}

View File

@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator;
use InvalidArgumentException;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MagicMethodGenerator;
use ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\Util\InterceptorGenerator;
use ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesMap;
use ProxyManager\ProxyGenerator\Util\GetMethodIfExists;
use ProxyManager\ProxyGenerator\Util\PublicScopeSimulator;
use ReflectionClass;
/**
* Magic `__unset` for method interceptor value holder objects
*/
class MagicUnset extends MagicMethodGenerator
{
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct(
ReflectionClass $originalClass,
PropertyGenerator $valueHolder,
PropertyGenerator $prefixInterceptors,
PropertyGenerator $suffixInterceptors,
PublicPropertiesMap $publicProperties
) {
parent::__construct($originalClass, '__unset', [new ParameterGenerator('name')]);
$parent = GetMethodIfExists::get($originalClass, '__unset');
$valueHolderName = $valueHolder->getName();
$callParent = PublicScopeSimulator::getPublicAccessSimulationCode(
PublicScopeSimulator::OPERATION_UNSET,
'name',
null,
$valueHolder,
'returnValue',
$originalClass
);
if (! $publicProperties->isEmpty()) {
$callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n"
. ' unset($this->' . $valueHolderName . '->$name);'
. "\n} else {\n " . $callParent . "\n}\n\n";
}
$callParent .= '$returnValue = false;';
$this->setBody(InterceptorGenerator::createInterceptedMethodBody(
$callParent,
$this,
$valueHolder,
$prefixInterceptors,
$suffixInterceptors,
$parent
));
}
}

View File

@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MethodGenerator;
use ProxyManager\ProxyGenerator\Util\Properties;
use ProxyManager\ProxyGenerator\Util\UnsetPropertiesGenerator;
use ReflectionClass;
/**
* The `staticProxyConstructor` implementation for access interceptor value holders
*/
class StaticProxyConstructor extends MethodGenerator
{
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct(
ReflectionClass $originalClass,
PropertyGenerator $valueHolder,
PropertyGenerator $prefixInterceptors,
PropertyGenerator $suffixInterceptors
) {
parent::__construct('staticProxyConstructor', [], self::FLAG_PUBLIC | self::FLAG_STATIC);
$prefix = new ParameterGenerator('prefixInterceptors');
$suffix = new ParameterGenerator('suffixInterceptors');
$prefix->setDefaultValue([]);
$suffix->setDefaultValue([]);
$prefix->setType('array');
$suffix->setType('array');
$this->setParameter(new ParameterGenerator('wrappedObject'));
$this->setParameter($prefix);
$this->setParameter($suffix);
$this->setReturnType($originalClass->getName());
$this->setDocBlock(
"Constructor to setup interceptors\n\n"
. '@param \\' . $originalClass->getName() . " \$wrappedObject\n"
. "@param \\Closure[] \$prefixInterceptors method interceptors to be used before method logic\n"
. "@param \\Closure[] \$suffixInterceptors method interceptors to be used before method logic\n\n"
. '@return self'
);
$this->setBody(
'static $reflection;' . "\n\n"
. '$reflection = $reflection ?? new \ReflectionClass(__CLASS__);' . "\n"
. '$instance = $reflection->newInstanceWithoutConstructor();' . "\n\n"
. UnsetPropertiesGenerator::generateSnippet(Properties::fromReflectionClass($originalClass), 'instance')
. '$instance->' . $valueHolder->getName() . " = \$wrappedObject;\n"
. '$instance->' . $prefixInterceptors->getName() . " = \$prefixInterceptors;\n"
. '$instance->' . $suffixInterceptors->getName() . " = \$suffixInterceptors;\n\n"
. 'return $instance;'
);
}
}

View File

@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\Util;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MethodGenerator;
use ProxyManager\Generator\Util\ProxiedMethodReturnExpression;
use ReflectionMethod;
use function array_keys;
use function implode;
use function str_replace;
use function var_export;
/**
* Utility to create pre- and post- method interceptors around a given method body
*
* @private - this class is just here as a small utility for this component, don't use it in your own code
*/
class InterceptorGenerator
{
private const TEMPLATE = <<<'PHP'
if (isset($this->{{$prefixInterceptorsName}}[{{$name}}])) {
$returnEarly = false;
$prefixReturnValue = $this->{{$prefixInterceptorsName}}[{{$name}}]->__invoke($this, $this->{{$valueHolderName}}, {{$name}}, {{$paramsString}}, $returnEarly);
if ($returnEarly) {
{{$returnEarlyPrefixExpression}}
}
}
{{$methodBody}}
if (isset($this->{{$suffixInterceptorsName}}[{{$name}}])) {
$returnEarly = false;
$suffixReturnValue = $this->{{$suffixInterceptorsName}}[{{$name}}]->__invoke($this, $this->{{$valueHolderName}}, {{$name}}, {{$paramsString}}, $returnValue, $returnEarly);
if ($returnEarly) {
{{$returnEarlySuffixExpression}}
}
}
{{$returnExpression}}
PHP;
/**
* @param string $methodBody the body of the previously generated code.
* It MUST assign the return value to a variable
* `$returnValue` instead of directly returning
*/
public static function createInterceptedMethodBody(
string $methodBody,
MethodGenerator $method,
PropertyGenerator $valueHolder,
PropertyGenerator $prefixInterceptors,
PropertyGenerator $suffixInterceptors,
?ReflectionMethod $originalMethod
): string {
$name = var_export($method->getName(), true);
$valueHolderName = $valueHolder->getName();
$prefixInterceptorsName = $prefixInterceptors->getName();
$suffixInterceptorsName = $suffixInterceptors->getName();
$params = [];
foreach ($method->getParameters() as $parameter) {
$parameterName = $parameter->getName();
$symbol = $parameter->getPassedByReference() ? '&$' : '$';
$params[] = var_export($parameterName, true) . ' => ' . $symbol . $parameterName;
}
$paramsString = 'array(' . implode(', ', $params) . ')';
$replacements = [
'{{$prefixInterceptorsName}}' => $prefixInterceptorsName,
'{{$name}}' => $name,
'{{$valueHolderName}}' => $valueHolderName,
'{{$paramsString}}' => $paramsString,
'{{$returnEarlyPrefixExpression}}' => ProxiedMethodReturnExpression::generate('$prefixReturnValue', $originalMethod),
'{{$methodBody}}' => $methodBody,
'{{$suffixInterceptorsName}}' => $suffixInterceptorsName,
'{{$returnEarlySuffixExpression}}' => ProxiedMethodReturnExpression::generate('$suffixReturnValue', $originalMethod),
'{{$returnExpression}}' => ProxiedMethodReturnExpression::generate('$returnValue', $originalMethod),
];
return str_replace(array_keys($replacements), $replacements, self::TEMPLATE);
}
}

View File

@@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator;
use InvalidArgumentException;
use Laminas\Code\Generator\ClassGenerator;
use Laminas\Code\Generator\MethodGenerator;
use Laminas\Code\Reflection\MethodReflection;
use ProxyManager\Exception\InvalidProxiedClassException;
use ProxyManager\Generator\Util\ClassGeneratorUtils;
use ProxyManager\Proxy\AccessInterceptorValueHolderInterface;
use ProxyManager\ProxyGenerator\AccessInterceptor\MethodGenerator\MagicWakeup;
use ProxyManager\ProxyGenerator\AccessInterceptor\MethodGenerator\SetMethodPrefixInterceptor;
use ProxyManager\ProxyGenerator\AccessInterceptor\MethodGenerator\SetMethodSuffixInterceptor;
use ProxyManager\ProxyGenerator\AccessInterceptor\PropertyGenerator\MethodPrefixInterceptors;
use ProxyManager\ProxyGenerator\AccessInterceptor\PropertyGenerator\MethodSuffixInterceptors;
use ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\InterceptedMethod;
use ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\MagicClone;
use ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\MagicGet;
use ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\MagicIsset;
use ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\MagicSet;
use ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\MagicUnset;
use ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\StaticProxyConstructor;
use ProxyManager\ProxyGenerator\Assertion\CanProxyAssertion;
use ProxyManager\ProxyGenerator\LazyLoadingValueHolder\PropertyGenerator\ValueHolderProperty;
use ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesMap;
use ProxyManager\ProxyGenerator\Util\Properties;
use ProxyManager\ProxyGenerator\Util\ProxiedMethodsFilter;
use ProxyManager\ProxyGenerator\ValueHolder\MethodGenerator\Constructor;
use ProxyManager\ProxyGenerator\ValueHolder\MethodGenerator\GetWrappedValueHolderValue;
use ProxyManager\ProxyGenerator\ValueHolder\MethodGenerator\MagicSleep;
use ReflectionClass;
use ReflectionMethod;
use function array_map;
use function array_merge;
/**
* Generator for proxies implementing {@see \ProxyManager\Proxy\ValueHolderInterface}
* and {@see \ProxyManager\Proxy\AccessInterceptorInterface}
*
* {@inheritDoc}
*/
class AccessInterceptorValueHolderGenerator implements ProxyGeneratorInterface
{
/**
* {@inheritDoc}
*
* @return void
*
* @throws InvalidArgumentException
* @throws InvalidProxiedClassException
*/
public function generate(ReflectionClass $originalClass, ClassGenerator $classGenerator)
{
CanProxyAssertion::assertClassCanBeProxied($originalClass);
$publicProperties = new PublicPropertiesMap(Properties::fromReflectionClass($originalClass));
$interfaces = [AccessInterceptorValueHolderInterface::class];
if ($originalClass->isInterface()) {
$interfaces[] = $originalClass->getName();
} else {
$classGenerator->setExtendedClass($originalClass->getName());
}
$classGenerator->setImplementedInterfaces($interfaces);
$classGenerator->addPropertyFromGenerator($valueHolder = new ValueHolderProperty($originalClass));
$classGenerator->addPropertyFromGenerator($prefixInterceptors = new MethodPrefixInterceptors());
$classGenerator->addPropertyFromGenerator($suffixInterceptors = new MethodSuffixInterceptors());
$classGenerator->addPropertyFromGenerator($publicProperties);
array_map(
static function (MethodGenerator $generatedMethod) use ($originalClass, $classGenerator): void {
ClassGeneratorUtils::addMethodIfNotFinal($originalClass, $classGenerator, $generatedMethod);
},
array_merge(
array_map(
$this->buildMethodInterceptor($prefixInterceptors, $suffixInterceptors, $valueHolder),
ProxiedMethodsFilter::getProxiedMethods($originalClass)
),
[
Constructor::generateMethod($originalClass, $valueHolder),
new StaticProxyConstructor($originalClass, $valueHolder, $prefixInterceptors, $suffixInterceptors),
new GetWrappedValueHolderValue($valueHolder),
new SetMethodPrefixInterceptor($prefixInterceptors),
new SetMethodSuffixInterceptor($suffixInterceptors),
new MagicGet(
$originalClass,
$valueHolder,
$prefixInterceptors,
$suffixInterceptors,
$publicProperties
),
new MagicSet(
$originalClass,
$valueHolder,
$prefixInterceptors,
$suffixInterceptors,
$publicProperties
),
new MagicIsset(
$originalClass,
$valueHolder,
$prefixInterceptors,
$suffixInterceptors,
$publicProperties
),
new MagicUnset(
$originalClass,
$valueHolder,
$prefixInterceptors,
$suffixInterceptors,
$publicProperties
),
new MagicClone($originalClass, $valueHolder, $prefixInterceptors, $suffixInterceptors),
new MagicSleep($originalClass, $valueHolder),
new MagicWakeup($originalClass),
]
)
);
}
private function buildMethodInterceptor(
MethodPrefixInterceptors $prefixes,
MethodSuffixInterceptors $suffixes,
ValueHolderProperty $valueHolder
): callable {
return static function (ReflectionMethod $method) use ($prefixes, $suffixes, $valueHolder): InterceptedMethod {
return InterceptedMethod::generateMethod(
new MethodReflection($method->getDeclaringClass()->getName(), $method->getName()),
$valueHolder,
$prefixes,
$suffixes
);
};
}
}

View File

@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\Assertion;
use BadMethodCallException;
use ProxyManager\Exception\InvalidProxiedClassException;
use ReflectionClass;
use ReflectionMethod;
use function array_filter;
/**
* Assertion that verifies that a class can be proxied
*/
final class CanProxyAssertion
{
/**
* Disabled constructor: not meant to be instantiated
*
* @throws BadMethodCallException
*/
public function __construct()
{
throw new BadMethodCallException('Unsupported constructor.');
}
/**
* @throws InvalidProxiedClassException
*/
public static function assertClassCanBeProxied(ReflectionClass $originalClass, bool $allowInterfaces = true): void
{
self::isNotFinal($originalClass);
self::hasNoAbstractProtectedMethods($originalClass);
if ($allowInterfaces) {
return;
}
self::isNotInterface($originalClass);
}
/**
* @throws InvalidProxiedClassException
*/
private static function isNotFinal(ReflectionClass $originalClass): void
{
if ($originalClass->isFinal()) {
throw InvalidProxiedClassException::finalClassNotSupported($originalClass);
}
}
/**
* @throws InvalidProxiedClassException
*/
private static function hasNoAbstractProtectedMethods(ReflectionClass $originalClass): void
{
$protectedAbstract = array_filter(
$originalClass->getMethods(),
static function (ReflectionMethod $method): bool {
return $method->isAbstract() && $method->isProtected();
}
);
if ($protectedAbstract) {
throw InvalidProxiedClassException::abstractProtectedMethodsNotSupported($originalClass);
}
}
/**
* @throws InvalidProxiedClassException
*/
private static function isNotInterface(ReflectionClass $originalClass): void
{
if ($originalClass->isInterface()) {
throw InvalidProxiedClassException::interfaceNotSupported($originalClass);
}
}
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoading\MethodGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MethodGenerator;
use ProxyManager\ProxyGenerator\Util\Properties;
use ProxyManager\ProxyGenerator\Util\UnsetPropertiesGenerator;
/**
* The `staticProxyConstructor` implementation for lazy loading proxies
*/
class StaticProxyConstructor extends MethodGenerator
{
/**
* Static constructor
*
* @throws InvalidArgumentException
*/
public function __construct(PropertyGenerator $initializerProperty, Properties $properties)
{
parent::__construct('staticProxyConstructor', [], self::FLAG_PUBLIC | self::FLAG_STATIC);
$this->setParameter(new ParameterGenerator('initializer'));
$this->setDocBlock("Constructor for lazy initialization\n\n@param \\Closure|null \$initializer");
$this->setBody(
'static $reflection;' . "\n\n"
. '$reflection = $reflection ?? new \ReflectionClass(__CLASS__);' . "\n"
. '$instance = $reflection->newInstanceWithoutConstructor();' . "\n\n"
. UnsetPropertiesGenerator::generateSnippet($properties, 'instance')
. '$instance->' . $initializerProperty->getName() . ' = $initializer;' . "\n\n"
. 'return $instance;'
);
}
}

View File

@@ -0,0 +1,267 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use LogicException;
use ProxyManager\Generator\MethodGenerator;
use ProxyManager\Generator\Util\IdentifierSuffixer;
use ProxyManager\Generator\ValueGenerator;
use ProxyManager\ProxyGenerator\Util\Properties;
use ReflectionIntersectionType;
use ReflectionNamedType;
use ReflectionProperty;
use ReflectionType;
use ReflectionUnionType;
use function array_map;
use function assert;
use function get_class;
use function implode;
use function sprintf;
use function str_replace;
use function var_export;
/**
* Implementation for {@see \ProxyManager\Proxy\LazyLoadingInterface::isProxyInitialized}
* for lazy loading value holder objects
*/
class CallInitializer extends MethodGenerator
{
public function __construct(
PropertyGenerator $initializerProperty,
PropertyGenerator $initTracker,
Properties $properties
) {
$docBlock = <<<'DOCBLOCK'
Triggers initialization logic for this ghost object
@param string $methodName
@param mixed[] $parameters
@return mixed
DOCBLOCK;
parent::__construct(
IdentifierSuffixer::getIdentifier('callInitializer'),
[
new ParameterGenerator('methodName'),
new ParameterGenerator('parameters', 'array'),
],
self::FLAG_PRIVATE,
null,
$docBlock
);
$initializer = $initializerProperty->getName();
$initialization = $initTracker->getName();
$bodyTemplate = <<<'PHP'
if ($this->%s || ! $this->%s) {
return;
}
$this->%s = true;
%s
%s
$result = $this->%s->__invoke($this, $methodName, $parameters, $this->%s, $properties);
%s$this->%s = false;
return $result;
PHP;
$referenceableProperties = $properties->withoutNonReferenceableProperties();
$nonReferenceableProperties = $properties->onlyNonReferenceableProperties();
$this->setBody(sprintf(
$bodyTemplate,
$initialization,
$initializer,
$initialization,
$this->propertiesInitializationCode($referenceableProperties),
$this->propertiesReferenceArrayCode($referenceableProperties, $nonReferenceableProperties),
$initializer,
$initializer,
$this->propertiesNonReferenceableCode($nonReferenceableProperties),
$initialization
));
}
private function propertiesInitializationCode(Properties $properties): string
{
$scopedPropertyGroups = [];
$nonScopedProperties = [];
foreach ($properties->getInstanceProperties() as $property) {
if ($property->isPrivate() || (\PHP_VERSION_ID >= 80100 && $property->isReadOnly())) {
$scopedPropertyGroups[$property->getDeclaringClass()->getName()][$property->getName()] = $property;
} else {
$nonScopedProperties[] = $property;
}
}
$assignments = [];
foreach ($nonScopedProperties as $property) {
$assignments[] = '$this->'
. $property->getName()
. ' = ' . $this->getExportedPropertyDefaultValue($property)
. ';';
}
foreach ($scopedPropertyGroups as $className => $scopedProperties) {
$cacheKey = 'cache' . str_replace('\\', '_', $className);
$assignments[] = 'static $' . $cacheKey . ";\n\n"
. '$' . $cacheKey . ' ?? $' . $cacheKey . " = \\Closure::bind(static function (\$instance) {\n"
. $this->getPropertyDefaultsAssignments($scopedProperties) . "\n"
. '}, null, ' . var_export($className, true) . ");\n\n"
. '$' . $cacheKey . "(\$this);\n\n";
}
return implode("\n", $assignments) . "\n\n";
}
/**
* @param ReflectionProperty[] $properties
*/
private function getPropertyDefaultsAssignments(array $properties): string
{
return implode(
"\n",
array_map(
function (ReflectionProperty $property): string {
return ' $instance->' . $property->getName()
. ' = ' . $this->getExportedPropertyDefaultValue($property) . ';';
},
$properties
)
);
}
private function propertiesReferenceArrayCode(Properties $properties, Properties $nonReferenceableProperties): string
{
$assignments = [];
$nonReferenceablePropertiesDefinition = '';
foreach ($properties->getAccessibleProperties() as $propertyInternalName => $property) {
$assignments[] = ' '
. var_export($propertyInternalName, true) . ' => & $this->' . $property->getName()
. ',';
}
foreach ($nonReferenceableProperties->getInstanceProperties() as $propertyInternalName => $property) {
$propertyAlias = $property->getName() . ($property->isPrivate() ? '_on_' . str_replace('\\', '_', $property->getDeclaringClass()->getName()) : '');
$propertyType = $property->getType();
assert($propertyType !== null);
$nonReferenceablePropertiesDefinition .= sprintf(" public %s $%s;\n", self::getReferenceableType($propertyType), $propertyAlias);
$assignments[] = sprintf(' %s => & $nonReferenceableProperties->%s,', var_export($propertyInternalName, true), $propertyAlias);
}
$code = $nonReferenceableProperties->empty() ? '' : sprintf("\$nonReferenceableProperties = new class() {\n%s};\n", $nonReferenceablePropertiesDefinition);
$code .= "\$properties = [\n" . implode("\n", $assignments) . "\n];\n\n";
// must use assignments, as direct reference during array definition causes a fatal error (not sure why)
foreach ($properties->getGroupedPrivateProperties() as $className => $classPrivateProperties) {
$cacheKey = 'cacheFetch' . str_replace('\\', '_', $className);
$code .= 'static $' . $cacheKey . ";\n\n"
. '$' . $cacheKey . ' ?? $' . $cacheKey
. " = \\Closure::bind(function (\$instance, array & \$properties) {\n"
. $this->generatePrivatePropertiesAssignmentsCode($classPrivateProperties)
. '}, null, ' . var_export($className, true) . ");\n\n"
. '$' . $cacheKey . '($this, $properties);';
}
return $code;
}
/**
* @param array<string, ReflectionProperty> $properties indexed by internal name
*/
private function generatePrivatePropertiesAssignmentsCode(array $properties): string
{
$code = '';
foreach ($properties as $property) {
$key = "\0" . $property->getDeclaringClass()->getName() . "\0" . $property->getName();
$code .= ' $properties[' . var_export($key, true) . '] = '
. '& $instance->' . $property->getName() . ";\n";
}
return $code;
}
private function getExportedPropertyDefaultValue(ReflectionProperty $property): string
{
$name = $property->getName();
$defaults = $property->getDeclaringClass()->getDefaultProperties();
return (new ValueGenerator($defaults[$name] ?? null))->generate();
}
private function propertiesNonReferenceableCode(Properties $properties): string
{
if ($properties->empty()) {
return '';
}
$code = [];
$scopedPropertyGroups = [];
foreach ($properties->getInstanceProperties() as $propertyInternalName => $property) {
if (! $property->isPrivate() && (\PHP_VERSION_ID < 80100 || ! $property->isReadOnly())) {
$propertyAlias = $property->getName() . ($property->isPrivate() ? '_on_' . str_replace('\\', '_', $property->getDeclaringClass()->getName()) : '');
$code[] = sprintf('isset($nonReferenceableProperties->%s) && $this->%s = $nonReferenceableProperties->%1$s;', $propertyAlias, $property->getName());
} else {
$scopedPropertyGroups[$property->getDeclaringClass()->getName()][$propertyInternalName] = $property;
}
}
foreach ($scopedPropertyGroups as $className => $scopedProperties) {
$cacheKey = 'cacheAssign' . str_replace('\\', '_', $className);
$code[] = 'static $' . $cacheKey . ";\n";
$code[] = '$' . $cacheKey . ' ?? $' . $cacheKey . ' = \Closure::bind(function ($instance, $nonReferenceableProperties) {';
foreach ($scopedProperties as $property) {
$propertyAlias = $property->getName() . ($property->isPrivate() ? '_on_' . str_replace('\\', '_', $property->getDeclaringClass()->getName()) : '');
$code[] = sprintf(' isset($nonReferenceableProperties->%s) && $instance->%s = $nonReferenceableProperties->%1$s;', $propertyAlias, $property->getName());
}
$code[] = '}, null, ' . var_export($className, true) . ");\n";
$code[] = '$' . $cacheKey . '($this, $nonReferenceableProperties);';
}
return implode("\n", $code) . "\n";
}
private static function getReferenceableType(ReflectionType $type): string
{
if ($type instanceof ReflectionNamedType) {
return '?' . ($type->isBuiltin() ? '' : '\\') . $type->getName();
}
if ($type instanceof ReflectionIntersectionType) {
return self::getReferenceableType($type->getTypes()[0]);
}
if (! $type instanceof ReflectionUnionType) {
throw new LogicException('Unexpected ' . get_class($type));
}
$union = 'null';
foreach ($type->getTypes() as $subType) {
$union .= '|' . ($subType->isBuiltin() ? '' : '\\') . $subType->getName();
}
return $union;
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MethodGenerator;
/**
* Implementation for {@see \ProxyManager\Proxy\LazyLoadingInterface::getProxyInitializer}
* for lazy loading value holder objects
*/
class GetProxyInitializer extends MethodGenerator
{
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct(PropertyGenerator $initializerProperty)
{
parent::__construct('getProxyInitializer');
$this->setReturnType('?\\Closure');
$this->setBody('return $this->' . $initializerProperty->getName() . ';');
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\MethodGenerator as LaminasMethodGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MethodGenerator;
/**
* Implementation for {@see \ProxyManager\Proxy\LazyLoadingInterface::initializeProxy}
* for lazy loading ghost objects
*/
class InitializeProxy extends MethodGenerator
{
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct(PropertyGenerator $initializerProperty, LaminasMethodGenerator $callInitializer)
{
parent::__construct('initializeProxy');
$this->setReturnType('bool');
$this->setBody(
'return $this->' . $initializerProperty->getName() . ' && $this->' . $callInitializer->getName()
. '(\'initializeProxy\', []);'
);
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MethodGenerator;
/**
* Implementation for {@see \ProxyManager\Proxy\LazyLoadingInterface::isProxyInitialized}
* for lazy loading value holder objects
*/
class IsProxyInitialized extends MethodGenerator
{
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct(PropertyGenerator $initializerProperty)
{
parent::__construct('isProxyInitialized');
$this->setReturnType('bool');
$this->setBody('return ! $this->' . $initializerProperty->getName() . ';');
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator;
use Laminas\Code\Generator\MethodGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MagicMethodGenerator;
use ReflectionClass;
/**
* Magic `__clone` for lazy loading ghost objects
*/
class MagicClone extends MagicMethodGenerator
{
/**
* Constructor
*/
public function __construct(
ReflectionClass $originalClass,
PropertyGenerator $initializerProperty,
MethodGenerator $callInitializer
) {
parent::__construct($originalClass, '__clone');
$this->setBody(
'$this->' . $initializerProperty->getName() . ' && $this->' . $callInitializer->getName()
. '(\'__clone\', []);'
. ($originalClass->hasMethod('__clone') ? "\n\nparent::__clone();" : '')
);
}
}

View File

@@ -0,0 +1,141 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator;
use InvalidArgumentException;
use Laminas\Code\Generator\MethodGenerator;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MagicMethodGenerator;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator\InitializationTracker;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator\PrivatePropertiesMap;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator\ProtectedPropertiesMap;
use ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesMap;
use ProxyManager\ProxyGenerator\Util\PublicScopeSimulator;
use ReflectionClass;
use function implode;
use function sprintf;
/**
* Magic `__get` for lazy loading ghost objects
*/
class MagicGet extends MagicMethodGenerator
{
private $callParentTemplate = <<<'PHP'
$this->%s && ! $this->%s && $this->%s('__get', array('name' => $name));
if (isset(self::$%s[$name])) {
return $this->$name;
}
if (isset(self::$%s[$name])) {
if ($this->%s) {
return $this->$name;
}
// check protected property access via compatible class
$callers = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
$caller = isset($callers[1]) ? $callers[1] : [];
$object = isset($caller['object']) ? $caller['object'] : '';
$expectedType = self::$%s[$name];
if ($object instanceof $expectedType) {
return $this->$name;
}
$class = isset($caller['class']) ? $caller['class'] : '';
if ($class === $expectedType || is_subclass_of($class, $expectedType) || $class === 'ReflectionProperty') {
return $this->$name;
}
} elseif (isset(self::$%s[$name])) {
// check private property access via same class
$callers = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
$caller = isset($callers[1]) ? $callers[1] : [];
$class = isset($caller['class']) ? $caller['class'] : '';
static $accessorCache = [];
if (isset(self::$%s[$name][$class])) {
$cacheKey = $class . '#' . $name;
$accessor = isset($accessorCache[$cacheKey])
? $accessorCache[$cacheKey]
: $accessorCache[$cacheKey] = \Closure::bind(static function & ($instance) use ($name) {
%s
}, null, $class);
return $accessor($this);
}
if ($this->%s || 'ReflectionProperty' === $class) {
$tmpClass = key(self::$%s[$name]);
$cacheKey = $tmpClass . '#' . $name;
$accessor = isset($accessorCache[$cacheKey])
? $accessorCache[$cacheKey]
: $accessorCache[$cacheKey] = \Closure::bind(static function & ($instance) use ($name) {
%s
}, null, $tmpClass);
return $accessor($this);
}
}
%s
PHP;
/**
* @throws InvalidArgumentException
*/
public function __construct(
ReflectionClass $originalClass,
PropertyGenerator $initializerProperty,
MethodGenerator $callInitializer,
PublicPropertiesMap $publicProperties,
ProtectedPropertiesMap $protectedProperties,
PrivatePropertiesMap $privateProperties,
InitializationTracker $initializationTracker
) {
parent::__construct($originalClass, '__get', [new ParameterGenerator('name')]);
$override = $originalClass->hasMethod('__get');
$parentAccess = 'return parent::__get($name);';
if (! $override) {
$parentAccess = PublicScopeSimulator::getPublicAccessSimulationCode(
PublicScopeSimulator::OPERATION_GET,
'name'
);
}
$readOnlyPropertyNames = $privateProperties->getReadOnlyPropertyNames();
if ($readOnlyPropertyNames) {
$privateReturnCode = sprintf('\in_array($name, [\'%s\'], true) ? $value = $instance->$name : $value = & $instance->$name;', implode("', '", $readOnlyPropertyNames));
$privateReturnCode .= "\n\n return \$value;";
} else {
$privateReturnCode = 'return $instance->$name;';
}
$this->setBody(sprintf(
$this->callParentTemplate,
$initializerProperty->getName(),
$initializationTracker->getName(),
$callInitializer->getName(),
$publicProperties->getName(),
$protectedProperties->getName(),
$initializationTracker->getName(),
$protectedProperties->getName(),
$privateProperties->getName(),
$privateProperties->getName(),
$privateReturnCode,
$initializationTracker->getName(),
$privateProperties->getName(),
$privateReturnCode,
$parentAccess
));
}
}

View File

@@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator;
use InvalidArgumentException;
use Laminas\Code\Generator\MethodGenerator;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MagicMethodGenerator;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator\PrivatePropertiesMap;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator\ProtectedPropertiesMap;
use ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesMap;
use ProxyManager\ProxyGenerator\Util\PublicScopeSimulator;
use ReflectionClass;
use function sprintf;
/**
* Magic `__isset` method for lazy loading ghost objects
*/
class MagicIsset extends MagicMethodGenerator
{
private $callParentTemplate = <<<'PHP'
%s
if (isset(self::$%s[$name])) {
return isset($this->$name);
}
if (isset(self::$%s[$name])) {
// check protected property access via compatible class
$callers = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
$caller = isset($callers[1]) ? $callers[1] : [];
$object = isset($caller['object']) ? $caller['object'] : '';
$expectedType = self::$%s[$name];
if ($object instanceof $expectedType) {
return isset($this->$name);
}
$class = isset($caller['class']) ? $caller['class'] : '';
if ($class === $expectedType || is_subclass_of($class, $expectedType)) {
return isset($this->$name);
}
} else {
// check private property access via same class
$callers = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
$caller = isset($callers[1]) ? $callers[1] : [];
$class = isset($caller['class']) ? $caller['class'] : '';
static $accessorCache = [];
if (isset(self::$%s[$name][$class])) {
$cacheKey = $class . '#' . $name;
$accessor = isset($accessorCache[$cacheKey])
? $accessorCache[$cacheKey]
: $accessorCache[$cacheKey] = \Closure::bind(static function ($instance) use ($name) {
return isset($instance->$name);
}, null, $class);
return $accessor($this);
}
if ('ReflectionProperty' === $class) {
$tmpClass = key(self::$%s[$name]);
$cacheKey = $tmpClass . '#' . $name;
$accessor = isset($accessorCache[$cacheKey])
? $accessorCache[$cacheKey]
: $accessorCache[$cacheKey] = \Closure::bind(static function ($instance) use ($name) {
return isset($instance->$name);
}, null, $tmpClass);
return $accessor($this);
}
}
%s
PHP;
/**
* @throws InvalidArgumentException
*/
public function __construct(
ReflectionClass $originalClass,
PropertyGenerator $initializerProperty,
MethodGenerator $callInitializer,
PublicPropertiesMap $publicProperties,
ProtectedPropertiesMap $protectedProperties,
PrivatePropertiesMap $privateProperties
) {
parent::__construct($originalClass, '__isset', [new ParameterGenerator('name')]);
$override = $originalClass->hasMethod('__isset');
$parentAccess = 'return parent::__isset($name);';
if (! $override) {
$parentAccess = PublicScopeSimulator::getPublicAccessSimulationCode(
PublicScopeSimulator::OPERATION_ISSET,
'name'
);
}
$this->setBody(sprintf(
$this->callParentTemplate,
'$this->' . $initializerProperty->getName() . ' && $this->' . $callInitializer->getName()
. '(\'__isset\', array(\'name\' => $name));',
$publicProperties->getName(),
$protectedProperties->getName(),
$protectedProperties->getName(),
$privateProperties->getName(),
$privateProperties->getName(),
$parentAccess
));
}
}

View File

@@ -0,0 +1,125 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator;
use InvalidArgumentException;
use Laminas\Code\Generator\MethodGenerator;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MagicMethodGenerator;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator\PrivatePropertiesMap;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator\ProtectedPropertiesMap;
use ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesMap;
use ProxyManager\ProxyGenerator\Util\PublicScopeSimulator;
use ReflectionClass;
use function sprintf;
/**
* Magic `__set` for lazy loading ghost objects
*/
class MagicSet extends MagicMethodGenerator
{
private $callParentTemplate = <<<'PHP'
%s
if (isset(self::$%s[$name])) {
return ($this->$name = $value);
}
if (isset(self::$%s[$name])) {
// check protected property access via compatible class
$callers = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
$caller = isset($callers[1]) ? $callers[1] : [];
$object = isset($caller['object']) ? $caller['object'] : '';
$expectedType = self::$%s[$name];
if ($object instanceof $expectedType) {
return ($this->$name = $value);
}
$class = isset($caller['class']) ? $caller['class'] : '';
if ($class === $expectedType || is_subclass_of($class, $expectedType) || $class === 'ReflectionProperty') {
return ($this->$name = $value);
}
} elseif (isset(self::$%s[$name])) {
// check private property access via same class
$callers = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
$caller = isset($callers[1]) ? $callers[1] : [];
$class = isset($caller['class']) ? $caller['class'] : '';
static $accessorCache = [];
if (isset(self::$%s[$name][$class])) {
$cacheKey = $class . '#' . $name;
$accessor = isset($accessorCache[$cacheKey])
? $accessorCache[$cacheKey]
: $accessorCache[$cacheKey] = \Closure::bind(static function ($instance, $value) use ($name) {
return ($instance->$name = $value);
}, null, $class);
return $accessor($this, $value);
}
if ('ReflectionProperty' === $class) {
$tmpClass = key(self::$%s[$name]);
$cacheKey = $tmpClass . '#' . $name;
$accessor = isset($accessorCache[$cacheKey])
? $accessorCache[$cacheKey]
: $accessorCache[$cacheKey] = \Closure::bind(static function ($instance, $value) use ($name) {
return ($instance->$name = $value);
}, null, $tmpClass);
return $accessor($this, $value);
}
}
%s
PHP;
/**
* @throws InvalidArgumentException
*/
public function __construct(
ReflectionClass $originalClass,
PropertyGenerator $initializerProperty,
MethodGenerator $callInitializer,
PublicPropertiesMap $publicProperties,
ProtectedPropertiesMap $protectedProperties,
PrivatePropertiesMap $privateProperties
) {
parent::__construct(
$originalClass,
'__set',
[new ParameterGenerator('name'), new ParameterGenerator('value')]
);
$override = $originalClass->hasMethod('__set');
$parentAccess = 'return parent::__set($name, $value);';
if (! $override) {
$parentAccess = PublicScopeSimulator::getPublicAccessSimulationCode(
PublicScopeSimulator::OPERATION_SET,
'name',
'value'
);
}
$this->setBody(sprintf(
$this->callParentTemplate,
'$this->' . $initializerProperty->getName() . ' && $this->' . $callInitializer->getName()
. '(\'__set\', array(\'name\' => $name, \'value\' => $value));',
$publicProperties->getName(),
$protectedProperties->getName(),
$protectedProperties->getName(),
$privateProperties->getName(),
$privateProperties->getName(),
$privateProperties->getName(),
$parentAccess
));
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator;
use Laminas\Code\Generator\MethodGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MagicMethodGenerator;
use ReflectionClass;
/**
* Magic `__sleep` for lazy loading ghost objects
*/
class MagicSleep extends MagicMethodGenerator
{
/**
* Constructor
*/
public function __construct(
ReflectionClass $originalClass,
PropertyGenerator $initializerProperty,
MethodGenerator $callInitializer
) {
parent::__construct($originalClass, '__sleep');
$this->setBody(
'$this->' . $initializerProperty->getName() . ' && $this->' . $callInitializer->getName()
. '(\'__sleep\', []);' . "\n\n"
. ($originalClass->hasMethod('__sleep') ? 'return parent::__sleep();' : 'return array_keys((array) $this);')
);
}
}

View File

@@ -0,0 +1,126 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator;
use InvalidArgumentException;
use Laminas\Code\Generator\MethodGenerator;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MagicMethodGenerator;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator\PrivatePropertiesMap;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator\ProtectedPropertiesMap;
use ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesMap;
use ProxyManager\ProxyGenerator\Util\PublicScopeSimulator;
use ReflectionClass;
use function sprintf;
/**
* Magic `__unset` method for lazy loading ghost objects
*/
class MagicUnset extends MagicMethodGenerator
{
private $callParentTemplate = <<<'PHP'
%s
if (isset(self::$%s[$name])) {
unset($this->$name);
return;
}
if (isset(self::$%s[$name])) {
// check protected property access via compatible class
$callers = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
$caller = isset($callers[1]) ? $callers[1] : [];
$object = isset($caller['object']) ? $caller['object'] : '';
$expectedType = self::$%s[$name];
if ($object instanceof $expectedType) {
unset($this->$name);
return;
}
$class = isset($caller['class']) ? $caller['class'] : '';
if ($class === $expectedType || is_subclass_of($class, $expectedType) || $class === 'ReflectionProperty') {
unset($this->$name);
return;
}
} elseif (isset(self::$%s[$name])) {
// check private property access via same class
$callers = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
$caller = isset($callers[1]) ? $callers[1] : [];
$class = isset($caller['class']) ? $caller['class'] : '';
static $accessorCache = [];
if (isset(self::$%s[$name][$class])) {
$cacheKey = $class . '#' . $name;
$accessor = isset($accessorCache[$cacheKey])
? $accessorCache[$cacheKey]
: $accessorCache[$cacheKey] = \Closure::bind(static function ($instance) use ($name) {
unset($instance->$name);
}, null, $class);
return $accessor($this);
}
if ('ReflectionProperty' === $class) {
$tmpClass = key(self::$%s[$name]);
$cacheKey = $tmpClass . '#' . $name;
$accessor = isset($accessorCache[$cacheKey])
? $accessorCache[$cacheKey]
: $accessorCache[$cacheKey] = \Closure::bind(static function ($instance) use ($name) {
unset($instance->$name);
}, null, $tmpClass);
return $accessor($this);
}
}
%s
PHP;
/**
* @throws InvalidArgumentException
*/
public function __construct(
ReflectionClass $originalClass,
PropertyGenerator $initializerProperty,
MethodGenerator $callInitializer,
PublicPropertiesMap $publicProperties,
ProtectedPropertiesMap $protectedProperties,
PrivatePropertiesMap $privateProperties
) {
parent::__construct($originalClass, '__unset', [new ParameterGenerator('name')]);
$override = $originalClass->hasMethod('__unset');
$parentAccess = 'return parent::__unset($name);';
if (! $override) {
$parentAccess = PublicScopeSimulator::getPublicAccessSimulationCode(
PublicScopeSimulator::OPERATION_UNSET,
'name'
);
}
$this->setBody(sprintf(
$this->callParentTemplate,
'$this->' . $initializerProperty->getName() . ' && $this->' . $callInitializer->getName()
. '(\'__unset\', array(\'name\' => $name));',
$publicProperties->getName(),
$protectedProperties->getName(),
$protectedProperties->getName(),
$privateProperties->getName(),
$privateProperties->getName(),
$privateProperties->getName(),
$parentAccess
));
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator;
use Closure;
use Laminas\Code\Generator\ParameterGenerator;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MethodGenerator;
/**
* Implementation for {@see \ProxyManager\Proxy\LazyLoadingInterface::setProxyInitializer}
* for lazy loading value holder objects
*/
class SetProxyInitializer extends MethodGenerator
{
/**
* Constructor
*/
public function __construct(PropertyGenerator $initializerProperty)
{
parent::__construct(
'setProxyInitializer',
[(new ParameterGenerator('initializer', '?' . Closure::class))->setDefaultValue(null)],
self::FLAG_PUBLIC,
'$this->' . $initializerProperty->getName() . ' = $initializer;'
);
$this->setReturnType('void');
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MethodGenerator;
/**
* Destructor that skips the original destructor when the proxy is not initialized.
*/
class SkipDestructor extends MethodGenerator
{
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct(PropertyGenerator $initializerProperty)
{
parent::__construct('__destruct');
$this->setBody(
'$this->' . $initializerProperty->getName() . ' || parent::__destruct();'
);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\Util\IdentifierSuffixer;
/**
* Property that contains the initializer for a lazy object
*/
class InitializationTracker extends PropertyGenerator
{
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct()
{
parent::__construct(IdentifierSuffixer::getIdentifier('initializationTracker'));
$this->setVisibility(self::VISIBILITY_PRIVATE);
$this->setDocBlock('@var bool tracks initialization status - true while the object is initializing');
$this->setDefaultValue(false);
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\Util\IdentifierSuffixer;
/**
* Property that contains the initializer for a lazy object
*/
class InitializerProperty extends PropertyGenerator
{
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct()
{
parent::__construct(IdentifierSuffixer::getIdentifier('initializer'));
$this->setVisibility(self::VISIBILITY_PRIVATE);
$this->setDocBlock('@var \\Closure|null initializer responsible for generating the wrapped object');
}
}

View File

@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\Util\IdentifierSuffixer;
use ProxyManager\ProxyGenerator\Util\Properties;
/**
* Property that contains the initializer for a lazy object
*/
class PrivatePropertiesMap extends PropertyGenerator
{
public const KEY_DEFAULT_VALUE = 'defaultValue';
/** @var list<string> */
private $readOnlyPropertyNames = [];
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct(Properties $properties)
{
parent::__construct(
IdentifierSuffixer::getIdentifier('privateProperties')
);
$this->setVisibility(self::VISIBILITY_PRIVATE);
$this->setStatic(true);
$this->setDocBlock(
'@var array[][] visibility and default value of defined properties, indexed by property name and class name'
);
$this->setDefaultValue($this->getMap($properties));
}
/**
* @return list<string>
*/
public function getReadOnlyPropertyNames(): array
{
return $this->readOnlyPropertyNames;
}
/**
* @return array<string, array<class-string, bool>>
*/
private function getMap(Properties $properties): array
{
$map = [];
foreach ($properties->getInstanceProperties() as $property) {
if (\PHP_VERSION_ID >= 80100 && $property->isReadOnly()) {
$this->readOnlyPropertyNames[] = $property->getName();
} elseif (! $property->isPrivate()) {
continue;
}
$map[$property->getName()][$property->getDeclaringClass()->getName()] = true;
}
return $map;
}
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\Util\IdentifierSuffixer;
use ProxyManager\ProxyGenerator\Util\Properties;
/**
* Property that contains the protected instance lazy-loadable properties of an object
*/
class ProtectedPropertiesMap extends PropertyGenerator
{
public const KEY_DEFAULT_VALUE = 'defaultValue';
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct(Properties $properties)
{
parent::__construct(
IdentifierSuffixer::getIdentifier('protectedProperties')
);
$this->setVisibility(self::VISIBILITY_PRIVATE);
$this->setStatic(true);
$this->setDocBlock(
'@var string[][] declaring class name of defined protected properties, indexed by property name'
);
$this->setDefaultValue($this->getMap($properties));
}
/** @return string[] */
private function getMap(Properties $properties): array
{
$map = [];
foreach ($properties->getProtectedProperties() as $property) {
if (\PHP_VERSION_ID >= 80100 && $property->isReadOnly()) {
continue;
}
$map[$property->getName()] = $property->getDeclaringClass()->getName();
}
return $map;
}
}

View File

@@ -0,0 +1,161 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator;
use InvalidArgumentException;
use Laminas\Code\Generator\ClassGenerator;
use Laminas\Code\Generator\MethodGenerator;
use Laminas\Code\Reflection\MethodReflection;
use ProxyManager\Exception\InvalidProxiedClassException;
use ProxyManager\Generator\MethodGenerator as ProxyManagerMethodGenerator;
use ProxyManager\Generator\Util\ClassGeneratorUtils;
use ProxyManager\Proxy\GhostObjectInterface;
use ProxyManager\ProxyGenerator\Assertion\CanProxyAssertion;
use ProxyManager\ProxyGenerator\LazyLoading\MethodGenerator\StaticProxyConstructor;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\CallInitializer;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\GetProxyInitializer;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\InitializeProxy;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\IsProxyInitialized;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\MagicClone;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\MagicGet;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\MagicIsset;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\MagicSet;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\MagicSleep;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\MagicUnset;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\SetProxyInitializer;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\SkipDestructor;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator\InitializationTracker;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator\InitializerProperty;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator\PrivatePropertiesMap;
use ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator\ProtectedPropertiesMap;
use ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesMap;
use ProxyManager\ProxyGenerator\Util\Properties;
use ProxyManager\ProxyGenerator\Util\ProxiedMethodsFilter;
use ReflectionClass;
use ReflectionMethod;
use function array_map;
use function array_merge;
/**
* Generator for proxies implementing {@see \ProxyManager\Proxy\GhostObjectInterface}
*
* {@inheritDoc}
*/
class LazyLoadingGhostGenerator implements ProxyGeneratorInterface
{
/**
* {@inheritDoc}
*
* @psalm-param array{skippedProperties?: array<int, string>, skipDestructor?: bool} $proxyOptions
*
* @return void
*
* @throws InvalidProxiedClassException
* @throws InvalidArgumentException
*/
public function generate(ReflectionClass $originalClass, ClassGenerator $classGenerator, array $proxyOptions = [])
{
CanProxyAssertion::assertClassCanBeProxied($originalClass, false);
$filteredProperties = Properties::fromReflectionClass($originalClass)
->filter($proxyOptions['skippedProperties'] ?? []);
$publicProperties = new PublicPropertiesMap($filteredProperties, true);
$privateProperties = new PrivatePropertiesMap($filteredProperties);
$protectedProperties = new ProtectedPropertiesMap($filteredProperties);
$skipDestructor = ($proxyOptions['skipDestructor'] ?? false) && $originalClass->hasMethod('__destruct');
$classGenerator->setExtendedClass($originalClass->getName());
$classGenerator->setImplementedInterfaces([GhostObjectInterface::class]);
$classGenerator->addPropertyFromGenerator($initializer = new InitializerProperty());
$classGenerator->addPropertyFromGenerator($initializationTracker = new InitializationTracker());
$classGenerator->addPropertyFromGenerator($publicProperties);
$classGenerator->addPropertyFromGenerator($privateProperties);
$classGenerator->addPropertyFromGenerator($protectedProperties);
$init = new CallInitializer($initializer, $initializationTracker, $filteredProperties);
array_map(
static function (MethodGenerator $generatedMethod) use ($originalClass, $classGenerator): void {
ClassGeneratorUtils::addMethodIfNotFinal($originalClass, $classGenerator, $generatedMethod);
},
array_merge(
$this->getAbstractProxiedMethods($originalClass, $skipDestructor),
[
$init,
new StaticProxyConstructor($initializer, $filteredProperties),
new MagicGet(
$originalClass,
$initializer,
$init,
$publicProperties,
$protectedProperties,
$privateProperties,
$initializationTracker
),
new MagicSet(
$originalClass,
$initializer,
$init,
$publicProperties,
$protectedProperties,
$privateProperties
),
new MagicIsset(
$originalClass,
$initializer,
$init,
$publicProperties,
$protectedProperties,
$privateProperties
),
new MagicUnset(
$originalClass,
$initializer,
$init,
$publicProperties,
$protectedProperties,
$privateProperties
),
new MagicClone($originalClass, $initializer, $init),
new MagicSleep($originalClass, $initializer, $init),
new SetProxyInitializer($initializer),
new GetProxyInitializer($initializer),
new InitializeProxy($initializer, $init),
new IsProxyInitialized($initializer),
],
$skipDestructor ? [new SkipDestructor($initializer)] : []
)
);
}
/**
* Retrieves all abstract methods to be proxied
*
* @return MethodGenerator[]
*/
private function getAbstractProxiedMethods(ReflectionClass $originalClass, bool $skipDestructor): array
{
$excludedMethods = ProxiedMethodsFilter::DEFAULT_EXCLUDED;
if ($skipDestructor) {
$excludedMethods[] = '__destruct';
}
return array_map(
static function (ReflectionMethod $method): ProxyManagerMethodGenerator {
$generated = ProxyManagerMethodGenerator::fromReflectionWithoutBodyAndDocBlock(
new MethodReflection($method->getDeclaringClass()->getName(), $method->getName())
);
$generated->setAbstract(false);
return $generated;
},
ProxiedMethodsFilter::getAbstractProxiedMethods($originalClass, $excludedMethods)
);
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoadingValueHolder\MethodGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MethodGenerator;
/**
* Implementation for {@see \ProxyManager\Proxy\LazyLoadingInterface::getProxyInitializer}
* for lazy loading value holder objects
*/
class GetProxyInitializer extends MethodGenerator
{
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct(PropertyGenerator $initializerProperty)
{
parent::__construct('getProxyInitializer');
$this->setReturnType('?\\Closure');
$this->setBody('return $this->' . $initializerProperty->getName() . ';');
}
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoadingValueHolder\MethodGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MethodGenerator;
/**
* Implementation for {@see \ProxyManager\Proxy\LazyLoadingInterface::initializeProxy}
* for lazy loading value holder objects
*/
class InitializeProxy extends MethodGenerator
{
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct(PropertyGenerator $initializerProperty, PropertyGenerator $valueHolderProperty)
{
parent::__construct('initializeProxy');
$this->setReturnType('bool');
$initializer = $initializerProperty->getName();
$valueHolder = $valueHolderProperty->getName();
$this->setBody(
'return $this->' . $initializer . ' && ($this->' . $initializer
. '->__invoke($' . $valueHolder
. ', $this, \'initializeProxy\', array(), $this->' . $initializer . ') || 1)'
. ' && $this->' . $valueHolder . ' = $' . $valueHolder . ';'
);
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoadingValueHolder\MethodGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\PropertyGenerator;
use ProxyManager\Generator\MethodGenerator;
/**
* Implementation for {@see \ProxyManager\Proxy\LazyLoadingInterface::isProxyInitialized}
* for lazy loading value holder objects
*/
class IsProxyInitialized extends MethodGenerator
{
/**
* Constructor
*
* @throws InvalidArgumentException
*/
public function __construct(PropertyGenerator $valueHolderProperty)
{
parent::__construct('isProxyInitialized');
$this->setReturnType('bool');
$this->setBody('return null !== $this->' . $valueHolderProperty->getName() . ';');
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace ProxyManager\ProxyGenerator\LazyLoadingValueHolder\MethodGenerator;
use Laminas\Code\Generator\Exception\InvalidArgumentException;
use Laminas\Code\Generator\PropertyGenerator;
use Laminas\Code\Reflection\MethodReflection;
use ProxyManager\Generator\MethodGenerator;
use ProxyManager\Generator\Util\ProxiedMethodReturnExpression;
use function implode;
use function var_export;
/**
* Method decorator for lazy loading value holder objects
*/
class LazyLoadingMethodInterceptor extends MethodGenerator
{
/**
* @throws InvalidArgumentException
*/
public static function generateMethod(
MethodReflection $originalMethod,
PropertyGenerator $initializerProperty,
PropertyGenerator $valueHolderProperty
): self {
$method = static::fromReflectionWithoutBodyAndDocBlock($originalMethod);
$initializerName = $initializerProperty->getName();
$valueHolderName = $valueHolderProperty->getName();
$parameters = $originalMethod->getParameters();
$methodName = $originalMethod->getName();
$initializerParams = [];
$forwardedParams = [];
foreach ($parameters as $parameter) {
$parameterName = $parameter->getName();
$variadicPrefix = $parameter->isVariadic() ? '...' : '';
$initializerParams[] = var_export($parameterName, true) . ' => $' . $parameterName;
$forwardedParams[] = $variadicPrefix . '$' . $parameterName;
}
$method->setBody(
'$this->' . $initializerName
. ' && ($this->' . $initializerName
. '->__invoke($' . $valueHolderName . ', $this, ' . var_export($methodName, true)
. ', array(' . implode(', ', $initializerParams) . '), $this->' . $initializerName . ') || 1)'
. ' && $this->' . $valueHolderName . ' = $' . $valueHolderName . ";\n\n"
. ProxiedMethodReturnExpression::generate(
'$this->' . $valueHolderName . '->' . $methodName . '(' . implode(', ', $forwardedParams) . ')',
$originalMethod
)
);
return $method;
}
}

Some files were not shown because too many files have changed in this diff Show More