Skip to content

Instantly share code, notes, and snippets.

@goodevilgenius
Last active September 2, 2021 21:27
Show Gist options
  • Select an option

  • Save goodevilgenius/c34b5027dc5d6fe6677d81b32bdd397d to your computer and use it in GitHub Desktop.

Select an option

Save goodevilgenius/c34b5027dc5d6fe6677d81b32bdd397d to your computer and use it in GitHub Desktop.

Revisions

  1. goodevilgenius revised this gist Sep 2, 2021. 1 changed file with 12 additions and 15 deletions.
    27 changes: 12 additions & 15 deletions spy.php
    Original file line number Diff line number Diff line change
    @@ -49,41 +49,38 @@ public function __construct($classOrObject)
    [null, $classOrObject] :
    [null, null]
    );
    $this->initCaller();
    $this->initGetter();
    $this->initSetter();
    }

    /**
    * Initialize the closure used by $this->__call.
    * Get the closure used by $this->__call.
    */
    protected function initCaller(): void
    protected function getCaller(): \Closure
    {
    $this->caller = (
    return $this->caller ??= (
    $this->object ?
    fn (string $method, array $args) => $this->$method(...$args) :
    fn (string $method, array $args) => static::$method($args)
    )->bindTo($this->object, $this->class);
    }

    /**
    * Initialize the closure used by $this->__get.
    * Get the closure used by $this->__get.
    */
    protected function initGetter(): void
    protected function getGetter(): \Closure
    {
    $this->getter = (
    return $this->getter ??= (
    $this->object ?
    fn (string $key) => $this->$key :
    fn (string $key) => static::$$key
    )->bindTo($this->object, $this->class);
    }

    /**
    * Initialize the closure used by $this->__set.
    * Get the closure used by $this->__set.
    */
    protected function initSetter(): void
    protected function getSetter(): \Closure
    {
    $this->setter = (
    return $this->setter ??= (
    $this->object ?
    fn (string $key, $value) => $this->$key = $value :
    fn (string $key, $value) => static::$$key = $value
    @@ -100,17 +97,17 @@ public function call(callable $cb, ...$args)

    public function __call(string $method, array $args)
    {
    return ($this->caller)($method, $args);
    return ($this->getCaller())($method, $args);
    }

    public function __get(string $key)
    {
    return ($this->getter)($key);
    return ($this->getGetter())($key);
    }

    public function __set(string $key, $value)
    {
    return ($this->setter)($key, $value);
    return ($this->getSetter())($key, $value);
    }
    }

  2. goodevilgenius revised this gist Sep 2, 2021. 1 changed file with 49 additions and 0 deletions.
    49 changes: 49 additions & 0 deletions spy.php
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,34 @@
    <?php

    /**
    * Method for accessing private/protected properties/methods without Reflection.
    *
    * This is particularly useful when debugging from the command-line, for example, with psysh.
    *
    * Static methods/properties can be accessed by passing the class instead.
    * Constants are not acccessible.
    *
    * Usage:
    * $object;
    * spy($object)->prop; // Return private prop on $object
    *
    * // These two lines are equivalent
    * $objSpy = spy($object);
    * $objSpy = new ClassProxy($object);
    *
    * $objSpy->protectedMethod($param1);
    * $objSpy->call(fn () => strtolower($this->prop)); // Returns lowercased version of private prop on $object
    *
    * spy(SomeClass::class)->staticProjectedProperty;
    */

    declare(strict_types=1);

    /**
    * Proxies all calls to another object/class, via Closures.
    *
    * This allows access to private/protected methods and properties without Reflection.
    */
    class ClassProxy
    {
    protected ?string $class;
    @@ -10,6 +37,9 @@ class ClassProxy
    protected Closure $getter;
    protected Closure $setter;

    /**
    * @param string|object $classOrObject
    */
    public function __construct($classOrObject)
    {
    [$this->object, $this->class] = is_object($classOrObject) ?
    @@ -24,6 +54,9 @@ public function __construct($classOrObject)
    $this->initSetter();
    }

    /**
    * Initialize the closure used by $this->__call.
    */
    protected function initCaller(): void
    {
    $this->caller = (
    @@ -33,6 +66,9 @@ protected function initCaller(): void
    )->bindTo($this->object, $this->class);
    }

    /**
    * Initialize the closure used by $this->__get.
    */
    protected function initGetter(): void
    {
    $this->getter = (
    @@ -42,6 +78,9 @@ protected function initGetter(): void
    )->bindTo($this->object, $this->class);
    }

    /**
    * Initialize the closure used by $this->__set.
    */
    protected function initSetter(): void
    {
    $this->setter = (
    @@ -51,6 +90,9 @@ protected function initSetter(): void
    )->bindTo($this->object, $this->class);
    }

    /**
    * Run arbitry code on object, with access to private/protected props/methods.
    */
    public function call(callable $cb, ...$args)
    {
    return (\Closure::fromCallable($cb)->bindTo($this->object, $this->class))(...$args);
    @@ -72,6 +114,13 @@ public function __set(string $key, $value)
    }
    }

    /**
    * Instantiates a ClassProxy.
    *
    * Useful for quick spying.
    *
    * @param string|object $classOrObject
    */
    function spy($classOrObject): ClassProxy
    {
    return new ClassProxy($classOrObject);
  3. goodevilgenius created this gist Sep 2, 2021.
    78 changes: 78 additions & 0 deletions spy.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,78 @@
    <?php

    declare(strict_types=1);

    class ClassProxy
    {
    protected ?string $class;
    protected ?object $object;
    protected Closure $caller;
    protected Closure $getter;
    protected Closure $setter;

    public function __construct($classOrObject)
    {
    [$this->object, $this->class] = is_object($classOrObject) ?
    [$classOrObject, get_class($classOrObject)] :
    (
    is_string($classOrObject) && class_exists($classOrObject) ?
    [null, $classOrObject] :
    [null, null]
    );
    $this->initCaller();
    $this->initGetter();
    $this->initSetter();
    }

    protected function initCaller(): void
    {
    $this->caller = (
    $this->object ?
    fn (string $method, array $args) => $this->$method(...$args) :
    fn (string $method, array $args) => static::$method($args)
    )->bindTo($this->object, $this->class);
    }

    protected function initGetter(): void
    {
    $this->getter = (
    $this->object ?
    fn (string $key) => $this->$key :
    fn (string $key) => static::$$key
    )->bindTo($this->object, $this->class);
    }

    protected function initSetter(): void
    {
    $this->setter = (
    $this->object ?
    fn (string $key, $value) => $this->$key = $value :
    fn (string $key, $value) => static::$$key = $value
    )->bindTo($this->object, $this->class);
    }

    public function call(callable $cb, ...$args)
    {
    return (\Closure::fromCallable($cb)->bindTo($this->object, $this->class))(...$args);
    }

    public function __call(string $method, array $args)
    {
    return ($this->caller)($method, $args);
    }

    public function __get(string $key)
    {
    return ($this->getter)($key);
    }

    public function __set(string $key, $value)
    {
    return ($this->setter)($key, $value);
    }
    }

    function spy($classOrObject): ClassProxy
    {
    return new ClassProxy($classOrObject);
    }