Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
53 / 53
100.00% covered (success)
100.00%
1 / 1
CRAP
100.00% covered (success)
100.00%
1 / 1
MethodReflection
100.00% covered (success)
100.00%
53 / 53
100.00% covered (success)
100.00%
1 / 1
19
100.00% covered (success)
100.00%
1 / 1
 parse
100.00% covered (success)
100.00%
53 / 53
100.00% covered (success)
100.00%
1 / 1
19
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\Generator\Literal;
19use Pop\Code\Generator\NoValue;
20use Pop\Code\Reflection\Support\AttributeCollector;
21use Pop\Code\Reflection\Support\SourceBodyExtractor;
22use Pop\Code\Reflection\Support\TypeNormalizer;
23
24/**
25 * Method reflection code class
26 *
27 * @category   Pop
28 * @package    Pop\Code
29 * @author     Nick Sagona, III <dev@noladev.com>
30 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
31 * @license    https://www.popphp.org/license     New BSD License
32 * @version    6.0.0
33 */
34class MethodReflection extends AbstractReflection
35{
36
37    /**
38     * Method to parse a method
39     *
40     * @param  mixed   $code
41     * @param  ?string $name
42     * @return Generator\MethodGenerator
43     */
44    public static function parse(mixed $code, ?string $name = null): Generator\MethodGenerator
45    {
46        if ($code->isProtected()) {
47            $visibility = 'protected';
48        } else if ($code->isPrivate()) {
49            $visibility = 'private';
50        } else {
51            $visibility = 'public';
52        }
53
54        $docblock = null;
55        $doc      = $code->getDocComment();
56        if (($doc !== false) && (str_contains($doc, '/*'))) {
57            $docblock = DocblockReflection::parse($doc);
58            $docblock->setIndent(4);
59        }
60
61        $method = new Generator\MethodGenerator($code->getName(), $visibility, $code->isStatic());
62        foreach ($code->getAttributes() as $reflectionAttribute) {
63            $method->addAttribute(AttributeCollector::build($reflectionAttribute));
64        }
65        if ($docblock !== null) {
66            $method->setDocblock($docblock);
67        }
68
69        $reflectionParams = $code->getParameters();
70        $declaringClass   = $code->getDeclaringClass();
71
72        // An interface method is always reported abstract by native reflection, but the
73        // `abstract` keyword is implicit -- and syntactically forbidden -- inside an interface
74        // body. Only apply the flag for a real abstract method on a class.
75        if ($code->isAbstract() && !$declaringClass->isInterface()) {
76            $method->setAsAbstract(true);
77        } else if ($code->isFinal()) {
78            $method->setAsFinal(true);
79        }
80
81        foreach ($reflectionParams as $key => $reflectionParam) {
82            $paramName  = $reflectionParam->getName();
83            $paramType  = TypeNormalizer::resolveReflectionType($reflectionParam->getType());
84
85            if (!$reflectionParam->isDefaultValueAvailable()) {
86                $paramValue = new NoValue();
87            } else if (($constantName = $reflectionParam->getDefaultValueConstantName()) !== null) {
88                $paramValue = new Literal($constantName);
89            } else {
90                $paramValue = $reflectionParam->getDefaultValue();
91            }
92
93            $paramAttributes = [];
94            foreach ($reflectionParam->getAttributes() as $reflectionAttribute) {
95                $paramAttributes[] = AttributeCollector::build($reflectionAttribute);
96            }
97
98            if ($reflectionParam->isPromoted()) {
99                $promotedProperty = $declaringClass->getProperty($paramName);
100                if ($promotedProperty->isProtected()) {
101                    $promotedVisibility = 'protected';
102                } else if ($promotedProperty->isPrivate()) {
103                    $promotedVisibility = 'private';
104                } else {
105                    $promotedVisibility = 'public';
106                }
107                $method->addPromotedArgument(
108                    $paramName, $promotedVisibility, $paramValue, $paramType, $promotedProperty->isReadOnly(), $paramAttributes
109                );
110            } else {
111                $method->addArgument(
112                    $paramName, $paramValue, $paramType, $reflectionParam->isVariadic(), $reflectionParam->isPassedByReference(),
113                    $paramAttributes
114                );
115            }
116        }
117
118        // Parse the body if available. Concrete methods always get an explicit body (even if
119        // empty) so they render with braces rather than as a bodyless abstract/interface stub.
120        $body = SourceBodyExtractor::extract($code, true);
121        if (!$code->isAbstract()) {
122            $method->setBody($body ?? '');
123        }
124
125        // Get return type(s)
126        $returnType = TypeNormalizer::resolveReflectionType($code->getReturnType());
127        if ($returnType !== null) {
128            $method->addReturnType($returnType);
129        }
130
131        return $method;
132    }
133
134}