1<?php 2 3/* 4 * This file is part of the Prophecy. 5 * (c) Konstantin Kudryashov <ever.zet@gmail.com> 6 * Marcello Duarte <marcello.duarte@gmail.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Prophecy\Prophecy; 13 14use SebastianBergmann\Comparator\ComparisonFailure; 15use Prophecy\Comparator\Factory as ComparatorFactory; 16use Prophecy\Call\Call; 17use Prophecy\Doubler\LazyDouble; 18use Prophecy\Argument\ArgumentsWildcard; 19use Prophecy\Call\CallCenter; 20use Prophecy\Exception\Prophecy\ObjectProphecyException; 21use Prophecy\Exception\Prophecy\MethodProphecyException; 22use Prophecy\Exception\Prediction\AggregateException; 23use Prophecy\Exception\Prediction\PredictionException; 24 25/** 26 * Object prophecy. 27 * 28 * @author Konstantin Kudryashov <ever.zet@gmail.com> 29 */ 30class ObjectProphecy implements ProphecyInterface 31{ 32 private $lazyDouble; 33 private $callCenter; 34 private $revealer; 35 private $comparatorFactory; 36 37 /** 38 * @var MethodProphecy[][] 39 */ 40 private $methodProphecies = array(); 41 42 /** 43 * Initializes object prophecy. 44 * 45 * @param LazyDouble $lazyDouble 46 * @param CallCenter $callCenter 47 * @param RevealerInterface $revealer 48 * @param ComparatorFactory $comparatorFactory 49 */ 50 public function __construct( 51 LazyDouble $lazyDouble, 52 CallCenter $callCenter = null, 53 RevealerInterface $revealer = null, 54 ComparatorFactory $comparatorFactory = null 55 ) { 56 $this->lazyDouble = $lazyDouble; 57 $this->callCenter = $callCenter ?: new CallCenter; 58 $this->revealer = $revealer ?: new Revealer; 59 60 $this->comparatorFactory = $comparatorFactory ?: ComparatorFactory::getInstance(); 61 } 62 63 /** 64 * Forces double to extend specific class. 65 * 66 * @param string $class 67 * 68 * @return $this 69 */ 70 public function willExtend($class) 71 { 72 $this->lazyDouble->setParentClass($class); 73 74 return $this; 75 } 76 77 /** 78 * Forces double to implement specific interface. 79 * 80 * @param string $interface 81 * 82 * @return $this 83 */ 84 public function willImplement($interface) 85 { 86 $this->lazyDouble->addInterface($interface); 87 88 return $this; 89 } 90 91 /** 92 * Sets constructor arguments. 93 * 94 * @param array $arguments 95 * 96 * @return $this 97 */ 98 public function willBeConstructedWith(array $arguments = null) 99 { 100 $this->lazyDouble->setArguments($arguments); 101 102 return $this; 103 } 104 105 /** 106 * Reveals double. 107 * 108 * @return object 109 * 110 * @throws \Prophecy\Exception\Prophecy\ObjectProphecyException If double doesn't implement needed interface 111 */ 112 public function reveal() 113 { 114 $double = $this->lazyDouble->getInstance(); 115 116 if (null === $double || !$double instanceof ProphecySubjectInterface) { 117 throw new ObjectProphecyException( 118 "Generated double must implement ProphecySubjectInterface, but it does not.\n". 119 'It seems you have wrongly configured doubler without required ClassPatch.', 120 $this 121 ); 122 } 123 124 $double->setProphecy($this); 125 126 return $double; 127 } 128 129 /** 130 * Adds method prophecy to object prophecy. 131 * 132 * @param MethodProphecy $methodProphecy 133 * 134 * @throws \Prophecy\Exception\Prophecy\MethodProphecyException If method prophecy doesn't 135 * have arguments wildcard 136 */ 137 public function addMethodProphecy(MethodProphecy $methodProphecy) 138 { 139 $argumentsWildcard = $methodProphecy->getArgumentsWildcard(); 140 if (null === $argumentsWildcard) { 141 throw new MethodProphecyException(sprintf( 142 "Can not add prophecy for a method `%s::%s()`\n". 143 "as you did not specify arguments wildcard for it.", 144 get_class($this->reveal()), 145 $methodProphecy->getMethodName() 146 ), $methodProphecy); 147 } 148 149 $methodName = $methodProphecy->getMethodName(); 150 151 if (!isset($this->methodProphecies[$methodName])) { 152 $this->methodProphecies[$methodName] = array(); 153 } 154 155 $this->methodProphecies[$methodName][] = $methodProphecy; 156 } 157 158 /** 159 * Returns either all or related to single method prophecies. 160 * 161 * @param null|string $methodName 162 * 163 * @return MethodProphecy[] 164 */ 165 public function getMethodProphecies($methodName = null) 166 { 167 if (null === $methodName) { 168 return $this->methodProphecies; 169 } 170 171 if (!isset($this->methodProphecies[$methodName])) { 172 return array(); 173 } 174 175 return $this->methodProphecies[$methodName]; 176 } 177 178 /** 179 * Makes specific method call. 180 * 181 * @param string $methodName 182 * @param array $arguments 183 * 184 * @return mixed 185 */ 186 public function makeProphecyMethodCall($methodName, array $arguments) 187 { 188 $arguments = $this->revealer->reveal($arguments); 189 $return = $this->callCenter->makeCall($this, $methodName, $arguments); 190 191 return $this->revealer->reveal($return); 192 } 193 194 /** 195 * Finds calls by method name & arguments wildcard. 196 * 197 * @param string $methodName 198 * @param ArgumentsWildcard $wildcard 199 * 200 * @return Call[] 201 */ 202 public function findProphecyMethodCalls($methodName, ArgumentsWildcard $wildcard) 203 { 204 return $this->callCenter->findCalls($methodName, $wildcard); 205 } 206 207 /** 208 * Checks that registered method predictions do not fail. 209 * 210 * @throws \Prophecy\Exception\Prediction\AggregateException If any of registered predictions fail 211 */ 212 public function checkProphecyMethodsPredictions() 213 { 214 $exception = new AggregateException(sprintf("%s:\n", get_class($this->reveal()))); 215 $exception->setObjectProphecy($this); 216 217 foreach ($this->methodProphecies as $prophecies) { 218 foreach ($prophecies as $prophecy) { 219 try { 220 $prophecy->checkPrediction(); 221 } catch (PredictionException $e) { 222 $exception->append($e); 223 } 224 } 225 } 226 227 if (count($exception->getExceptions())) { 228 throw $exception; 229 } 230 } 231 232 /** 233 * Creates new method prophecy using specified method name and arguments. 234 * 235 * @param string $methodName 236 * @param array $arguments 237 * 238 * @return MethodProphecy 239 */ 240 public function __call($methodName, array $arguments) 241 { 242 $arguments = new ArgumentsWildcard($this->revealer->reveal($arguments)); 243 244 foreach ($this->getMethodProphecies($methodName) as $prophecy) { 245 $argumentsWildcard = $prophecy->getArgumentsWildcard(); 246 $comparator = $this->comparatorFactory->getComparatorFor( 247 $argumentsWildcard, $arguments 248 ); 249 250 try { 251 $comparator->assertEquals($argumentsWildcard, $arguments); 252 return $prophecy; 253 } catch (ComparisonFailure $failure) {} 254 } 255 256 return new MethodProphecy($this, $methodName, $arguments); 257 } 258 259 /** 260 * Tries to get property value from double. 261 * 262 * @param string $name 263 * 264 * @return mixed 265 */ 266 public function __get($name) 267 { 268 return $this->reveal()->$name; 269 } 270 271 /** 272 * Tries to set property value to double. 273 * 274 * @param string $name 275 * @param mixed $value 276 */ 277 public function __set($name, $value) 278 { 279 $this->reveal()->$name = $this->revealer->reveal($value); 280 } 281} 282