Created
May 23, 2018 17:13
-
-
Save Jckf/cdeffbfc56cb3acb5f246ed8b7adddc2 to your computer and use it in GitHub Desktop.
Real method overloading in PHP
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
<?php | |
trait Overloads { | |
/** @var ReflectionMethod[] $olMethods **/ | |
protected $olMethods; | |
/** | |
* Get methods from this class. | |
* | |
* Caches results to avoid recreating reflection objects for every call. | |
* | |
* @return ReflectionMethod[] | |
*/ | |
protected function olGetMethods() { | |
if (!$this->olMethods) | |
$this->olMethods = (new ReflectionClass($this))->getMethods(); | |
return $this->olMethods; | |
} | |
/** | |
* Find a method that matches the given name and arguments. | |
* | |
* @param string $methodName Name of method to find. | |
* @param array $arguments Arguments to match against method signature. | |
* | |
* @return string|null Name of applicable method. Null if none is found. | |
*/ | |
protected function olFindMethod(string $methodName, array $arguments) { | |
foreach ($this->olGetMethods() as $method) { | |
if ($this->olIsSameMethod($methodName, $method->getName()) && $this->olIsAcceptableArguments($arguments, $method->getParameters())) | |
return $method->getName(); | |
} | |
return null; | |
} | |
/** | |
* Check if the invoked method name is concidered the same as the subject. | |
* | |
* @param string $invoked Name of the invoked method. | |
* @param string $subject Name of the method to validate. | |
* | |
* @return boolean | |
*/ | |
protected function olIsSameMethod(string $invoked, string $subject) { | |
return $invoked == preg_replace('/\d+$/', '', $subject, 1); | |
} | |
/** | |
* Validate an array of arguments against a method's accepted parameters. | |
* | |
* @param array $provided Provided arguments. | |
* @param ReflectionParameter[] $accepts Method parameters. | |
* | |
* @return boolean | |
*/ | |
protected function olIsAcceptableArguments(array $provided, array $accepts) { | |
foreach ($accepts as $i => $parameter) { | |
// Missing parameter. | |
if (!$parameter->isOptional() && !array_key_exists($i, $provided)) | |
return false; | |
// Type hinted. | |
if ($parameter->hasType() && $parameter->getType() != (is_object($provided[$i]) ? get_class($provided[$i]) : gettype($provided[$i]))) | |
return false; | |
} | |
return true; | |
} | |
public function __call(string $methodName, array $arguments) { | |
$method = $this->olFindMethod($methodName, $arguments); | |
if (is_null($method)) { | |
throw new Exception('Call to undefined method ' . get_class($this) . '::' . $methodName . '(' . implode(', ', array_map(function ($argument) { | |
return is_object($argument) ? get_class($argument) : gettype($argument); | |
}, $arguments)) . ')'); | |
} | |
return call_user_func_array([ $this, $method ], $arguments); | |
} | |
} |
Be aware that this trait exposes private and protected methods as if they were public.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example usage: