Skip to content

Instantly share code, notes, and snippets.

@SamMousa
Last active April 4, 2019 09:49
Show Gist options
  • Save SamMousa/22dfaf40b89d21748afdd238de2fd8a8 to your computer and use it in GitHub Desktop.
Save SamMousa/22dfaf40b89d21748afdd238de2fd8a8 to your computer and use it in GitHub Desktop.
Benchmarks 2 approaches of reflection
<?php
$precision = 'ms';
$inner = 50000;
$outer = 50;
class Provider
{
/**
* Derives the interface type of the first argument of a callable.
*
* @param callable $callable The callable for which we want the parameter type.
* @return string The interface the parameter is type hinted on.
*/
public function getParameterType(callable $callable): string
{
// This try-catch is only here to keep OCD linters happy about uncaught reflection exceptions.
try {
switch (true) {
// See note on isClassCallable() for why this must be the first case.
case $this->isClassCallable($callable):
$reflect = new \ReflectionClass($callable[0]);
$params = $reflect->getMethod($callable[1])->getParameters();
break;
case $this->isFunctionCallable($callable):
case $this->isClosureCallable($callable):
$reflect = new \ReflectionFunction($callable);
$params = $reflect->getParameters();
break;
case $this->isObjectCallable($callable):
$reflect = new \ReflectionObject($callable[0]);
$params = $reflect->getMethod($callable[1])->getParameters();
break;
case $this->isInvokable($callable):
$params = (new \ReflectionMethod($callable, '__invoke'))->getParameters();
break;
default:
throw new \InvalidArgumentException('Not a recognized type of callable');
}
$reflectedType = $params[0]->getType();
if ($reflectedType === null) {
throw new \InvalidArgumentException('Listeners must declare an object type they can accept.');
}
$type = $reflectedType->getName();
} catch (\ReflectionException $e) {
throw new \RuntimeException('Type error registering listener.', 0, $e);
}
return $type;
}
/**
* Determines if a callable represents a function.
*
* Or at least a reasonable approximation, since a function name may not be defined yet.
*
* @param callable $callable
* @return True if the callable represents a function, false otherwise.
*/
private function isFunctionCallable(callable $callable): bool
{
// We can't check for function_exists() because it may be included later by the time it matters.
return is_string($callable);
}
/**
* Determines if a callable represents a closure/anonymous function.
*
* @param callable $callable
* @return True if the callable represents a closure object, false otherwise.
*/
private function isClosureCallable(callable $callable): bool
{
return $callable instanceof \Closure;
}
/**
* @param callable $callable
* @return True if the callable represents an invokable object, false otherwise.
*/
private function isInvokable(callable $callable): bool
{
return is_object($callable);
}
/**
* Determines if a callable represents a method on an object.
*
* @param callable $callable
* @return True if the callable represents a method object, false otherwise.
*/
private function isObjectCallable(callable $callable): bool
{
return is_array($callable) && is_object($callable[0]);
}
/**
* Determines if a callable represents a static class method.
*
* The parameter here is untyped so that this method may be called with an
* array that represents a class name and a non-static method. The routine
* to determine the parameter type is identical to a static method, but such
* an array is still not technically callable. Omitting the parameter type here
* allows us to use this method to handle both cases.
*
* Note that this method must therefore be the first in the switch statement
* above, or else subsequent calls will break as the array is not going to satisfy
* the callable type hint but it would pass `is_callable()`. Because PHP.
*
* @param callable $callable
* @return True if the callable represents a static method, false otherwise.
*/
private function isClassCallable($callable): bool
{
return is_array($callable) && is_string($callable[0]) && class_exists($callable[0]);
}
}
class A {
}
class B {
public function __invoke(A $a)
{
}
public function test(A $a)
{
}
public static function testStatic(A $A)
{
}
}
function global_function(A $A)
{
}
$closure = function(A $a) {
return $a;
};
$targets = [
'closure' => $closure,
'invokable' => new B,
'callable' => [new B, 'test'],
'staticCallable' => [B::class, 'testStatic'],
'global function' => 'global_function'
];
$reflectors = [];
$provider = new Provider();
$reflectors['autodetect'] = function(callable $callable) use ($provider): string {
return $provider->getParameterType($callable);
};
$reflectors['fromCallable'] = function(callable $callable): string {
$closure = Closure::fromCallable($callable);
$info = new ReflectionFunction($closure);
return $info->getParameters()[0]->getType()->getName();
};
switch($precision) {
case 'ms':
$multiplier = 1000;
break;
case 'ns':
$multiplier = 1000000;
break;
default:
die("Unknown precision: $precision\n");
}
foreach($reflectors as $key => $reflector) {
foreach($targets as $target => $callable) {
for ($o = 0; $o < $outer; $o++) {
echo '.';
$names = [];
$start = microtime(true);
for ($i = 0; $i < $inner; $i++) {
$reflector($callable);
unset($info);
}
$times[] = $multiplier * (microtime(true) - $start);
}
echo "\n";
echo "Reflector: $key\n";
echo "Target: $target\n";
echo "Executed $outer runs of $inner reps.\n";
echo "Average: " . number_format(array_sum($times) / count($times), 0) . "ms\n";
echo "Max: " . number_format(max($times), 0) . "ms\n";
echo "Min: " . number_format(min($times), 0) . "ms\n";
}
}
..................................................
Reflector: autodetect
Target: closure
Executed 50 runs of 50000 reps.
Average: 42ms
Max: 44ms
Min: 41ms
..................................................
Reflector: autodetect
Target: invokable
Executed 50 runs of 50000 reps.
Average: 49ms
Max: 60ms
Min: 41ms
..................................................
Reflector: autodetect
Target: callable
Executed 50 runs of 50000 reps.
Average: 56ms
Max: 72ms
Min: 41ms
..................................................
Reflector: autodetect
Target: staticCallable
Executed 50 runs of 50000 reps.
Average: 61ms
Max: 77ms
Min: 41ms
..................................................
Reflector: autodetect
Target: global function
Executed 50 runs of 50000 reps.
Average: 57ms
Max: 77ms
Min: 41ms
..................................................
Reflector: fromCallable
Target: closure
Executed 50 runs of 50000 reps.
Average: 53ms
Max: 77ms
Min: 30ms
..................................................
Reflector: fromCallable
Target: invokable
Executed 50 runs of 50000 reps.
Average: 51ms
Max: 77ms
Min: 30ms
..................................................
Reflector: fromCallable
Target: callable
Executed 50 runs of 50000 reps.
Average: 49ms
Max: 77ms
Min: 30ms
..................................................
Reflector: fromCallable
Target: staticCallable
Executed 50 runs of 50000 reps.
Average: 49ms
Max: 77ms
Min: 30ms
..................................................
Reflector: fromCallable
Target: global function
Executed 50 runs of 50000 reps.
Average: 48ms
Max: 77ms
Min: 30ms
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment