PageRenderTime 26ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/src/PHPSpec/Specification/Interceptor.php

http://github.com/phpspec/phpspec
PHP | 330 lines | 162 code | 30 blank | 138 comment | 12 complexity | e93fc740e0ac8167eac934c6b9709923 MD5 | raw file
  1. <?php
  2. /**
  3. * PHPSpec
  4. *
  5. * LICENSE
  6. *
  7. * This file is subject to the GNU Lesser General Public License Version 3
  8. * that is bundled with this package in the file LICENSE.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://www.gnu.org/licenses/lgpl-3.0.txt
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@phpspec.net so we can send you a copy immediately.
  14. *
  15. * @category PHPSpec
  16. * @package PHPSpec
  17. * @copyright Copyright (c) 2007-2009 P??draic Brady, Travis Swicegood
  18. * @copyright Copyright (c) 2010-2012 P??draic Brady, Travis Swicegood,
  19. * Marcello Duarte
  20. * @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU Lesser General Public Licence Version 3
  21. */
  22. namespace PHPSpec\Specification;
  23. use \PHPSpec\Specification\Result\Failure,
  24. \PHPSpec\Specification\Result\Error,
  25. \PHPSpec\Specification\Interceptor\InterceptorFactory;
  26. /**
  27. * @category PHPSpec
  28. * @package PHPSpec
  29. * @copyright Copyright (c) 2007-2009 P??draic Brady, Travis Swicegood
  30. * @copyright Copyright (c) 2010-2012 P??draic Brady, Travis Swicegood,
  31. * Marcello Duarte
  32. * @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU Lesser General Public Licence Version 3
  33. */
  34. abstract class Interceptor
  35. {
  36. /**
  37. * Expectation forces matcher result to fail if it returns false
  38. */
  39. const SHOULD = 'should';
  40. /**
  41. * Expectation forces matcher result to fail if it returns true
  42. */
  43. const SHOULD_NOT = 'should not';
  44. /**
  45. * The actual value
  46. *
  47. * @var mixed
  48. */
  49. protected $_actualValue;
  50. /**
  51. * The expectation
  52. *
  53. * @var mixed
  54. */
  55. protected $_expectation;
  56. /**
  57. * The expected value
  58. *
  59. * @var mixed
  60. */
  61. protected $_expectedValue;
  62. /**
  63. * List of valid matchers
  64. *
  65. * @var array
  66. */
  67. protected $_matchers = array(
  68. 'be', 'beAnInstanceOf', 'beEmpty', 'beEqualTo', 'beFalse',
  69. 'beGreaterThan', 'beGreaterThanOrEqualTo', 'beInteger',
  70. 'beLessThan', 'beLessThanOrEqualTo', 'beNull', 'beString', 'beTrue',
  71. 'equal', 'match', 'throwException'
  72. );
  73. /**
  74. * Creates an interceptor with the intercepted actual value
  75. *
  76. * @param mixed $value
  77. */
  78. public function __construct($value)
  79. {
  80. $this->_actualValue = $value;
  81. }
  82. /**
  83. * Checks whether a expectation is being invoked
  84. *
  85. * @param string $attribute
  86. *
  87. * @return \PHPSpec\Specification\Interceptor|mixed
  88. */
  89. public function __get($attribute)
  90. {
  91. switch ($attribute) {
  92. case 'should' :
  93. $this->_expectation = self::SHOULD;
  94. return $this;
  95. case 'shouldNot' :
  96. $this->_expectation = self::SHOULD_NOT;
  97. return $this;
  98. default :
  99. if (method_exists($this->_actualValue, '__get')) {
  100. $parentInterceptor = new \ReflectionMethod(
  101. $this->_actualValue, '__get'
  102. );
  103. $value = $parentInterceptor->invokeArgs(
  104. $this->_actualValue, array($attribute)
  105. );
  106. return InterceptorFactory::create($value, $this);
  107. }
  108. }
  109. }
  110. /**
  111. * Invokes a matcher
  112. *
  113. * @param string $method
  114. * @param array $args
  115. * @return boolean|mixed
  116. */
  117. public function __call($method, $args)
  118. {
  119. if (in_array($method, $this->_matchers)) {
  120. $this->setExpectedValue($args);
  121. $this->createMatcher($method);
  122. $this->performMatching();
  123. return true;
  124. }
  125. if (\PHPSpec\Matcher\MatcherRepository::has($method)) {
  126. $this->setExpectedValue($args);
  127. $expected = !is_array($this->getExpectedValue()) ?
  128. array($this->getExpectedValue()) :
  129. $this->getExpectedValue();
  130. $this->_matcher = new \PHPSpec\Matcher\UserDefined(
  131. $method, $expected
  132. );
  133. $this->performMatching();
  134. return true;
  135. }
  136. if (method_exists($this->_actualValue, '__call')) {
  137. $parentInterceptor = new \ReflectionMethod(
  138. $this->_actualValue, '__call'
  139. );
  140. return $parentInterceptor->invokeArgs(
  141. $this->_actualValue, $args
  142. );
  143. }
  144. if (!$this instanceof Interceptor\Object &&
  145. $this->_expectation !== null) {
  146. throw new \BadMethodCallException(
  147. "Call to undefined method $method"
  148. );
  149. }
  150. }
  151. /**
  152. * Sets an Expected value with which to instantiate any new Matcher
  153. *
  154. * @param mixed $value
  155. * @return null
  156. */
  157. public function setExpectedValue($value)
  158. {
  159. $this->_expectedValue = $value;
  160. }
  161. /**
  162. * Gets an Expected value with which to instantiate any new Matcher
  163. *
  164. * @return mixed
  165. */
  166. public function getExpectedValue()
  167. {
  168. return $this->_expectedValue;
  169. }
  170. /**
  171. * Sets the Actual value
  172. *
  173. * @param mixed $value
  174. * @return null
  175. */
  176. public function setActualValue($value)
  177. {
  178. $this->_actualValue = $value;
  179. }
  180. /**
  181. * Gets an Expected value with which to instantiate any new Matcher
  182. *
  183. * @return mixed
  184. */
  185. public function getActualValue()
  186. {
  187. return $this->_actualValue;
  188. }
  189. /**
  190. * Returns the Expectation ruling how Matcher results are
  191. * interpreted (whether a matcher boolean result counts as a pass
  192. * or a fail).
  193. *
  194. * @return string (self::SHOULD | self::SHOULD_NOT)
  195. */
  196. public function getExpectation()
  197. {
  198. return $this->_expectation;
  199. }
  200. /**
  201. * Adds a matcher
  202. *
  203. * @param string $matcher
  204. */
  205. public function addMatcher($matcher)
  206. {
  207. $this->_matchers[] = $matcher;
  208. }
  209. /**
  210. * Adds many matcher at once
  211. *
  212. * @param array $matchers
  213. */
  214. public function addMatchers(array $matchers)
  215. {
  216. $this->_matchers = array_merge($this->_matchers, $matchers);
  217. }
  218. /**
  219. * Sets the matchers. Replaces existing ones (!)
  220. *
  221. * @param array $matchers
  222. */
  223. public function setMatchers(array $matchers)
  224. {
  225. $this->_matchers = $matchers;
  226. }
  227. /**
  228. * Creates a new Matcher object based on calls
  229. * to the DSL grammer. (factory)
  230. *
  231. * @param DSL method call which was found to be a Matcher reference
  232. */
  233. protected function createMatcher($matcher)
  234. {
  235. $matcher = strtoupper($matcher[0]) . substr($matcher, 1);
  236. $expected = $this->assertExpectedIsArray();
  237. try {
  238. $matcherClass = '\PHPSpec\Matcher\\' . $matcher;
  239. $reflectedMatcher = new \ReflectionClass($matcherClass);
  240. $this->_matcher = $reflectedMatcher->newInstanceArgs($expected);
  241. } catch (\ReflectionException $e) {
  242. try {
  243. $matcherClass = '\PHPSpec\Context\Zend\Matcher\\' . $matcher;
  244. $reflectedMatcher = new \ReflectionClass($matcherClass);
  245. $this->_matcher = $reflectedMatcher->newInstanceArgs(
  246. $expected
  247. );
  248. } catch(\ReflectionException $e) {
  249. throw new \PHPSpec\Exception("Could not find matcher $matcher");
  250. }
  251. }
  252. }
  253. /**
  254. * Asserts the expected value is in an array
  255. *
  256. * !FIXME this is only being used because the way Closure specification
  257. * and throwException matcher work
  258. *
  259. * @return array
  260. */
  261. protected function assertExpectedIsArray()
  262. {
  263. return !is_array($this->getExpectedValue()) ?
  264. array($this->getExpectedValue()) :
  265. $this->getExpectedValue();
  266. }
  267. /**
  268. * Performs a Matcher operation and set the returned boolean result for
  269. * further analysis, e.g. comparison to the boolean Expectation.
  270. *
  271. * @param array $args
  272. * @return null
  273. */
  274. protected function performMatching()
  275. {
  276. $actual = $this->getActualValue();
  277. if (is_array($actual) && $this->_composedActual) {
  278. $args = $actual;
  279. } else {
  280. $args = array($actual);
  281. }
  282. $result = call_user_func_array(
  283. array($this->_matcher, 'matches'), $args
  284. );
  285. if ($this->getExpectation() === self::SHOULD) {
  286. if ($result === false) {
  287. throw new Failure($this->_matcher->getFailureMessage());
  288. }
  289. } elseif ($this->getExpectation() === self::SHOULD_NOT) {
  290. if ($result === true) {
  291. throw new Failure($this->_matcher->getNegativeFailureMessage());
  292. }
  293. } elseif (empty($this->_expectation)) {
  294. throw new Error(
  295. 'Missing expectation "should" or "shouldNot". ' .
  296. 'Make sure you use them as properties and not as methods.'
  297. );
  298. }
  299. }
  300. }