• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

brick / reflection / 13180054222

06 Feb 2025 01:38PM UTC coverage: 79.856%. Remained the same
13180054222

push

github

BenMorel
Prepare for release

111 of 139 relevant lines covered (79.86%)

10.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.89
/src/ReflectionTools.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Brick\Reflection;
6

7
use Brick\VarExporter\VarExporter;
8
use Closure;
9
use Exception;
10
use Reflection;
11
use ReflectionClass;
12
use ReflectionFunction;
13
use ReflectionFunctionAbstract;
14
use ReflectionIntersectionType;
15
use ReflectionMethod;
16
use ReflectionNamedType;
17
use ReflectionProperty;
18
use ReflectionType;
19
use ReflectionUnionType;
20

21
/**
22
 * Tools for the Reflection API.
23
 */
24
class ReflectionTools
25
{
26
    /**
27
     * Returns reflections of all the non-static methods that make up one object.
28
     *
29
     * Like ReflectionClass::getMethods(), this method:
30
     *
31
     * - does not return overridden protected or public class methods, and only returns the overriding one;
32
     * - returns methods inside a class in the order they are declared.
33
     *
34
     * Unlike ReflectionClass::getMethods(), this method:
35
     *
36
     * - returns the private methods of parent classes;
37
     * - returns methods in hierarchical order: methods from parent classes are returned first.
38
     *
39
     * @return ReflectionMethod[]
40
     */
41
    public function getClassMethods(ReflectionClass $class) : array
42
    {
43
        $classes = $this->getClassHierarchy($class);
6✔
44

45
        $methods = [];
6✔
46

47
        foreach ($classes as $hClass) {
6✔
48
            $hClassName = $hClass->getName();
6✔
49

50
            foreach ($hClass->getMethods() as $method) {
6✔
51
                if ($method->isStatic()) {
6✔
52
                    // exclude static methods
53
                    continue;
1✔
54
                }
55

56
                if ($method->getDeclaringClass()->getName() !== $hClassName) {
5✔
57
                    // exclude inherited methods
58
                    continue;
1✔
59
                }
60

61
                $methods[] = $method;
5✔
62
            }
63
        }
64

65
        return $this->filterReflectors($methods);
6✔
66
    }
67

68
    /**
69
     * Returns reflections of all the non-static properties that make up one object.
70
     *
71
     * Like ReflectionClass::getProperties(), this method:
72
     *
73
     * - does not return overridden protected or public class properties, and only returns the overriding one;
74
     * - returns properties inside a class in the order they are declared.
75
     *
76
     * Unlike ReflectionClass::getProperties(), this method:
77
     *
78
     * - returns the private properties of parent classes;
79
     * - returns properties in hierarchical order: properties from parent classes are returned first.
80
     *
81
     * @return ReflectionProperty[]
82
     */
83
    public function getClassProperties(ReflectionClass $class) : array
84
    {
85
        $classes = $this->getClassHierarchy($class);
6✔
86

87
        /** @var ReflectionProperty[] $properties */
88
        $properties = [];
6✔
89

90
        foreach ($classes as $hClass) {
6✔
91
            $hClassName = $hClass->getName();
6✔
92

93
            foreach ($hClass->getProperties() as $property) {
6✔
94
                if ($property->isStatic()) {
6✔
95
                    // exclude static properties
96
                    continue;
1✔
97
                }
98

99
                if ($property->getDeclaringClass()->getName() !== $hClassName) {
5✔
100
                    // exclude inherited properties
101
                    continue;
1✔
102
                }
103

104
                $properties[] = $property;
5✔
105
            }
106
        }
107

108
        return $this->filterReflectors($properties);
6✔
109
    }
110

111
    /**
112
     * Returns the hierarchy of classes, starting from the first ancestor and ending with the class itself.
113
     *
114
     * @return ReflectionClass[]
115
     */
116
    public function getClassHierarchy(ReflectionClass $class) : array
117
    {
118
        $classes = [];
12✔
119

120
        while ($class) {
12✔
121
            $classes[] = $class;
12✔
122
            $class = $class->getParentClass();
12✔
123
        }
124

125
        return array_reverse($classes);
12✔
126
    }
127

128
    /**
129
     * Returns a reflection object for any callable.
130
     */
131
    public function getReflectionFunction(callable $function) : ReflectionFunctionAbstract
132
    {
133
        if (is_array($function)) {
×
134
            return new ReflectionMethod($function[0], $function[1]);
×
135
        }
136

137
        if ($function instanceof Closure) {
×
138
            return new ReflectionFunction($function);
×
139
        }
140

141
        if (is_object($function)) {
×
142
            return new ReflectionMethod($function, '__invoke');
×
143
        }
144

145
        return new ReflectionFunction($function);
×
146
    }
147

148
    /**
149
     * Returns a meaningful name for the given function, including the class name if it is a method.
150
     *
151
     * Example for a method: Namespace\Class::method
152
     * Example for a function: strlen
153
     * Example for a closure: {closure}
154
     */
155
    public function getFunctionName(ReflectionFunctionAbstract $function) : string
156
    {
157
        if ($function instanceof ReflectionMethod) {
×
158
            return $function->getDeclaringClass()->getName() . '::' . $function->getName();
×
159
        }
160

161
        return $function->getName();
×
162
    }
163

164
    /**
165
     * Exports the function signature.
166
     *
167
     * @param ReflectionFunctionAbstract $function         The function to export.
168
     * @param int                        $excludeModifiers An optional bitmask of modifiers to exclude.
169
     */
170
    public function exportFunctionSignature(ReflectionFunctionAbstract $function, int $excludeModifiers = 0) : string
171
    {
172
        $result = '';
34✔
173

174
        if ($function instanceof ReflectionMethod) {
34✔
175
            $modifiers = $function->getModifiers();
34✔
176
            $modifiers &= ~ $excludeModifiers;
34✔
177

178
            foreach (Reflection::getModifierNames($modifiers) as $modifier) {
34✔
179
                $result .= $modifier . ' ';
34✔
180
            }
181
        }
182

183
        $result .= 'function ';
34✔
184

185
        if ($function->returnsReference()) {
34✔
186
            $result .= '& ';
2✔
187
        }
188

189
        $result .= $function->getShortName();
34✔
190
        $result .= '(' . $this->exportFunctionParameters($function) . ')';
34✔
191

192
        if (null !== $returnType = $function->getReturnType()) {
34✔
193
            $result .= ': ' . $this->exportType($returnType);
18✔
194
        }
195

196
        return $result;
34✔
197
    }
198

199
    private function exportFunctionParameters(ReflectionFunctionAbstract $function) : string
200
    {
201
        $result = '';
34✔
202

203
        foreach ($function->getParameters() as $key => $parameter) {
34✔
204
            if ($key !== 0) {
23✔
205
                $result .= ', ';
10✔
206
            }
207

208
            if (null !== $type = $parameter->getType()) {
23✔
209
                $result .= $this->exportType($type) . ' ';
21✔
210
            }
211

212
            if ($parameter->isPassedByReference()) {
23✔
213
                $result .= '& ';
7✔
214
            }
215

216
            if ($parameter->isVariadic()) {
23✔
217
                $result .= '...';
4✔
218
            }
219

220
            $result .= '$' . $parameter->getName();
23✔
221

222
            if ($parameter->isDefaultValueAvailable()) {
23✔
223
                if ($parameter->isDefaultValueConstant()) {
9✔
224
                    $result .= ' = ' . '\\' . $parameter->getDefaultValueConstantName();
2✔
225
                } else {
226
                    $result .= ' = ' . VarExporter::export($parameter->getDefaultValue(), VarExporter::INLINE_ARRAY);
8✔
227
                }
228
            }
229
        }
230

231
        return $result;
34✔
232
    }
233

234
    /**
235
     * @psalm-suppress RedundantCondition https://github.com/vimeo/psalm/pull/8201
236
     */
237
    private function exportType(ReflectionType $type, bool $inUnion = false): string
238
    {
239
        if ($type instanceof ReflectionUnionType) {
31✔
240
            return implode('|', array_map(
6✔
241
                fn (ReflectionType $type) => $this->exportType($type, true),
6✔
242
                $type->getTypes(),
6✔
243
            ));
6✔
244
        }
245

246
        if ($type instanceof ReflectionIntersectionType) {
31✔
247
            $result = implode('&', array_map(
2✔
248
                fn (ReflectionType $type) => $this->exportType($type),
2✔
249
                $type->getTypes(),
2✔
250
            ));
2✔
251

252
            return $inUnion ? "($result)" : $result;
2✔
253
        }
254

255
        if (! $type instanceof ReflectionNamedType) {
31✔
256
            throw new Exception('Unsupported ReflectionType class: ' . $type::class);
×
257
        }
258

259
        $result = '';
31✔
260

261
        if ($type->allowsNull() && $type->getName() !== 'mixed' && $type->getName() !== 'null') {
31✔
262
            $result .= '?';
15✔
263
        }
264

265
        if (! $type->isBuiltin() && $type->getName() !== 'self' && $type->getName() !== 'static') {
31✔
266
            $result .= '\\';
9✔
267
        }
268

269
        $result .= $type->getName();
31✔
270

271
        return $result;
31✔
272
    }
273

274
    /**
275
     * Filters a list of ReflectionProperty or ReflectionMethod objects.
276
     *
277
     * This method removes overridden properties, while keeping original order.
278
     *
279
     * @template T of ReflectionProperty|ReflectionMethod
280
     *
281
     * @param T[] $reflectors
282
     *
283
     * @return T[]
284
     */
285
    private function filterReflectors(array $reflectors) : array
286
    {
287
        $filteredReflectors = [];
12✔
288

289
        foreach ($reflectors as $index => $reflector) {
12✔
290
            if ($reflector->isPrivate()) {
10✔
291
                $filteredReflectors[] = $reflector;
6✔
292
                continue;
6✔
293
            }
294

295
            foreach ($reflectors as $index2 => $reflector2) {
10✔
296
                if ($index2 <= $index) {
10✔
297
                    continue;
10✔
298
                }
299

300
                if ($reflector->getName() === $reflector2->getName()) {
10✔
301
                    // overridden
302
                    continue 2;
4✔
303
                }
304
            }
305

306
            $filteredReflectors[] = $reflector;
10✔
307
        }
308

309
        return $filteredReflectors;
12✔
310
    }
311
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc