Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
88.24% covered (success)
88.24%
60 / 68
0.00% covered (danger)
0.00%
0 / 1
CRAP
0.00% covered (danger)
0.00%
0 / 1
EnumReflection
88.24% covered (success)
88.24%
60 / 68
0.00% covered (danger)
0.00%
0 / 1
32.56
0.00% covered (danger)
0.00%
0 / 1
 parse
88.24% covered (success)
88.24%
60 / 68
0.00% covered (danger)
0.00%
0 / 1
32.56
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <dev@noladev.com>
8 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Code\Reflection;
16
17use Pop\Code\Generator;
18use Pop\Code\Reflection\Support\UseStatementParser;
19use Pop\Code\Reflection\Support\AttributeCollector;
20use Pop\Code\Reflection\Support\NamespaceImportResolver;
21
22/**
23 * Enum reflection code class
24 *
25 * @category   Pop
26 * @package    Pop\Code
27 * @author     Nick Sagona, III <dev@noladev.com>
28 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
29 * @license    https://www.popphp.org/license     New BSD License
30 * @version    6.0.0
31 */
32class EnumReflection extends AbstractReflection
33{
34
35    /**
36     * Method to parse an enum
37     *
38     * @param  mixed   $code
39     * @param  ?string $name
40     * @return Generator\EnumGenerator
41     */
42    public static function parse(mixed $code, ?string $name = null): Generator\EnumGenerator
43    {
44        $reflection     = new \ReflectionEnum($code);
45        $reflectionName = $reflection->getShortName();
46        $reflectionFile = $reflection->getFileName();
47        $fileContents   = null;
48
49        if (!empty($reflectionFile) && file_exists($reflectionFile)) {
50            $fileContents = file_get_contents($reflectionFile);
51        }
52
53        if (($name === null) && !empty($reflectionName)) {
54            $name = $reflectionName;
55        }
56
57        $enum = new Generator\EnumGenerator($name);
58
59        if ($reflection->isBacked()) {
60            $enum->setBackingType((string) $reflection->getBackingType());
61        }
62
63        // Detect and set namespace
64        if (($reflection->inNamespace()) && ($fileContents !== null)) {
65            $enum->setNamespace(NamespaceReflection::parse($fileContents, $reflection->getNamespaceName()));
66        }
67
68        // Shared across enum-level attributes, interfaces, and case-level attributes below --
69        // see NamespaceImportResolver.
70        $importResolver = new NamespaceImportResolver();
71
72        // Detect attributes
73        foreach ($reflection->getAttributes() as $reflectionAttribute) {
74            [$attributeReference, $needsImport] = $importResolver->resolve($reflectionAttribute->getName(), $reflection->getNamespaceName());
75            if ($needsImport) {
76                if (!$enum->hasNamespace()) {
77                    $enum->setNamespace(new Generator\NamespaceGenerator());
78                }
79                $enum->getNamespace()->addUse($reflectionAttribute->getName());
80            }
81            $enum->addAttribute(AttributeCollector::build($reflectionAttribute, $attributeReference));
82        }
83
84        // Detect and set the enum doc block
85        $enumDocBlock = $reflection->getDocComment();
86        if (!empty($enumDocBlock) && (str_contains($enumDocBlock, '/*'))) {
87            $enum->setDocblock(DocblockReflection::parse($enumDocBlock));
88        }
89
90        // Detect implemented interfaces, excluding the implicitly-added UnitEnum/BackedEnum
91        $interfaces    = $reflection->getInterfaces();
92        $interfacesAry = [];
93        foreach ($interfaces as $interface) {
94            if (in_array($interface->getName(), ['UnitEnum', 'BackedEnum'], true)) {
95                continue;
96            }
97            [$interfaceReference, $needsImport] = $importResolver->resolve($interface->getName(), $reflection->getNamespaceName());
98            if ($needsImport) {
99                if (!$enum->hasNamespace()) {
100                    $enum->setNamespace(new Generator\NamespaceGenerator());
101                }
102                $enum->getNamespace()->addUse($interface->getName());
103            }
104            $interfacesAry[] = $interfaceReference;
105        }
106        $enum->addInterfaces($interfacesAry);
107
108        // Detect used traits
109        if ($fileContents !== null) {
110            foreach (UseStatementParser::parse($fileContents) as $use => $as) {
111                $enum->addUse($use, $as);
112            }
113        }
114
115        // Detect cases
116        $caseNames = [];
117        foreach ($reflection->getCases() as $case) {
118            $caseNames[] = $case->getName();
119
120            $value    = ($case instanceof \ReflectionEnumBackedCase) ? $case->getBackingValue() : null;
121            $enumCase = new Generator\EnumCaseGenerator($case->getName(), $value);
122
123            $caseDocBlock = $case->getDocComment();
124            if (!empty($caseDocBlock) && (str_contains($caseDocBlock, '/*'))) {
125                $caseDocblock = DocblockReflection::parse($caseDocBlock);
126                $caseDocblock->setIndent(4);
127                $enumCase->setDocblock($caseDocblock);
128            }
129
130            foreach ($case->getAttributes() as $reflectionAttribute) {
131                [$attributeReference, $needsImport] = $importResolver->resolve($reflectionAttribute->getName(), $reflection->getNamespaceName());
132                if ($needsImport) {
133                    if (!$enum->hasNamespace()) {
134                        $enum->setNamespace(new Generator\NamespaceGenerator());
135                    }
136                    $enum->getNamespace()->addUse($reflectionAttribute->getName());
137                }
138                $enumCase->addAttribute(AttributeCollector::build($reflectionAttribute, $attributeReference));
139            }
140
141            $enum->addCase($enumCase);
142        }
143
144        // Detect constants, excluding cases (getReflectionConstants() reports both)
145        foreach ($reflection->getReflectionConstants() as $constant) {
146            if (in_array($constant->getName(), $caseNames, true)) {
147                continue;
148            }
149            $enum->addConstant(ConstantReflection::parse($constant));
150        }
151
152        // Detect methods, skipping the internal cases()/from()/tryFrom() methods PHP synthesizes
153        // on every enum (backed enums get from()/tryFrom() too) -- re-emitting these as empty
154        // user-declared methods causes a fatal "Cannot redeclare" error when the generated code
155        // is loaded.
156        $methods = $reflection->getMethods();
157        if (count($methods) > 0) {
158            foreach ($methods as $method) {
159                if ($method->isInternal()) {
160                    continue;
161                }
162                $enum->addMethod(MethodReflection::parse($method, $method->name));
163            }
164        }
165
166        return $enum;
167    }
168
169}