Last active
August 29, 2015 14:12
-
-
Save martinamps/779d15ff854f4a75dee0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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