Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 200 | Zur aktuellen Revision | Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/*
4
 * This file is part of Psy Shell.
5
 *
6
 * (c) 2012-2022 Justin Hileman
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Psy\Formatter;
13
 
14
use Psy\Reflection\ReflectionClassConstant;
15
use Psy\Reflection\ReflectionConstant_;
16
use Psy\Reflection\ReflectionLanguageConstruct;
17
use Psy\Util\Json;
18
use Symfony\Component\Console\Formatter\OutputFormatter;
19
 
20
/**
21
 * An abstract representation of a function, class or property signature.
22
 */
23
class SignatureFormatter implements ReflectorFormatter
24
{
25
    /**
26
     * Format a signature for the given reflector.
27
     *
28
     * Defers to subclasses to do the actual formatting.
29
     *
30
     * @param \Reflector $reflector
31
     *
32
     * @return string Formatted signature
33
     */
34
    public static function format(\Reflector $reflector): string
35
    {
36
        switch (true) {
37
            case $reflector instanceof \ReflectionFunction:
38
            case $reflector instanceof ReflectionLanguageConstruct:
39
                return self::formatFunction($reflector);
40
 
41
            // this case also covers \ReflectionObject:
42
            case $reflector instanceof \ReflectionClass:
43
                return self::formatClass($reflector);
44
 
45
            case $reflector instanceof ReflectionClassConstant:
46
            case $reflector instanceof \ReflectionClassConstant:
47
                return self::formatClassConstant($reflector);
48
 
49
            case $reflector instanceof \ReflectionMethod:
50
                return self::formatMethod($reflector);
51
 
52
            case $reflector instanceof \ReflectionProperty:
53
                return self::formatProperty($reflector);
54
 
55
            case $reflector instanceof ReflectionConstant_:
56
                return self::formatConstant($reflector);
57
 
58
            default:
59
                throw new \InvalidArgumentException('Unexpected Reflector class: '.\get_class($reflector));
60
        }
61
    }
62
 
63
    /**
64
     * Print the signature name.
65
     *
66
     * @param \Reflector $reflector
67
     *
68
     * @return string Formatted name
69
     */
70
    public static function formatName(\Reflector $reflector): string
71
    {
72
        return $reflector->getName();
73
    }
74
 
75
    /**
76
     * Print the method, property or class modifiers.
77
     *
78
     * @param \Reflector $reflector
79
     *
80
     * @return string Formatted modifiers
81
     */
82
    private static function formatModifiers(\Reflector $reflector): string
83
    {
84
        return \implode(' ', \array_map(function ($modifier) {
85
            return \sprintf('<keyword>%s</keyword>', $modifier);
86
        }, \Reflection::getModifierNames($reflector->getModifiers())));
87
    }
88
 
89
    /**
90
     * Format a class signature.
91
     *
92
     * @param \ReflectionClass $reflector
93
     *
94
     * @return string Formatted signature
95
     */
96
    private static function formatClass(\ReflectionClass $reflector): string
97
    {
98
        $chunks = [];
99
 
100
        if ($modifiers = self::formatModifiers($reflector)) {
101
            $chunks[] = $modifiers;
102
        }
103
 
104
        if ($reflector->isTrait()) {
105
            $chunks[] = 'trait';
106
        } else {
107
            $chunks[] = $reflector->isInterface() ? 'interface' : 'class';
108
        }
109
 
110
        $chunks[] = \sprintf('<class>%s</class>', self::formatName($reflector));
111
 
112
        if ($parent = $reflector->getParentClass()) {
113
            $chunks[] = 'extends';
114
            $chunks[] = \sprintf('<class>%s</class>', $parent->getName());
115
        }
116
 
117
        $interfaces = $reflector->getInterfaceNames();
118
        if (!empty($interfaces)) {
119
            \sort($interfaces);
120
 
121
            $chunks[] = $reflector->isInterface() ? 'extends' : 'implements';
122
            $chunks[] = \implode(', ', \array_map(function ($name) {
123
                return \sprintf('<class>%s</class>', $name);
124
            }, $interfaces));
125
        }
126
 
127
        return \implode(' ', $chunks);
128
    }
129
 
130
    /**
131
     * Format a constant signature.
132
     *
133
     * @param ReflectionClassConstant|\ReflectionClassConstant $reflector
134
     *
135
     * @return string Formatted signature
136
     */
137
    private static function formatClassConstant($reflector): string
138
    {
139
        $value = $reflector->getValue();
140
        $style = self::getTypeStyle($value);
141
 
142
        return \sprintf(
143
            '<keyword>const</keyword> <const>%s</const> = <%s>%s</%s>',
144
            self::formatName($reflector),
145
            $style,
146
            OutputFormatter::escape(Json::encode($value)),
147
            $style
148
        );
149
    }
150
 
151
    /**
152
     * Format a constant signature.
153
     *
154
     * @param ReflectionConstant_ $reflector
155
     *
156
     * @return string Formatted signature
157
     */
158
    private static function formatConstant(ReflectionConstant_ $reflector): string
159
    {
160
        $value = $reflector->getValue();
161
        $style = self::getTypeStyle($value);
162
 
163
        return \sprintf(
164
            '<keyword>define</keyword>(<string>%s</string>, <%s>%s</%s>)',
165
            OutputFormatter::escape(Json::encode($reflector->getName())),
166
            $style,
167
            OutputFormatter::escape(Json::encode($value)),
168
            $style
169
        );
170
    }
171
 
172
    /**
173
     * Helper for getting output style for a given value's type.
174
     *
175
     * @param mixed $value
176
     *
177
     * @return string
178
     */
179
    private static function getTypeStyle($value): string
180
    {
181
        if (\is_int($value) || \is_float($value)) {
182
            return 'number';
183
        } elseif (\is_string($value)) {
184
            return 'string';
185
        } elseif (\is_bool($value) || $value === null) {
186
            return 'bool';
187
        } else {
188
            return 'strong'; // @codeCoverageIgnore
189
        }
190
    }
191
 
192
    /**
193
     * Format a property signature.
194
     *
195
     * @param \ReflectionProperty $reflector
196
     *
197
     * @return string Formatted signature
198
     */
199
    private static function formatProperty(\ReflectionProperty $reflector): string
200
    {
201
        return \sprintf(
202
            '%s <strong>$%s</strong>',
203
            self::formatModifiers($reflector),
204
            $reflector->getName()
205
        );
206
    }
207
 
208
    /**
209
     * Format a function signature.
210
     *
211
     * @param \ReflectionFunction $reflector
212
     *
213
     * @return string Formatted signature
214
     */
215
    private static function formatFunction(\ReflectionFunctionAbstract $reflector): string
216
    {
217
        return \sprintf(
218
            '<keyword>function</keyword> %s<function>%s</function>(%s)%s',
219
            $reflector->returnsReference() ? '&' : '',
220
            self::formatName($reflector),
221
            \implode(', ', self::formatFunctionParams($reflector)),
222
            self::formatFunctionReturnType($reflector)
223
        );
224
    }
225
 
226
    /**
227
     * Format a function signature's return type (if available).
228
     *
229
     * @param \ReflectionFunctionAbstract $reflector
230
     *
231
     * @return string Formatted return type
232
     */
233
    private static function formatFunctionReturnType(\ReflectionFunctionAbstract $reflector): string
234
    {
235
        if (!\method_exists($reflector, 'hasReturnType') || !$reflector->hasReturnType()) {
236
            return '';
237
        }
238
 
239
        return \sprintf(': %s', self::formatReflectionType($reflector->getReturnType()));
240
    }
241
 
242
    /**
243
     * Format a method signature.
244
     *
245
     * @param \ReflectionMethod $reflector
246
     *
247
     * @return string Formatted signature
248
     */
249
    private static function formatMethod(\ReflectionMethod $reflector): string
250
    {
251
        return \sprintf(
252
            '%s %s',
253
            self::formatModifiers($reflector),
254
            self::formatFunction($reflector)
255
        );
256
    }
257
 
258
    /**
259
     * Print the function params.
260
     *
261
     * @param \ReflectionFunctionAbstract $reflector
262
     *
263
     * @return array
264
     */
265
    private static function formatFunctionParams(\ReflectionFunctionAbstract $reflector): array
266
    {
267
        $params = [];
268
        foreach ($reflector->getParameters() as $param) {
269
            $hint = '';
270
            try {
271
                if (\method_exists($param, 'getType')) {
272
                    $hint = self::formatReflectionType($param->getType());
273
                } else {
274
                    if ($param->isArray()) {
275
                        $hint = '<keyword>array</keyword>';
276
                    } elseif ($class = $param->getClass()) {
277
                        $hint = \sprintf('<class>%s</class>', $class->getName());
278
                    }
279
                }
280
            } catch (\Throwable $e) {
281
                // sometimes we just don't know...
282
                // bad class names, or autoloaded classes that haven't been loaded yet, or whathaveyou.
283
                // come to think of it, the only time I've seen this is with the intl extension.
284
 
285
                // Hax: we'll try to extract it :P
286
 
287
                // @codeCoverageIgnoreStart
288
                $chunks = \explode('$'.$param->getName(), (string) $param);
289
                $chunks = \explode(' ', \trim($chunks[0]));
290
                $guess = \end($chunks);
291
 
292
                $hint = \sprintf('<urgent>%s</urgent>', OutputFormatter::escape($guess));
293
                // @codeCoverageIgnoreEnd
294
            }
295
 
296
            if ($param->isOptional()) {
297
                if (!$param->isDefaultValueAvailable()) {
298
                    $value = 'unknown';
299
                    $typeStyle = 'urgent';
300
                } else {
301
                    $value = $param->getDefaultValue();
302
                    $typeStyle = self::getTypeStyle($value);
303
                    $value = \is_array($value) ? '[]' : ($value === null ? 'null' : \var_export($value, true));
304
                }
305
                $default = \sprintf(' = <%s>%s</%s>', $typeStyle, OutputFormatter::escape($value), $typeStyle);
306
            } else {
307
                $default = '';
308
            }
309
 
310
            $params[] = \sprintf(
311
                '%s%s%s<strong>$%s</strong>%s',
312
                $param->isPassedByReference() ? '&' : '',
313
                $hint,
314
                $hint !== '' ? ' ' : '',
315
                $param->getName(),
316
                $default
317
            );
318
        }
319
 
320
        return $params;
321
    }
322
 
323
    /**
324
     * Print function param or return type(s).
325
     *
326
     * @param \ReflectionType $type
327
     *
328
     * @return string
329
     */
330
    private static function formatReflectionType(\ReflectionType $type = null): string
331
    {
332
        if ($type === null) {
333
            return '';
334
        }
335
 
336
        $types = $type instanceof \ReflectionUnionType ? $type->getTypes() : [$type];
337
        $formattedTypes = [];
338
 
339
        foreach ($types as $type) {
340
            $typeStyle = $type->isBuiltin() ? 'keyword' : 'class';
341
 
342
            // PHP 7.0 didn't have `getName` on reflection types, so wheee!
343
            $typeName = \method_exists($type, 'getName') ? $type->getName() : (string) $type;
344
 
345
            // @todo Do we want to include the ? for nullable types? Maybe only sometimes?
346
            $formattedTypes[] = \sprintf('<%s>%s</%s>', $typeStyle, OutputFormatter::escape($typeName), $typeStyle);
347
        }
348
 
349
        return \implode('|', $formattedTypes);
350
    }
351
}