BoundMethod.php 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. <?php
  2. namespace Illuminate\Container;
  3. use Closure;
  4. use ReflectionMethod;
  5. use ReflectionFunction;
  6. use InvalidArgumentException;
  7. class BoundMethod
  8. {
  9. /**
  10. * Call the given Closure / class@method and inject its dependencies.
  11. *
  12. * @param \Illuminate\Container\Container $container
  13. * @param callable|string $callback
  14. * @param array $parameters
  15. * @param string|null $defaultMethod
  16. * @return mixed
  17. */
  18. public static function call($container, $callback, array $parameters = [], $defaultMethod = null)
  19. {
  20. if (static::isCallableWithAtSign($callback) || $defaultMethod) {
  21. return static::callClass($container, $callback, $parameters, $defaultMethod);
  22. }
  23. return static::callBoundMethod($container, $callback, function () use ($container, $callback, $parameters) {
  24. return call_user_func_array(
  25. $callback, static::getMethodDependencies($container, $callback, $parameters)
  26. );
  27. });
  28. }
  29. /**
  30. * Call a string reference to a class using Class@method syntax.
  31. *
  32. * @param \Illuminate\Container\Container $container
  33. * @param string $target
  34. * @param array $parameters
  35. * @param string|null $defaultMethod
  36. * @return mixed
  37. *
  38. * @throws \InvalidArgumentException
  39. */
  40. protected static function callClass($container, $target, array $parameters = [], $defaultMethod = null)
  41. {
  42. $segments = explode('@', $target);
  43. // We will assume an @ sign is used to delimit the class name from the method
  44. // name. We will split on this @ sign and then build a callable array that
  45. // we can pass right back into the "call" method for dependency binding.
  46. $method = count($segments) == 2
  47. ? $segments[1] : $defaultMethod;
  48. if (is_null($method)) {
  49. throw new InvalidArgumentException('Method not provided.');
  50. }
  51. return static::call(
  52. $container, [$container->make($segments[0]), $method], $parameters
  53. );
  54. }
  55. /**
  56. * Call a method that has been bound to the container.
  57. *
  58. * @param \Illuminate\Container\Container $container
  59. * @param callable $callback
  60. * @param mixed $default
  61. * @return mixed
  62. */
  63. protected static function callBoundMethod($container, $callback, $default)
  64. {
  65. if (! is_array($callback)) {
  66. return $default instanceof Closure ? $default() : $default;
  67. }
  68. // Here we need to turn the array callable into a Class@method string we can use to
  69. // examine the container and see if there are any method bindings for this given
  70. // method. If there are, we can call this method binding callback immediately.
  71. $method = static::normalizeMethod($callback);
  72. if ($container->hasMethodBinding($method)) {
  73. return $container->callMethodBinding($method, $callback[0]);
  74. }
  75. return $default instanceof Closure ? $default() : $default;
  76. }
  77. /**
  78. * Normalize the given callback into a Class@method string.
  79. *
  80. * @param callable $callback
  81. * @return string
  82. */
  83. protected static function normalizeMethod($callback)
  84. {
  85. $class = is_string($callback[0]) ? $callback[0] : get_class($callback[0]);
  86. return "{$class}@{$callback[1]}";
  87. }
  88. /**
  89. * Get all dependencies for a given method.
  90. *
  91. * @param \Illuminate\Container\Container $container
  92. * @param callable|string $callback
  93. * @param array $parameters
  94. * @return array
  95. */
  96. protected static function getMethodDependencies($container, $callback, array $parameters = [])
  97. {
  98. $dependencies = [];
  99. foreach (static::getCallReflector($callback)->getParameters() as $parameter) {
  100. static::addDependencyForCallParameter($container, $parameter, $parameters, $dependencies);
  101. }
  102. return array_merge($dependencies, $parameters);
  103. }
  104. /**
  105. * Get the proper reflection instance for the given callback.
  106. *
  107. * @param callable|string $callback
  108. * @return \ReflectionFunctionAbstract
  109. */
  110. protected static function getCallReflector($callback)
  111. {
  112. if (is_string($callback) && strpos($callback, '::') !== false) {
  113. $callback = explode('::', $callback);
  114. }
  115. return is_array($callback)
  116. ? new ReflectionMethod($callback[0], $callback[1])
  117. : new ReflectionFunction($callback);
  118. }
  119. /**
  120. * Get the dependency for the given call parameter.
  121. *
  122. * @param \Illuminate\Container\Container $container
  123. * @param \ReflectionParameter $parameter
  124. * @param array $parameters
  125. * @param array $dependencies
  126. * @return mixed
  127. */
  128. protected static function addDependencyForCallParameter($container, $parameter,
  129. array &$parameters, &$dependencies)
  130. {
  131. if (array_key_exists($parameter->name, $parameters)) {
  132. $dependencies[] = $parameters[$parameter->name];
  133. unset($parameters[$parameter->name]);
  134. } elseif ($parameter->getClass() && array_key_exists($parameter->getClass()->name, $parameters)) {
  135. $dependencies[] = $parameters[$parameter->getClass()->name];
  136. unset($parameters[$parameter->getClass()->name]);
  137. } elseif ($parameter->getClass()) {
  138. $dependencies[] = $container->make($parameter->getClass()->name);
  139. } elseif ($parameter->isDefaultValueAvailable()) {
  140. $dependencies[] = $parameter->getDefaultValue();
  141. }
  142. }
  143. /**
  144. * Determine if the given string is in Class@method syntax.
  145. *
  146. * @param mixed $callback
  147. * @return bool
  148. */
  149. protected static function isCallableWithAtSign($callback)
  150. {
  151. return is_string($callback) && strpos($callback, '@') !== false;
  152. }
  153. }