Last active
September 11, 2017 15:02
-
-
Save nicolas-grekas/45a3b0fc9ecb96a1f793d2e363823523 to your computer and use it in GitHub Desktop.
Wondering about dynamic proxies in PHP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// Very early draft. Not applicable yet. | |
// TODO: specify behavior for __destruct() | |
// Only public methods/properties can be intercepted, final ones also | |
// Magic methods are intercepted as regular ones | |
// Undefined properties/methods access are excluded from interception | |
// method_exists()/property_exists()/isset() are excluded from interception | |
// new method: Reflection::getInterceptor(): ?object | |
// true === $fooProxy instanceof Foo | |
// Foo::class === get_class($fooProxy) | |
// $fooProxy is transparent to all reflection-like methods | |
// serialize($foo) === serialize($fooProxy) | |
// as regular objects, interceptors are interceptable of course | |
abstract class Interceptor | |
{ | |
const INTERCEPT_CALL = 1; | |
const INTERCEPT_GET = 2; | |
const INTERCEPT_SET = 3; | |
const INTERCEPT_UNSET = 4; | |
private $interceptedInstance; | |
private $class; | |
public static function proxy(self $interceptor, $inPlace = false): object | |
{ | |
// returns a proxy of $interceptor->getInterceptedInstance(). | |
// $this->getClass() is used for reflection/instanceof checks | |
// to prevent calling getInterceptedInstance(), thus prevent instanciating | |
// the object when doing so | |
// if $inPlace is true, $interceptor->getInterceptedInstance() is returned, | |
// with the intercept/interceptRef methods attached to it. | |
// Should we allow multi-in-place interception? | |
// Should we allow this at all? | |
} | |
public function __construct(string $class) | |
{ | |
$this->class = $class; | |
} | |
abstract public function createInterceptedInstance(): object; | |
final public function getInterceptedInstance(): object | |
{ | |
if (null === $this->interceptedInstance) { | |
$interceptedInstance = $this->createInterceptedInstance(); | |
if (get_class($innner) !== $this->class)) { | |
throw new TypeError('...'); | |
} | |
$this->interceptedInstance = $interceptedInstance; | |
} | |
return $this->interceptedInstance; | |
} | |
final public function getClass(): string | |
{ | |
return $this->class; | |
} | |
public function intercept(int $type, string $methodOrProperty, array $args = null) | |
{ | |
// For calls, $args contains references or values, | |
// as specified by the intercepted method's signature | |
// or by the access type for properties (e.g $foo->bar = &$someVar) | |
switch ($type) { | |
case self::INTERCEPT_CALL: return $this->getInterceptedInstance()->$methodOrProperty(...$args); | |
case self::INTERCEPT_GET: return $this->getInterceptedInstance()->$methodOrProperty; | |
case self::INTERCEPT_SET: $this->getInterceptedInstance()->$methodOrProperty = &$args[0]; break; | |
case self::INTERCEPT_UNSET: unset($this->getInterceptedInstance()->$methodOrProperty); break; | |
} | |
} | |
public function &interceptRef(int $type, string $methodOrProperty, array $args) | |
{ | |
// the engine calls this when the intercepted method's signature returns by ref | |
// or when a reference to a property is required (e.g $someVar = &$foo->bar) | |
switch ($type) { | |
case self::INTERCEPT_CALL: return $this->getInterceptedInstance()->$methodOrProperty(...$args); | |
case self::INTERCEPT_GET: return $this->getInterceptedInstance()->$methodOrProperty; | |
} | |
} | |
} | |
// -- EXAMPLE -- | |
// A generic lazy instantiator | |
class LazyInterceptor extends Interceptor | |
{ | |
private $factory; | |
public function __construct(callable $factory, string $class) | |
{ | |
$this->factory = $factory; | |
parent::__construct($class); | |
} | |
public function createInterceptedInstance(): object | |
{ | |
$factory = $this->factory; | |
return $factory(); | |
} | |
} | |
class Foo | |
{ | |
function doSomething() | |
{ | |
} | |
} | |
$lazyFoo = Interceptor::proxy(new LazyInterceptor(function () { return new Foo(); }, Foo::class)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Answer from Ryan: all of them, no need to chose :)