Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.77% covered (success)
96.77%
30 / 31
0.00% covered (danger)
0.00%
0 / 1
CRAP
0.00% covered (danger)
0.00%
0 / 1
FunctionReflection
96.77% covered (success)
96.77%
30 / 31
0.00% covered (danger)
0.00%
0 / 1
10
0.00% covered (danger)
0.00%
0 / 1
 parse
96.77% covered (success)
96.77%
30 / 31
0.00% covered (danger)
0.00%
0 / 1
10
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\FunctionGenerator;
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;
23use ReflectionException;
24
25/**
26 * Function reflection code class
27 *
28 * @category   Pop
29 * @package    Pop\Code
30 * @author     Nick Sagona, III <dev@noladev.com>
31 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
32 * @license    https://www.popphp.org/license     New BSD License
33 * @version    6.0.0
34 */
35class FunctionReflection extends AbstractReflection
36{
37
38    /**
39     * Method to parse a function or closure
40     *
41     * @param  mixed   $code
42     * @param  ?string $name
43     * @throws ReflectionException
44     * @return FunctionGenerator
45     */
46    public static function parse(mixed $code, ?string $name = null): FunctionGenerator
47    {
48        $reflection       = new \ReflectionFunction($code);
49        $reflectionName   = $reflection->getName();
50        $reflectionParams = $reflection->getParameters();
51        // PHP 8.4 renamed closures from the bare '{closure}' to '{closure:file:line}' (or
52        // '{closure:Class::method():line}' for one declared inside a method) -- match the prefix
53        // rather than the exact old name, or isClosure is never true on this library's own
54        // minimum supported PHP version, and the mangled name gets used as a literal function
55        // name in the rendered output instead of being detected as a closure at all.
56        $isClosure        = str_starts_with($reflectionName, '{closure');
57
58        if (($name === null) && !($isClosure)) {
59            $name = $reflectionName;
60        }
61
62        $function = new FunctionGenerator($name, $isClosure);
63        foreach ($reflection->getAttributes() as $reflectionAttribute) {
64            $function->addAttribute(AttributeCollector::build($reflectionAttribute));
65        }
66
67        foreach ($reflectionParams as $key => $reflectionParam) {
68            $paramName  = $reflectionParam->getName();
69            $paramType  = TypeNormalizer::resolveReflectionType($reflectionParam->getType());
70
71            if (!$reflectionParam->isDefaultValueAvailable()) {
72                $paramValue = new NoValue();
73            } else if (($constantName = $reflectionParam->getDefaultValueConstantName()) !== null) {
74                $paramValue = new Literal($constantName);
75            } else {
76                $paramValue = $reflectionParam->getDefaultValue();
77            }
78
79            $paramAttributes = [];
80            foreach ($reflectionParam->getAttributes() as $reflectionAttribute) {
81                $paramAttributes[] = AttributeCollector::build($reflectionAttribute);
82            }
83
84            $function->addArgument(
85                $paramName, $paramValue, $paramType, $reflectionParam->isVariadic(), $reflectionParam->isPassedByReference(),
86                $paramAttributes
87            );
88        }
89
90        // Parse the body if available
91        $body = SourceBodyExtractor::extract($reflection, false);
92        if ($body !== null) {
93            $function->setBody($body, 0);
94        }
95
96        // Get return type(s)
97        $returnType = TypeNormalizer::resolveReflectionType($reflection->getReturnType());
98        if ($returnType !== null) {
99            $function->addReturnType($returnType);
100        }
101
102        return $function;
103    }
104
105}