Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.68% covered (success)
75.68%
28 / 37
0.00% covered (danger)
0.00%
0 / 1
CRAP
0.00% covered (danger)
0.00%
0 / 1
InterfaceReflection
75.68% covered (success)
75.68%
28 / 37
0.00% covered (danger)
0.00%
0 / 1
22.66
0.00% covered (danger)
0.00%
0 / 1
 parse
75.68% covered (success)
75.68%
28 / 37
0.00% covered (danger)
0.00%
0 / 1
22.66
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\AttributeCollector;
19use Pop\Code\Reflection\Support\InterfaceHierarchyResolver;
20use Pop\Code\Reflection\Support\NamespaceImportResolver;
21use ReflectionException;
22
23/**
24 * Interface reflection code class
25 *
26 * @category   Pop
27 * @package    Pop\Code
28 * @author     Nick Sagona, III <dev@noladev.com>
29 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
30 * @license    https://www.popphp.org/license     New BSD License
31 * @version    6.0.0
32 */
33class InterfaceReflection extends AbstractReflection
34{
35
36    /**
37     * Method to parse an interface
38     *
39     * @param  mixed   $code
40     * @param  ?string $name
41     * @throws Exception|ReflectionException
42     * @return Generator\InterfaceGenerator
43     */
44    public static function parse(mixed $code, ?string $name = null): Generator\InterfaceGenerator
45    {
46        $reflection     = new \ReflectionClass($code);
47        $reflectionName = $reflection->getShortName();
48
49        if (($name === null) && !empty($reflectionName)) {
50            $name = $reflectionName;
51        }
52
53        if (!$reflection->isInterface()) {
54            throw new Exception('Error: The code is not an interface.');
55        }
56
57        $interface = new Generator\InterfaceGenerator($name);
58
59        // Detect and set namespace
60        if ($reflection->inNamespace()) {
61            $file = $reflection->getFileName();
62            if (!empty($file) && file_exists($file)) {
63                $interface->setNamespace(NamespaceReflection::parse(file_get_contents($file), $reflection->getNamespaceName()));
64            }
65        }
66
67        // Shared across attributes and parent interfaces below -- see NamespaceImportResolver.
68        $importResolver = new NamespaceImportResolver();
69
70        // Detect attributes
71        foreach ($reflection->getAttributes() as $reflectionAttribute) {
72            [$attributeReference, $needsImport] = $importResolver->resolve($reflectionAttribute->getName(), $reflection->getNamespaceName());
73            if ($needsImport) {
74                if (!$interface->hasNamespace()) {
75                    $interface->setNamespace(new Generator\NamespaceGenerator());
76                }
77                $interface->getNamespace()->addUse($reflectionAttribute->getName());
78            }
79            $interface->addAttribute(AttributeCollector::build($reflectionAttribute, $attributeReference));
80        }
81
82        // Detect and set the class doc block
83        $interfaceDocBlock = $reflection->getDocComment();
84        if (!empty($interfaceDocBlock) && (str_contains($interfaceDocBlock, '/*'))) {
85            $interface->setDocblock(DocblockReflection::parse($interfaceDocBlock));
86        }
87
88        // Detect parent interface(s) -- for an interface, getParentClass() always returns false
89        // (that API is for class `extends`); the interfaces it extends are reported via
90        // getInterfaces() instead. That API returns the full transitive closure though (e.g. for
91        // `interface C extends B` where `B extends A`, reflecting C reports both A and B) -- so a
92        // candidate is kept as a *direct* parent only if no other candidate in the same set
93        // already reports it as one of its own interfaces (i.e. it isn't reachable through
94        // another candidate already in the list) -- see InterfaceHierarchyResolver.
95        $allParents = $reflection->getInterfaces();
96        foreach (InterfaceHierarchyResolver::direct($allParents) as $candidateName => $candidate) {
97            [$parentReference, $needsImport] = $importResolver->resolve($candidateName, $reflection->getNamespaceName());
98            if ($needsImport) {
99                if (!$interface->hasNamespace()) {
100                    $interface->setNamespace(new Generator\NamespaceGenerator());
101                }
102                $interface->getNamespace()->addUse($candidateName);
103            }
104            $interface->addParent($parentReference);
105        }
106
107        // Detect constants
108        foreach ($reflection->getReflectionConstants() as $constant) {
109            $interface->addConstant(ConstantReflection::parse($constant));
110        }
111
112        // Detect methods
113        $methods = $reflection->getMethods();
114        if (count($methods) > 0) {
115            foreach ($methods as $method) {
116                $interface->addMethod(MethodReflection::parse($method, $method->name));
117            }
118        }
119
120        return $interface;
121    }
122
123}