/vendor/phpspec/prophecy/src/Prophecy/Call/CallCenter.php

https://gitlab.com/Japang-Jawara/jawara-penilaian · PHP · 248 lines · 151 code · 33 blank · 64 comment · 15 complexity · 9d236f2ae527c13b5ac4bf250ba48d15 MD5 · raw file

  1. <?php
  2. /*
  3. * This file is part of the Prophecy.
  4. * (c) Konstantin Kudryashov <ever.zet@gmail.com>
  5. * Marcello Duarte <marcello.duarte@gmail.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Prophecy\Call;
  11. use Prophecy\Exception\Prophecy\MethodProphecyException;
  12. use Prophecy\Prophecy\ObjectProphecy;
  13. use Prophecy\Argument\ArgumentsWildcard;
  14. use Prophecy\Util\StringUtil;
  15. use Prophecy\Exception\Call\UnexpectedCallException;
  16. use SplObjectStorage;
  17. /**
  18. * Calls receiver & manager.
  19. *
  20. * @author Konstantin Kudryashov <ever.zet@gmail.com>
  21. */
  22. class CallCenter
  23. {
  24. private $util;
  25. /**
  26. * @var Call[]
  27. */
  28. private $recordedCalls = array();
  29. /**
  30. * @var SplObjectStorage
  31. */
  32. private $unexpectedCalls;
  33. /**
  34. * Initializes call center.
  35. *
  36. * @param StringUtil $util
  37. */
  38. public function __construct(StringUtil $util = null)
  39. {
  40. $this->util = $util ?: new StringUtil;
  41. $this->unexpectedCalls = new SplObjectStorage();
  42. }
  43. /**
  44. * Makes and records specific method call for object prophecy.
  45. *
  46. * @param ObjectProphecy $prophecy
  47. * @param string $methodName
  48. * @param array $arguments
  49. *
  50. * @return mixed Returns null if no promise for prophecy found or promise return value.
  51. *
  52. * @throws \Prophecy\Exception\Call\UnexpectedCallException If no appropriate method prophecy found
  53. */
  54. public function makeCall(ObjectProphecy $prophecy, $methodName, array $arguments)
  55. {
  56. // For efficiency exclude 'args' from the generated backtrace
  57. if (PHP_VERSION_ID >= 50400) {
  58. // Limit backtrace to last 3 calls as we don't use the rest
  59. // Limit argument was introduced in PHP 5.4.0
  60. $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
  61. } elseif (defined('DEBUG_BACKTRACE_IGNORE_ARGS')) {
  62. // DEBUG_BACKTRACE_IGNORE_ARGS was introduced in PHP 5.3.6
  63. $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
  64. } else {
  65. $backtrace = debug_backtrace();
  66. }
  67. $file = $line = null;
  68. if (isset($backtrace[2]) && isset($backtrace[2]['file'])) {
  69. $file = $backtrace[2]['file'];
  70. $line = $backtrace[2]['line'];
  71. }
  72. // If no method prophecies defined, then it's a dummy, so we'll just return null
  73. if ('__destruct' === strtolower($methodName) || 0 == count($prophecy->getMethodProphecies())) {
  74. $this->recordedCalls[] = new Call($methodName, $arguments, null, null, $file, $line);
  75. return null;
  76. }
  77. // There are method prophecies, so it's a fake/stub. Searching prophecy for this call
  78. $matches = $this->findMethodProphecies($prophecy, $methodName, $arguments);
  79. // If fake/stub doesn't have method prophecy for this call - throw exception
  80. if (!count($matches)) {
  81. $this->unexpectedCalls->attach(new Call($methodName, $arguments, null, null, $file, $line), $prophecy);
  82. $this->recordedCalls[] = new Call($methodName, $arguments, null, null, $file, $line);
  83. return null;
  84. }
  85. // Sort matches by their score value
  86. @usort($matches, function ($match1, $match2) { return $match2[0] - $match1[0]; });
  87. $score = $matches[0][0];
  88. // If Highest rated method prophecy has a promise - execute it or return null instead
  89. $methodProphecy = $matches[0][1];
  90. $returnValue = null;
  91. $exception = null;
  92. if ($promise = $methodProphecy->getPromise()) {
  93. try {
  94. $returnValue = $promise->execute($arguments, $prophecy, $methodProphecy);
  95. } catch (\Exception $e) {
  96. $exception = $e;
  97. }
  98. }
  99. if ($methodProphecy->hasReturnVoid() && $returnValue !== null) {
  100. throw new MethodProphecyException(
  101. "The method \"$methodName\" has a void return type, but the promise returned a value",
  102. $methodProphecy
  103. );
  104. }
  105. $this->recordedCalls[] = $call = new Call(
  106. $methodName, $arguments, $returnValue, $exception, $file, $line
  107. );
  108. $call->addScore($methodProphecy->getArgumentsWildcard(), $score);
  109. if (null !== $exception) {
  110. throw $exception;
  111. }
  112. return $returnValue;
  113. }
  114. /**
  115. * Searches for calls by method name & arguments wildcard.
  116. *
  117. * @param string $methodName
  118. * @param ArgumentsWildcard $wildcard
  119. *
  120. * @return Call[]
  121. */
  122. public function findCalls($methodName, ArgumentsWildcard $wildcard)
  123. {
  124. $methodName = strtolower($methodName);
  125. return array_values(
  126. array_filter($this->recordedCalls, function (Call $call) use ($methodName, $wildcard) {
  127. return $methodName === strtolower($call->getMethodName())
  128. && 0 < $call->getScore($wildcard)
  129. ;
  130. })
  131. );
  132. }
  133. /**
  134. * @throws UnexpectedCallException
  135. */
  136. public function checkUnexpectedCalls()
  137. {
  138. /** @var Call $call */
  139. foreach ($this->unexpectedCalls as $call) {
  140. $prophecy = $this->unexpectedCalls[$call];
  141. // If fake/stub doesn't have method prophecy for this call - throw exception
  142. if (!count($this->findMethodProphecies($prophecy, $call->getMethodName(), $call->getArguments()))) {
  143. throw $this->createUnexpectedCallException($prophecy, $call->getMethodName(), $call->getArguments());
  144. }
  145. }
  146. }
  147. private function createUnexpectedCallException(ObjectProphecy $prophecy, $methodName,
  148. array $arguments)
  149. {
  150. $classname = get_class($prophecy->reveal());
  151. $indentationLength = 8; // looks good
  152. $argstring = implode(
  153. ",\n",
  154. $this->indentArguments(
  155. array_map(array($this->util, 'stringify'), $arguments),
  156. $indentationLength
  157. )
  158. );
  159. $expected = array();
  160. foreach (call_user_func_array('array_merge', $prophecy->getMethodProphecies()) as $methodProphecy) {
  161. $expected[] = sprintf(
  162. " - %s(\n" .
  163. "%s\n" .
  164. " )",
  165. $methodProphecy->getMethodName(),
  166. implode(
  167. ",\n",
  168. $this->indentArguments(
  169. array_map('strval', $methodProphecy->getArgumentsWildcard()->getTokens()),
  170. $indentationLength
  171. )
  172. )
  173. );
  174. }
  175. return new UnexpectedCallException(
  176. sprintf(
  177. "Unexpected method call on %s:\n".
  178. " - %s(\n".
  179. "%s\n".
  180. " )\n".
  181. "expected calls were:\n".
  182. "%s",
  183. $classname, $methodName, $argstring, implode("\n", $expected)
  184. ),
  185. $prophecy, $methodName, $arguments
  186. );
  187. }
  188. private function indentArguments(array $arguments, $indentationLength)
  189. {
  190. return preg_replace_callback(
  191. '/^/m',
  192. function () use ($indentationLength) {
  193. return str_repeat(' ', $indentationLength);
  194. },
  195. $arguments
  196. );
  197. }
  198. /**
  199. * @param ObjectProphecy $prophecy
  200. * @param string $methodName
  201. * @param array $arguments
  202. *
  203. * @return array
  204. */
  205. private function findMethodProphecies(ObjectProphecy $prophecy, $methodName, array $arguments)
  206. {
  207. $matches = array();
  208. foreach ($prophecy->getMethodProphecies($methodName) as $methodProphecy) {
  209. if (0 < $score = $methodProphecy->getArgumentsWildcard()->scoreArguments($arguments)) {
  210. $matches[] = array($score, $methodProphecy);
  211. }
  212. }
  213. return $matches;
  214. }
  215. }