Callback.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. <?php
  2. /**
  3. * This file is part of the Nette Framework (https://nette.org)
  4. * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
  5. */
  6. declare(strict_types=1);
  7. namespace Nette\Utils;
  8. use Nette;
  9. use function is_array, is_object, is_string;
  10. /**
  11. * PHP callable tools.
  12. */
  13. final class Callback
  14. {
  15. use Nette\StaticClass;
  16. /**
  17. * @param string|object|callable $callable class, object, callable
  18. * @deprecated use Closure::fromCallable()
  19. */
  20. public static function closure($callable, string $method = null): \Closure
  21. {
  22. trigger_error(__METHOD__ . '() is deprecated, use Closure::fromCallable().', E_USER_DEPRECATED);
  23. try {
  24. return \Closure::fromCallable($method === null ? $callable : [$callable, $method]);
  25. } catch (\TypeError $e) {
  26. throw new Nette\InvalidArgumentException($e->getMessage());
  27. }
  28. }
  29. /**
  30. * Invokes callback.
  31. * @return mixed
  32. * @deprecated
  33. */
  34. public static function invoke($callable, ...$args)
  35. {
  36. trigger_error(__METHOD__ . '() is deprecated, use native invoking.', E_USER_DEPRECATED);
  37. self::check($callable);
  38. return $callable(...$args);
  39. }
  40. /**
  41. * Invokes callback with an array of parameters.
  42. * @return mixed
  43. * @deprecated
  44. */
  45. public static function invokeArgs($callable, array $args = [])
  46. {
  47. trigger_error(__METHOD__ . '() is deprecated, use native invoking.', E_USER_DEPRECATED);
  48. self::check($callable);
  49. return $callable(...$args);
  50. }
  51. /**
  52. * Invokes internal PHP function with own error handler.
  53. * @return mixed
  54. */
  55. public static function invokeSafe(string $function, array $args, callable $onError)
  56. {
  57. $prev = set_error_handler(function ($severity, $message, $file) use ($onError, &$prev, $function): ?bool {
  58. if ($file === __FILE__) {
  59. $msg = ini_get('html_errors')
  60. ? Html::htmlToText($message)
  61. : $message;
  62. $msg = preg_replace("#^$function\\(.*?\\): #", '', $msg);
  63. if ($onError($msg, $severity) !== false) {
  64. return null;
  65. }
  66. }
  67. return $prev ? $prev(...func_get_args()) : false;
  68. });
  69. try {
  70. return $function(...$args);
  71. } finally {
  72. restore_error_handler();
  73. }
  74. }
  75. /**
  76. * Checks that $callable is valid PHP callback. Otherwise throws exception. If the $syntax is set to true, only verifies
  77. * that $callable has a valid structure to be used as a callback, but does not verify if the class or method actually exists.
  78. * @param mixed $callable
  79. * @return callable
  80. * @throws Nette\InvalidArgumentException
  81. */
  82. public static function check($callable, bool $syntax = false)
  83. {
  84. if (!is_callable($callable, $syntax)) {
  85. throw new Nette\InvalidArgumentException(
  86. $syntax
  87. ? 'Given value is not a callable type.'
  88. : sprintf("Callback '%s' is not callable.", self::toString($callable))
  89. );
  90. }
  91. return $callable;
  92. }
  93. /**
  94. * Converts PHP callback to textual form. Class or method may not exists.
  95. * @param mixed $callable
  96. */
  97. public static function toString($callable): string
  98. {
  99. if ($callable instanceof \Closure) {
  100. $inner = self::unwrap($callable);
  101. return '{closure' . ($inner instanceof \Closure ? '}' : ' ' . self::toString($inner) . '}');
  102. } elseif (is_string($callable) && $callable[0] === "\0") {
  103. return '{lambda}';
  104. } else {
  105. is_callable(is_object($callable) ? [$callable, '__invoke'] : $callable, true, $textual);
  106. return $textual;
  107. }
  108. }
  109. /**
  110. * Returns reflection for method or function used in PHP callback.
  111. * @param callable $callable type check is escalated to ReflectionException
  112. * @return \ReflectionMethod|\ReflectionFunction
  113. * @throws \ReflectionException if callback is not valid
  114. */
  115. public static function toReflection($callable): \ReflectionFunctionAbstract
  116. {
  117. if ($callable instanceof \Closure) {
  118. $callable = self::unwrap($callable);
  119. }
  120. if (is_string($callable) && strpos($callable, '::')) {
  121. return new \ReflectionMethod($callable);
  122. } elseif (is_array($callable)) {
  123. return new \ReflectionMethod($callable[0], $callable[1]);
  124. } elseif (is_object($callable) && !$callable instanceof \Closure) {
  125. return new \ReflectionMethod($callable, '__invoke');
  126. } else {
  127. return new \ReflectionFunction($callable);
  128. }
  129. }
  130. /**
  131. * Checks whether PHP callback is function or static method.
  132. */
  133. public static function isStatic(callable $callable): bool
  134. {
  135. return is_array($callable) ? is_string($callable[0]) : is_string($callable);
  136. }
  137. /**
  138. * Unwraps closure created by Closure::fromCallable().
  139. */
  140. public static function unwrap(\Closure $closure): callable
  141. {
  142. $r = new \ReflectionFunction($closure);
  143. if (substr($r->name, -1) === '}') {
  144. return $closure;
  145. } elseif ($obj = $r->getClosureThis()) {
  146. return [$obj, $r->name];
  147. } elseif ($class = $r->getClosureScopeClass()) {
  148. return [$class->name, $r->name];
  149. } else {
  150. return $r->name;
  151. }
  152. }
  153. }