Last active
August 29, 2015 14:10
-
-
Save sagebind/dce90f77a4c8d190fe26 to your computer and use it in GitHub Desktop.
Delegate
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 | |
/* | |
* Copyright 2014 Stephen Coakley <[email protected]> | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); you may not | |
* use this file except in compliance with the License. You may obtain a copy | |
* of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
* License for the specific language governing permissions and limitations | |
* under the License. | |
*/ | |
namespace Gyre; | |
/** | |
* Represents a delegate, which holds a reference to a static class method, | |
* instance method, function or closure for dynamic invocation. | |
*/ | |
final class Delegate | |
{ | |
/** | |
* A closure that invokes the method or function being delegated. | |
* @type \Closure | |
*/ | |
private $closure = null; | |
/** | |
* An array of parameters closed over the delegate. | |
* @type mixed[] | |
*/ | |
private $closedArgs = []; | |
/** | |
* The object context that the delegated method or function is called in. | |
* @type object | |
*/ | |
private $target = null; | |
/** | |
* The name of the class of the delegated method or function if it is a class method. | |
* @type string | |
*/ | |
private $className = null; | |
/** | |
* The name of the method or function being delegated if not anonymous. | |
* @type string | |
*/ | |
private $methodName = null; | |
/** | |
* Creates a new delegate for a static class method or instance method. | |
* | |
* @param string|object $object A class name or a class instance. | |
* @param string $methodName The name of the method to delegate. | |
* @return Delegate A new delegate that refers to the given class method. | |
* | |
* @throws \InvalidArgumentException Thrown if a bad class or method name is passed. | |
* @throws \BadMethodCallException Thrown if the given class or method doesn't exist. | |
*/ | |
public static function forMethod($object, $methodName) | |
{ | |
$delegate = new static(); | |
// get class name and target object if an instance method | |
if (is_string($object)) { | |
$delegate->className = $object; | |
} elseif (is_object($object)) { | |
$delegate->target = $object; | |
$delegate->className = get_class($object); | |
} else { | |
throw new \InvalidArgumentException('First argument must be a class name or object instance.'); | |
} | |
// get method name | |
if (is_string($methodName)) { | |
$delegate->methodName = $methodName; | |
} else { | |
throw new \InvalidArgumentException('Second argument must be a method name.'); | |
} | |
// verify the given class and method exist | |
if (!is_callable([$object, $methodName])) { | |
throw new \BadMethodCallException('The given class or method does not exist.'); | |
} | |
// create a closure around the method call bound to the class to enable | |
// protected and private access and disallow access to this class | |
$delegate->closure = $delegate->createNonStaticWrapperClosure( | |
[$object, $methodName], | |
$delegate->target, | |
$delegate->className | |
); | |
return $delegate; | |
} | |
/** | |
* Creates a new delegate for a named user function. | |
* | |
* @param string $functionName The name of the function to be delegated. | |
* @return Delegate A new delegate that refers to the given function. | |
* | |
* @throws \InvalidArgumentException Thrown if a non-string is given for the function name. | |
* @throws \BadFunctionCallException Thrown if the given function isn't defined. | |
*/ | |
public static function forFunction($functionName) | |
{ | |
$delegate = new static(); | |
if (!is_string($functionName)) { | |
throw new \InvalidArgumentException('Function name must be a string.'); | |
} | |
// verify the function exists | |
if (!function_exists($functionName)) { | |
throw new \BadFunctionCallException('The given function is not defined.'); | |
} | |
$delegate->methodName = $functionName; | |
$delegate->closure = $delegate->createNonStaticWrapperClosure($functionName, null, 'static'); | |
return $delegate; | |
} | |
/** | |
* Creates a new delegate for a given closure. | |
* | |
* @param \Closure $closure A closure to be delegated. | |
* @return Delegate A new delegate that refers to the given closure. | |
*/ | |
public static function forClosure(\Closure $closure) | |
{ | |
$delegate = new static(); | |
$delegate->closure = $closure; | |
return $delegate; | |
} | |
/** | |
* Checks if the delegated function is a class instance method. | |
* | |
* @return boolean True if the delegated function is a class instance | |
* method, otherwise false. | |
*/ | |
public function isInstanceMethod() | |
{ | |
return $this->target != null; | |
} | |
/** | |
* Checks if the delegated function is a static class method. | |
* | |
* @return boolean True if the delegated function is a static class method, | |
* otherwise false. | |
*/ | |
public function isStaticMethod() | |
{ | |
return $this->target == null && $this->className != null; | |
} | |
/** | |
* Checks if the delegated function is a non-class, named function. | |
* | |
* @return boolean True if the delegated function is a non-class, named | |
* function, otherwise false. | |
*/ | |
public function isFunction() | |
{ | |
return $this->className == null && $this->methodName != null; | |
} | |
/** | |
* Checks if the delegated function is a closure. | |
* | |
* @return boolean True if the delegated function is a closure, otherwise false1. | |
*/ | |
public function isClosure() | |
{ | |
return $this->methodName == null; | |
} | |
/** | |
* Gets the object context the delegated method is invoked in. | |
* | |
* @return object The object context the delegated method is invoked in. | |
*/ | |
public function getTargetObject() | |
{ | |
return $this->target; | |
} | |
/** | |
* Gets the name of the class the delegated method belongs to. | |
* | |
* @return string The class name the delegated method belongs to. | |
*/ | |
public function getClassName() | |
{ | |
return $this->className; | |
} | |
/** | |
* Gets the name of the delegated method or function if exists. | |
* | |
* @return string The name of the delegated method or function. | |
*/ | |
public function getMethodName() | |
{ | |
return $this->methodName; | |
} | |
/** | |
* Gets a closure for the delegated method or function. | |
* | |
* @return \Closure | |
*/ | |
public function getClosure() | |
{ | |
return $this->closure; | |
} | |
/** | |
* Gets arguments that are closed over the delegate. | |
* | |
* Closed arguments are arguments that are defined beforehand to be passed | |
* to the delegated method or function at invoke time. | |
* | |
* @return array | |
*/ | |
public function getArgs() | |
{ | |
return $this->closedArgs; | |
} | |
/** | |
* Adds closed arguments to the delegate. | |
* | |
* @param mixed $param1,... Arguments to close over the delegate invocation. | |
* @return Delegate | |
*/ | |
public function addArgs() | |
{ | |
$this->closedArgs = array_merge($this->closedArgs, func_get_args()); | |
return $this; | |
} | |
/** | |
* Removes any closed arguments from the delegate. | |
*/ | |
public function clearArgs() | |
{ | |
$this->closedArgs = []; | |
} | |
/** | |
* Gets a reflection object for the delegated method or function. | |
* | |
* @return \ReflectionFunctionAbstract | |
*/ | |
public function getReflection() | |
{ | |
if ($this->className != null) { | |
return new \ReflectionMethod($this->className, $this->methodName); | |
} elseif ($this->methodName != null) { | |
return new \ReflectionFunction($this->methodName); | |
} else { | |
return new \ReflectionFunction($this->closure); | |
} | |
} | |
/** | |
* Invokes the delegated method or function. Any arguments passed will be | |
* passed to the delegated method or function. | |
* | |
* @param mixed $param1,... Arguments to pass as parameters to the delegated method or function. | |
* @return mixed The return value of the delegated method or function. | |
*/ | |
public function invoke() | |
{ | |
return $this->invokeArgs(func_get_args()); | |
} | |
/** | |
* Invokes the delegated method or function with an array of parameters. | |
* | |
* @param array $args An array of arguments to pass to the delegated method or function as parameters. | |
* @return mixed The return value of the delegated method or function. | |
*/ | |
public function invokeArgs($args) | |
{ | |
// shift closed args to the beginning | |
$args = array_merge($this->closedArgs, $args); | |
// invoke the closure and return the result | |
return call_user_func_array($this->closure, $args); | |
} | |
/** | |
* Implements callable magic for the delegate that invokes the delegated | |
* method or function. | |
* | |
* @param mixed $param1,... Arguments to pass as parameters to the delegated method or function. | |
* @return mixed The return value of the delegated method or function. | |
*/ | |
public function __invoke() | |
{ | |
return $this->invokeArgs(func_get_args()); | |
} | |
/** | |
* Creates a new non-static closure that wraps around the invocation of a | |
* callable with a given binding and class scope. | |
* | |
* A work-around for a behavior that apparently isn't a bug. See | |
* {@see https://bugs.php.net/bug.php?id=64761} for more information. | |
* | |
* @param mixed $callable The callable that the closure should invoke. | |
* @param object $target The object to bind the closure to, or NULL for the closure to be unbound. | |
* @param mixed $scope The class scope the closure is to be associated, or 'static' to keep the current one. | |
* @return \Closure | |
* | |
* @see \Closure::bind() | |
*/ | |
private function createNonStaticWrapperClosure($callable, $target, $scope = 'static') | |
{ | |
return \Closure::bind(function() use ($callable) { | |
return call_user_func_array($callable, func_get_args()); | |
}, $target, $scope); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment