1<?php 2/* 3 * This file is part of the PHPUnit_MockObject package. 4 * 5 * (c) Sebastian Bergmann <sebastian@phpunit.de> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11use SebastianBergmann\Exporter\Exporter; 12 13/** 14 * Represents a static invocation. 15 * 16 * @since Class available since Release 1.0.0 17 */ 18class PHPUnit_Framework_MockObject_Invocation_Static implements PHPUnit_Framework_MockObject_Invocation, PHPUnit_Framework_SelfDescribing 19{ 20 /** 21 * @var array 22 */ 23 protected static $uncloneableExtensions = [ 24 'mysqli' => true, 25 'SQLite' => true, 26 'sqlite3' => true, 27 'tidy' => true, 28 'xmlwriter' => true, 29 'xsl' => true 30 ]; 31 32 /** 33 * @var array 34 */ 35 protected static $uncloneableClasses = [ 36 'Closure', 37 'COMPersistHelper', 38 'IteratorIterator', 39 'RecursiveIteratorIterator', 40 'SplFileObject', 41 'PDORow', 42 'ZipArchive' 43 ]; 44 45 /** 46 * @var string 47 */ 48 public $className; 49 50 /** 51 * @var string 52 */ 53 public $methodName; 54 55 /** 56 * @var array 57 */ 58 public $parameters; 59 60 /** 61 * @var string 62 */ 63 public $returnType; 64 65 /** 66 * @var bool 67 */ 68 public $returnTypeNullable = false; 69 70 /** 71 * @param string $className 72 * @param string $methodName 73 * @param array $parameters 74 * @param string $returnType 75 * @param bool $cloneObjects 76 */ 77 public function __construct($className, $methodName, array $parameters, $returnType, $cloneObjects = false) 78 { 79 $this->className = $className; 80 $this->methodName = $methodName; 81 $this->parameters = $parameters; 82 83 if (strpos($returnType, '?') === 0) { 84 $returnType = substr($returnType, 1); 85 $this->returnTypeNullable = true; 86 } 87 88 $this->returnType = $returnType; 89 90 if (!$cloneObjects) { 91 return; 92 } 93 94 foreach ($this->parameters as $key => $value) { 95 if (is_object($value)) { 96 $this->parameters[$key] = $this->cloneObject($value); 97 } 98 } 99 } 100 101 /** 102 * @return string 103 */ 104 public function toString() 105 { 106 $exporter = new Exporter; 107 108 return sprintf( 109 '%s::%s(%s)%s', 110 $this->className, 111 $this->methodName, 112 implode( 113 ', ', 114 array_map( 115 [$exporter, 'shortenedExport'], 116 $this->parameters 117 ) 118 ), 119 $this->returnType ? sprintf(': %s', $this->returnType) : '' 120 ); 121 } 122 123 /** 124 * @return mixed Mocked return value. 125 */ 126 public function generateReturnValue() 127 { 128 switch ($this->returnType) { 129 case '': return; 130 case 'string': return $this->returnTypeNullable ? null : ''; 131 case 'float': return $this->returnTypeNullable ? null : 0.0; 132 case 'int': return $this->returnTypeNullable ? null : 0; 133 case 'bool': return $this->returnTypeNullable ? null : false; 134 case 'array': return $this->returnTypeNullable ? null : []; 135 case 'void': return; 136 137 case 'callable': 138 case 'Closure': 139 return function () {}; 140 141 case 'Traversable': 142 case 'Generator': 143 $generator = function () { yield; }; 144 145 return $generator(); 146 147 default: 148 if ($this->returnTypeNullable) { 149 return null; 150 } 151 152 $generator = new PHPUnit_Framework_MockObject_Generator; 153 154 return $generator->getMock($this->returnType, [], [], '', false); 155 } 156 } 157 158 /** 159 * @param object $original 160 * 161 * @return object 162 */ 163 protected function cloneObject($original) 164 { 165 $cloneable = null; 166 $object = new ReflectionObject($original); 167 168 // Check the blacklist before asking PHP reflection to work around 169 // https://bugs.php.net/bug.php?id=53967 170 if ($object->isInternal() && 171 isset(self::$uncloneableExtensions[$object->getExtensionName()])) { 172 $cloneable = false; 173 } 174 175 if ($cloneable === null) { 176 foreach (self::$uncloneableClasses as $class) { 177 if ($original instanceof $class) { 178 $cloneable = false; 179 break; 180 } 181 } 182 } 183 184 if ($cloneable === null && method_exists($object, 'isCloneable')) { 185 $cloneable = $object->isCloneable(); 186 } 187 188 if ($cloneable === null && $object->hasMethod('__clone')) { 189 $method = $object->getMethod('__clone'); 190 $cloneable = $method->isPublic(); 191 } 192 193 if ($cloneable === null) { 194 $cloneable = true; 195 } 196 197 if ($cloneable) { 198 try { 199 return clone $original; 200 } catch (Exception $e) { 201 return $original; 202 } 203 } else { 204 return $original; 205 } 206 } 207} 208