Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Zur aktuellen Revision | Details | Vergleich mit vorheriger | 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
 *
200 lars 6
 * (c) 2012-2023 Justin Hileman
148 lars 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
     *
200 lars 66
     * @param \ReflectionClass|ReflectionClassConstant|\ReflectionClassConstant|\ReflectionFunctionAbstract $reflector
148 lars 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
     *
200 lars 78
     * @param \ReflectionMethod|\ReflectionProperty|\ReflectionClass $reflector
148 lars 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
    private static function getTypeStyle($value): string
178
    {
179
        if (\is_int($value) || \is_float($value)) {
180
            return 'number';
181
        } elseif (\is_string($value)) {
182
            return 'string';
183
        } elseif (\is_bool($value) || $value === null) {
184
            return 'bool';
185
        } else {
186
            return 'strong'; // @codeCoverageIgnore
187
        }
188
    }
189
 
190
    /**
191
     * Format a property signature.
192
     *
193
     * @param \ReflectionProperty $reflector
194
     *
195
     * @return string Formatted signature
196
     */
197
    private static function formatProperty(\ReflectionProperty $reflector): string
198
    {
199
        return \sprintf(
200
            '%s <strong>$%s</strong>',
201
            self::formatModifiers($reflector),
202
            $reflector->getName()
203
        );
204
    }
205
 
206
    /**
207
     * Format a function signature.
208
     *
209
     * @param \ReflectionFunction $reflector
210
     *
211
     * @return string Formatted signature
212
     */
213
    private static function formatFunction(\ReflectionFunctionAbstract $reflector): string
214
    {
215
        return \sprintf(
216
            '<keyword>function</keyword> %s<function>%s</function>(%s)%s',
217
            $reflector->returnsReference() ? '&' : '',
218
            self::formatName($reflector),
219
            \implode(', ', self::formatFunctionParams($reflector)),
220
            self::formatFunctionReturnType($reflector)
221
        );
222
    }
223
 
224
    /**
225
     * Format a function signature's return type (if available).
226
     *
227
     * @param \ReflectionFunctionAbstract $reflector
228
     *
229
     * @return string Formatted return type
230
     */
231
    private static function formatFunctionReturnType(\ReflectionFunctionAbstract $reflector): string
232
    {
233
        if (!\method_exists($reflector, 'hasReturnType') || !$reflector->hasReturnType()) {
234
            return '';
235
        }
236
 
237
        return \sprintf(': %s', self::formatReflectionType($reflector->getReturnType()));
238
    }
239
 
240
    /**
241
     * Format a method signature.
242
     *
243
     * @param \ReflectionMethod $reflector
244
     *
245
     * @return string Formatted signature
246
     */
247
    private static function formatMethod(\ReflectionMethod $reflector): string
248
    {
249
        return \sprintf(
250
            '%s %s',
251
            self::formatModifiers($reflector),
252
            self::formatFunction($reflector)
253
        );
254
    }
255
 
256
    /**
257
     * Print the function params.
258
     *
259
     * @param \ReflectionFunctionAbstract $reflector
260
     *
261
     * @return array
262
     */
263
    private static function formatFunctionParams(\ReflectionFunctionAbstract $reflector): array
264
    {
265
        $params = [];
266
        foreach ($reflector->getParameters() as $param) {
267
            $hint = '';
268
            try {
269
                if (\method_exists($param, 'getType')) {
270
                    $hint = self::formatReflectionType($param->getType());
271
                } else {
272
                    if ($param->isArray()) {
273
                        $hint = '<keyword>array</keyword>';
274
                    } elseif ($class = $param->getClass()) {
275
                        $hint = \sprintf('<class>%s</class>', $class->getName());
276
                    }
277
                }
278
            } catch (\Throwable $e) {
279
                // sometimes we just don't know...
280
                // bad class names, or autoloaded classes that haven't been loaded yet, or whathaveyou.
281
                // come to think of it, the only time I've seen this is with the intl extension.
282
 
283
                // Hax: we'll try to extract it :P
284
 
285
                // @codeCoverageIgnoreStart
286
                $chunks = \explode('$'.$param->getName(), (string) $param);
287
                $chunks = \explode(' ', \trim($chunks[0]));
288
                $guess = \end($chunks);
289
 
290
                $hint = \sprintf('<urgent>%s</urgent>', OutputFormatter::escape($guess));
291
                // @codeCoverageIgnoreEnd
292
            }
293
 
294
            if ($param->isOptional()) {
295
                if (!$param->isDefaultValueAvailable()) {
296
                    $value = 'unknown';
297
                    $typeStyle = 'urgent';
298
                } else {
299
                    $value = $param->getDefaultValue();
300
                    $typeStyle = self::getTypeStyle($value);
301
                    $value = \is_array($value) ? '[]' : ($value === null ? 'null' : \var_export($value, true));
302
                }
303
                $default = \sprintf(' = <%s>%s</%s>', $typeStyle, OutputFormatter::escape($value), $typeStyle);
304
            } else {
305
                $default = '';
306
            }
307
 
308
            $params[] = \sprintf(
309
                '%s%s%s<strong>$%s</strong>%s',
310
                $param->isPassedByReference() ? '&' : '',
311
                $hint,
312
                $hint !== '' ? ' ' : '',
313
                $param->getName(),
314
                $default
315
            );
316
        }
317
 
318
        return $params;
319
    }
320
 
321
    /**
322
     * Print function param or return type(s).
323
     *
324
     * @param \ReflectionType $type
325
     */
326
    private static function formatReflectionType(\ReflectionType $type = null): string
327
    {
328
        if ($type === null) {
329
            return '';
330
        }
331
 
332
        $types = $type instanceof \ReflectionUnionType ? $type->getTypes() : [$type];
333
        $formattedTypes = [];
334
 
335
        foreach ($types as $type) {
336
            $typeStyle = $type->isBuiltin() ? 'keyword' : 'class';
337
 
338
            // PHP 7.0 didn't have `getName` on reflection types, so wheee!
339
            $typeName = \method_exists($type, 'getName') ? $type->getName() : (string) $type;
340
 
341
            // @todo Do we want to include the ? for nullable types? Maybe only sometimes?
342
            $formattedTypes[] = \sprintf('<%s>%s</%s>', $typeStyle, OutputFormatter::escape($typeName), $typeStyle);
343
        }
344
 
345
        return \implode('|', $formattedTypes);
346
    }
347
}