1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
20:
21: namespace Mockery;
22:
23: use Mockery\Generator\Generator;
24: use Mockery\Generator\MockConfigurationBuilder;
25: use Mockery\Loader\Loader as LoaderInterface;
26:
27: class Container
28: {
29: const BLOCKS = \Mockery::BLOCKS;
30:
31: 32: 33: 34: 35:
36: protected $_mocks = array();
37:
38: 39: 40: 41: 42:
43: protected $_allocatedOrder = 0;
44:
45: 46: 47: 48: 49:
50: protected $_currentOrder = 0;
51:
52: 53: 54: 55: 56:
57: protected $_groups = array();
58:
59: 60: 61:
62: protected $_generator;
63:
64: 65: 66:
67: protected $_loader;
68:
69: 70: 71:
72: protected $_namedMocks = array();
73:
74: public function __construct(Generator $generator = null, LoaderInterface $loader = null)
75: {
76: $this->_generator = $generator ?: \Mockery::getDefaultGenerator();
77: $this->_loader = $loader ?: \Mockery::getDefaultLoader();
78: }
79:
80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91:
92: public function mock()
93: {
94: $expectationClosure = null;
95: $quickdefs = array();
96: $constructorArgs = null;
97: $blocks = array();
98: $args = func_get_args();
99:
100: if (count($args) > 1) {
101: $finalArg = end($args);
102: reset($args);
103: if (is_callable($finalArg) && is_object($finalArg)) {
104: $expectationClosure = array_pop($args);
105: }
106: }
107:
108: $builder = new MockConfigurationBuilder();
109:
110: foreach ($args as $k => $arg) {
111: if ($arg instanceof MockConfigurationBuilder) {
112: $builder = $arg;
113: unset($args[$k]);
114: }
115: }
116: reset($args);
117:
118: $builder->setParameterOverrides(\Mockery::getConfiguration()->getInternalClassMethodParamMaps());
119:
120: while (count($args) > 0) {
121: $arg = current($args);
122:
123: if (is_string($arg) && strpos($arg, ',') && !strpos($arg, ']')) {
124: $interfaces = explode(',', str_replace(' ', '', $arg));
125: foreach ($interfaces as $i) {
126: if (!interface_exists($i, true) && !class_exists($i, true)) {
127: throw new \Mockery\Exception(
128: 'Class name follows the format for defining multiple'
129: . ' interfaces, however one or more of the interfaces'
130: . ' do not exist or are not included, or the base class'
131: . ' (which you may omit from the mock definition) does not exist'
132: );
133: }
134: }
135: $builder->addTargets($interfaces);
136: array_shift($args);
137:
138: continue;
139: } elseif (is_string($arg) && substr($arg, 0, 6) == 'alias:') {
140: $name = array_shift($args);
141: $name = str_replace('alias:', '', $name);
142: $builder->addTarget('stdClass');
143: $builder->setName($name);
144: continue;
145: } elseif (is_string($arg) && substr($arg, 0, 9) == 'overload:') {
146: $name = array_shift($args);
147: $name = str_replace('overload:', '', $name);
148: $builder->setInstanceMock(true);
149: $builder->addTarget('stdClass');
150: $builder->setName($name);
151: continue;
152: } elseif (is_string($arg) && substr($arg, strlen($arg)-1, 1) == ']') {
153: $parts = explode('[', $arg);
154: if (!class_exists($parts[0], true) && !interface_exists($parts[0], true)) {
155: throw new \Mockery\Exception('Can only create a partial mock from'
156: . ' an existing class or interface');
157: }
158: $class = $parts[0];
159: $parts[1] = str_replace(' ','', $parts[1]);
160: $partialMethods = explode(',', strtolower(rtrim($parts[1], ']')));
161: $builder->addTarget($class);
162: $builder->setWhiteListedMethods($partialMethods);
163: array_shift($args);
164: continue;
165: } elseif (is_string($arg) && (class_exists($arg, true) || interface_exists($arg, true))) {
166: $class = array_shift($args);
167: $builder->addTarget($class);
168: continue;
169: } elseif (is_string($arg)) {
170: $class = array_shift($args);
171: $builder->addTarget($class);
172: continue;
173: } elseif (is_object($arg)) {
174: $partial = array_shift($args);
175: $builder->addTarget($partial);
176: continue;
177: } elseif (is_array($arg) && !empty($arg) && array_keys($arg) !== range(0, count($arg) - 1)) {
178:
179: if(array_key_exists(self::BLOCKS, $arg)) $blocks = $arg[self::BLOCKS]; unset($arg[self::BLOCKS]);
180: $quickdefs = array_shift($args);
181: continue;
182: } elseif (is_array($arg)) {
183: $constructorArgs = array_shift($args);
184: continue;
185: }
186:
187: throw new \Mockery\Exception(
188: 'Unable to parse arguments sent to '
189: . get_class($this) . '::mock()'
190: );
191: }
192:
193: $builder->addBlackListedMethods($blocks);
194:
195: if (!is_null($constructorArgs)) {
196: $builder->addBlackListedMethod("__construct");
197: }
198:
199: if (!empty($partialMethods) && $constructorArgs === null) {
200: $constructorArgs = array();
201: }
202:
203: $config = $builder->getMockConfiguration();
204:
205: $this->checkForNamedMockClashes($config);
206:
207: $def = $this->getGenerator()->generate($config);
208:
209: if (class_exists($def->getClassName(), $attemptAutoload = false)) {
210: $rfc = new \ReflectionClass($def->getClassName());
211: if (!$rfc->implementsInterface("Mockery\MockInterface")) {
212: throw new \Mockery\Exception\RuntimeException("Could not load mock {$def->getClassName()}, class already exists");
213: }
214: }
215:
216: $this->getLoader()->load($def);
217:
218: $mock = $this->_getInstance($def->getClassName(), $constructorArgs);
219: $mock->mockery_init($this, $config->getTargetObject());
220:
221: if (!empty($quickdefs)) {
222: $mock->shouldReceive($quickdefs)->byDefault();
223: }
224: if (!empty($expectationClosure)) {
225: $expectationClosure($mock);
226: }
227: $this->rememberMock($mock);
228: return $mock;
229: }
230:
231: public function instanceMock()
232: {
233:
234: }
235:
236: public function getLoader()
237: {
238: return $this->_loader;
239: }
240:
241: public function getGenerator()
242: {
243: return $this->_generator;
244: }
245:
246: 247: 248: 249:
250: public function getKeyOfDemeterMockFor($method)
251: {
252:
253: $keys = array_keys($this->_mocks);
254: $match = preg_grep("/__demeter_{$method}$/", $keys);
255: if (count($match) == 1) {
256: $res = array_values($match);
257: if (count($res) > 0) {
258: return $res[0];
259: }
260: }
261: return null;
262: }
263:
264: 265: 266:
267: public function getMocks()
268: {
269: return $this->_mocks;
270: }
271:
272: 273: 274: 275: 276: 277:
278: public function mockery_teardown()
279: {
280: try {
281: $this->mockery_verify();
282: } catch (\Exception $e) {
283: $this->mockery_close();
284: throw $e;
285: }
286: }
287:
288: 289: 290: 291: 292:
293: public function mockery_verify()
294: {
295: foreach($this->_mocks as $mock) {
296: $mock->mockery_verify();
297: }
298: }
299:
300: 301: 302: 303: 304:
305: public function mockery_close()
306: {
307: foreach($this->_mocks as $mock) {
308: $mock->mockery_teardown();
309: }
310: $this->_mocks = array();
311: }
312:
313: 314: 315: 316: 317:
318: public function mockery_allocateOrder()
319: {
320: $this->_allocatedOrder += 1;
321: return $this->_allocatedOrder;
322: }
323:
324: 325: 326: 327: 328: 329:
330: public function mockery_setGroup($group, $order)
331: {
332: $this->_groups[$group] = $order;
333: }
334:
335: 336: 337: 338: 339:
340: public function mockery_getGroups()
341: {
342: return $this->_groups;
343: }
344:
345: 346: 347: 348: 349: 350:
351: public function mockery_setCurrentOrder($order)
352: {
353: $this->_currentOrder = $order;
354: return $this->_currentOrder;
355: }
356:
357: 358: 359: 360: 361:
362: public function mockery_getCurrentOrder()
363: {
364: return $this->_currentOrder;
365: }
366:
367: 368: 369: 370: 371: 372: 373: 374:
375: public function mockery_validateOrder($method, $order, \Mockery\MockInterface $mock)
376: {
377: if ($order < $this->_currentOrder) {
378: $exception = new \Mockery\Exception\InvalidOrderException(
379: 'Method ' . $method . ' called out of order: expected order '
380: . $order . ', was ' . $this->_currentOrder
381: );
382: $exception->setMock($mock)
383: ->setMethodName($method)
384: ->setExpectedOrder($order)
385: ->setActualOrder($this->_currentOrder);
386: throw $exception;
387: }
388: $this->mockery_setCurrentOrder($order);
389: }
390:
391: 392: 393: 394: 395:
396: public function mockery_getExpectationCount()
397: {
398: $count = 0;
399: foreach($this->_mocks as $mock) {
400: $count += $mock->mockery_getExpectationCount();
401: }
402: return $count;
403: }
404:
405: 406: 407: 408: 409: 410:
411: public function rememberMock(\Mockery\MockInterface $mock)
412: {
413: if (!isset($this->_mocks[get_class($mock)])) {
414: $this->_mocks[get_class($mock)] = $mock;
415: } else {
416: 417: 418: 419:
420: $this->_mocks[] = $mock;
421: }
422: return $mock;
423: }
424:
425: 426: 427: 428: 429: 430: 431: 432:
433: public function self()
434: {
435: $mocks = array_values($this->_mocks);
436: $index = count($mocks) - 1;
437: return $mocks[$index];
438: }
439:
440: 441: 442: 443: 444: 445:
446: public function fetchMock($reference)
447: {
448: if (isset($this->_mocks[$reference])) return $this->_mocks[$reference];
449: }
450:
451: protected function _getInstance($mockName, $constructorArgs = null)
452: {
453: $r = new \ReflectionClass($mockName);
454:
455: if (null === $r->getConstructor()) {
456: $return = new $mockName;
457: return $return;
458: }
459:
460: if ($constructorArgs !== null) {
461: return $r->newInstanceArgs($constructorArgs);
462: }
463:
464: $isInternal = $r->isInternal();
465: $child = $r;
466: while (!$isInternal && $parent = $child->getParentClass()) {
467: $isInternal = $parent->isInternal();
468: $child = $parent;
469: }
470:
471: try {
472: if (version_compare(PHP_VERSION, '5.4') < 0 || $isInternal) {
473: $return = unserialize(sprintf(
474: '%s:%d:"%s":0:{}',
475:
476: (version_compare(PHP_VERSION, '5.4', '>') && $r->implementsInterface('Serializable') ? 'C' : 'O'),
477: strlen($mockName),
478: $mockName)
479: );
480: } else {
481: $return = $r->newInstanceWithoutConstructor();
482: }
483: } catch (\Exception $ex) {
484: $internalMockName = $mockName . '_Internal';
485:
486: if (!class_exists($internalMockName)) {
487: eval("class $internalMockName extends $mockName {" .
488: 'public function __construct() {}' .
489: '}');
490: }
491:
492: $return = new $internalMockName();
493: }
494:
495: return $return;
496: }
497:
498: 499: 500: 501: 502:
503: public function declareClass($fqcn)
504: {
505: if (false !== strpos($fqcn, '/')) {
506: throw new \Mockery\Exception(
507: 'Class name contains a forward slash instead of backslash needed '
508: . 'when employing namespaces'
509: );
510: }
511: if (false !== strpos($fqcn, "\\")) {
512: $parts = array_filter(explode("\\", $fqcn), function ($part) {
513: return $part !== "";
514: });
515: $cl = array_pop($parts);
516: $ns = implode("\\", $parts);
517: eval(" namespace $ns { class $cl {} }");
518: } else {
519: eval(" class $fqcn {} ");
520: }
521: }
522:
523: protected function checkForNamedMockClashes($config)
524: {
525: $name = $config->getName();
526:
527: if (!$name) {
528: return;
529: }
530:
531: $hash = $config->getHash();
532:
533: if (isset($this->_namedMocks[$name])) {
534: if ($hash !== $this->_namedMocks[$name]) {
535: throw new \Mockery\Exception(
536: "The mock named '$name' has been already defined with a different mock configuration"
537: );
538: }
539: }
540:
541: $this->_namedMocks[$name] = $hash;
542: }
543: }
544: