1 : <?php
2 : /**
3 : * Mockery
4 : *
5 : * LICENSE
6 : *
7 : * This source file is subject to the new BSD license that is bundled
8 : * with this package in the file LICENSE.txt.
9 : * It is also available through the world-wide-web at this URL:
10 : * http://github.com/padraic/mockery/blob/master/LICENSE
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 padraic@php.net so we can send you a copy immediately.
14 : *
15 : * @category Mockery
16 : * @package Mockery
17 : * @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
18 : * @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
19 : */
20 :
21 : namespace Mockery;
22 :
23 : class Generator
24 : {
25 :
26 : /**
27 : * Generates a Mock Object class with all Mockery methods whose
28 : * intent is basically to provide the mock object with the same
29 : * class type hierarchy as a typical instance of the class being
30 : * mocked.
31 : *
32 : * @param string $className
33 : * @param string $mockName
34 : * @param string $allowFinal
35 : * @return string Classname of the mock class created
36 : */
37 : public static function createClassMock($className, $mockName = null,
38 : $allowFinal = false, $block = array(), $makeInstanceMock = false,
39 : $partialMethods = array())
40 : {
41 26 : if (is_null($mockName)) $mockName = uniqid('Mockery_');
42 26 : $definition = '';
43 26 : $inheritance = '';
44 26 : $interfaceInheritance = array();
45 26 : $classData = array();
46 26 : $classNameInherited = '';
47 26 : $classIsFinal = false;
48 26 : $callTypehinting = false;
49 26 : $useStandardMethods = true;
50 26 : if (is_array($className)) {
51 0 : foreach ($className as $interface) {
52 0 : $class = new \ReflectionClass($interface);
53 0 : $classData[] = self::_analyseClass($class, $interface, $allowFinal);
54 0 : }
55 0 : } else {
56 26 : $class = new \ReflectionClass($className);
57 26 : $classData[] = self::_analyseClass($class, $className, $allowFinal);
58 : }
59 26 : foreach ($classData as $data) {
60 26 : if ($data['class']->isInterface()) {
61 0 : $interfaceInheritance[] = $data['className'];
62 26 : } elseif ($data['class']->isFinal() || $data['hasFinalMethods']) {
63 0 : $inheritance = ' extends ' . $data['className'];
64 0 : $classNameInherited = $data['className'];
65 0 : $classIsFinal = true;
66 0 : } else {
67 26 : $inheritance = ' extends ' . $data['className'] . ' implements \Mockery\MockInterface';
68 26 : $classNameInherited = $data['className'];
69 : }
70 26 : }
71 26 : if (count($interfaceInheritance) > 0) {
72 0 : if (!$classIsFinal) $interfaceInheritance[] = '\Mockery\MockInterface';
73 0 : if (strlen($classNameInherited) > 0) $inheritance = ' extends ' . $classNameInherited;
74 0 : $inheritance .= ' implements ' . implode(', ', $interfaceInheritance);
75 0 : }
76 :
77 26 : $definition .= 'class ' . $mockName . $inheritance . PHP_EOL . '{' . PHP_EOL;
78 26 : foreach ($classData as $data) {
79 26 : if (!$data['class']->isFinal() && !$data['hasFinalMethods']) {
80 26 : $result = self::applyMockeryTo($data['class'], $data['publicMethods'], $block, $partialMethods);
81 26 : if ($result['callTypehinting']) $callTypehinting = true;
82 26 : $definition .= $result['definition'];
83 26 : $definition .= self::stubAbstractProtected($data['protectedMethods']);
84 26 : } else {
85 0 : $useStandardMethods = false;
86 : }
87 26 : }
88 26 : if ($useStandardMethods) $definition .= self::_getStandardMethods($callTypehinting, $makeInstanceMock);
89 26 : $definition .= PHP_EOL . '}';
90 26 : eval($definition);
91 26 : return $mockName;
92 : }
93 :
94 : protected static function _analyseClass($class, $className, $allowFinal = false)
95 : {
96 26 : if ($class->isFinal() && !$allowFinal) {
97 0 : throw new \Mockery\Exception(
98 0 : 'The class ' . $className . ' is marked final and its methods'
99 0 : . ' cannot be replaced. Classes marked final can be passed in'
100 0 : . ' to \Mockery::mock() as instantiated objects to create a'
101 0 : . ' partial mock, but only if the mock is not subject to type'
102 0 : . ' hinting checks.'
103 0 : );
104 26 : } elseif ($class->isFinal()) {
105 0 : $className = '\\Mockery\\Mock';
106 0 : }
107 26 : $hasFinalMethods = false;
108 26 : $methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC);
109 26 : $protected = $class->getMethods(\ReflectionMethod::IS_PROTECTED);
110 26 : foreach ($methods as $method) {
111 26 : if ($method->isFinal() && !$allowFinal) {
112 0 : throw new \Mockery\Exception(
113 0 : 'The method ' . $method->getName()
114 0 : . ' is marked final and it is not possible to generate a '
115 0 : . 'mock object with such a method defined. You should instead '
116 0 : . 'pass an instance of this object to Mockery to create a '
117 0 : . 'partial mock.'
118 0 : );
119 26 : } elseif ($method->isFinal()) {
120 0 : $className = '\\Mockery\\Mock';
121 0 : $hasFinalMethods = true;
122 0 : }
123 26 : }
124 : return array(
125 26 : 'class' => $class,
126 26 : 'className' => $className,
127 26 : 'hasFinalMethods' => $hasFinalMethods,
128 26 : 'publicMethods' => $methods,
129 : 'protectedMethods' => $protected
130 26 : );
131 : }
132 :
133 : /**
134 : * Add all Mockery methods for mocks to the class being defined
135 : *
136 : *
137 : */
138 : public static function applyMockeryTo(\ReflectionClass $class,
139 : array $methods, array $block, $partialMethods = array())
140 : {
141 26 : $definition = '';
142 26 : $callTypehinting = false;
143 : /**
144 : * TODO: Worry about all these other method types later.
145 : */
146 26 : foreach ($methods as $method) {
147 26 : if(in_array($method->getName(), $block)) continue;
148 26 : if (count($partialMethods) > 0 && !in_array(strtolower($method->getName()), $partialMethods)) {
149 0 : continue;
150 : }
151 26 : if (!$method->isDestructor()
152 26 : && !$method->isStatic()
153 26 : && $method->getName() !== '__call'
154 26 : && $method->getName() !== '__clone'
155 26 : && $method->getName() !== '__wakeup'
156 26 : && $method->getName() !== '__set'
157 26 : && $method->getName() !== '__get'
158 26 : && $method->getName() !== '__isset') {
159 26 : $definition .= self::_replacePublicMethod($method);
160 26 : }
161 26 : if ($method->getName() == '__call') {
162 26 : $params = $method->getParameters();
163 26 : if ($params[1]->isArray()) {
164 0 : $callTypehinting = true;
165 0 : }
166 26 : }
167 26 : }
168 26 : return array('definition'=>$definition, 'callTypehinting'=>$callTypehinting);
169 : }
170 :
171 : public static function stubAbstractProtected(array $methods)
172 : {
173 26 : $definition = '';
174 26 : foreach ($methods as $method) {
175 0 : if ($method->isAbstract()) {
176 0 : $definition .= self::_replaceProtectedAbstractMethod($method);
177 0 : }
178 26 : }
179 26 : return $definition;
180 : }
181 :
182 : /**
183 : * Attempts to replace defined public (non-static) methods so they all
184 : * redirect to the Mock Object's __call() interceptor
185 : *
186 : * TODO: Add exclusions for partial mock support
187 : */
188 : protected static function _replacePublicMethod(\ReflectionMethod $method)
189 : {
190 26 : $body = '';
191 26 : $name = $method->getName();
192 26 : if ($name !== '__construct' && $method->isPublic()) {
193 : /**
194 : * Purpose of this block is to create an argument array where
195 : * references are preserved (func_get_args() does not preserve
196 : * references)
197 : */
198 : $body = <<<BODY
199 : \$stack = debug_backtrace();
200 : \$args = array();
201 : if (isset(\$stack[0]['args'])) {
202 : for(\$i=0; \$i<count(\$stack[0]['args']); \$i++) {
203 : \$args[\$i] =& \$stack[0]['args'][\$i];
204 : }
205 : }
206 26 : return \$this->__call('$name', \$args);
207 26 : BODY;
208 26 : }
209 26 : $methodParams = self::_renderPublicMethodParameters($method);
210 26 : $paramDef = implode(',', $methodParams);
211 26 : if ($method->isPublic()) {
212 26 : $access = 'public';
213 26 : } elseif($method->isProtected()) {
214 0 : $access = 'protected';
215 0 : } else {
216 0 : $access = 'private';
217 : }
218 26 : if ($method->isStatic()) {
219 0 : $access .= ' static';
220 0 : }
221 26 : return $access . ' function ' . $name . '(' . $paramDef . ')'
222 26 : . '{' . $body . '}';
223 : }
224 :
225 : protected static function _renderPublicMethodParameters(\ReflectionMethod $method)
226 : {
227 26 : $class = $method->getDeclaringClass();
228 26 : if ($class->isInternal()) { // check for parameter overrides for internal PHP classes
229 0 : $paramMap = \Mockery::getConfiguration()
230 0 : ->getInternalClassMethodParamMap($class->getName(), $method->getName());
231 0 : if (!is_null($paramMap)) return $paramMap;
232 0 : }
233 26 : $methodParams = array();
234 26 : $params = $method->getParameters();
235 26 : foreach ($params as $param) {
236 26 : $paramDef = '';
237 26 : if ($param->isArray()) {
238 0 : $paramDef .= 'array ';
239 26 : } elseif ($param->getClass()) {
240 0 : $paramDef .= $param->getClass()->getName() . ' ';
241 0 : }
242 26 : $paramDef .= ($param->isPassedByReference() ? '&' : '') . '$' . $param->getName();
243 26 : if ($param->isDefaultValueAvailable()) {
244 26 : $default = var_export($param->getDefaultValue(), true);
245 26 : if ($default == '') {
246 0 : $default = 'null';
247 0 : }
248 26 : $paramDef .= ' = ' . $default;
249 26 : } else if ($param->isOptional()) {
250 0 : $paramDef .= ' = null';
251 0 : }
252 :
253 26 : $methodParams[] = $paramDef;
254 26 : }
255 26 : return $methodParams;
256 : }
257 :
258 : /**
259 : * Replace abstract protected methods (the only enforceable type outside
260 : * of public methods). The replacement is just a stub that does nothing.
261 : */
262 : protected static function _replaceProtectedAbstractMethod(\ReflectionMethod $method)
263 : {
264 0 : $body = '';
265 0 : $name = $method->getName();
266 0 : $methodParams = array();
267 0 : $params = $method->getParameters();
268 0 : foreach ($params as $param) {
269 0 : $paramDef = '';
270 0 : if ($param->isArray()) {
271 0 : $paramDef .= 'array ';
272 0 : } elseif ($param->getClass()) {
273 0 : $paramDef .= $param->getClass()->getName() . ' ';
274 0 : }
275 0 : $paramDef .= ($param->isPassedByReference() ? '&' : '') . '$' . $param->getName();
276 0 : if ($param->isDefaultValueAvailable()) {
277 0 : $default = var_export($param->getDefaultValue(), true);
278 0 : if ($default == '') {
279 0 : $default = 'null';
280 0 : }
281 0 : $paramDef .= ' = ' . $default;
282 0 : } else if ($param->isOptional()) {
283 0 : $paramDef .= ' = null';
284 0 : }
285 0 : $methodParams[] = $paramDef;
286 0 : }
287 0 : $paramDef = implode(',', $methodParams);
288 0 : $access = 'protected';
289 0 : return $access . ' function ' . $name . '(' . $paramDef . ')'
290 0 : . '{' . $body . '}';
291 : }
292 :
293 : /**
294 : * NOTE: The code below is taken from Mockery\Mock and should
295 : * be an exact copy with only one difference - we define the Mockery\Mock
296 : * constructor as a public init method (since the original class
297 : * constructor is often not replaceable, e.g. for interface adherance)
298 : *
299 : * Return a string def of the standard Mock Object API needed for all mocks
300 : *
301 : */
302 : public static function _getStandardMethods($callTypehint = true, $makeInstanceMock = false)
303 : {
304 26 : $typehint = $callTypehint ? 'array' : '';
305 : $std = <<<MOCK
306 : protected static \$_mockery_staticClassName = '';
307 :
308 : protected \$_mockery_expectations = array();
309 :
310 : protected \$_mockery_lastExpectation = null;
311 :
312 : protected \$_mockery_ignoreMissing = false;
313 :
314 : protected \$_mockery_verified = false;
315 :
316 : protected \$_mockery_name = null;
317 :
318 : protected \$_mockery_allocatedOrder = 0;
319 :
320 : protected \$_mockery_currentOrder = 0;
321 :
322 : protected \$_mockery_groups = array();
323 :
324 : protected \$_mockery_container = null;
325 :
326 : protected \$_mockery_partial = null;
327 :
328 : protected \$_mockery_disableExpectationMatching = false;
329 :
330 : protected \$_mockery_mockableMethods = array();
331 :
332 : protected \$_mockery_mockableProperties = array();
333 :
334 : public function mockery_init(\$name, \Mockery\Container \$container = null, \$partialObject = null)
335 : {
336 : \$this->_mockery_name = \$name;
337 : if(is_null(\$container)) {
338 : \$container = new \Mockery\Container;
339 : }
340 : \$this->_mockery_container = \$container;
341 : if (!is_null(\$partialObject)) {
342 : \$this->_mockery_partial = \$partialObject;
343 : }
344 : if (!\Mockery::getConfiguration()->mockingNonExistentMethodsAllowed()) {
345 : if (isset(\$this->_mockery_partial)) {
346 : \$reflected = new \ReflectionObject(\$this->_mockery_partial);
347 : } else {
348 : \$reflected = new \ReflectionClass(\$this->_mockery_name);
349 : }
350 : \$methods = \$reflected->getMethods(\ReflectionMethod::IS_PUBLIC);
351 : foreach (\$methods as \$method) {
352 : if (!\$method->isStatic()) \$this->_mockery_mockableMethods[] = \$method->getName();
353 : }
354 : }
355 : }
356 :
357 : public function shouldReceive()
358 : {
359 : \$self = \$this;
360 : \$lastExpectation = \Mockery::parseShouldReturnArgs(
361 : \$this, func_get_args(), function(\$method) use (\$self) {
362 : \$director = \$self->mockery_getExpectationsFor(\$method);
363 : if (!\$director) {
364 : \$director = new \Mockery\ExpectationDirector(\$method, \$self);
365 : \$self->mockery_setExpectationsFor(\$method, \$director);
366 : }
367 : \$expectation = new \Mockery\Expectation(\$self, \$method);
368 : \$director->addExpectation(\$expectation);
369 : return \$expectation;
370 : }
371 : );
372 : return \$lastExpectation;
373 : }
374 :
375 : public function shouldIgnoreMissing()
376 : {
377 : \$this->_mockery_ignoreMissing = true;
378 : }
379 :
380 : public function shouldExpect(Closure \$closure)
381 : {
382 : \$recorder = new \Mockery\Recorder(\$this, \$this->_mockery_partial);
383 : \$this->_mockery_disableExpectationMatching = true;
384 : \$closure(\$recorder);
385 : \$this->_mockery_disableExpectationMatching = false;
386 : return \$this;
387 : }
388 :
389 : public function byDefault()
390 : {
391 : foreach (\$this->_mockery_expectations as \$director) {
392 : \$exps = \$director->getExpectations();
393 : foreach (\$exps as \$exp) {
394 : \$exp->byDefault();
395 : }
396 : }
397 : return \$this;
398 : }
399 :
400 : public function __call(\$method, $typehint \$args)
401 : {
402 : if (isset(\$this->_mockery_expectations[\$method])
403 : && !\$this->_mockery_disableExpectationMatching) {
404 : \$handler = \$this->_mockery_expectations[\$method];
405 : return \$handler->call(\$args);
406 : } elseif (!is_null(\$this->_mockery_partial) && method_exists(\$this->_mockery_partial, \$method)) {
407 : return call_user_func_array(array(\$this->_mockery_partial, \$method), \$args);
408 : } elseif (\$this->_mockery_ignoreMissing) {
409 : \$return = new \Mockery\Undefined;
410 : return \$return;
411 : }
412 : throw new \BadMethodCallException(
413 : 'Method ' . \$this->_mockery_name . '::' . \$method . '() does not exist on this mock object'
414 : );
415 : }
416 :
417 : public function mockery_verify()
418 : {
419 : if (\$this->_mockery_verified) return true;
420 : if (isset(\$this->_mockery_ignoreVerification)
421 : && \$this->_mockery_ignoreVerification == true) {
422 : return true;
423 : }
424 : \$this->_mockery_verified = true;
425 : foreach(\$this->_mockery_expectations as \$director) {
426 : \$director->verify();
427 : }
428 : }
429 :
430 : public function mockery_teardown()
431 : {
432 :
433 : }
434 :
435 : public function mockery_allocateOrder()
436 : {
437 : \$this->_mockery_allocatedOrder += 1;
438 : return \$this->_mockery_allocatedOrder;
439 : }
440 :
441 : public function mockery_setGroup(\$group, \$order)
442 : {
443 : \$this->_mockery_groups[\$group] = \$order;
444 : }
445 :
446 : public function mockery_getGroups()
447 : {
448 : return \$this->_mockery_groups;
449 : }
450 :
451 : public function mockery_setCurrentOrder(\$order)
452 : {
453 : \$this->_mockery_currentOrder = \$order;
454 : return \$this->_mockery_currentOrder;
455 : }
456 :
457 : public function mockery_getCurrentOrder()
458 : {
459 : return \$this->_mockery_currentOrder;
460 : }
461 :
462 : public function mockery_validateOrder(\$method, \$order)
463 : {
464 : if (isset(\$this->_mockery_ignoreVerification)
465 : && \$this->_mockery_ignoreVerification === false) {
466 : return;
467 : }
468 : if (\$order < \$this->_mockery_currentOrder) {
469 : throw new \Mockery\Exception(
470 : 'Method ' . \$this->_mockery_name . '::' . \$method . '()'
471 : . ' called out of order: expected order '
472 : . \$order . ', was ' . \$this->_mockery_currentOrder
473 : );
474 : }
475 : \$this->mockery_setCurrentOrder(\$order);
476 : }
477 :
478 : public function mockery_setExpectationsFor(\$method, \Mockery\ExpectationDirector \$director)
479 : {
480 : \$this->_mockery_expectations[\$method] = \$director;
481 : }
482 :
483 : public function mockery_getExpectationsFor(\$method)
484 : {
485 : if (isset(\$this->_mockery_expectations[\$method])) {
486 : return \$this->_mockery_expectations[\$method];
487 : }
488 : }
489 :
490 : public function mockery_findExpectation(\$method, array \$args)
491 : {
492 : if (!isset(\$this->_mockery_expectations[\$method])) {
493 : return null;
494 : }
495 : \$director = \$this->_mockery_expectations[\$method];
496 : return \$director->findExpectation(\$args);
497 : }
498 :
499 : public function mockery_getContainer()
500 : {
501 : return \$this->_mockery_container;
502 : }
503 :
504 : public function mockery_getName()
505 : {
506 : return \$this->_mockery_name;
507 : }
508 :
509 : public function mockery_getMockableMethods()
510 : {
511 : return \$this->_mockery_mockableMethods;
512 : }
513 :
514 : public function mockery_getMockableProperties()
515 : {
516 : return \$this->_mockery_mockableProperties;
517 : }
518 :
519 : //** Everything below this line is not copied from/needed for Mockery/Mock **//
520 :
521 : public function __wakeup()
522 : {
523 : /**
524 : * This does not add __wakeup method support. It's a blind method and any
525 : * expected __wakeup work will NOT be performed. It merely cuts off
526 : * annoying errors where a __wakeup exists but is not essential when
527 : * mocking
528 : */
529 : }
530 :
531 26 : public static function __callStatic(\$method, $typehint \$args)
532 : {
533 : try {
534 : \$associatedRealObject = \Mockery::fetchMock(__CLASS__);
535 : return \$associatedRealObject->__call(\$method, \$args);
536 : } catch (\BadMethodCallException \$e) {
537 : throw new \BadMethodCallException(
538 : 'Static method ' . \$associatedRealObject->mockery_getName() . '::' . \$method
539 : . '() does not exist on this mock object'
540 : );
541 : }
542 : }
543 :
544 : public function mockery_getExpectations()
545 : {
546 : return \$this->_mockery_expectations;
547 : }
548 26 :
549 26 : MOCK;
550 : /**
551 : * Note: An instance mock allows the declaration of an instantiable class
552 : * which imports cloned expectations from an existing mock object. In effect
553 : * it enables pseudo-overloading of the "new" operator.
554 : */
555 26 : if ($makeInstanceMock) {
556 : $mim = <<<MOCK
557 :
558 : protected \$_mockery_ignoreVerification = true;
559 :
560 : public function __construct()
561 : {
562 : \$this->_mockery_ignoreVerification = false;
563 : \$associatedRealObject = \Mockery::fetchMock(__CLASS__);
564 : \$directors = \$associatedRealObject->mockery_getExpectations();
565 : foreach (\$directors as \$method=>\$director) {
566 : \$expectations = \$director->getExpectations();
567 : // get the director method needed
568 : \$existingDirector = \$this->mockery_getExpectationsFor(\$method);
569 : if (!\$existingDirector) {
570 : \$existingDirector = new \Mockery\ExpectationDirector(\$method, \$this);
571 : \$this->mockery_setExpectationsFor(\$method, \$existingDirector);
572 : }
573 : foreach (\$expectations as \$expectation) {
574 : \$clonedExpectation = clone \$expectation;
575 : \$existingDirector->addExpectation(\$clonedExpectation);
576 : }
577 : }
578 : \Mockery::getContainer()->rememberMock(\$this);
579 : }
580 0 : MOCK;
581 0 : $std .= $mim;
582 0 : }
583 26 : return $std;
584 : }
585 :
586 :
587 : }
|