1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* PHP Smart Analysis project 2015-2016 |
4
|
|
|
* |
5
|
|
|
* @author Patsura Dmitry https://github.com/ovr <[email protected]> |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
namespace PHPSA\Compiler\Expression; |
9
|
|
|
|
10
|
|
|
use Ovr\PHPReflection\Reflector; |
11
|
|
|
use PHPSA\CompiledExpression; |
12
|
|
|
use PHPSA\Context; |
13
|
|
|
use PHPSA\Definition\ClosureDefinition; |
14
|
|
|
|
15
|
|
|
class FunctionCall extends AbstractExpressionCompiler |
16
|
|
|
{ |
17
|
|
|
protected $name = 'PhpParser\Node\Expr\FuncCall'; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* @param \PhpParser\Node\Expr\FuncCall $expr |
21
|
|
|
* @param Context $context |
22
|
|
|
* @return CompiledExpression |
23
|
|
|
*/ |
24
|
|
|
protected function compile($expr, Context $context) |
|
|
|
|
25
|
|
|
{ |
26
|
|
|
$expressionCompiler = $context->getExpressionCompiler(); |
27
|
|
|
$fNameExpression = $expressionCompiler->compile($expr->name); |
28
|
|
|
$name = $fNameExpression->getValue(); |
29
|
|
|
|
30
|
|
|
$compiler = $context->application->compiler; |
31
|
|
|
$exists = false; |
|
|
|
|
32
|
|
|
$arguments = $this->parseArgs($expr, clone $context); |
33
|
|
|
|
34
|
|
|
// is it a Closure |
35
|
|
|
if ($fNameExpression->isCallable() && $name instanceof ClosureDefinition) { |
36
|
|
|
return $name->run($this->parseArgs($expr, clone $context), $context); |
37
|
|
|
} |
38
|
|
|
|
39
|
|
|
// is the function name a correct string |
40
|
|
|
if (!$fNameExpression->isString() || !$fNameExpression->isCorrectValue()) { |
41
|
|
|
$context->debug( |
42
|
|
|
'Unexpected function name type ' . $fNameExpression->getTypeName(), |
43
|
|
|
$expr->name |
44
|
|
|
); |
45
|
|
|
|
46
|
|
|
return new CompiledExpression(); |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
// namespace check for correct functionDefinition |
50
|
|
|
if ($context->scope) { |
51
|
|
|
$functionDefinition = $compiler->getFunctionNS($name, $context->scope->getNamespace()); |
|
|
|
|
52
|
|
|
} else { |
53
|
|
|
$functionDefinition = $compiler->getFunction($name); |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
// does the function exist |
57
|
|
|
if ($functionDefinition) { |
58
|
|
|
if (!$functionDefinition->isCompiled()) { |
59
|
|
|
$functionDefinition->compile(clone $context); |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
$exists = true; |
63
|
|
|
} else { |
64
|
|
|
$exists = function_exists($name); |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
if (!$exists) { |
68
|
|
|
$context->notice( |
69
|
|
|
'language_error', |
70
|
|
|
sprintf('Function %s() does not exist', (string)$expr->name), |
71
|
|
|
$expr |
72
|
|
|
); |
73
|
|
|
} else { |
74
|
|
|
$reflector = new Reflector(Reflector::manuallyFactory()); |
75
|
|
|
$functionReflection = $reflector->getFunction($name); |
76
|
|
|
if ($functionReflection) { |
77
|
|
|
$argumentsSuccessPass = $this->checkArguments($arguments, $functionReflection); |
|
|
|
|
78
|
|
|
|
79
|
|
|
// when everything is ok we run the function |
80
|
|
|
if ($argumentsSuccessPass && $functionReflection->isRunnable()) { |
81
|
|
|
array_walk( |
82
|
|
|
$arguments, |
83
|
|
|
function (&$item) { |
84
|
|
|
/** @var CompiledExpression $item */ |
85
|
|
|
$item = $item->getValue(); |
86
|
|
|
} |
87
|
|
|
); |
88
|
|
|
|
89
|
|
|
return new CompiledExpression( |
90
|
|
|
$functionReflection->getReturnType(), |
91
|
|
|
$functionReflection->run($arguments) |
92
|
|
|
); |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
return new CompiledExpression($functionReflection->getReturnType()); |
96
|
|
|
} |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
return new CompiledExpression(); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* @param \PhpParser\Node\Expr\FuncCall $expr |
104
|
|
|
* @return CompiledExpression[] |
105
|
|
|
*/ |
106
|
|
|
protected function parseArgs($expr, Context $context) |
107
|
|
|
{ |
108
|
|
|
$arguments = []; |
109
|
|
|
|
110
|
|
|
foreach ($expr->args as $argument) { |
111
|
|
|
$arguments[] = $context->getExpressionCompiler()->compile($argument->value); |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
return $arguments; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* @param CompiledExpression[] $arguments |
119
|
|
|
* @param \Ovr\PHPReflection\FunctionReflection $functionReflection |
120
|
|
|
* @return bool |
121
|
|
|
*/ |
122
|
|
|
protected function checkArguments(array $arguments, $functionReflection) |
123
|
|
|
{ |
124
|
|
|
foreach ($arguments as $key => $argument) { |
125
|
|
|
$parameter = $functionReflection->getParameter($key); |
126
|
|
|
$paramType = $parameter->getType(); |
127
|
|
|
$argumentType = $argument->getType(); |
128
|
|
|
|
129
|
|
|
$numberTypes = [CompiledExpression::INTEGER, CompiledExpression::DOUBLE]; |
130
|
|
|
$callableTypes = [CompiledExpression::STRING, CompiledExpression::ARR]; |
131
|
|
|
|
132
|
|
|
// the paramtype is equal to the argument type or mixed |
133
|
|
|
// or paramtype is number and argumenttype is integer, double |
134
|
|
|
// or paramtype is callable and argumenttype is string, array |
135
|
|
|
if (!($paramType == $argumentType || $paramType == CompiledExpression::MIXED) |
136
|
|
|
&& !($paramType == CompiledExpression::NUMBER && in_array($argumentType, $numberTypes)) |
137
|
|
|
&& !($paramType == CompiledExpression::CALLABLE_TYPE && in_array($argumentType, $callableTypes))) { |
138
|
|
|
return false; |
139
|
|
|
} |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
// argumentcount != paramcount |
143
|
|
|
if (count($arguments) != $functionReflection->getNumberOfRequiredParameters()) { |
144
|
|
|
return false; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
return true; |
148
|
|
|
} |
149
|
|
|
} |
150
|
|
|
|
A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.
You can also find more information in the “Code” section of your repository.