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\Generator;/*** This class describes the configuration of mocks and hides away some of the* reflection implementation*/class MockConfiguration{/*** A class that we'd like to mock*/protected $targetClass;protected $targetClassName;/*** A number of interfaces we'd like to mock, keyed by name to attempt to* keep unique*/protected $targetInterfaces = array();protected $targetInterfaceNames = array();/*** A number of traits we'd like to mock, keyed by name to attempt to* keep unique*/protected $targetTraits = array();protected $targetTraitNames = array();/*** An object we'd like our mock to proxy to*/protected $targetObject;/*** The class name we'd like to use for a generated mock*/protected $name;/*** Methods that should specifically not be mocked** This is currently populated with stuff we don't know how to deal with,* should really be somewhere else*/protected $blackListedMethods = array();/*** If not empty, only these methods will be mocked*/protected $whiteListedMethods = array();/*** An instance mock is where we override the original class before it's* autoloaded*/protected $instanceMock = false;/*** Param overrides*/protected $parameterOverrides = array();/*** Instance cache of all methods*/protected $allMethods;/*** If true, overrides original class destructor*/protected $mockOriginalDestructor = false;protected $constantsMap = array();public function __construct(array $targets = array(),array $blackListedMethods = array(),array $whiteListedMethods = array(),$name = null,$instanceMock = false,array $parameterOverrides = array(),$mockOriginalDestructor = false,array $constantsMap = array()) {$this->addTargets($targets);$this->blackListedMethods = $blackListedMethods;$this->whiteListedMethods = $whiteListedMethods;$this->name = $name;$this->instanceMock = $instanceMock;$this->parameterOverrides = $parameterOverrides;$this->mockOriginalDestructor = $mockOriginalDestructor;$this->constantsMap = $constantsMap;}/*** Attempt to create a hash of the configuration, in order to allow caching** @TODO workout if this will work** @return string*/public function getHash(){$vars = array('targetClassName' => $this->targetClassName,'targetInterfaceNames' => $this->targetInterfaceNames,'targetTraitNames' => $this->targetTraitNames,'name' => $this->name,'blackListedMethods' => $this->blackListedMethods,'whiteListedMethod' => $this->whiteListedMethods,'instanceMock' => $this->instanceMock,'parameterOverrides' => $this->parameterOverrides,'mockOriginalDestructor' => $this->mockOriginalDestructor);return md5(serialize($vars));}/*** Gets a list of methods from the classes, interfaces and objects and* filters them appropriately. Lot's of filtering going on, perhaps we could* have filter classes to iterate through*/public function getMethodsToMock(){$methods = $this->getAllMethods();foreach ($methods as $key => $method) {if ($method->isFinal()) {unset($methods[$key]);}}/*** Whitelist trumps everything else*/if (count($this->getWhiteListedMethods())) {$whitelist = array_map('strtolower', $this->getWhiteListedMethods());$methods = array_filter($methods, function ($method) use ($whitelist) {return $method->isAbstract() || in_array(strtolower($method->getName()), $whitelist);});return $methods;}/*** Remove blacklisted methods*/if (count($this->getBlackListedMethods())) {$blacklist = array_map('strtolower', $this->getBlackListedMethods());$methods = array_filter($methods, function ($method) use ($blacklist) {return !in_array(strtolower($method->getName()), $blacklist);});}/*** Internal objects can not be instantiated with newInstanceArgs and if* they implement Serializable, unserialize will have to be called. As* such, we can't mock it and will need a pass to add a dummy* implementation*/if ($this->getTargetClass()&& $this->getTargetClass()->implementsInterface("Serializable")&& $this->getTargetClass()->hasInternalAncestor()) {$methods = array_filter($methods, function ($method) {return $method->getName() !== "unserialize";});}return array_values($methods);}/*** We declare the __call method to handle undefined stuff, if the class* we're mocking has also defined it, we need to comply with their interface*/public function requiresCallTypeHintRemoval(){foreach ($this->getAllMethods() as $method) {if ("__call" === $method->getName()) {$params = $method->getParameters();return !$params[1]->isArray();}}return false;}/*** We declare the __callStatic method to handle undefined stuff, if the class* we're mocking has also defined it, we need to comply with their interface*/public function requiresCallStaticTypeHintRemoval(){foreach ($this->getAllMethods() as $method) {if ("__callStatic" === $method->getName()) {$params = $method->getParameters();return !$params[1]->isArray();}}return false;}public function rename($className){$targets = array();if ($this->targetClassName) {$targets[] = $this->targetClassName;}if ($this->targetInterfaceNames) {$targets = array_merge($targets, $this->targetInterfaceNames);}if ($this->targetTraitNames) {$targets = array_merge($targets, $this->targetTraitNames);}if ($this->targetObject) {$targets[] = $this->targetObject;}return new self($targets,$this->blackListedMethods,$this->whiteListedMethods,$className,$this->instanceMock,$this->parameterOverrides,$this->mockOriginalDestructor,$this->constantsMap);}protected function addTarget($target){if (is_object($target)) {$this->setTargetObject($target);$this->setTargetClassName(get_class($target));return $this;}if ($target[0] !== "\\") {$target = "\\" . $target;}if (class_exists($target)) {$this->setTargetClassName($target);return $this;}if (interface_exists($target)) {$this->addTargetInterfaceName($target);return $this;}if (trait_exists($target)) {$this->addTargetTraitName($target);return $this;}/*** Default is to set as class, or interface if class already set** Don't like this condition, can't remember what the default* targetClass is for*/if ($this->getTargetClassName()) {$this->addTargetInterfaceName($target);return $this;}$this->setTargetClassName($target);}protected function addTargets($interfaces){foreach ($interfaces as $interface) {$this->addTarget($interface);}}public function getTargetClassName(){return $this->targetClassName;}public function getTargetClass(){if ($this->targetClass) {return $this->targetClass;}if (!$this->targetClassName) {return null;}if (class_exists($this->targetClassName)) {$alias = null;if (strpos($this->targetClassName, '@') !== false) {$alias = (new MockNameBuilder())->addPart('anonymous_class')->addPart(md5($this->targetClassName))->build();class_alias($this->targetClassName, $alias);}$dtc = DefinedTargetClass::factory($this->targetClassName, $alias);if ($this->getTargetObject() == false && $dtc->isFinal()) {throw new \Mockery\Exception('The class ' . $this->targetClassName . ' is marked final and its methods'. ' cannot be replaced. Classes marked final can be passed in'. ' to \Mockery::mock() as instantiated objects to create a'. ' partial mock, but only if the mock is not subject to type'. ' hinting checks.');}$this->targetClass = $dtc;} else {$this->targetClass = UndefinedTargetClass::factory($this->targetClassName);}return $this->targetClass;}public function getTargetTraits(){if (!empty($this->targetTraits)) {return $this->targetTraits;}foreach ($this->targetTraitNames as $targetTrait) {$this->targetTraits[] = DefinedTargetClass::factory($targetTrait);}$this->targetTraits = array_unique($this->targetTraits); // just in casereturn $this->targetTraits;}public function getTargetInterfaces(){if (!empty($this->targetInterfaces)) {return $this->targetInterfaces;}foreach ($this->targetInterfaceNames as $targetInterface) {if (!interface_exists($targetInterface)) {$this->targetInterfaces[] = UndefinedTargetClass::factory($targetInterface);continue;}$dtc = DefinedTargetClass::factory($targetInterface);$extendedInterfaces = array_keys($dtc->getInterfaces());$extendedInterfaces[] = $targetInterface;$traversableFound = false;$iteratorShiftedToFront = false;foreach ($extendedInterfaces as $interface) {if (!$traversableFound && preg_match("/^\\?Iterator(|Aggregate)$/i", $interface)) {break;}if (preg_match("/^\\\\?IteratorAggregate$/i", $interface)) {$this->targetInterfaces[] = DefinedTargetClass::factory("\\IteratorAggregate");$iteratorShiftedToFront = true;} elseif (preg_match("/^\\\\?Iterator$/i", $interface)) {$this->targetInterfaces[] = DefinedTargetClass::factory("\\Iterator");$iteratorShiftedToFront = true;} elseif (preg_match("/^\\\\?Traversable$/i", $interface)) {$traversableFound = true;}}if ($traversableFound && !$iteratorShiftedToFront) {$this->targetInterfaces[] = DefinedTargetClass::factory("\\IteratorAggregate");}/*** We never straight up implement Traversable*/if (!preg_match("/^\\\\?Traversable$/i", $targetInterface)) {$this->targetInterfaces[] = $dtc;}}$this->targetInterfaces = array_unique($this->targetInterfaces); // just in casereturn $this->targetInterfaces;}public function getTargetObject(){return $this->targetObject;}public function getName(){return $this->name;}/*** Generate a suitable name based on the config*/public function generateName(){$nameBuilder = new MockNameBuilder();if ($this->getTargetObject()) {$className = get_class($this->getTargetObject());$nameBuilder->addPart(strpos($className, '@') !== false ? md5($className) : $className);}if ($this->getTargetClass()) {$className = $this->getTargetClass()->getName();$nameBuilder->addPart(strpos($className, '@') !== false ? md5($className) : $className);}foreach ($this->getTargetInterfaces() as $targetInterface) {$nameBuilder->addPart($targetInterface->getName());}return $nameBuilder->build();}public function getShortName(){$parts = explode("\\", $this->getName());return array_pop($parts);}public function getNamespaceName(){$parts = explode("\\", $this->getName());array_pop($parts);if (count($parts)) {return implode("\\", $parts);}return "";}public function getBlackListedMethods(){return $this->blackListedMethods;}public function getWhiteListedMethods(){return $this->whiteListedMethods;}public function isInstanceMock(){return $this->instanceMock;}public function getParameterOverrides(){return $this->parameterOverrides;}public function isMockOriginalDestructor(){return $this->mockOriginalDestructor;}protected function setTargetClassName($targetClassName){$this->targetClassName = $targetClassName;}protected function getAllMethods(){if ($this->allMethods) {return $this->allMethods;}$classes = $this->getTargetInterfaces();if ($this->getTargetClass()) {$classes[] = $this->getTargetClass();}$methods = array();foreach ($classes as $class) {$methods = array_merge($methods, $class->getMethods());}foreach ($this->getTargetTraits() as $trait) {foreach ($trait->getMethods() as $method) {if ($method->isAbstract()) {$methods[] = $method;}}}$names = array();$methods = array_filter($methods, function ($method) use (&$names) {if (in_array($method->getName(), $names)) {return false;}$names[] = $method->getName();return true;});return $this->allMethods = $methods;}/*** If we attempt to implement Traversable, we must ensure we are also* implementing either Iterator or IteratorAggregate, and that whichever one* it is comes before Traversable in the list of implements.*/protected function addTargetInterfaceName($targetInterface){$this->targetInterfaceNames[] = $targetInterface;}protected function addTargetTraitName($targetTraitName){$this->targetTraitNames[] = $targetTraitName;}protected function setTargetObject($object){$this->targetObject = $object;}public function getConstantsMap(){return $this->constantsMap;}}