Chris@14: Chris@14: * Chris@14: * For the full copyright and license information, please view the LICENSE Chris@14: * file that was distributed with this source code. Chris@14: */ Chris@14: namespace PHPUnit\Framework\MockObject\Invocation; Chris@14: Chris@14: use PHPUnit\Framework\MockObject\Generator; Chris@14: use PHPUnit\Framework\MockObject\Invocation; Chris@14: use PHPUnit\Framework\SelfDescribing; Chris@14: use ReflectionObject; Chris@14: use SebastianBergmann\Exporter\Exporter; Chris@14: Chris@14: /** Chris@14: * Represents a static invocation. Chris@14: */ Chris@14: class StaticInvocation implements Invocation, SelfDescribing Chris@14: { Chris@14: /** Chris@14: * @var array Chris@14: */ Chris@14: private static $uncloneableExtensions = [ Chris@14: 'mysqli' => true, Chris@14: 'SQLite' => true, Chris@14: 'sqlite3' => true, Chris@14: 'tidy' => true, Chris@14: 'xmlwriter' => true, Chris@14: 'xsl' => true Chris@14: ]; Chris@14: Chris@14: /** Chris@14: * @var array Chris@14: */ Chris@14: private static $uncloneableClasses = [ Chris@14: 'Closure', Chris@14: 'COMPersistHelper', Chris@14: 'IteratorIterator', Chris@14: 'RecursiveIteratorIterator', Chris@14: 'SplFileObject', Chris@14: 'PDORow', Chris@14: 'ZipArchive' Chris@14: ]; Chris@14: Chris@14: /** Chris@14: * @var string Chris@14: */ Chris@14: private $className; Chris@14: Chris@14: /** Chris@14: * @var string Chris@14: */ Chris@14: private $methodName; Chris@14: Chris@14: /** Chris@14: * @var array Chris@14: */ Chris@14: private $parameters; Chris@14: Chris@14: /** Chris@14: * @var string Chris@14: */ Chris@14: private $returnType; Chris@14: Chris@14: /** Chris@14: * @var bool Chris@14: */ Chris@14: private $isReturnTypeNullable = false; Chris@14: Chris@14: /** Chris@14: * @param string $className Chris@14: * @param string $methodName Chris@14: * @param array $parameters Chris@14: * @param string $returnType Chris@14: * @param bool $cloneObjects Chris@14: */ Chris@14: public function __construct($className, $methodName, array $parameters, $returnType, $cloneObjects = false) Chris@14: { Chris@14: $this->className = $className; Chris@14: $this->methodName = $methodName; Chris@14: $this->parameters = $parameters; Chris@14: Chris@14: if (\strpos($returnType, '?') === 0) { Chris@14: $returnType = \substr($returnType, 1); Chris@14: $this->isReturnTypeNullable = true; Chris@14: } Chris@14: Chris@14: $this->returnType = $returnType; Chris@14: Chris@14: if (!$cloneObjects) { Chris@14: return; Chris@14: } Chris@14: Chris@14: foreach ($this->parameters as $key => $value) { Chris@14: if (\is_object($value)) { Chris@14: $this->parameters[$key] = $this->cloneObject($value); Chris@14: } Chris@14: } Chris@14: } Chris@14: Chris@14: public function getClassName(): string Chris@14: { Chris@14: return $this->className; Chris@14: } Chris@14: Chris@14: public function getMethodName(): string Chris@14: { Chris@14: return $this->methodName; Chris@14: } Chris@14: Chris@14: public function getParameters(): array Chris@14: { Chris@14: return $this->parameters; Chris@14: } Chris@14: Chris@14: public function getReturnType(): string Chris@14: { Chris@14: return $this->returnType; Chris@14: } Chris@14: Chris@14: public function isReturnTypeNullable(): bool Chris@14: { Chris@14: return $this->isReturnTypeNullable; Chris@14: } Chris@14: Chris@14: /** Chris@14: * @return mixed Mocked return value Chris@14: * Chris@14: * @throws \ReflectionException Chris@14: * @throws \PHPUnit\Framework\MockObject\RuntimeException Chris@14: * @throws \PHPUnit\Framework\Exception Chris@14: */ Chris@14: public function generateReturnValue() Chris@14: { Chris@14: if ($this->isReturnTypeNullable) { Chris@14: return; Chris@14: } Chris@14: Chris@14: switch (\strtolower($this->returnType)) { Chris@14: case '': Chris@14: case 'void': Chris@14: return; Chris@14: Chris@14: case 'string': Chris@14: return ''; Chris@14: Chris@14: case 'float': Chris@14: return 0.0; Chris@14: Chris@14: case 'int': Chris@14: return 0; Chris@14: Chris@14: case 'bool': Chris@14: return false; Chris@14: Chris@14: case 'array': Chris@14: return []; Chris@14: Chris@14: case 'object': Chris@14: return new \stdClass; Chris@14: Chris@14: case 'callable': Chris@14: case 'closure': Chris@14: return function () { Chris@14: }; Chris@14: Chris@14: case 'traversable': Chris@14: case 'generator': Chris@14: case 'iterable': Chris@14: $generator = function () { Chris@14: yield; Chris@14: }; Chris@14: Chris@14: return $generator(); Chris@14: Chris@14: default: Chris@14: $generator = new Generator; Chris@14: Chris@14: return $generator->getMock($this->returnType, [], [], '', false); Chris@14: } Chris@14: } Chris@14: Chris@14: public function toString(): string Chris@14: { Chris@14: $exporter = new Exporter; Chris@14: Chris@14: return \sprintf( Chris@14: '%s::%s(%s)%s', Chris@14: $this->className, Chris@14: $this->methodName, Chris@14: \implode( Chris@14: ', ', Chris@14: \array_map( Chris@14: [$exporter, 'shortenedExport'], Chris@14: $this->parameters Chris@14: ) Chris@14: ), Chris@14: $this->returnType ? \sprintf(': %s', $this->returnType) : '' Chris@14: ); Chris@14: } Chris@14: Chris@14: /** Chris@14: * @param object $original Chris@14: * Chris@14: * @return object Chris@14: */ Chris@14: private function cloneObject($original) Chris@14: { Chris@14: $cloneable = null; Chris@14: $object = new ReflectionObject($original); Chris@14: Chris@14: // Check the blacklist before asking PHP reflection to work around Chris@14: // https://bugs.php.net/bug.php?id=53967 Chris@14: if ($object->isInternal() && Chris@14: isset(self::$uncloneableExtensions[$object->getExtensionName()])) { Chris@14: $cloneable = false; Chris@14: } Chris@14: Chris@14: if ($cloneable === null) { Chris@14: foreach (self::$uncloneableClasses as $class) { Chris@14: if ($original instanceof $class) { Chris@14: $cloneable = false; Chris@14: Chris@14: break; Chris@14: } Chris@14: } Chris@14: } Chris@14: Chris@14: if ($cloneable === null) { Chris@14: $cloneable = $object->isCloneable(); Chris@14: } Chris@14: Chris@14: if ($cloneable === null && $object->hasMethod('__clone')) { Chris@14: $method = $object->getMethod('__clone'); Chris@14: $cloneable = $method->isPublic(); Chris@14: } Chris@14: Chris@14: if ($cloneable === null) { Chris@14: $cloneable = true; Chris@14: } Chris@14: Chris@14: if ($cloneable) { Chris@14: try { Chris@14: return clone $original; Chris@14: } catch (\Exception $e) { Chris@14: return $original; Chris@14: } Chris@14: } else { Chris@14: return $original; Chris@14: } Chris@14: } Chris@14: }