Skip to content

Instantly share code, notes, and snippets.

@cereal-s
Forked from eckenroed/Overloader.php
Last active September 30, 2017 17:01
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 cereal-s/bc9fe985002b7500a2da09f83022bb95 to your computer and use it in GitHub Desktop.
Save cereal-s/bc9fe985002b7500a2da09f83022bb95 to your computer and use it in GitHub Desktop.
<?php
/**
* Trait helps to allow for some limited overloading of
* methods in the class this is attached to.
*/
trait Overloader {
/**
* Where the magic happens
* @param string $name The name of the function that was called
* @param array $args An array containing arguments
*/
public function __call($method, $parameters)
{
// We'll need some information about our class and methods
// before we can route this call (if it is routable)
$reflection = new \ReflectionClass(get_class($this));
// Grab the possible matches
$matches = $this->__filterMethodsForMatches($method, $reflection->getMethods());
// If matches are found, let's evaulate them to see if they can be called.
if( count($matches) > 0 ) :
// We'll take a look at the parameters and then get the datatypes
// for each one.
$parameterTypes = $this->__resolveparameterTypes($parameters);
/**
* In order for there to be a match a few conditions must be met.
* 1) The first part of the function name must match exactly
* 2) The number of paramters passed must be in the same order
* as the definition and match the type.
*/
foreach($matches as $candidate ) :
// Get information about the candidate
$info = $this->__candidateMatch($reflection->getMethod($candidate), $parameterTypes);
if( $info['provided_parameters'] == $info['matched_parameter_types'] && $info['primary_parameter_matches']) :
return call_user_func_array([$this, $candidate], $parameters);
endif;
endforeach;
endif;
/**
* First, if this method is being called by
* the parent, let's make sure we get it out of the way
* first.
*/
if( is_callable(['parent', '__call']) ) :
parent::__call($method, $parameters);
endif;
}
/**
* Filter through all the methods in the class and look for any that will be possible matches.
* @param string $methodName The name of the method that was called
* @param array $classMethods The PHP Reflection list of methods
* @return array Possible Matches
*/
private function __filterMethodsForMatches($methodName, $classMethods)
{
// We'll return this at the end if we find any matches
$possibleMatches = [];
// Cycle through each method found and see if it
// matches the pattern we are looking for
foreach($classMethods as $classMethod ) :
if(strstr($classMethod->name, $methodName)) {
array_push($possibleMatches, $classMethod->name);
}
endforeach;
return $possibleMatches;
}
/**
* Creates an array matching the order of the parameters provided
* but instead of the datatype of the parameter.
*
* @param array $parameters [description]
* @return array
*/
private function __resolveparameterTypes($parameters = [])
{
if( empty($parameters) ) return [];
$resolved = [];
foreach( $parameters as $parameter ) :
switch(gettype($parameter)) {
case 'double':
$resolved[] = 'float'; // gettype() returns 'double' instead of float
break;
case 'object':
$reflector = new \ReflectionClass($parameter);
$resolved[] = $reflector->getName();
break;
// boolean is an alias for bool
// gettype returns the alias which make fail the signature check
case 'boolean':
$resolved[] = 'bool';
break;
default:
$resolved[] = gettype($parameter);
}
endforeach;
return $resolved;
}
/**
* Checks to see if a given candidate matches based on the information provided
* @param ReflectionMethod $candidate The candidate method
* @param array $paramaterTypes [description]
* @return [array] [description]
*/
private function __candidateMatch($candidate, $parameterTypes = [])
{
$has_params = count($candidate->getParameters());
$info = [
'defined_parameters' => $has_params,
'provided_parameters' => count($parameterTypes),
'matched_parameter_types' => 0,
'primary_parameter_matches' => false,
];
if(0 < $has_params)
{
// We'll spin through the candidates paramter requirements and see if they match the appropriate types
foreach ($candidate->getParameters() as $index => $parameter)
{
// Some methods may have default values attached at the end
if( ! array_key_exists($index, $parameterTypes))
break;
if( ! is_null($parameter->getType()) && $parameter->getType() == $parameterTypes[$index])
{
$info['matched_parameter_types']++;
if( $index == 0 )
$info['primary_parameter_matches'] = true;
}
}
}
else
$info['primary_parameter_matches'] = true;
return $info;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment