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