Skip to content

Instantly share code, notes, and snippets.

@martinamps
Last active August 29, 2015 14:12
Show Gist options
  • Save martinamps/779d15ff854f4a75dee0 to your computer and use it in GitHub Desktop.
Save martinamps/779d15ff854f4a75dee0 to your computer and use it in GitHub Desktop.
class Test {
use Overload;
public function __construct() {
self::add('test', function($a, $b, $c = false) {
echo '2-3 args: a => ', $a, ' b => ', $b, ' c => ', $c, PHP_EOL;
});
self::add('test', function() {
echo 'no args', PHP_EOL;
});
self::add('test', function(Numeric $i) {
echo 'numeric: ', $i, PHP_EOL;
});
self::add('test', function(StdClass $test) {
echo 'object: ', print_r($test, true);
});
self::add('test', function(Array $arr) {
echo 'one array: ', print_r($arr, true);
});
self::add('test', function(String $str) {
echo 'string: ', $str, PHP_EOL;
});
self::add('test', function(String $str, Numeric $num) {
echo 'string: ', $str, ' numeric: ', $num, PHP_EOL;
});
self::add('test', [$this, 'one_arg']);
}
public function one_arg($arg) {
echo 'one arg: ', print_r($arg, true);
}
}
$t = new Test();
$t->test(1);
$t->test([1]);
$t->test((object)[1]);
$t->test('str');
$t->test('str', 42);
$t->test(1, 2);
$t->test(1, 2, 3);
$t->test(1);
$t->test('str');
/*
numeric: 1
one array: Array
(
[0] => 1
)
object: stdClass Object
(
[0] => 1
)
string: str
2-3 args: a => str b => 42 c =>
2-3 args: a => 1 b => 2 c =>
2-3 args: a => 1 b => 2 c => 3
numeric: 1
string: str
*/
interface OverloadHint {
public function isValid($val);
}
class Numeric implements \OverloadHint {
public function isValid($val) {
return is_numeric($val);
}
}
class Scalar implements \OverloadHint {
public function isValid($val) {
return is_scalar($val);
}
}
class String implements \OverloadHint {
public function isValid($val) {
return is_string($val);
}
}
trait Overload {
protected $funcs = [];
public function __call($func, $args) {
if (!isset($this->funcs[$func])) {
throw new \Exception(sprintf('Function doesn\'t exist %s:%s', __CLASS__, $func));
}
foreach ($this->funcs[$func] as $f => $fn) {
if (!in_array(count($args), range($fn['required'], $fn['possible']))) {
continue;
}
foreach ($args as $idx => $arg) {
switch (1) {
case $fn['types'][$idx] == 'array' && !is_array($arg):
continue 3;
case $fn['types'][$idx] == 'callable' && !is_callable($arg):
continue 3;
case $fn['types'][$idx] == 'notnull' && is_null($arg):
continue 3;
case $fn['types'][$idx] == 'object' && !is_object($arg):
continue 3;
case $fn['types'][$idx] instanceof \OverloadHint && !$fn['types'][$idx]->isValid($arg):
continue 3;
}
}
return $fn['callable'](...$args);
}
throw new \Exception('No compatible function signature found');
}
public function add($name, $fn) {
if (!isset($this->funcs[$name])) {
$this->func[$name] = [];
}
if (is_array($fn)) {
$ref = new \ReflectionMethod(...$fn);
} elseif (is_object($fn) && !$fn instanceof \Closure) {
$ref = (new ReflectionObject($callable))->getMethod('__invoke');
} else {
$ref = new \ReflectionFunction($fn);
}
$required = $ref->getNumberOfParameters();
$possible = $ref->getNumberOfRequiredParameters();
$params = $ref->getParameters();
$types = [];
$code = '';
foreach ($params as $idx => $param) {
$t = NULL;
if ($param->isArray()) {
$t = 'array';
} elseif ($param->isCallable()) {
$t = 'callable';
} elseif ($param->allowsNull()) {
$t = 'notnull';
} elseif ($class = $param->getClass()) {
if ($class->getName() == 'stdClass') {
$t = 'object';
} else {
$t = $class->newInstance();
if (!$t instanceof \OverloadHint) {
$t = NULL;
}
$code = self::getCodeWithoutHint($ref, $idx, $code);
eval('$fn = ' . $code . ';');
}
}
$types[] = $t;
}
$this->funcs[$name][] = [
'callable' => $fn,
'required' => $required,
'possible' => $possible,
'types' => $types,
];
}
private function getCodeWithoutHint($ref, $idx, $source = '') {
if (!strlen($source)) {
$source = file($ref->getFileName());
$source = implode('', array_slice($source, $ref->getStartLine() - 1, $ref->getEndLine() - $ref->getStartLine() + 1 ));
}
preg_match_all('/function\s*\((.*)}/ims', $source, $matches);
$source = explode("\n", $matches[0][0]);
$params = mb_substr(explode('(', $source[0])[1], 0, - 3);
$split = array_map('trim', explode(',', $params));
if ($idx > count($split) + 1) {
throw new \LogicException('invalid offset');
}
$split[$idx] = explode(' ', $split[$idx])[1];
$source[0] = str_replace($params, implode(',', $split), $source[0]);
$source = implode("\n", $source);
$diff = mb_substr_count($source, '}') - mb_substr_count($source, '{');
if ($diff) {
$equal = array_slice(explode('}', $source), 0, -$diff);
$source = implode('}', $explode);
}
return $source;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment