Skip to content

Instantly share code, notes, and snippets.

@ezzatron
Created May 9, 2016 05:59
Show Gist options
  • Save ezzatron/ef3007e7e99c1f635e0f67d14a15ccc6 to your computer and use it in GitHub Desktop.
Save ezzatron/ef3007e7e99c1f635e0f67d14a15ccc6 to your computer and use it in GitHub Desktop.
PHP type-checking proxy
<?php
class TypeCheckingProxy
{
public function __construct($object)
{
$this->object = $object;
$this->class = get_class($object);
$reflector = new ReflectionObject($object);
$this->methods = [];
foreach ($reflector->getMethods() as $method) {
if ($method->isStatic() || !$method->isPublic()) {
continue;
}
$this->methods[$method->getName()] = $method->getParameters();
}
}
public function __call($name, array $arguments)
{
if (isset($this->methods[$name])) {
try {
return $this->object->$name(...$arguments);
} catch (TypeError $e) {
foreach ($this->methods[$name] as $i => $parameter) {
$argumentExists = array_key_exists($i, $arguments);
if ($type = $parameter->getType()) {
$expectedType = strval($type);
} else {
$expectedType = null;
}
if (!$argumentExists && !$parameter->isOptional()) {
if ($type) {
throw new InvalidArgumentException(
sprintf(
'Missing argument of type %s for %s.',
$expectedType,
var_export($parameter->getName(), true)
)
);
}
throw new InvalidArgumentException(
sprintf(
'Missing argument for %s.',
var_export($parameter->getName(), true)
)
);
}
if (!$type) {
continue;
}
switch ($expectedType) {
case 'array':
case 'callable':
case 'float':
case 'string':
break;
case 'bool':
$expectedType = 'boolean';
break;
case 'int':
$expectedType = 'integer';
break;
default:
$expectedType = 'object';
}
if ($argumentExists) {
$actualType = gettype($arguments[$i]);
switch ($actualType) {
case 'double':
$actualType = 'float';
break;
case 'NULL':
$actualType = 'null';
break;
}
} else {
$actualType = '<none>';
}
if ($actualType !== $expectedType) {
throw new InvalidArgumentException(
sprintf(
'Expected argument of type %s for %s, ' .
'but received %s.',
$expectedType,
var_export($parameter->getName(), true),
$actualType
)
);
}
}
}
}
throw new BadMethodCallException(
sprintf(
'Call to undefined method %s::%s().',
$this->class,
$name
)
);
}
private $object;
private $class;
private $methods;
}
class Api
{
public function foo(string $a, int $b)
{
return __METHOD__ . ' ' . implode(', ', func_get_args());
}
}
$o = new TypeCheckingProxy(new Api());
try {
$o->foo('a', 'b');
} catch (Throwable $e) {
printf("Caught: %s\n", $e->getMessage());
}
try {
$o->foo('a');
} catch (Throwable $e) {
printf("Caught: %s\n", $e->getMessage());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment