Skip to content

Commit

Permalink
Extract method calling out of the container.
Browse files Browse the repository at this point in the history
This extracts the method calling features of the container into a
separate class.
  • Loading branch information
taylorotwell committed Dec 22, 2016
1 parent 1a1969b commit 1fa8ea0
Show file tree
Hide file tree
Showing 2 changed files with 200 additions and 129 deletions.
154 changes: 25 additions & 129 deletions src/Illuminate/Container/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,17 @@ protected function getClosure($abstract, $concrete)
};
}

/**
* Determine if the container has a method binding.
*
* @param string $method
* @return bool
*/
public function hasMethodBinding($method)
{
return isset($this->methodBindings[$method]);
}

/**
* Bind a callback to resolve with Container::call.
*
Expand All @@ -245,6 +256,18 @@ public function bindMethod($method, $callback)
$this->methodBindings[$this->normalize($method)] = $callback;
}

/**
* Get the method binding for the given method.
*
* @param string $method
* @param mixed $instance
* @return mixed
*/
public function callMethodBinding($method, $instance)
{
return call_user_func($this->methodBindings[$method], $instance, $this);
}

/**
* Add a contextual binding to the container.
*
Expand Down Expand Up @@ -466,134 +489,7 @@ public function wrap(Closure $callback, array $parameters = [])
*/
public function call($callback, array $parameters = [], $defaultMethod = null)
{
if ($this->isCallableWithAtSign($callback) || $defaultMethod) {
return $this->callClass($callback, $parameters, $defaultMethod);
}

return $this->callBoundMethod($callback, function () use ($callback, $parameters) {
$dependencies = $this->getMethodDependencies($callback, $parameters);

return call_user_func_array($callback, $dependencies);
});
}

/**
* Determine if the given string is in Class@method syntax.
*
* @param mixed $callback
* @return bool
*/
protected function isCallableWithAtSign($callback)
{
return is_string($callback) && strpos($callback, '@') !== false;
}

/**
* Get all dependencies for a given method.
*
* @param callable|string $callback
* @param array $parameters
* @return array
*/
protected function getMethodDependencies($callback, array $parameters = [])
{
$dependencies = [];

foreach ($this->getCallReflector($callback)->getParameters() as $parameter) {
$this->addDependencyForCallParameter($parameter, $parameters, $dependencies);
}

return array_merge($dependencies, $parameters);
}

/**
* Get the proper reflection instance for the given callback.
*
* @param callable|string $callback
* @return \ReflectionFunctionAbstract
*/
protected function getCallReflector($callback)
{
if (is_string($callback) && strpos($callback, '::') !== false) {
$callback = explode('::', $callback);
}

if (is_array($callback)) {
return new ReflectionMethod($callback[0], $callback[1]);
}

return new ReflectionFunction($callback);
}

/**
* Get the dependency for the given call parameter.
*
* @param \ReflectionParameter $parameter
* @param array $parameters
* @param array $dependencies
* @return mixed
*/
protected function addDependencyForCallParameter(ReflectionParameter $parameter, array &$parameters, &$dependencies)
{
if (array_key_exists($parameter->name, $parameters)) {
$dependencies[] = $parameters[$parameter->name];

unset($parameters[$parameter->name]);
} elseif ($parameter->getClass()) {
$dependencies[] = $this->make($parameter->getClass()->name);
} elseif ($parameter->isDefaultValueAvailable()) {
$dependencies[] = $parameter->getDefaultValue();
}
}

/**
* Call a string reference to a class using Class@method syntax.
*
* @param string $target
* @param array $parameters
* @param string|null $defaultMethod
* @return mixed
*
* @throws \InvalidArgumentException
*/
protected function callClass($target, array $parameters = [], $defaultMethod = null)
{
$segments = explode('@', $target);

// If the listener has an @ sign, we will assume it is being used to delimit
// the class name from the handle method name. This allows for handlers
// to run multiple handler methods in a single class for convenience.
$method = count($segments) == 2 ? $segments[1] : $defaultMethod;

if (is_null($method)) {
throw new InvalidArgumentException('Method not provided.');
}

return $this->call([$this->make($segments[0]), $method], $parameters);
}

/**
* Call a method that has been bound to the container.
*
* @param callable $callback
* @param mixed $default
* @return mixed
*/
protected function callBoundMethod($callback, $default)
{
if (! is_array($callback)) {
return value($default);
}

$class = is_string($callback[0]) ? $callback[0] : get_class($callback[0]);

$method = $this->normalize("{$class}@{$callback[1]}");

if (! isset($this->methodBindings[$method])) {
return value($default);
}

return $this->methodBindings[$method]($callback[0], $this);
return MethodCaller::call($this, $callback, $parameters, $defaultMethod);
}

/**
Expand Down Expand Up @@ -701,7 +597,7 @@ protected function getContextualConcrete($abstract)
* @param mixed $service
* @return mixed
*/
protected function normalize($service)
public function normalize($service)
{
return is_string($service) ? ltrim($service, '\\') : $service;
}
Expand Down
175 changes: 175 additions & 0 deletions src/Illuminate/Container/MethodCaller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
<?php

namespace Illuminate\Container;

use LogicException;
use ReflectionClass;
use ReflectionMethod;
use ReflectionFunction;
use ReflectionParameter;
use InvalidArgumentException;

class MethodCaller
{
/**
* Call the given Closure / class@method and inject its dependencies.
*
* @param \Illuminate\Container\Container $container
* @param callable|string $callback
* @param array $parameters
* @param string|null $defaultMethod
* @return mixed
*/
public static function call($container, $callback, array $parameters = [], $defaultMethod = null)
{
if (static::isCallableWithAtSign($callback) || $defaultMethod) {
return static::callClass($container, $callback, $parameters, $defaultMethod);
}

return static::callBoundMethod($container, $callback, function () use ($container, $callback, $parameters) {
return call_user_func_array(
$callback, static::getMethodDependencies($container, $callback, $parameters)
);
});
}

/**
* Call a string reference to a class using Class@method syntax.
*
* @param \Illuminate\Container\Container $container
* @param string $target
* @param array $parameters
* @param string|null $defaultMethod
* @return mixed
*
* @throws \InvalidArgumentException
*/
protected static function callClass($container, $target, array $parameters = [], $defaultMethod = null)
{
$segments = explode('@', $target);

// We will assume an @ sign is used to delimit the class name from the method
// name. We will split on this @ sign and then build a callable array that
// we can pass right back into the "call" method for dependency binding.
$method = count($segments) == 2
? $segments[1] : $defaultMethod;

if (is_null($method)) {
throw new InvalidArgumentException('Method not provided.');
}

return static::call(
$container, [$container->make($segments[0]), $method], $parameters
);
}

/**
* Call a method that has been bound to the container.
*
* @param \Illuminate\Container\Container $container
* @param callable $callback
* @param mixed $default
* @return mixed
*/
protected static function callBoundMethod($container, $callback, $default)
{
if (! is_array($callback)) {
return value($default);
}

// Here we need to turn the array callable into a Class@method string we can use to
// examine the container and see if there are any method bindings for this given
// method. If there are, we can call this method binding callback immediately.
$method = static::normalizeMethod($container, $callback);

if ($container->hasMethodBinding($method)) {
return $container->callMethodBinding($method, $callback[0]);
}

return value($default);
}

/**
* Normalize the given callback into a Class@method string.
*
* @param \Illuminate\Container\Container $container
* @param callable $callback
* @return string
*/
protected static function normalizeMethod($container, $callback)
{
$class = is_string($callback[0]) ? $callback[0] : get_class($callback[0]);

return $container->normalize("{$class}@{$callback[1]}");
}

/**
* Get all dependencies for a given method.
*
* @param \Illuminate\Container\Container
* @param callable|string $callback
* @param array $parameters
* @return array
*/
protected static function getMethodDependencies($container, $callback, array $parameters = [])
{
$dependencies = [];

foreach (static::getCallReflector($callback)->getParameters() as $parameter) {
static::addDependencyForCallParameter($container, $parameter, $parameters, $dependencies);
}

return array_merge($dependencies, $parameters);
}

/**
* Get the proper reflection instance for the given callback.
*
* @param callable|string $callback
* @return \ReflectionFunctionAbstract
*/
protected static function getCallReflector($callback)
{
if (is_string($callback) && strpos($callback, '::') !== false) {
$callback = explode('::', $callback);
}

return is_array($callback)
? new ReflectionMethod($callback[0], $callback[1])
: new ReflectionFunction($callback);
}

/**
* Get the dependency for the given call parameter.
*
* @param \Illuminate\Container\Container $container
* @param \ReflectionParameter $parameter
* @param array $parameters
* @param array $dependencies
* @return mixed
*/
protected static function addDependencyForCallParameter($container, $parameter,
array &$parameters, &$dependencies)
{
if (array_key_exists($parameter->name, $parameters)) {
$dependencies[] = $parameters[$parameter->name];

unset($parameters[$parameter->name]);
} elseif ($parameter->getClass()) {
$dependencies[] = $container->make($parameter->getClass()->name);
} elseif ($parameter->isDefaultValueAvailable()) {
$dependencies[] = $parameter->getDefaultValue();
}
}

/**
* Determine if the given string is in Class@method syntax.
*
* @param mixed $callback
* @return bool
*/
protected static function isCallableWithAtSign($callback)
{
return is_string($callback) && strpos($callback, '@') !== false;
}
}

0 comments on commit 1fa8ea0

Please sign in to comment.