Completed
Pull Request — master (#82)
by Loren
05:32
created

ReflectionFunction::__toString()   D

Complexity

Conditions 9
Paths 15

Size

Total Lines 44
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 9.0022

Importance

Changes 0
Metric Value
dl 0
loc 44
ccs 32
cts 33
cp 0.9697
rs 4.909
c 0
b 0
f 0
cc 9
eloc 33
nc 15
nop 0
crap 9.0022
1
<?php
2
/**
3
 * Parser Reflection API
4
 *
5
 * @copyright Copyright 2015, Lisachenko Alexander <[email protected]>
6
 *
7
 * This source file is subject to the license that is bundled
8
 * with this source code in the file LICENSE.
9
 */
10
11
namespace Go\ParserReflection;
12
13
use Go\ParserReflection\Traits\InternalPropertiesEmulationTrait;
14
use Go\ParserReflection\Traits\ReflectionFunctionLikeTrait;
15
use PhpParser\Node\Stmt\Function_;
16
use ReflectionFunction as BaseReflectionFunction;
17
18
/**
19
 * AST-based reflection for function
20
 */
21
class ReflectionFunction extends BaseReflectionFunction implements ReflectionInterface
22
{
23
    use ReflectionFunctionLikeTrait, InternalPropertiesEmulationTrait;
24
25
    /**
26
     * Name of the function
27
     *
28
     * @var string|\Closure
29
     */
30
    private $functionName;
31
32
    /**
33
     * Initializes reflection instance for given AST-node
34
     *
35
     * @param string|\Closure $functionName The name of the function to reflect or a closure.
36
     * @param Function_|null  $functionNode Function node AST
37
     */
38 672
    public function __construct($functionName, Function_ $functionNode = null)
39
    {
40 672
        $this->functionName = $functionName;
41 672
        $namespaceParts     = explode('\\', $functionName);
42
        // Remove the last one part with function name
43 672
        $shortName = array_pop($namespaceParts);
44 672
        $this->namespaceName = join('\\', $namespaceParts);
45 672
        $this->functionLikeNode = $functionNode;
46
        // Let's unset original read-only property to have a control over it via __get
47 672
        unset($this->name);
48
49 672
        if ($this->isParsedNodeMissing()) {
50
            // This will be implemented later:
51
            // $this->functionLikeNode = ReflectionEngine::parseFunction($functionName);
52
            throw new \InvalidArgumentException("PhpParser\\Node for function {$functionName}() must be provided.");
53
        }
54 672
        if ($this->functionLikeNode && ($shortName !== $this->functionLikeNode->name)) {
55
            throw new \InvalidArgumentException("PhpParser\\Node\\Stmt\\Function_'s name does not match provided function name.");
56
        }
57 672
    }
58
59
    /**
60
     * Are we missing the parser node?
61
     */
62 672
    private function isParsedNodeMissing()
63
    {
64 672
        if ($this->functionLikeNode) {
65 558
            return false;
66
        }
67 114
        $isUserDefined = true;
68 114
        if ($this->wasIncluded()) {
69 114
            $nativeRef = new BaseReflectionFunction($this->getName());
70 114
            $isUserDefined = $nativeRef->isUserDefined();
71
        }
72 114
        return $isUserDefined;
73
    }
74
75
    /**
76
     * Emulating original behaviour of reflection
77
     */
78 1
    public function ___debugInfo()
79
    {
80 1
        $nodeName = 'unknown';
81
82 1
        if ($this->functionLikeNode instanceof Function_) {
83 1
            $nodeName = $this->functionLikeNode->name;
84
        }
85
86 1
        return ['name' => $nodeName];
87
    }
88
89
    /**
90
     * Returns an AST-node for function
91
     *
92
     * @return Function_
93
     */
94
    public function getNode()
95
    {
96
        return $this->functionLikeNode;
97
    }
98
99
    /**
100
     * {@inheritDoc}
101
     */
102 1
    public function getClosure()
103
    {
104 1
        $this->initializeInternalReflection();
105
106 1
        return parent::getClosure();
107
    }
108
109
    /**
110
     * {@inheritDoc}
111
     */
112 1
    public function invoke($args = null)
113
    {
114 1
        $this->initializeInternalReflection();
115
116 1
        return call_user_func_array('parent::invoke', func_get_args());
117
    }
118
119
    /**
120
     * {@inheritDoc}
121
     */
122 1
    public function invokeArgs(array $args)
123
    {
124 1
        $this->initializeInternalReflection();
125
126 1
        return parent::invokeArgs($args);
127
    }
128
129
    /**
130
     * Checks if function is disabled
131
     *
132
     * Only internal functions can be disabled using disable_functions directive.
133
     * User-defined functions are unaffected.
134
     */
135 34
    public function isDisabled()
136
    {
137 34
        if (!$this->functionLikeNode) {
138 3
            $this->initializeInternalReflection();
139 3
            return parent::isDisabled();
140
        }
141 31
        return false;
142
    }
143
144
    /**
145
     * Returns textual representation of function
146
     *
147
     * @return string
148
     */
149 35
    public function __toString()
150
    {
151 35
        $origin = 'user';
152 35
        $source = '';
153 35
        if (!$this->isUserDefined()) {
154 4
            $origin = 'internal';
155 4
            if ($this->isDeprecated()) {
156
                $origin .= ', deprecated';
157
            }
158 4
            $phpExt = $this->getExtension();
159 4
            if ($phpExt) {
160 4
                $origin .= ':' . $phpExt->getName();
0 ignored issues
show
Bug introduced by Loren Osborn
Consider using $phpExt->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
161
            }
162
        } else {
163 31
            $source = sprintf("\n  @@ %s %d - %d", $this->getFileName(), $this->getStartLine(), $this->getEndLine());
164
        }
165
        // Internally $this->getReturnType() !== null is the same as $this->hasReturnType()
166 35
        $returnType    = $this->getReturnType();
167 35
        $hasReturnType = $returnType !== null;
168 35
        $paramFormat   = '';
169 35
        $returnFormat  = '';
170 35
        if (($this->getNumberOfParameters() > 0) || $hasReturnType) {
171 24
            $paramFormat  = "\n\n  - Parameters [%d] {%s\n  }";
172 24
            $returnFormat = $hasReturnType ? "\n  - Return [ %s ]" : '';
173
        }
174 35
        $reflectionFormat = "%sFunction [ <%s> function %s ] {%s{$paramFormat}{$returnFormat}\n}\n";
175
176 35
        return sprintf(
177 35
            $reflectionFormat,
178 35
            $this->getDocComment() ? $this->getDocComment() . "\n" : '',
179 35
            $origin,
180 35
            $this->getName(),
181 35
            $source,
182 35
            count($this->getParameters()),
183 35
            array_reduce(
184 35
                $this->getParameters(),
185 35
                (function ($str, ReflectionParameter $param) {
186 18
                    return $str . "\n    " . $param;
187 35
                }),
188 35
                ''
189
            ),
190 35
            $returnType ? ReflectionType::convertToDisplayType($returnType) : ''
191
        );
192
    }
193
194
195
    /**
196
     * Implementation of internal reflection initialization
197
     *
198
     * @return void
199
     */
200 149
    protected function __initialize()
201
    {
202 149
        parent::__construct($this->functionName);
203 149
    }
204
205
    /**
206
     * Has class been loaded by PHP.
207
     *
208
     * @return bool
209
     *     If class file was included.
210
     */
211 114
    public function wasIncluded()
212
    {
213 114
        return function_exists($this->functionName);
214
    }
215
}
216