1<?php 2/* 3 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 4 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 5 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 6 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 7 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 8 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 9 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 10 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 11 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 12 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 13 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 * 15 * This software consists of voluntary contributions made by many individuals 16 * and is licensed under the MIT license. 17 */ 18 19declare(strict_types=1); 20 21namespace ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator; 22 23use ProxyManager\Generator\MagicMethodGenerator; 24use Zend\Code\Generator\ParameterGenerator; 25use ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator\PrivatePropertiesMap; 26use ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator\ProtectedPropertiesMap; 27use ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesMap; 28use ProxyManager\ProxyGenerator\Util\PublicScopeSimulator; 29use ReflectionClass; 30use Zend\Code\Generator\MethodGenerator; 31use Zend\Code\Generator\PropertyGenerator; 32 33/** 34 * Magic `__isset` method for lazy loading ghost objects 35 * 36 * @author Marco Pivetta <ocramius@gmail.com> 37 * @license MIT 38 */ 39class MagicIsset extends MagicMethodGenerator 40{ 41 /** 42 * @var string 43 */ 44 private $callParentTemplate = <<<'PHP' 45%s 46 47if (isset(self::$%s[$name])) { 48 return isset($this->$name); 49} 50 51if (isset(self::$%s[$name])) { 52 // check protected property access via compatible class 53 $callers = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2); 54 $caller = isset($callers[1]) ? $callers[1] : []; 55 $object = isset($caller['object']) ? $caller['object'] : ''; 56 $expectedType = self::$%s[$name]; 57 58 if ($object instanceof $expectedType) { 59 return isset($this->$name); 60 } 61 62 $class = isset($caller['class']) ? $caller['class'] : ''; 63 64 if ($class === $expectedType || is_subclass_of($class, $expectedType)) { 65 return isset($this->$name); 66 } 67} else { 68 // check private property access via same class 69 $callers = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2); 70 $caller = isset($callers[1]) ? $callers[1] : []; 71 $class = isset($caller['class']) ? $caller['class'] : ''; 72 73 static $accessorCache = []; 74 75 if (isset(self::$%s[$name][$class])) { 76 $cacheKey = $class . '#' . $name; 77 $accessor = isset($accessorCache[$cacheKey]) 78 ? $accessorCache[$cacheKey] 79 : $accessorCache[$cacheKey] = \Closure::bind(function ($instance) use ($name) { 80 return isset($instance->$name); 81 }, null, $class); 82 83 return $accessor($this); 84 } 85 86 if ('ReflectionProperty' === $class) { 87 $tmpClass = key(self::$%s[$name]); 88 $cacheKey = $tmpClass . '#' . $name; 89 $accessor = isset($accessorCache[$cacheKey]) 90 ? $accessorCache[$cacheKey] 91 : $accessorCache[$cacheKey] = \Closure::bind(function ($instance) use ($name) { 92 return isset($instance->$name); 93 }, null, $tmpClass); 94 95 return $accessor($this); 96 } 97} 98 99%s 100PHP; 101 102 /** 103 * @param ReflectionClass $originalClass 104 * @param PropertyGenerator $initializerProperty 105 * @param MethodGenerator $callInitializer 106 * @param PublicPropertiesMap $publicProperties 107 * @param ProtectedPropertiesMap $protectedProperties 108 * @param PrivatePropertiesMap $privateProperties 109 * 110 * @throws \Zend\Code\Generator\Exception\InvalidArgumentException 111 * @throws \InvalidArgumentException 112 */ 113 public function __construct( 114 ReflectionClass $originalClass, 115 PropertyGenerator $initializerProperty, 116 MethodGenerator $callInitializer, 117 PublicPropertiesMap $publicProperties, 118 ProtectedPropertiesMap $protectedProperties, 119 PrivatePropertiesMap $privateProperties 120 ) { 121 parent::__construct($originalClass, '__isset', [new ParameterGenerator('name')]); 122 123 $override = $originalClass->hasMethod('__isset'); 124 125 $this->setDocBlock(($override ? "{@inheritDoc}\n" : '') . '@param string $name'); 126 127 $parentAccess = 'return parent::__isset($name);'; 128 129 if (! $override) { 130 $parentAccess = PublicScopeSimulator::getPublicAccessSimulationCode( 131 PublicScopeSimulator::OPERATION_ISSET, 132 'name' 133 ); 134 } 135 136 $this->setBody(sprintf( 137 $this->callParentTemplate, 138 '$this->' . $initializerProperty->getName() . ' && $this->' . $callInitializer->getName() 139 . '(\'__isset\', array(\'name\' => $name));', 140 $publicProperties->getName(), 141 $protectedProperties->getName(), 142 $protectedProperties->getName(), 143 $privateProperties->getName(), 144 $privateProperties->getName(), 145 $parentAccess 146 )); 147 } 148} 149