Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
File Manager
/
vendor
/
mockery
/
mockery
/
library
/
Mockery
/
Generator
:
MockConfiguration-20250317063843.php
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<?php /** * Mockery (https://docs.mockery.io/) * * @copyright https://github.com/mockery/mockery/blob/HEAD/COPYRIGHT.md * @license https://github.com/mockery/mockery/blob/HEAD/LICENSE BSD 3-Clause License * @link https://github.com/mockery/mockery for the canonical source repository */ namespace Mockery\Generator; use Mockery\Exception; use Serializable; use function array_filter; use function array_keys; use function array_map; use function array_merge; use function array_pop; use function array_unique; use function array_values; use function class_alias; use function class_exists; use function explode; use function get_class; use function implode; use function in_array; use function interface_exists; use function is_object; use function md5; use function preg_match; use function serialize; use function strpos; use function strtolower; use function trait_exists; /** * This class describes the configuration of mocks and hides away some of the * reflection implementation */ class MockConfiguration { /** * Instance cache of all methods * * @var list<Method> */ protected $allMethods = []; /** * 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 = []; protected $constantsMap = []; /** * An instance mock is where we override the original class before it's autoloaded * * @var bool */ protected $instanceMock = false; /** * If true, overrides original class destructor * * @var bool */ protected $mockOriginalDestructor = false; /** * The class name we'd like to use for a generated mock * * @var string|null */ protected $name; /** * Param overrides * * @var array<string,mixed> */ protected $parameterOverrides = []; /** * A class that we'd like to mock * @var TargetClassInterface|null */ protected $targetClass; /** * @var class-string|null */ protected $targetClassName; /** * @var array<class-string> */ protected $targetInterfaceNames = []; /** * A number of interfaces we'd like to mock, keyed by name to attempt to keep unique * * @var array<TargetClassInterface> */ protected $targetInterfaces = []; /** * An object we'd like our mock to proxy to * * @var object|null */ protected $targetObject; /** * @var array<string> */ protected $targetTraitNames = []; /** * A number of traits we'd like to mock, keyed by name to attempt to keep unique * * @var array<string,DefinedTargetClass> */ protected $targetTraits = []; /** * If not empty, only these methods will be mocked * * @var array<string> */ protected $whiteListedMethods = []; /** * @param array<class-string|object> $targets * @param array<string> $blackListedMethods * @param array<string> $whiteListedMethods * @param string|null $name * @param bool $instanceMock * @param array<string,mixed> $parameterOverrides * @param bool $mockOriginalDestructor * @param array<string,array<scalar>|scalar> $constantsMap */ public function __construct( array $targets = [], array $blackListedMethods = [], array $whiteListedMethods = [], $name = null, $instanceMock = false, array $parameterOverrides = [], $mockOriginalDestructor = false, array $constantsMap = [] ) { $this->addTargets($targets); $this->blackListedMethods = $blackListedMethods; $this->whiteListedMethods = $whiteListedMethods; $this->name = $name; $this->instanceMock = $instanceMock; $this->parameterOverrides = $parameterOverrides; $this->mockOriginalDestructor = $mockOriginalDestructor; $this->constantsMap = $constantsMap; } /** * Generate a suitable name based on the config * * @return string */ public function generateName() { $nameBuilder = new MockNameBuilder(); $targetObject = $this->getTargetObject(); if ($targetObject !== null) { $className = get_class($targetObject); $nameBuilder->addPart(strpos($className, '@') !== false ? md5($className) : $className); } $targetClass = $this->getTargetClass(); if ($targetClass instanceof TargetClassInterface) { $className = $targetClass->getName(); $nameBuilder->addPart(strpos($className, '@') !== false ? md5($className) : $className); } foreach ($this->getTargetInterfaces() as $targetInterface) { $nameBuilder->addPart($targetInterface->getName()); } return $nameBuilder->build(); } /** * @return array<string> */ public function getBlackListedMethods() { return $this->blackListedMethods; } /** * @return array<string,scalar|array<scalar>> */ public function getConstantsMap() { return $this->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 = [ '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 * * @return list<Method> */ public function getMethodsToMock() { $methods = $this->getAllMethods(); foreach ($methods as $key => $method) { if ($method->isFinal()) { unset($methods[$key]); } } /** * Whitelist trumps everything else */ $whiteListedMethods = $this->getWhiteListedMethods(); if ($whiteListedMethods !== []) { $whitelist = array_map('strtolower', $whiteListedMethods); return array_filter($methods, static function ($method) use ($whitelist) { if ($method->isAbstract()) { return true; } return in_array(strtolower($method->getName()), $whitelist, true); }); } /** * Remove blacklisted methods */ $blackListedMethods = $this->getBlackListedMethods(); if ($blackListedMethods !== []) { $blacklist = array_map('strtolower', $blackListedMethods); $methods = array_filter($methods, static function ($method) use ($blacklist) { return ! in_array(strtolower($method->getName()), $blacklist, true); }); } /** * 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 */ $targetClass = $this->getTargetClass(); if ( $targetClass !== null && $targetClass->implementsInterface(Serializable::class) && $targetClass->hasInternalAncestor() ) { $methods = array_filter($methods, static function ($method) { return $method->getName() !== 'unserialize'; }); } return array_values($methods); } /** * @return string|null */ public function getName() { return $this->name; } /** * @return string */ public function getNamespaceName() { $parts = explode('\\', $this->getName()); array_pop($parts); if ($parts !== []) { return implode('\\', $parts); } return ''; } /** * @return array<string,mixed> */ public function getParameterOverrides() { return $this->parameterOverrides; } /** * @return string */ public function getShortName() { $parts = explode('\\', $this->getName()); return array_pop($parts); } /** * @return null|TargetClassInterface */ 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() === null && $dtc->isFinal()) { throw new 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; } /** * @return class-string|null */ public function getTargetClassName() { return $this->targetClassName; } /** * @return list<TargetClassInterface> */ public function getTargetInterfaces() { if ($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; continue; } if (preg_match('/^\\\\?Iterator$/i', $interface)) { $this->targetInterfaces[] = DefinedTargetClass::factory('\\Iterator'); $iteratorShiftedToFront = true; continue; } if (preg_match('/^\\\\?Traversable$/i', $interface)) { $traversableFound = true; } } if ($traversableFound && ! $iteratorShiftedToFront) { $this->targetInterfaces[] = DefinedTargetClass::factory('\\IteratorAggregate'); } /** * We never straight up implement Traversable */ $isTraversable = preg_match('/^\\\\?Traversable$/i', $targetInterface); if ($isTraversable === 0 || $isTraversable === false) { $this->targetInterfaces[] = $dtc; } } return $this->targetInterfaces = array_unique($this->targetInterfaces); } /** * @return object|null */ public function getTargetObject() { return $this->targetObject; } /** * @return list<TargetClassInterface> */ public function getTargetTraits() { if ($this->targetTraits !== []) { return $this->targetTraits; } foreach ($this->targetTraitNames as $targetTrait) { $this->targetTraits[] = DefinedTargetClass::factory($targetTrait); } $this->targetTraits = array_unique($this->targetTraits); // just in case return $this->targetTraits; } /** * @return array<string> */ public function getWhiteListedMethods() { return $this->whiteListedMethods; } /** * @return bool */ public function isInstanceMock() { return $this->instanceMock; } /** * @return bool */ public function isMockOriginalDestructor() { return $this->mockOriginalDestructor; } /** * @param class-string $className * @return self */ public function rename($className) { $targets = []; 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 ); } /** * 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 * * @return bool */ public function requiresCallStaticTypeHintRemoval() { foreach ($this->getAllMethods() as $method) { if ($method->getName() === '__callStatic') { $params = $method->getParameters(); if (! array_key_exists(1, $params)) { return false; } return ! $params[1]->isArray(); } } return false; } /** * 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 * * @return bool */ public function requiresCallTypeHintRemoval() { foreach ($this->getAllMethods() as $method) { if ($method->getName() === '__call') { $params = $method->getParameters(); return ! $params[1]->isArray(); } } return false; } /** * @param class-string|object $target */ protected function addTarget($target) { if (is_object($target)) { $this->setTargetObject($target); $this->setTargetClassName(get_class($target)); return; } if ($target[0] !== '\\') { $target = '\\' . $target; } if (class_exists($target)) { $this->setTargetClassName($target); return; } if (interface_exists($target)) { $this->addTargetInterfaceName($target); return; } if (trait_exists($target)) { $this->addTargetTraitName($target); return; } /** * 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->setTargetClassName($target); } /** * 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. * * @param class-string $targetInterface */ protected function addTargetInterfaceName($targetInterface) { $this->targetInterfaceNames[] = $targetInterface; } /** * @param array<class-string> $interfaces */ protected function addTargets($interfaces) { foreach ($interfaces as $interface) { $this->addTarget($interface); } } /** * @param class-string $targetTraitName */ protected function addTargetTraitName($targetTraitName) { $this->targetTraitNames[] = $targetTraitName; } /** * @return list<Method> */ protected function getAllMethods() { if ($this->allMethods) { return $this->allMethods; } $classes = $this->getTargetInterfaces(); if ($this->getTargetClass()) { $classes[] = $this->getTargetClass(); } $methods = []; 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 = []; $methods = array_filter($methods, static function ($method) use (&$names) { if (in_array($method->getName(), $names, true)) { return false; } $names[] = $method->getName(); return true; }); return $this->allMethods = $methods; } /** * @param class-string $targetClassName */ protected function setTargetClassName($targetClassName) { $this->targetClassName = $targetClassName; } /** * @param object $object */ protected function setTargetObject($object) { $this->targetObject = $object; } }