Exception
2 years ago
AbstractProxyFactory.php
2 years ago
Autoloader.php
2 years ago
Proxy.php
2 years ago
ProxyDefinition.php
2 years ago
ProxyGenerator.php
9 months ago
index.php
2 years ago
ProxyGenerator.php
734 lines
| 1 | <?php |
| 2 | namespace MailPoetVendor\Doctrine\Common\Proxy; |
| 3 | if (!defined('ABSPATH')) exit; |
| 4 | use BackedEnum; |
| 5 | use MailPoetVendor\Doctrine\Common\Proxy\Exception\InvalidArgumentException; |
| 6 | use MailPoetVendor\Doctrine\Common\Proxy\Exception\UnexpectedValueException; |
| 7 | use MailPoetVendor\Doctrine\Common\Util\ClassUtils; |
| 8 | use MailPoetVendor\Doctrine\Persistence\Mapping\ClassMetadata; |
| 9 | use ReflectionIntersectionType; |
| 10 | use ReflectionMethod; |
| 11 | use ReflectionNamedType; |
| 12 | use ReflectionParameter; |
| 13 | use ReflectionProperty; |
| 14 | use ReflectionType; |
| 15 | use ReflectionUnionType; |
| 16 | use function array_combine; |
| 17 | use function array_diff; |
| 18 | use function array_key_exists; |
| 19 | use function array_map; |
| 20 | use function array_slice; |
| 21 | use function array_unique; |
| 22 | use function assert; |
| 23 | use function bin2hex; |
| 24 | use function call_user_func; |
| 25 | use function chmod; |
| 26 | use function class_exists; |
| 27 | use function dirname; |
| 28 | use function explode; |
| 29 | use function file; |
| 30 | use function file_put_contents; |
| 31 | use function get_class; |
| 32 | use function implode; |
| 33 | use function in_array; |
| 34 | use function interface_exists; |
| 35 | use function is_callable; |
| 36 | use function is_dir; |
| 37 | use function is_scalar; |
| 38 | use function is_string; |
| 39 | use function is_writable; |
| 40 | use function lcfirst; |
| 41 | use function ltrim; |
| 42 | use function method_exists; |
| 43 | use function mkdir; |
| 44 | use function preg_match; |
| 45 | use function preg_match_all; |
| 46 | use function preg_replace; |
| 47 | use function preg_split; |
| 48 | use function random_bytes; |
| 49 | use function rename; |
| 50 | use function rtrim; |
| 51 | use function sprintf; |
| 52 | use function str_replace; |
| 53 | use function strpos; |
| 54 | use function strrev; |
| 55 | use function strtolower; |
| 56 | use function strtr; |
| 57 | use function substr; |
| 58 | use function trim; |
| 59 | use function var_export; |
| 60 | use const DIRECTORY_SEPARATOR; |
| 61 | use const PHP_VERSION_ID; |
| 62 | use const PREG_SPLIT_DELIM_CAPTURE; |
| 63 | class ProxyGenerator |
| 64 | { |
| 65 | public const PATTERN_MATCH_ID_METHOD = <<<'EOT' |
| 66 | ((?(DEFINE) |
| 67 | (?<type>\\?[a-z_\x7f-\xff][\w\x7f-\xff]*(?:\\[a-z_\x7f-\xff][\w\x7f-\xff]*)*) |
| 68 | (?<intersection_type>(?&type)\s*&\s*(?&type)) |
| 69 | (?<union_type>(?:(?:\(\s*(?&intersection_type)\s*\))|(?&type))(?:\s*\|\s*(?:(?:\(\s*(?&intersection_type)\s*\))|(?&type)))+) |
| 70 | )(?:public\s+)?(?:function\s+%s\s*\(\)\s*)\s*(?::\s*(?:(?&union_type)|(?&intersection_type)|(?:\??(?&type)))\s*)?{\s*return\s*\$this->%s;\s*})i |
| 71 | EOT; |
| 72 | private $proxyNamespace; |
| 73 | private $proxyDirectory; |
| 74 | protected $placeholders = ['baseProxyInterface' => Proxy::class, 'additionalProperties' => '']; |
| 75 | protected $proxyClassTemplate = '<?php |
| 76 | namespace <namespace>; |
| 77 | <enumUseStatements> |
| 78 | class <proxyShortClassName> extends \\<className> implements \\<baseProxyInterface> |
| 79 | { |
| 80 | public $__initializer__; |
| 81 | public $__cloner__; |
| 82 | public $__isInitialized__ = false; |
| 83 | public static $lazyPropertiesNames = <lazyPropertiesNames>; |
| 84 | public static $lazyPropertiesDefaults = <lazyPropertiesDefaults>; |
| 85 | <additionalProperties> |
| 86 | <constructorImpl> |
| 87 | <magicGet> |
| 88 | <magicSet> |
| 89 | <magicIsset> |
| 90 | <sleepImpl> |
| 91 | <wakeupImpl> |
| 92 | <cloneImpl> |
| 93 | public function __load(): void |
| 94 | { |
| 95 | $this->__initializer__ && $this->__initializer__->__invoke($this, \'__load\', []); |
| 96 | } |
| 97 | public function __isInitialized(): bool |
| 98 | { |
| 99 | return $this->__isInitialized__; |
| 100 | } |
| 101 | public function __setInitialized($initialized): void |
| 102 | { |
| 103 | $this->__isInitialized__ = $initialized; |
| 104 | } |
| 105 | public function __setInitializer(?\\Closure $initializer = null): void |
| 106 | { |
| 107 | $this->__initializer__ = $initializer; |
| 108 | } |
| 109 | public function __getInitializer(): ?\\Closure |
| 110 | { |
| 111 | return $this->__initializer__; |
| 112 | } |
| 113 | public function __setCloner(?\\Closure $cloner = null): void |
| 114 | { |
| 115 | $this->__cloner__ = $cloner; |
| 116 | } |
| 117 | public function __getCloner(): ?\\Closure |
| 118 | { |
| 119 | return $this->__cloner__; |
| 120 | } |
| 121 | public function __getLazyProperties(): array |
| 122 | { |
| 123 | return self::$lazyPropertiesDefaults; |
| 124 | } |
| 125 | <methods> |
| 126 | } |
| 127 | '; |
| 128 | public function __construct($proxyDirectory, $proxyNamespace) |
| 129 | { |
| 130 | if (!$proxyDirectory) { |
| 131 | throw InvalidArgumentException::proxyDirectoryRequired(); |
| 132 | } |
| 133 | if (!$proxyNamespace) { |
| 134 | throw InvalidArgumentException::proxyNamespaceRequired(); |
| 135 | } |
| 136 | $this->proxyDirectory = $proxyDirectory; |
| 137 | $this->proxyNamespace = $proxyNamespace; |
| 138 | } |
| 139 | public function setPlaceholder($name, $placeholder) |
| 140 | { |
| 141 | if (!is_string($placeholder) && !is_callable($placeholder)) { |
| 142 | throw InvalidArgumentException::invalidPlaceholder($name); |
| 143 | } |
| 144 | $this->placeholders[$name] = $placeholder; |
| 145 | } |
| 146 | public function setProxyClassTemplate($proxyClassTemplate) |
| 147 | { |
| 148 | $this->proxyClassTemplate = (string) $proxyClassTemplate; |
| 149 | } |
| 150 | public function generateProxyClass(ClassMetadata $class, $fileName = \false) |
| 151 | { |
| 152 | $this->verifyClassCanBeProxied($class); |
| 153 | preg_match_all('(<([a-zA-Z]+)>)', $this->proxyClassTemplate, $placeholderMatches); |
| 154 | $placeholderMatches = array_combine($placeholderMatches[0], $placeholderMatches[1]); |
| 155 | $placeholders = []; |
| 156 | foreach ($placeholderMatches as $placeholder => $name) { |
| 157 | $placeholders[$placeholder] = $this->placeholders[$name] ?? [$this, 'generate' . $name]; |
| 158 | } |
| 159 | foreach ($placeholders as &$placeholder) { |
| 160 | if (!is_callable($placeholder)) { |
| 161 | continue; |
| 162 | } |
| 163 | $placeholder = call_user_func($placeholder, $class); |
| 164 | } |
| 165 | $proxyCode = strtr($this->proxyClassTemplate, $placeholders); |
| 166 | if (!$fileName) { |
| 167 | $proxyClassName = $this->generateNamespace($class) . '\\' . $this->generateProxyShortClassName($class); |
| 168 | if (!class_exists($proxyClassName)) { |
| 169 | eval(substr($proxyCode, 5)); |
| 170 | } |
| 171 | return; |
| 172 | } |
| 173 | $parentDirectory = dirname($fileName); |
| 174 | if (!is_dir($parentDirectory) && @mkdir($parentDirectory, 0775, \true) === \false) { |
| 175 | throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory); |
| 176 | } |
| 177 | if (!is_writable($parentDirectory)) { |
| 178 | throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory); |
| 179 | } |
| 180 | $tmpFileName = $fileName . '.' . bin2hex(random_bytes(12)); |
| 181 | file_put_contents($tmpFileName, $proxyCode); |
| 182 | @chmod($tmpFileName, 0664); |
| 183 | rename($tmpFileName, $fileName); |
| 184 | } |
| 185 | private function verifyClassCanBeProxied(ClassMetadata $class) |
| 186 | { |
| 187 | if ($class->getReflectionClass()->isFinal()) { |
| 188 | throw InvalidArgumentException::classMustNotBeFinal($class->getName()); |
| 189 | } |
| 190 | if ($class->getReflectionClass()->isAbstract()) { |
| 191 | throw InvalidArgumentException::classMustNotBeAbstract($class->getName()); |
| 192 | } |
| 193 | if (PHP_VERSION_ID >= 80200 && $class->getReflectionClass()->isReadOnly()) { |
| 194 | throw InvalidArgumentException::classMustNotBeReadOnly($class->getName()); |
| 195 | } |
| 196 | } |
| 197 | private function generateProxyShortClassName(ClassMetadata $class) |
| 198 | { |
| 199 | $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace); |
| 200 | $parts = explode('\\', strrev($proxyClassName), 2); |
| 201 | return strrev($parts[0]); |
| 202 | } |
| 203 | private function generateNamespace(ClassMetadata $class) |
| 204 | { |
| 205 | $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace); |
| 206 | $parts = explode('\\', strrev($proxyClassName), 2); |
| 207 | return strrev($parts[1]); |
| 208 | } |
| 209 | public function generateEnumUseStatements(ClassMetadata $class) : string |
| 210 | { |
| 211 | if (PHP_VERSION_ID < 80100) { |
| 212 | return "\n"; |
| 213 | } |
| 214 | $defaultProperties = $class->getReflectionClass()->getDefaultProperties(); |
| 215 | $lazyLoadedPublicProperties = $this->getLazyLoadedPublicPropertiesNames($class); |
| 216 | $enumClasses = []; |
| 217 | foreach ($class->getReflectionClass()->getProperties(ReflectionProperty::IS_PUBLIC) as $property) { |
| 218 | $name = $property->getName(); |
| 219 | if (!in_array($name, $lazyLoadedPublicProperties, \true)) { |
| 220 | continue; |
| 221 | } |
| 222 | if (array_key_exists($name, $defaultProperties) && $defaultProperties[$name] instanceof BackedEnum) { |
| 223 | $enumClassNameParts = explode('\\', get_class($defaultProperties[$name])); |
| 224 | $enumClasses[] = $enumClassNameParts[0]; |
| 225 | } |
| 226 | } |
| 227 | return implode("\n", array_map(static function ($className) { |
| 228 | return 'use ' . $className . ';'; |
| 229 | }, array_unique($enumClasses))) . "\n"; |
| 230 | } |
| 231 | private function generateClassName(ClassMetadata $class) |
| 232 | { |
| 233 | return ltrim($class->getName(), '\\'); |
| 234 | } |
| 235 | private function generateLazyPropertiesNames(ClassMetadata $class) |
| 236 | { |
| 237 | $lazyPublicProperties = $this->getLazyLoadedPublicPropertiesNames($class); |
| 238 | $values = []; |
| 239 | foreach ($lazyPublicProperties as $name) { |
| 240 | $values[$name] = null; |
| 241 | } |
| 242 | return var_export($values, \true); |
| 243 | } |
| 244 | private function generateLazyPropertiesDefaults(ClassMetadata $class) |
| 245 | { |
| 246 | return var_export($this->getLazyLoadedPublicProperties($class), \true); |
| 247 | } |
| 248 | private function generateConstructorImpl(ClassMetadata $class) |
| 249 | { |
| 250 | $constructorImpl = <<<'EOT' |
| 251 | public function __construct(?\Closure $initializer = null, ?\Closure $cloner = null) |
| 252 | { |
| 253 | EOT; |
| 254 | $toUnset = array_map(static function (string $name) : string { |
| 255 | return '$this->' . $name; |
| 256 | }, $this->getLazyLoadedPublicPropertiesNames($class)); |
| 257 | return $constructorImpl . ($toUnset === [] ? '' : ' unset(' . implode(', ', $toUnset) . ");\n") . <<<'EOT' |
| 258 | $this->__initializer__ = $initializer; |
| 259 | $this->__cloner__ = $cloner; |
| 260 | } |
| 261 | EOT; |
| 262 | } |
| 263 | private function generateMagicGet(ClassMetadata $class) |
| 264 | { |
| 265 | $lazyPublicProperties = $this->getLazyLoadedPublicPropertiesNames($class); |
| 266 | $reflectionClass = $class->getReflectionClass(); |
| 267 | $hasParentGet = \false; |
| 268 | $returnReference = ''; |
| 269 | $inheritDoc = ''; |
| 270 | $name = '$name'; |
| 271 | $parametersString = '$name'; |
| 272 | $returnTypeHint = null; |
| 273 | if ($reflectionClass->hasMethod('__get')) { |
| 274 | $hasParentGet = \true; |
| 275 | $inheritDoc = '{@inheritDoc}'; |
| 276 | $methodReflection = $reflectionClass->getMethod('__get'); |
| 277 | if ($methodReflection->returnsReference()) { |
| 278 | $returnReference = '& '; |
| 279 | } |
| 280 | $methodParameters = $methodReflection->getParameters(); |
| 281 | $name = '$' . $methodParameters[0]->getName(); |
| 282 | $parametersString = $this->buildParametersString($methodReflection->getParameters(), ['name']); |
| 283 | $returnTypeHint = $this->getMethodReturnType($methodReflection); |
| 284 | } |
| 285 | if (empty($lazyPublicProperties) && !$hasParentGet) { |
| 286 | return ''; |
| 287 | } |
| 288 | $magicGet = <<<EOT |
| 289 | public function {$returnReference}__get({$parametersString}){$returnTypeHint} |
| 290 | { |
| 291 | EOT; |
| 292 | if (!empty($lazyPublicProperties)) { |
| 293 | $magicGet .= <<<'EOT' |
| 294 | if (\array_key_exists($name, self::$lazyPropertiesNames)) { |
| 295 | $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', [$name]); |
| 296 | EOT; |
| 297 | if ($returnTypeHint === ': void') { |
| 298 | $magicGet .= "\n return;"; |
| 299 | } else { |
| 300 | $magicGet .= "\n return \$this->\$name;"; |
| 301 | } |
| 302 | $magicGet .= <<<'EOT' |
| 303 | } |
| 304 | EOT; |
| 305 | } |
| 306 | if ($hasParentGet) { |
| 307 | $magicGet .= <<<'EOT' |
| 308 | $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', [$name]); |
| 309 | EOT; |
| 310 | if ($returnTypeHint === ': void') { |
| 311 | $magicGet .= <<<'EOT' |
| 312 | parent::__get($name); |
| 313 | return; |
| 314 | EOT; |
| 315 | } elseif ($returnTypeHint === ': never') { |
| 316 | $magicGet .= <<<'EOT' |
| 317 | parent::__get($name); |
| 318 | EOT; |
| 319 | } else { |
| 320 | $magicGet .= <<<'EOT' |
| 321 | return parent::__get($name); |
| 322 | EOT; |
| 323 | } |
| 324 | } else { |
| 325 | $magicGet .= sprintf(<<<EOT |
| 326 | trigger_error(sprintf('Undefined property: %%s::\$%%s', __CLASS__, %s), E_USER_NOTICE); |
| 327 | EOT |
| 328 | , $name); |
| 329 | } |
| 330 | return $magicGet . "\n }"; |
| 331 | } |
| 332 | private function generateMagicSet(ClassMetadata $class) |
| 333 | { |
| 334 | $lazyPublicProperties = $this->getLazyLoadedPublicPropertiesNames($class); |
| 335 | $reflectionClass = $class->getReflectionClass(); |
| 336 | $hasParentSet = \false; |
| 337 | $inheritDoc = ''; |
| 338 | $parametersString = '$name, $value'; |
| 339 | $returnTypeHint = null; |
| 340 | if ($reflectionClass->hasMethod('__set')) { |
| 341 | $hasParentSet = \true; |
| 342 | $inheritDoc = '{@inheritDoc}'; |
| 343 | $methodReflection = $reflectionClass->getMethod('__set'); |
| 344 | $parametersString = $this->buildParametersString($methodReflection->getParameters(), ['name', 'value']); |
| 345 | $returnTypeHint = $this->getMethodReturnType($methodReflection); |
| 346 | } |
| 347 | if (empty($lazyPublicProperties) && !$hasParentSet) { |
| 348 | return ''; |
| 349 | } |
| 350 | $magicSet = <<<EOT |
| 351 | public function __set({$parametersString}){$returnTypeHint} |
| 352 | { |
| 353 | EOT; |
| 354 | if (!empty($lazyPublicProperties)) { |
| 355 | $magicSet .= <<<'EOT' |
| 356 | if (\array_key_exists($name, self::$lazyPropertiesNames)) { |
| 357 | $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', [$name, $value]); |
| 358 | $this->$name = $value; |
| 359 | return; |
| 360 | } |
| 361 | EOT; |
| 362 | } |
| 363 | if ($hasParentSet) { |
| 364 | $magicSet .= <<<'EOT' |
| 365 | $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', [$name, $value]); |
| 366 | EOT; |
| 367 | if ($returnTypeHint === ': void') { |
| 368 | $magicSet .= <<<'EOT' |
| 369 | parent::__set($name, $value); |
| 370 | return; |
| 371 | EOT; |
| 372 | } elseif ($returnTypeHint === ': never') { |
| 373 | $magicSet .= <<<'EOT' |
| 374 | parent::__set($name, $value); |
| 375 | EOT; |
| 376 | } else { |
| 377 | $magicSet .= <<<'EOT' |
| 378 | return parent::__set($name, $value); |
| 379 | EOT; |
| 380 | } |
| 381 | } else { |
| 382 | $magicSet .= ' $this->$name = $value;'; |
| 383 | } |
| 384 | return $magicSet . "\n }"; |
| 385 | } |
| 386 | private function generateMagicIsset(ClassMetadata $class) |
| 387 | { |
| 388 | $lazyPublicProperties = $this->getLazyLoadedPublicPropertiesNames($class); |
| 389 | $hasParentIsset = $class->getReflectionClass()->hasMethod('__isset'); |
| 390 | $parametersString = '$name'; |
| 391 | $returnTypeHint = null; |
| 392 | if ($hasParentIsset) { |
| 393 | $methodReflection = $class->getReflectionClass()->getMethod('__isset'); |
| 394 | $parametersString = $this->buildParametersString($methodReflection->getParameters(), ['name']); |
| 395 | $returnTypeHint = $this->getMethodReturnType($methodReflection); |
| 396 | } |
| 397 | if (empty($lazyPublicProperties) && !$hasParentIsset) { |
| 398 | return ''; |
| 399 | } |
| 400 | $inheritDoc = $hasParentIsset ? '{@inheritDoc}' : ''; |
| 401 | $magicIsset = <<<EOT |
| 402 | public function __isset({$parametersString}){$returnTypeHint} |
| 403 | { |
| 404 | EOT; |
| 405 | if (!empty($lazyPublicProperties)) { |
| 406 | $magicIsset .= <<<'EOT' |
| 407 | if (\array_key_exists($name, self::$lazyPropertiesNames)) { |
| 408 | $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', [$name]); |
| 409 | return isset($this->$name); |
| 410 | } |
| 411 | EOT; |
| 412 | } |
| 413 | if ($hasParentIsset) { |
| 414 | $magicIsset .= <<<'EOT' |
| 415 | $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', [$name]); |
| 416 | return parent::__isset($name); |
| 417 | EOT; |
| 418 | } else { |
| 419 | $magicIsset .= ' return false;'; |
| 420 | } |
| 421 | return $magicIsset . "\n }"; |
| 422 | } |
| 423 | private function generateSleepImpl(ClassMetadata $class) |
| 424 | { |
| 425 | $reflectionClass = $class->getReflectionClass(); |
| 426 | $hasParentSleep = $reflectionClass->hasMethod('__sleep'); |
| 427 | $inheritDoc = $hasParentSleep ? '{@inheritDoc}' : ''; |
| 428 | $returnTypeHint = $hasParentSleep ? $this->getMethodReturnType($reflectionClass->getMethod('__sleep')) : ''; |
| 429 | $sleepImpl = <<<EOT |
| 430 | public function __sleep(){$returnTypeHint} |
| 431 | { |
| 432 | EOT; |
| 433 | if ($hasParentSleep) { |
| 434 | return $sleepImpl . <<<'EOT' |
| 435 | $properties = array_merge(['__isInitialized__'], parent::__sleep()); |
| 436 | if ($this->__isInitialized__) { |
| 437 | $properties = array_diff($properties, array_keys(self::$lazyPropertiesNames)); |
| 438 | } |
| 439 | return $properties; |
| 440 | } |
| 441 | EOT; |
| 442 | } |
| 443 | $allProperties = ['__isInitialized__']; |
| 444 | foreach ($class->getReflectionClass()->getProperties() as $prop) { |
| 445 | assert($prop instanceof ReflectionProperty); |
| 446 | if ($prop->isStatic()) { |
| 447 | continue; |
| 448 | } |
| 449 | $allProperties[] = $prop->isPrivate() ? "\x00" . $prop->getDeclaringClass()->getName() . "\x00" . $prop->getName() : $prop->getName(); |
| 450 | } |
| 451 | $lazyPublicProperties = $this->getLazyLoadedPublicPropertiesNames($class); |
| 452 | $protectedProperties = array_diff($allProperties, $lazyPublicProperties); |
| 453 | foreach ($allProperties as &$property) { |
| 454 | $property = var_export($property, \true); |
| 455 | } |
| 456 | foreach ($protectedProperties as &$property) { |
| 457 | $property = var_export($property, \true); |
| 458 | } |
| 459 | $allProperties = implode(', ', $allProperties); |
| 460 | $protectedProperties = implode(', ', $protectedProperties); |
| 461 | return $sleepImpl . <<<EOT |
| 462 | if (\$this->__isInitialized__) { |
| 463 | return [{$allProperties}]; |
| 464 | } |
| 465 | return [{$protectedProperties}]; |
| 466 | } |
| 467 | EOT; |
| 468 | } |
| 469 | private function generateWakeupImpl(ClassMetadata $class) |
| 470 | { |
| 471 | $reflectionClass = $class->getReflectionClass(); |
| 472 | $hasParentWakeup = $reflectionClass->hasMethod('__wakeup'); |
| 473 | $unsetPublicProperties = []; |
| 474 | foreach ($this->getLazyLoadedPublicPropertiesNames($class) as $lazyPublicProperty) { |
| 475 | $unsetPublicProperties[] = '$this->' . $lazyPublicProperty; |
| 476 | } |
| 477 | $shortName = $this->generateProxyShortClassName($class); |
| 478 | $inheritDoc = $hasParentWakeup ? '{@inheritDoc}' : ''; |
| 479 | $returnTypeHint = $hasParentWakeup ? $this->getMethodReturnType($reflectionClass->getMethod('__wakeup')) : ''; |
| 480 | $wakeupImpl = <<<EOT |
| 481 | public function __wakeup(){$returnTypeHint} |
| 482 | { |
| 483 | if ( ! \$this->__isInitialized__) { |
| 484 | \$this->__initializer__ = function ({$shortName} \$proxy) { |
| 485 | \$proxy->__setInitializer(null); |
| 486 | \$proxy->__setCloner(null); |
| 487 | \$existingProperties = get_object_vars(\$proxy); |
| 488 | foreach (\$proxy::\$lazyPropertiesDefaults as \$property => \$defaultValue) { |
| 489 | if ( ! array_key_exists(\$property, \$existingProperties)) { |
| 490 | \$proxy->\$property = \$defaultValue; |
| 491 | } |
| 492 | } |
| 493 | }; |
| 494 | EOT; |
| 495 | if (!empty($unsetPublicProperties)) { |
| 496 | $wakeupImpl .= "\n unset(" . implode(', ', $unsetPublicProperties) . ');'; |
| 497 | } |
| 498 | $wakeupImpl .= "\n }"; |
| 499 | if ($hasParentWakeup) { |
| 500 | $wakeupImpl .= "\n parent::__wakeup();"; |
| 501 | } |
| 502 | $wakeupImpl .= "\n }"; |
| 503 | return $wakeupImpl; |
| 504 | } |
| 505 | private function generateCloneImpl(ClassMetadata $class) |
| 506 | { |
| 507 | $reflectionClass = $class->getReflectionClass(); |
| 508 | $hasParentClone = $reflectionClass->hasMethod('__clone'); |
| 509 | $returnTypeHint = $hasParentClone ? $this->getMethodReturnType($reflectionClass->getMethod('__clone')) : ''; |
| 510 | $inheritDoc = $hasParentClone ? '{@inheritDoc}' : ''; |
| 511 | $callParentClone = $hasParentClone ? "\n parent::__clone();\n" : ''; |
| 512 | return <<<EOT |
| 513 | public function __clone(){$returnTypeHint} |
| 514 | { |
| 515 | \$this->__cloner__ && \$this->__cloner__->__invoke(\$this, '__clone', []); |
| 516 | {$callParentClone} } |
| 517 | EOT; |
| 518 | } |
| 519 | private function generateMethods(ClassMetadata $class) |
| 520 | { |
| 521 | $methods = ''; |
| 522 | $methodNames = []; |
| 523 | $reflectionMethods = $class->getReflectionClass()->getMethods(ReflectionMethod::IS_PUBLIC); |
| 524 | $skippedMethods = ['__sleep' => \true, '__clone' => \true, '__wakeup' => \true, '__get' => \true, '__set' => \true, '__isset' => \true]; |
| 525 | foreach ($reflectionMethods as $method) { |
| 526 | $name = $method->getName(); |
| 527 | if ($method->isConstructor() || isset($skippedMethods[strtolower($name)]) || isset($methodNames[$name]) || $method->isFinal() || $method->isStatic() || !$method->isPublic()) { |
| 528 | continue; |
| 529 | } |
| 530 | $methodNames[$name] = \true; |
| 531 | $methods .= "\n /**\n" . " * {@inheritDoc}\n" . " */\n" . ' public function '; |
| 532 | if ($method->returnsReference()) { |
| 533 | $methods .= '&'; |
| 534 | } |
| 535 | $methods .= $name . '(' . $this->buildParametersString($method->getParameters()) . ')'; |
| 536 | $methods .= $this->getMethodReturnType($method); |
| 537 | $methods .= "\n" . ' {' . "\n"; |
| 538 | if ($this->isShortIdentifierGetter($method, $class)) { |
| 539 | $identifier = lcfirst(substr($name, 3)); |
| 540 | $fieldType = $class->getTypeOfField($identifier); |
| 541 | $cast = in_array($fieldType, ['integer', 'smallint'], \true) ? '(int) ' : ''; |
| 542 | $methods .= ' if ($this->__isInitialized__ === false) {' . "\n"; |
| 543 | $methods .= ' '; |
| 544 | $methods .= $this->shouldProxiedMethodReturn($method) ? 'return ' : ''; |
| 545 | $methods .= $cast . ' parent::' . $method->getName() . "();\n"; |
| 546 | $methods .= ' }' . "\n\n"; |
| 547 | } |
| 548 | $invokeParamsString = implode(', ', $this->getParameterNamesForInvoke($method->getParameters())); |
| 549 | $callParamsString = implode(', ', $this->getParameterNamesForParentCall($method->getParameters())); |
| 550 | $methods .= "\n \$this->__initializer__ " . '&& $this->__initializer__->__invoke($this, ' . var_export($name, \true) . ', [' . $invokeParamsString . ']);' . "\n\n " . ($this->shouldProxiedMethodReturn($method) ? 'return ' : '') . 'parent::' . $name . '(' . $callParamsString . ');' . "\n" . ' }' . "\n"; |
| 551 | } |
| 552 | return $methods; |
| 553 | } |
| 554 | public function getProxyFileName($className, $baseDirectory = null) |
| 555 | { |
| 556 | $baseDirectory = $baseDirectory ?: $this->proxyDirectory; |
| 557 | return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . Proxy::MARKER . str_replace('\\', '', $className) . '.php'; |
| 558 | } |
| 559 | private function isShortIdentifierGetter($method, ClassMetadata $class) |
| 560 | { |
| 561 | $identifier = lcfirst(substr($method->getName(), 3)); |
| 562 | $startLine = $method->getStartLine(); |
| 563 | $endLine = $method->getEndLine(); |
| 564 | $cheapCheck = $method->getNumberOfParameters() === 0 && substr($method->getName(), 0, 3) === 'get' && in_array($identifier, $class->getIdentifier(), \true) && $class->hasField($identifier) && $endLine - $startLine <= 4; |
| 565 | if ($cheapCheck) { |
| 566 | $code = file($method->getFileName()); |
| 567 | $code = trim(implode(' ', array_slice($code, $startLine - 1, $endLine - $startLine + 1))); |
| 568 | $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier); |
| 569 | if (preg_match($pattern, $code)) { |
| 570 | return \true; |
| 571 | } |
| 572 | } |
| 573 | return \false; |
| 574 | } |
| 575 | private function getLazyLoadedPublicPropertiesNames(ClassMetadata $class) : array |
| 576 | { |
| 577 | $properties = []; |
| 578 | foreach ($class->getReflectionClass()->getProperties(ReflectionProperty::IS_PUBLIC) as $property) { |
| 579 | $name = $property->getName(); |
| 580 | if (!$class->hasField($name) && !$class->hasAssociation($name) || $class->isIdentifier($name)) { |
| 581 | continue; |
| 582 | } |
| 583 | $properties[] = $name; |
| 584 | } |
| 585 | return $properties; |
| 586 | } |
| 587 | private function getLazyLoadedPublicProperties(ClassMetadata $class) |
| 588 | { |
| 589 | $defaultProperties = $class->getReflectionClass()->getDefaultProperties(); |
| 590 | $lazyLoadedPublicProperties = $this->getLazyLoadedPublicPropertiesNames($class); |
| 591 | $defaultValues = []; |
| 592 | foreach ($class->getReflectionClass()->getProperties(ReflectionProperty::IS_PUBLIC) as $property) { |
| 593 | $name = $property->getName(); |
| 594 | if (!in_array($name, $lazyLoadedPublicProperties, \true)) { |
| 595 | continue; |
| 596 | } |
| 597 | if (array_key_exists($name, $defaultProperties)) { |
| 598 | $defaultValues[$name] = $defaultProperties[$name]; |
| 599 | } elseif (method_exists($property, 'getType')) { |
| 600 | $propertyType = $property->getType(); |
| 601 | if ($propertyType !== null && $propertyType->allowsNull()) { |
| 602 | $defaultValues[$name] = null; |
| 603 | } |
| 604 | } |
| 605 | } |
| 606 | return $defaultValues; |
| 607 | } |
| 608 | private function buildParametersString(array $parameters, array $renameParameters = []) |
| 609 | { |
| 610 | $parameterDefinitions = []; |
| 611 | $i = -1; |
| 612 | foreach ($parameters as $param) { |
| 613 | assert($param instanceof ReflectionParameter); |
| 614 | $i++; |
| 615 | $parameterDefinition = ''; |
| 616 | $parameterType = $this->getParameterType($param); |
| 617 | if ($parameterType !== null) { |
| 618 | $parameterDefinition .= $parameterType . ' '; |
| 619 | } |
| 620 | if ($param->isPassedByReference()) { |
| 621 | $parameterDefinition .= '&'; |
| 622 | } |
| 623 | if ($param->isVariadic()) { |
| 624 | $parameterDefinition .= '...'; |
| 625 | } |
| 626 | $parameterDefinition .= '$' . ($renameParameters ? $renameParameters[$i] : $param->getName()); |
| 627 | $parameterDefinition .= $this->getParameterDefaultValue($param); |
| 628 | $parameterDefinitions[] = $parameterDefinition; |
| 629 | } |
| 630 | return implode(', ', $parameterDefinitions); |
| 631 | } |
| 632 | private function getParameterType(ReflectionParameter $parameter) |
| 633 | { |
| 634 | if (!$parameter->hasType()) { |
| 635 | return null; |
| 636 | } |
| 637 | $declaringFunction = $parameter->getDeclaringFunction(); |
| 638 | assert($declaringFunction instanceof ReflectionMethod); |
| 639 | return $this->formatType($parameter->getType(), $declaringFunction, $parameter); |
| 640 | } |
| 641 | private function getParameterDefaultValue(ReflectionParameter $parameter) |
| 642 | { |
| 643 | if (!$parameter->isDefaultValueAvailable()) { |
| 644 | return ''; |
| 645 | } |
| 646 | if (PHP_VERSION_ID < 80100 || is_scalar($parameter->getDefaultValue())) { |
| 647 | return ' = ' . var_export($parameter->getDefaultValue(), \true); |
| 648 | } |
| 649 | $value = rtrim(substr(explode('$' . $parameter->getName() . ' = ', (string) $parameter, 2)[1], 0, -2)); |
| 650 | if (strpos($value, '\\') !== \false || strpos($value, '::') !== \false) { |
| 651 | $value = preg_split("/('(?:[^'\\\\]*+(?:\\\\.)*+)*+')/", $value, -1, PREG_SPLIT_DELIM_CAPTURE); |
| 652 | foreach ($value as $i => $part) { |
| 653 | if ($i % 2 === 0) { |
| 654 | $value[$i] = preg_replace('/(?<![a-zA-Z0-9_\\x7f-\\xff\\\\])[a-zA-Z0-9_\\x7f-\\xff]++(?:\\\\[a-zA-Z0-9_\\x7f-\\xff]++|::)++/', '\\\\\\0', $part); |
| 655 | } |
| 656 | } |
| 657 | $value = implode('', $value); |
| 658 | } |
| 659 | return ' = ' . $value; |
| 660 | } |
| 661 | private function getParameterNamesForInvoke(array $parameters) |
| 662 | { |
| 663 | return array_map(static function (ReflectionParameter $parameter) { |
| 664 | return '$' . $parameter->getName(); |
| 665 | }, $parameters); |
| 666 | } |
| 667 | private function getParameterNamesForParentCall(array $parameters) |
| 668 | { |
| 669 | return array_map(static function (ReflectionParameter $parameter) { |
| 670 | $name = ''; |
| 671 | if ($parameter->isVariadic()) { |
| 672 | $name .= '...'; |
| 673 | } |
| 674 | $name .= '$' . $parameter->getName(); |
| 675 | return $name; |
| 676 | }, $parameters); |
| 677 | } |
| 678 | private function getMethodReturnType(ReflectionMethod $method) |
| 679 | { |
| 680 | if (!$method->hasReturnType()) { |
| 681 | return ''; |
| 682 | } |
| 683 | return ': ' . $this->formatType($method->getReturnType(), $method); |
| 684 | } |
| 685 | private function shouldProxiedMethodReturn(ReflectionMethod $method) |
| 686 | { |
| 687 | if (!$method->hasReturnType()) { |
| 688 | return \true; |
| 689 | } |
| 690 | return !in_array(strtolower($this->formatType($method->getReturnType(), $method)), ['void', 'never'], \true); |
| 691 | } |
| 692 | private function formatType(ReflectionType $type, ReflectionMethod $method, ?ReflectionParameter $parameter = null) |
| 693 | { |
| 694 | if ($type instanceof ReflectionUnionType) { |
| 695 | return implode('|', array_map(function (ReflectionType $unionedType) use($method, $parameter) { |
| 696 | if ($unionedType instanceof ReflectionIntersectionType) { |
| 697 | return '(' . $this->formatType($unionedType, $method, $parameter) . ')'; |
| 698 | } |
| 699 | return $this->formatType($unionedType, $method, $parameter); |
| 700 | }, $type->getTypes())); |
| 701 | } |
| 702 | if ($type instanceof ReflectionIntersectionType) { |
| 703 | return implode('&', array_map(function (ReflectionType $intersectedType) use($method, $parameter) { |
| 704 | return $this->formatType($intersectedType, $method, $parameter); |
| 705 | }, $type->getTypes())); |
| 706 | } |
| 707 | assert($type instanceof ReflectionNamedType); |
| 708 | $name = $type->getName(); |
| 709 | $nameLower = strtolower($name); |
| 710 | if ($nameLower === 'static') { |
| 711 | $name = 'static'; |
| 712 | } |
| 713 | if ($nameLower === 'self') { |
| 714 | $name = $method->getDeclaringClass()->getName(); |
| 715 | } |
| 716 | if ($nameLower === 'parent') { |
| 717 | $name = $method->getDeclaringClass()->getParentClass()->getName(); |
| 718 | } |
| 719 | if (!$type->isBuiltin() && !class_exists($name) && !interface_exists($name) && $name !== 'static') { |
| 720 | if ($parameter !== null) { |
| 721 | throw UnexpectedValueException::invalidParameterTypeHint($method->getDeclaringClass()->getName(), $method->getName(), $parameter->getName()); |
| 722 | } |
| 723 | throw UnexpectedValueException::invalidReturnTypeHint($method->getDeclaringClass()->getName(), $method->getName()); |
| 724 | } |
| 725 | if (!$type->isBuiltin() && $name !== 'static') { |
| 726 | $name = '\\' . $name; |
| 727 | } |
| 728 | if ($type->allowsNull() && !in_array($name, ['mixed', 'null'], \true)) { |
| 729 | $name = '?' . $name; |
| 730 | } |
| 731 | return $name; |
| 732 | } |
| 733 | } |
| 734 |