Blame | Letzte Änderung | Log anzeigen | RSS feed
<?php/*** Mockery** LICENSE** This source file is subject to the new BSD license that is bundled* with this package in the file LICENSE.txt.* It is also available through the world-wide-web at this URL:* http://github.com/padraic/mockery/blob/master/LICENSE* If you did not receive a copy of the license and are unable to* obtain it through the world-wide-web, please send an email* to padraic@php.net so we can send you a copy immediately.** @category Mockery* @package Mockery* @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)* @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License*/namespace Mockery;use Mockery\Generator\Generator;use Mockery\Generator\MockConfigurationBuilder;use Mockery\Loader\Loader as LoaderInterface;class Container{const BLOCKS = \Mockery::BLOCKS;/*** Store of mock objects** @var array*/protected $_mocks = array();/*** Order number of allocation** @var int*/protected $_allocatedOrder = 0;/*** Current ordered number** @var int*/protected $_currentOrder = 0;/*** Ordered groups** @var array*/protected $_groups = array();/*** @var Generator*/protected $_generator;/*** @var LoaderInterface*/protected $_loader;/*** @var array*/protected $_namedMocks = array();public function __construct(Generator $generator = null, LoaderInterface $loader = null){$this->_generator = $generator ?: \Mockery::getDefaultGenerator();$this->_loader = $loader ?: \Mockery::getDefaultLoader();}/*** Generates a new mock object for this container** I apologies in advance for this. A God Method just fits the API which* doesn't require differentiating between classes, interfaces, abstracts,* names or partials - just so long as it's something that can be mocked.* I'll refactor it one day so it's easier to follow.** @param array ...$args** @return Mock* @throws Exception\RuntimeException*/public function mock(...$args){$expectationClosure = null;$quickdefs = array();$constructorArgs = null;$blocks = array();$class = null;if (count($args) > 1) {$finalArg = end($args);reset($args);if (is_callable($finalArg) && is_object($finalArg)) {$expectationClosure = array_pop($args);}}$builder = new MockConfigurationBuilder();foreach ($args as $k => $arg) {if ($arg instanceof MockConfigurationBuilder) {$builder = $arg;unset($args[$k]);}}reset($args);$builder->setParameterOverrides(\Mockery::getConfiguration()->getInternalClassMethodParamMaps());$builder->setConstantsMap(\Mockery::getConfiguration()->getConstantsMap());while (count($args) > 0) {$arg = array_shift($args);// check for multiple interfacesif (is_string($arg)) {foreach (explode('|', $arg) as $type) {if ($arg === 'null') {// skip PHP 8 'null's} elseif (strpos($type, ',') && !strpos($type, ']')) {$interfaces = explode(',', str_replace(' ', '', $type));$builder->addTargets($interfaces);} elseif (substr($type, 0, 6) == 'alias:') {$type = str_replace('alias:', '', $type);$builder->addTarget('stdClass');$builder->setName($type);} elseif (substr($type, 0, 9) == 'overload:') {$type = str_replace('overload:', '', $type);$builder->setInstanceMock(true);$builder->addTarget('stdClass');$builder->setName($type);} elseif (substr($type, strlen($type)-1, 1) == ']') {$parts = explode('[', $type);if (!class_exists($parts[0], true) && !interface_exists($parts[0], true)) {throw new \Mockery\Exception('Can only create a partial mock from'. ' an existing class or interface');}$class = $parts[0];$parts[1] = str_replace(' ', '', $parts[1]);$partialMethods = array_filter(explode(',', strtolower(rtrim($parts[1], ']'))));$builder->addTarget($class);foreach ($partialMethods as $partialMethod) {if ($partialMethod[0] === '!') {$builder->addBlackListedMethod(substr($partialMethod, 1));continue;}$builder->addWhiteListedMethod($partialMethod);}} elseif (class_exists($type, true) || interface_exists($type, true) || trait_exists($type, true)) {$builder->addTarget($type);} elseif (!\Mockery::getConfiguration()->mockingNonExistentMethodsAllowed() && (!class_exists($type, true) && !interface_exists($type, true))) {throw new \Mockery\Exception("Mockery can't find '$type' so can't mock it");} else {if (!$this->isValidClassName($type)) {throw new \Mockery\Exception('Class name contains invalid characters');}$builder->addTarget($type);}break; // unions are "sum" types and not "intersections", and so we must only process the first part}} elseif (is_object($arg)) {$builder->addTarget($arg);} elseif (is_array($arg)) {if (!empty($arg) && array_keys($arg) !== range(0, count($arg) - 1)) {// if associative arrayif (array_key_exists(self::BLOCKS, $arg)) {$blocks = $arg[self::BLOCKS];}unset($arg[self::BLOCKS]);$quickdefs = $arg;} else {$constructorArgs = $arg;}} else {throw new \Mockery\Exception('Unable to parse arguments sent to '. get_class($this) . '::mock()');}}$builder->addBlackListedMethods($blocks);if (!is_null($constructorArgs)) {$builder->addBlackListedMethod("__construct"); // we need to pass through} else {$builder->setMockOriginalDestructor(true);}if (!empty($partialMethods) && $constructorArgs === null) {$constructorArgs = array();}$config = $builder->getMockConfiguration();$this->checkForNamedMockClashes($config);$def = $this->getGenerator()->generate($config);if (class_exists($def->getClassName(), $attemptAutoload = false)) {$rfc = new \ReflectionClass($def->getClassName());if (!$rfc->implementsInterface("Mockery\LegacyMockInterface")) {throw new \Mockery\Exception\RuntimeException("Could not load mock {$def->getClassName()}, class already exists");}}$this->getLoader()->load($def);$mock = $this->_getInstance($def->getClassName(), $constructorArgs);$mock->mockery_init($this, $config->getTargetObject(), $config->isInstanceMock());if (!empty($quickdefs)) {if (\Mockery::getConfiguration()->getQuickDefinitions()->shouldBeCalledAtLeastOnce()) {$mock->shouldReceive($quickdefs)->atLeast()->once();} else {$mock->shouldReceive($quickdefs)->byDefault();}}if (!empty($expectationClosure)) {$expectationClosure($mock);}$this->rememberMock($mock);return $mock;}public function instanceMock(){}public function getLoader(){return $this->_loader;}public function getGenerator(){return $this->_generator;}/*** @param string $method* @param string $parent* @return string|null*/public function getKeyOfDemeterMockFor($method, $parent){$keys = array_keys($this->_mocks);$match = preg_grep("/__demeter_" . md5($parent) . "_{$method}$/", $keys);if (count($match) == 1) {$res = array_values($match);if (count($res) > 0) {return $res[0];}}return null;}/*** @return array*/public function getMocks(){return $this->_mocks;}/*** Tear down tasks for this container** @throws \Exception* @return void*/public function mockery_teardown(){try {$this->mockery_verify();} catch (\Exception $e) {$this->mockery_close();throw $e;}}/*** Verify the container mocks** @return void*/public function mockery_verify(){foreach ($this->_mocks as $mock) {$mock->mockery_verify();}}/*** Retrieves all exceptions thrown by mocks** @return array*/public function mockery_thrownExceptions(){$e = [];foreach ($this->_mocks as $mock) {$e = array_merge($e, $mock->mockery_thrownExceptions());}return $e;}/*** Reset the container to its original state** @return void*/public function mockery_close(){foreach ($this->_mocks as $mock) {$mock->mockery_teardown();}$this->_mocks = array();}/*** Fetch the next available allocation order number** @return int*/public function mockery_allocateOrder(){$this->_allocatedOrder += 1;return $this->_allocatedOrder;}/*** Set ordering for a group** @param mixed $group* @param int $order*/public function mockery_setGroup($group, $order){$this->_groups[$group] = $order;}/*** Fetch array of ordered groups** @return array*/public function mockery_getGroups(){return $this->_groups;}/*** Set current ordered number** @param int $order* @return int The current order number that was set*/public function mockery_setCurrentOrder($order){$this->_currentOrder = $order;return $this->_currentOrder;}/*** Get current ordered number** @return int*/public function mockery_getCurrentOrder(){return $this->_currentOrder;}/*** Validate the current mock's ordering** @param string $method* @param int $order* @throws \Mockery\Exception* @return void*/public function mockery_validateOrder($method, $order, \Mockery\LegacyMockInterface $mock){if ($order < $this->_currentOrder) {$exception = new \Mockery\Exception\InvalidOrderException('Method ' . $method . ' called out of order: expected order '. $order . ', was ' . $this->_currentOrder);$exception->setMock($mock)->setMethodName($method)->setExpectedOrder($order)->setActualOrder($this->_currentOrder);throw $exception;}$this->mockery_setCurrentOrder($order);}/*** Gets the count of expectations on the mocks** @return int*/public function mockery_getExpectationCount(){$count = 0;foreach ($this->_mocks as $mock) {$count += $mock->mockery_getExpectationCount();}return $count;}/*** Store a mock and set its container reference** @param \Mockery\Mock $mock* @return \Mockery\LegacyMockInterface|\Mockery\MockInterface*/public function rememberMock(\Mockery\LegacyMockInterface $mock){if (!isset($this->_mocks[get_class($mock)])) {$this->_mocks[get_class($mock)] = $mock;} else {/*** This condition triggers for an instance mock where origin mock* is already remembered*/$this->_mocks[] = $mock;}return $mock;}/*** Retrieve the last remembered mock object, which is the same as saying* retrieve the current mock being programmed where you have yet to call* mock() to change it - thus why the method name is "self" since it will be* be used during the programming of the same mock.** @return \Mockery\Mock*/public function self(){$mocks = array_values($this->_mocks);$index = count($mocks) - 1;return $mocks[$index];}/*** Return a specific remembered mock according to the array index it* was stored to in this container instance** @return \Mockery\Mock*/public function fetchMock($reference){if (isset($this->_mocks[$reference])) {return $this->_mocks[$reference];}}protected function _getInstance($mockName, $constructorArgs = null){if ($constructorArgs !== null) {$r = new \ReflectionClass($mockName);return $r->newInstanceArgs($constructorArgs);}try {$instantiator = new Instantiator();$instance = $instantiator->instantiate($mockName);} catch (\Exception $ex) {$internalMockName = $mockName . '_Internal';if (!class_exists($internalMockName)) {eval("class $internalMockName extends $mockName {" .'public function __construct() {}' .'}');}$instance = new $internalMockName();}return $instance;}protected function checkForNamedMockClashes($config){$name = $config->getName();if (!$name) {return;}$hash = $config->getHash();if (isset($this->_namedMocks[$name])) {if ($hash !== $this->_namedMocks[$name]) {throw new \Mockery\Exception("The mock named '$name' has been already defined with a different mock configuration");}}$this->_namedMocks[$name] = $hash;}/*** see http://php.net/manual/en/language.oop5.basic.php* @param string $className* @return bool*/public function isValidClassName($className){$pos = strpos($className, '\\');if ($pos === 0) {$className = substr($className, 1); // remove the first backslash}// all the namespaces and class name should match the regex$invalidNames = array_filter(explode('\\', $className), function ($name) {return !preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $name);});return empty($invalidNames);}}