Skip to content

Instantly share code, notes, and snippets.

@Jckf
Created May 23, 2018 17:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Jckf/cdeffbfc56cb3acb5f246ed8b7adddc2 to your computer and use it in GitHub Desktop.
Save Jckf/cdeffbfc56cb3acb5f246ed8b7adddc2 to your computer and use it in GitHub Desktop.
Real method overloading in PHP
<?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);
}
}
@Jckf
Copy link
Author

Jckf commented May 23, 2018

Example usage:

class MyClass {
    use Overloads;

    public function setActive1(User $user) {
        $user->active = true;
    }

    public function setActive2(int $userId) {
        User::find($userId)->active = true;
    }
}

$myObject = new MyClass();

// You can pass either a user object, or a user ID.
$myObject->setActive(User::find(1));
$myObject->setActive(1);

@Jckf
Copy link
Author

Jckf commented May 23, 2018

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