/vendor/phpspec/phpspec/src/PhpSpec/Wrapper/Subject/Caller.php

https://gitlab.com/judielsm/Handora · PHP · 438 lines · 226 code · 59 blank · 153 comment · 26 complexity · 8e9af52e2f1c3d3efa2f553eb7175414 MD5 · raw file

  1. <?php
  2. /*
  3. * This file is part of PhpSpec, A php toolset to drive emergent
  4. * design by specification.
  5. *
  6. * (c) Marcello Duarte <marcello.duarte@gmail.com>
  7. * (c) Konstantin Kudryashov <ever.zet@gmail.com>
  8. *
  9. * For the full copyright and license information, please view the LICENSE
  10. * file that was distributed with this source code.
  11. */
  12. namespace PhpSpec\Wrapper\Subject;
  13. use PhpSpec\Exception\ExceptionFactory;
  14. use PhpSpec\Loader\Node\ExampleNode;
  15. use PhpSpec\Wrapper\Subject;
  16. use PhpSpec\Wrapper\Wrapper;
  17. use PhpSpec\Wrapper\Unwrapper;
  18. use PhpSpec\Event\MethodCallEvent;
  19. use Symfony\Component\EventDispatcher\EventDispatcherInterface as Dispatcher;
  20. use ReflectionClass;
  21. use ReflectionMethod;
  22. use ReflectionProperty;
  23. use ReflectionException;
  24. class Caller
  25. {
  26. /**
  27. * @var WrappedObject
  28. */
  29. private $wrappedObject;
  30. /**
  31. * @var \PhpSpec\Loader\Node\ExampleNode
  32. */
  33. private $example;
  34. /**
  35. * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
  36. */
  37. private $dispatcher;
  38. /**
  39. * @var \PhpSpec\Wrapper\Wrapper
  40. */
  41. private $wrapper;
  42. /**
  43. * @var \PhpSpec\Exception\ExceptionFactory
  44. */
  45. private $exceptionFactory;
  46. /**
  47. * @param WrappedObject $wrappedObject
  48. * @param ExampleNode $example
  49. * @param Dispatcher $dispatcher
  50. * @param ExceptionFactory $exceptions
  51. * @param Wrapper $wrapper
  52. */
  53. public function __construct(
  54. WrappedObject $wrappedObject,
  55. ExampleNode $example,
  56. Dispatcher $dispatcher,
  57. ExceptionFactory $exceptions,
  58. Wrapper $wrapper
  59. ) {
  60. $this->wrappedObject = $wrappedObject;
  61. $this->example = $example;
  62. $this->dispatcher = $dispatcher;
  63. $this->wrapper = $wrapper;
  64. $this->exceptionFactory = $exceptions;
  65. }
  66. /**
  67. * @param string $method
  68. * @param array $arguments
  69. *
  70. * @return Subject
  71. *
  72. * @throws \PhpSpec\Exception\Fracture\MethodNotFoundException
  73. * @throws \PhpSpec\Exception\Fracture\MethodNotVisibleException
  74. * @throws \PhpSpec\Exception\Wrapper\SubjectException
  75. */
  76. public function call($method, array $arguments = array())
  77. {
  78. if (null === $this->getWrappedObject()) {
  79. throw $this->callingMethodOnNonObject($method);
  80. }
  81. $subject = $this->wrappedObject->getInstance();
  82. $unwrapper = new Unwrapper();
  83. $arguments = $unwrapper->unwrapAll($arguments);
  84. if ($this->isObjectMethodAccessible($method)) {
  85. return $this->invokeAndWrapMethodResult($subject, $method, $arguments);
  86. }
  87. throw $this->methodNotFound($method, $arguments);
  88. }
  89. /**
  90. * @param string $property
  91. * @param mixed $value
  92. *
  93. * @return mixed
  94. *
  95. * @throws \PhpSpec\Exception\Wrapper\SubjectException
  96. * @throws \PhpSpec\Exception\Fracture\PropertyNotFoundException
  97. */
  98. public function set($property, $value = null)
  99. {
  100. if (null === $this->getWrappedObject()) {
  101. throw $this->settingPropertyOnNonObject($property);
  102. }
  103. $unwrapper = new Unwrapper();
  104. $value = $unwrapper->unwrapOne($value);
  105. if ($this->isObjectPropertyAccessible($property, true)) {
  106. return $this->getWrappedObject()->$property = $value;
  107. }
  108. throw $this->propertyNotFound($property);
  109. }
  110. /**
  111. * @param string $property
  112. *
  113. * @return Subject|string
  114. *
  115. * @throws \PhpSpec\Exception\Fracture\PropertyNotFoundException
  116. * @throws \PhpSpec\Exception\Wrapper\SubjectException
  117. */
  118. public function get($property)
  119. {
  120. if ($this->lookingForConstants($property) && $this->constantDefined($property)) {
  121. return constant($this->wrappedObject->getClassName().'::'.$property);
  122. }
  123. if (null === $this->getWrappedObject()) {
  124. throw $this->accessingPropertyOnNonObject($property);
  125. }
  126. if ($this->isObjectPropertyAccessible($property)) {
  127. return $this->wrap($this->getWrappedObject()->$property);
  128. }
  129. throw $this->propertyNotFound($property);
  130. }
  131. /**
  132. * @return object
  133. *
  134. * @throws \PhpSpec\Exception\Fracture\ClassNotFoundException
  135. */
  136. public function getWrappedObject()
  137. {
  138. if ($this->wrappedObject->isInstantiated()) {
  139. return $this->wrappedObject->getInstance();
  140. }
  141. if (null === $this->wrappedObject->getClassName() || !is_string($this->wrappedObject->getClassName())) {
  142. return $this->wrappedObject->getInstance();
  143. }
  144. if (!class_exists($this->wrappedObject->getClassName())) {
  145. throw $this->classNotFound();
  146. }
  147. if (is_object($this->wrappedObject->getInstance())) {
  148. $this->wrappedObject->setInstantiated(true);
  149. $instance = $this->wrappedObject->getInstance();
  150. } else {
  151. $instance = $this->instantiateWrappedObject();
  152. $this->wrappedObject->setInstance($instance);
  153. $this->wrappedObject->setInstantiated(true);
  154. }
  155. return $instance;
  156. }
  157. /**
  158. * @param string $property
  159. * @param bool $withValue
  160. *
  161. * @return bool
  162. */
  163. private function isObjectPropertyAccessible($property, $withValue = false)
  164. {
  165. if (!is_object($this->getWrappedObject())) {
  166. return false;
  167. }
  168. if (method_exists($this->getWrappedObject(), $withValue ? '__set' : '__get')) {
  169. return true;
  170. }
  171. if (!property_exists($this->getWrappedObject(), $property)) {
  172. return false;
  173. }
  174. $propertyReflection = new ReflectionProperty($this->getWrappedObject(), $property);
  175. return $propertyReflection->isPublic();
  176. }
  177. /**
  178. * @param string $method
  179. *
  180. * @return bool
  181. */
  182. private function isObjectMethodAccessible($method)
  183. {
  184. if (!is_object($this->getWrappedObject())) {
  185. return false;
  186. }
  187. if (method_exists($this->getWrappedObject(), '__call')) {
  188. return true;
  189. }
  190. if (!method_exists($this->getWrappedObject(), $method)) {
  191. return false;
  192. }
  193. $methodReflection = new ReflectionMethod($this->getWrappedObject(), $method);
  194. return $methodReflection->isPublic();
  195. }
  196. /**
  197. * @return object
  198. */
  199. private function instantiateWrappedObject()
  200. {
  201. if ($this->wrappedObject->getFactoryMethod()) {
  202. return $this->newInstanceWithFactoryMethod();
  203. }
  204. $reflection = new ReflectionClass($this->wrappedObject->getClassName());
  205. if (count($this->wrappedObject->getArguments())) {
  206. return $this->newInstanceWithArguments($reflection);
  207. }
  208. return $reflection->newInstance();
  209. }
  210. /**
  211. * @param object $subject
  212. * @param string $method
  213. * @param array $arguments
  214. *
  215. * @return Subject
  216. */
  217. private function invokeAndWrapMethodResult($subject, $method, array $arguments = array())
  218. {
  219. $this->dispatcher->dispatch(
  220. 'beforeMethodCall',
  221. new MethodCallEvent($this->example, $subject, $method, $arguments)
  222. );
  223. $returnValue = call_user_func_array(array($subject, $method), $arguments);
  224. $this->dispatcher->dispatch(
  225. 'afterMethodCall',
  226. new MethodCallEvent($this->example, $subject, $method, $arguments)
  227. );
  228. return $this->wrap($returnValue);
  229. }
  230. /**
  231. * @param mixed $value
  232. *
  233. * @return Subject
  234. */
  235. private function wrap($value)
  236. {
  237. return $this->wrapper->wrap($value);
  238. }
  239. /**
  240. * @param ReflectionClass $reflection
  241. *
  242. * @return object
  243. *
  244. * @throws \PhpSpec\Exception\Fracture\MethodNotFoundException
  245. * @throws \PhpSpec\Exception\Fracture\MethodNotVisibleException
  246. * @throws \Exception
  247. * @throws \ReflectionException
  248. */
  249. private function newInstanceWithArguments(ReflectionClass $reflection)
  250. {
  251. try {
  252. return $reflection->newInstanceArgs($this->wrappedObject->getArguments());
  253. } catch (ReflectionException $e) {
  254. if ($this->detectMissingConstructorMessage($e)) {
  255. throw $this->methodNotFound(
  256. '__construct',
  257. $this->wrappedObject->getArguments()
  258. );
  259. }
  260. throw $e;
  261. }
  262. }
  263. /**
  264. * @return mixed
  265. * @throws \PhpSpec\Exception\Fracture\MethodNotFoundException
  266. */
  267. private function newInstanceWithFactoryMethod()
  268. {
  269. $method = $this->wrappedObject->getFactoryMethod();
  270. if (!is_array($method)) {
  271. $className = $this->wrappedObject->getClassName();
  272. if (!method_exists($className, $method)) {
  273. throw $this->namedConstructorNotFound(
  274. $method,
  275. $this->wrappedObject->getArguments()
  276. );
  277. }
  278. }
  279. return call_user_func_array($method, $this->wrappedObject->getArguments());
  280. }
  281. /**
  282. * @param ReflectionException $exception
  283. *
  284. * @return bool
  285. */
  286. private function detectMissingConstructorMessage(ReflectionException $exception)
  287. {
  288. return strpos(
  289. $exception->getMessage(),
  290. 'does not have a constructor'
  291. ) !== 0;
  292. }
  293. /**
  294. * @return \PhpSpec\Exception\Fracture\ClassNotFoundException
  295. */
  296. private function classNotFound()
  297. {
  298. return $this->exceptionFactory->classNotFound($this->wrappedObject->getClassName());
  299. }
  300. /**
  301. * @param string $method
  302. * @param array $arguments
  303. *
  304. * @return \PhpSpec\Exception\Fracture\MethodNotFoundException|\PhpSpec\Exception\Fracture\MethodNotVisibleException
  305. */
  306. private function namedConstructorNotFound($method, array $arguments = array())
  307. {
  308. $className = $this->wrappedObject->getClassName();
  309. return $this->exceptionFactory->namedConstructorNotFound($className, $method, $arguments);
  310. }
  311. /**
  312. * @param $method
  313. * @param array $arguments
  314. * @return \PhpSpec\Exception\Fracture\MethodNotFoundException|\PhpSpec\Exception\Fracture\MethodNotVisibleException
  315. */
  316. private function methodNotFound($method, array $arguments = array())
  317. {
  318. $className = $this->wrappedObject->getClassName();
  319. if (!method_exists($className, $method)) {
  320. return $this->exceptionFactory->methodNotFound($className, $method, $arguments);
  321. }
  322. return $this->exceptionFactory->methodNotVisible($className, $method, $arguments);
  323. }
  324. /**
  325. * @param string $property
  326. *
  327. * @return \PhpSpec\Exception\Fracture\PropertyNotFoundException
  328. */
  329. private function propertyNotFound($property)
  330. {
  331. return $this->exceptionFactory->propertyNotFound($this->getWrappedObject(), $property);
  332. }
  333. /**
  334. * @param string $method
  335. *
  336. * @return \PhpSpec\Exception\Wrapper\SubjectException
  337. */
  338. private function callingMethodOnNonObject($method)
  339. {
  340. return $this->exceptionFactory->callingMethodOnNonObject($method);
  341. }
  342. /**
  343. * @param string $property
  344. *
  345. * @return \PhpSpec\Exception\Wrapper\SubjectException
  346. */
  347. private function settingPropertyOnNonObject($property)
  348. {
  349. return $this->exceptionFactory->settingPropertyOnNonObject($property);
  350. }
  351. /**
  352. * @param string $property
  353. *
  354. * @return \PhpSpec\Exception\Wrapper\SubjectException
  355. */
  356. private function accessingPropertyOnNonObject($property)
  357. {
  358. return $this->exceptionFactory->gettingPropertyOnNonObject($property);
  359. }
  360. /**
  361. * @param string $property
  362. *
  363. * @return bool
  364. */
  365. private function lookingForConstants($property)
  366. {
  367. return null !== $this->wrappedObject->getClassName() &&
  368. $property === strtoupper($property);
  369. }
  370. /**
  371. * @param string $property
  372. *
  373. * @return bool
  374. */
  375. public function constantDefined($property)
  376. {
  377. return defined($this->wrappedObject->getClassName().'::'.$property);
  378. }
  379. }