Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php declare(strict_types=1);
2
/*
3
 * This file is part of PHPUnit.
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
namespace PHPUnit\Framework\MockObject;
11
 
12
use const DIRECTORY_SEPARATOR;
13
use function explode;
14
use function implode;
15
use function is_object;
16
use function is_string;
17
use function preg_match;
18
use function preg_replace;
19
use function sprintf;
20
use function strlen;
21
use function strpos;
22
use function substr;
23
use function substr_count;
24
use function trim;
25
use function var_export;
26
use ReflectionMethod;
27
use ReflectionParameter;
28
use SebastianBergmann\Template\Exception as TemplateException;
29
use SebastianBergmann\Template\Template;
30
use SebastianBergmann\Type\ReflectionMapper;
31
use SebastianBergmann\Type\Type;
32
use SebastianBergmann\Type\UnknownType;
33
 
34
/**
35
 * @internal This class is not covered by the backward compatibility promise for PHPUnit
36
 */
37
final class MockMethod
38
{
39
    /**
40
     * @var Template[]
41
     */
42
    private static $templates = [];
43
 
44
    /**
45
     * @var string
46
     */
47
    private $className;
48
 
49
    /**
50
     * @var string
51
     */
52
    private $methodName;
53
 
54
    /**
55
     * @var bool
56
     */
57
    private $cloneArguments;
58
 
59
    /**
60
     * @var string string
61
     */
62
    private $modifier;
63
 
64
    /**
65
     * @var string
66
     */
67
    private $argumentsForDeclaration;
68
 
69
    /**
70
     * @var string
71
     */
72
    private $argumentsForCall;
73
 
74
    /**
75
     * @var Type
76
     */
77
    private $returnType;
78
 
79
    /**
80
     * @var string
81
     */
82
    private $reference;
83
 
84
    /**
85
     * @var bool
86
     */
87
    private $callOriginalMethod;
88
 
89
    /**
90
     * @var bool
91
     */
92
    private $static;
93
 
94
    /**
95
     * @var ?string
96
     */
97
    private $deprecation;
98
 
99
    /**
100
     * @throws ReflectionException
101
     * @throws RuntimeException
102
     */
103
    public static function fromReflection(ReflectionMethod $method, bool $callOriginalMethod, bool $cloneArguments): self
104
    {
105
        if ($method->isPrivate()) {
106
            $modifier = 'private';
107
        } elseif ($method->isProtected()) {
108
            $modifier = 'protected';
109
        } else {
110
            $modifier = 'public';
111
        }
112
 
113
        if ($method->isStatic()) {
114
            $modifier .= ' static';
115
        }
116
 
117
        if ($method->returnsReference()) {
118
            $reference = '&';
119
        } else {
120
            $reference = '';
121
        }
122
 
123
        $docComment = $method->getDocComment();
124
 
125
        if (is_string($docComment) &&
126
            preg_match('#\*[ \t]*+@deprecated[ \t]*+(.*?)\r?+\n[ \t]*+\*(?:[ \t]*+@|/$)#s', $docComment, $deprecation)) {
127
            $deprecation = trim(preg_replace('#[ \t]*\r?\n[ \t]*+\*[ \t]*+#', ' ', $deprecation[1]));
128
        } else {
129
            $deprecation = null;
130
        }
131
 
132
        return new self(
133
            $method->getDeclaringClass()->getName(),
134
            $method->getName(),
135
            $cloneArguments,
136
            $modifier,
137
            self::getMethodParametersForDeclaration($method),
138
            self::getMethodParametersForCall($method),
139
            (new ReflectionMapper)->fromReturnType($method),
140
            $reference,
141
            $callOriginalMethod,
142
            $method->isStatic(),
143
            $deprecation
144
        );
145
    }
146
 
147
    public static function fromName(string $fullClassName, string $methodName, bool $cloneArguments): self
148
    {
149
        return new self(
150
            $fullClassName,
151
            $methodName,
152
            $cloneArguments,
153
            'public',
154
            '',
155
            '',
156
            new UnknownType,
157
            '',
158
            false,
159
            false,
160
            null
161
        );
162
    }
163
 
164
    public function __construct(string $className, string $methodName, bool $cloneArguments, string $modifier, string $argumentsForDeclaration, string $argumentsForCall, Type $returnType, string $reference, bool $callOriginalMethod, bool $static, ?string $deprecation)
165
    {
166
        $this->className               = $className;
167
        $this->methodName              = $methodName;
168
        $this->cloneArguments          = $cloneArguments;
169
        $this->modifier                = $modifier;
170
        $this->argumentsForDeclaration = $argumentsForDeclaration;
171
        $this->argumentsForCall        = $argumentsForCall;
172
        $this->returnType              = $returnType;
173
        $this->reference               = $reference;
174
        $this->callOriginalMethod      = $callOriginalMethod;
175
        $this->static                  = $static;
176
        $this->deprecation             = $deprecation;
177
    }
178
 
179
    public function getName(): string
180
    {
181
        return $this->methodName;
182
    }
183
 
184
    /**
185
     * @throws RuntimeException
186
     */
187
    public function generateCode(): string
188
    {
189
        if ($this->static) {
190
            $templateFile = 'mocked_static_method.tpl';
191
        } elseif ($this->returnType->isNever() || $this->returnType->isVoid()) {
192
            $templateFile = sprintf(
193
                '%s_method_never_or_void.tpl',
194
                $this->callOriginalMethod ? 'proxied' : 'mocked'
195
            );
196
        } else {
197
            $templateFile = sprintf(
198
                '%s_method.tpl',
199
                $this->callOriginalMethod ? 'proxied' : 'mocked'
200
            );
201
        }
202
 
203
        $deprecation = $this->deprecation;
204
 
205
        if (null !== $this->deprecation) {
206
            $deprecation         = "The {$this->className}::{$this->methodName} method is deprecated ({$this->deprecation}).";
207
            $deprecationTemplate = $this->getTemplate('deprecation.tpl');
208
 
209
            $deprecationTemplate->setVar(
210
                [
211
                    'deprecation' => var_export($deprecation, true),
212
                ]
213
            );
214
 
215
            $deprecation = $deprecationTemplate->render();
216
        }
217
 
218
        $template = $this->getTemplate($templateFile);
219
 
220
        $template->setVar(
221
            [
222
                'arguments_decl'     => $this->argumentsForDeclaration,
223
                'arguments_call'     => $this->argumentsForCall,
224
                'return_declaration' => !empty($this->returnType->asString()) ? (': ' . $this->returnType->asString()) : '',
225
                'return_type'        => $this->returnType->asString(),
226
                'arguments_count'    => !empty($this->argumentsForCall) ? substr_count($this->argumentsForCall, ',') + 1 : 0,
227
                'class_name'         => $this->className,
228
                'method_name'        => $this->methodName,
229
                'modifier'           => $this->modifier,
230
                'reference'          => $this->reference,
231
                'clone_arguments'    => $this->cloneArguments ? 'true' : 'false',
232
                'deprecation'        => $deprecation,
233
            ]
234
        );
235
 
236
        return $template->render();
237
    }
238
 
239
    public function getReturnType(): Type
240
    {
241
        return $this->returnType;
242
    }
243
 
244
    /**
245
     * @throws RuntimeException
246
     */
247
    private function getTemplate(string $template): Template
248
    {
249
        $filename = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR . $template;
250
 
251
        if (!isset(self::$templates[$filename])) {
252
            try {
253
                self::$templates[$filename] = new Template($filename);
254
            } catch (TemplateException $e) {
255
                throw new RuntimeException(
256
                    $e->getMessage(),
257
                    $e->getCode(),
258
                    $e
259
                );
260
            }
261
        }
262
 
263
        return self::$templates[$filename];
264
    }
265
 
266
    /**
267
     * Returns the parameters of a function or method.
268
     *
269
     * @throws RuntimeException
270
     */
271
    private static function getMethodParametersForDeclaration(ReflectionMethod $method): string
272
    {
273
        $parameters = [];
274
        $types      = (new ReflectionMapper)->fromParameterTypes($method);
275
 
276
        foreach ($method->getParameters() as $i => $parameter) {
277
            $name = '$' . $parameter->getName();
278
 
279
            /* Note: PHP extensions may use empty names for reference arguments
280
             * or "..." for methods taking a variable number of arguments.
281
             */
282
            if ($name === '$' || $name === '$...') {
283
                $name = '$arg' . $i;
284
            }
285
 
286
            $default         = '';
287
            $reference       = '';
288
            $typeDeclaration = '';
289
 
290
            if (!$types[$i]->type()->isUnknown()) {
291
                $typeDeclaration = $types[$i]->type()->asString() . ' ';
292
            }
293
 
294
            if ($parameter->isPassedByReference()) {
295
                $reference = '&';
296
            }
297
 
298
            if ($parameter->isVariadic()) {
299
                $name = '...' . $name;
300
            } elseif ($parameter->isDefaultValueAvailable()) {
301
                $default = ' = ' . self::exportDefaultValue($parameter);
302
            } elseif ($parameter->isOptional()) {
303
                $default = ' = null';
304
            }
305
 
306
            $parameters[] = $typeDeclaration . $reference . $name . $default;
307
        }
308
 
309
        return implode(', ', $parameters);
310
    }
311
 
312
    /**
313
     * Returns the parameters of a function or method.
314
     *
315
     * @throws ReflectionException
316
     */
317
    private static function getMethodParametersForCall(ReflectionMethod $method): string
318
    {
319
        $parameters = [];
320
 
321
        foreach ($method->getParameters() as $i => $parameter) {
322
            $name = '$' . $parameter->getName();
323
 
324
            /* Note: PHP extensions may use empty names for reference arguments
325
             * or "..." for methods taking a variable number of arguments.
326
             */
327
            if ($name === '$' || $name === '$...') {
328
                $name = '$arg' . $i;
329
            }
330
 
331
            if ($parameter->isVariadic()) {
332
                continue;
333
            }
334
 
335
            if ($parameter->isPassedByReference()) {
336
                $parameters[] = '&' . $name;
337
            } else {
338
                $parameters[] = $name;
339
            }
340
        }
341
 
342
        return implode(', ', $parameters);
343
    }
344
 
345
    /**
346
     * @throws ReflectionException
347
     */
348
    private static function exportDefaultValue(ReflectionParameter $parameter): string
349
    {
350
        try {
351
            $defaultValue = $parameter->getDefaultValue();
352
 
353
            if (!is_object($defaultValue)) {
354
                return (string) var_export($defaultValue, true);
355
            }
356
 
357
            $parameterAsString = $parameter->__toString();
358
 
359
            return (string) explode(
360
                ' = ',
361
                substr(
362
                    substr(
363
                        $parameterAsString,
364
                        strpos($parameterAsString, '<optional> ') + strlen('<optional> ')
365
                    ),
366
                    0,
367
                    -2
368
                )
369
            )[1];
370
            // @codeCoverageIgnoreStart
371
        } catch (\ReflectionException $e) {
372
            throw new ReflectionException(
373
                $e->getMessage(),
374
                $e->getCode(),
375
                $e
376
            );
377
        }
378
        // @codeCoverageIgnoreEnd
379
    }
380
}