Skip to content

Instantly share code, notes, and snippets.

@johnkary
Last active November 18, 2022 23:58
Show Gist options
  • Save johnkary/904de9171396909c1842 to your computer and use it in GitHub Desktop.
Save johnkary/904de9171396909c1842 to your computer and use it in GitHub Desktop.
Exposing protected and private object methods using \Closure::bindTo(). Code below requires PHP 5.6+ due to argument unpacking (...$args). Could probably be written for PHP 5.4+ but didn't want to reinvent argument unpacking, else could also just use the Reflection implementation.
<?php
class User {
private $firstname;
private $lastname;
public function __construct($f, $l) {
$this->firstname = $f;
$this->lastname = $l;
}
private function buildFullName() {
return sprintf('%s %s', $this->firstname, $this->lastname);
}
private function greeting($say) {
return sprintf('%s, %s', $say, $this->buildFullName());
}
private function greetingToUser($say, User $user) {
return sprintf('%s from %s', $this->greeting($say), $user->buildFullName());
}
}
/**
* Exposes result of a protected/private method call outside the
* object's scope.
*
* @param object $object Object on which to call method
* @param string $method Method to call
* @param array $args Arguments to be unpacked and passed to Method
* @return mixed Result of invoking Method on Object given Arguments
*/
function xray($object, $methodToExpose, array $args = []) {
$fn = function () use ($methodToExpose, $args) {
return $this->$methodToExpose(...$args);
};
$exposed = $fn->bindTo($object, $object);
return $exposed();
};
// The traditional way
function xray_reflection($object, $methodToExpose, array $args = []) {
$m = new \ReflectionMethod($object, $methodToExpose);
$m->setAccessible(true);
return $m->invoke($object, ...$args);
};
$john = new User('John', 'Kary');
$chris = new User('Chris', 'Escalante');
echo implode("\n", [
xray($john, 'greeting', ['Hello']),
xray_reflection($chris, 'greetingToUser', ['Hello', $john]),
]);
// Output:
// Hello, John Kary
// Hello, Chris Escalante from John Kary
@docteurklein
Copy link

replace:

return $this->$methodToExpose(...$args);

with:

return call_user_func_array([$this, $methodToExpose], $args);

and you have it.

@mnakalay
Copy link

Sorry, I'm late to the party but this actually helped me solve a problem that Reflection didn't.

I have a class extending another and I needed to call a protected method that belongs to the parent's class using the child's object. Reflection only allows you to call methods using the object the method was declared in so it was not working.

Your method works also with the child object which solved my problem.

@zanbaldwin
Copy link

Thanks for this!
I ran into the problem of the (new ReflectionMethod($object, 'method'))->invoke(...) method not respecting declare(strict_types=1) whereas this Closure method does 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment