Completed
Push — 1.x ( c183a4...05b51a )
by Alexander
8s
created

FunctionProxy   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 211
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 3

Test Coverage

Coverage 0%

Importance

Changes 5
Bugs 1 Features 0
Metric Value
wmc 22
c 5
b 1
f 0
lcom 2
cbo 3
dl 0
loc 211
ccs 0
cts 105
cp 0
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 3
A getJoinPoint() 0 18 3
A injectJoinPoints() 0 4 1
A override() 0 6 1
D getJoinpointInvocationBody() 0 41 10
A __toString() 0 18 1
B getOverriddenFunction() 0 26 3
1
<?php
2
/*
3
 * Go! AOP framework
4
 *
5
 * @copyright Copyright 2013, 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\Proxy;
12
13
use Go\Aop\Advice;
14
use Go\Aop\Framework\ReflectionFunctionInvocation;
15
use Go\Aop\Intercept\FunctionInvocation;
16
use Go\Core\AspectContainer;
17
use Go\Core\AspectKernel;
18
use Go\Core\LazyAdvisorAccessor;
19
use ReflectionFunction;
20
use ReflectionParameter;
21
use TokenReflection\ReflectionFileNamespace;
22
23
/**
24
 * Function proxy builder that is used to generate a proxy-function from the list of joinpoints
25
 */
26
class FunctionProxy extends AbstractProxy
27
{
28
29
    /**
30
     * List of advices for functions
31
     *
32
     * @var array
33
     */
34
    protected static $functionAdvices = [];
35
36
    /**
37
     * Name for the current namespace
38
     *
39
     * @var string
40
     */
41
    protected $namespace = '';
42
43
    /**
44
     * Source code for functions
45
     *
46
     * @var array Name of the function => source code for it
47
     */
48
    protected $functionsCode = [];
49
50
    /**
51
     * Constructs functions stub class from namespace Reflection
52
     *
53
     * @param ReflectionFileNamespace $namespace Reflection of namespace
54
     * @param array $advices List of function advices
55
     *
56
     * @throws \InvalidArgumentException for invalid classes
57
     */
58
    public function __construct(ReflectionFileNamespace $namespace, array $advices = [])
59
    {
60
        parent::__construct($advices);
61
        $this->namespace = $namespace;
0 ignored issues
show
Documentation Bug introduced by
It seems like $namespace of type object<TokenReflection\ReflectionFileNamespace> is incompatible with the declared type string of property $namespace.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
62
63
        if (empty($advices[AspectContainer::FUNCTION_PREFIX])) {
64
            return;
65
        }
66
67
        foreach ($advices[AspectContainer::FUNCTION_PREFIX] as $pointName => $value) {
68
            $function = new ReflectionFunction($pointName);
69
            $this->override($function, $this->getJoinpointInvocationBody($function));
70
        }
71
    }
72
73
    /**
74
     * Returns a joinpoint for specific function in the namespace
75
     *
76
     * @param string $joinPointName Special joinpoint name
77
     * @param string $namespace Name of the namespace
78
     *
79
     * @return FunctionInvocation
80
     */
81
    public static function getJoinPoint($joinPointName, $namespace)
82
    {
83
        /** @var LazyAdvisorAccessor $accessor */
84
        static $accessor = null;
85
86
        if (!$accessor) {
87
            $accessor = AspectKernel::getInstance()->getContainer()->get('aspect.advisor.accessor');
88
        }
89
90
        $advices = self::$functionAdvices[$namespace][AspectContainer::FUNCTION_PREFIX][$joinPointName];
91
92
        $filledAdvices = [];
93
        foreach ($advices as $advisorName) {
94
            $filledAdvices[] = $accessor->$advisorName;
95
        }
96
97
        return new ReflectionFunctionInvocation($joinPointName, $filledAdvices);
98
    }
99
100
    /**
101
     * Inject advices for given trait
102
     *
103
     * NB This method will be used as a callback during source code evaluation to inject joinpoints
104
     *
105
     * @param string $namespace Aop child proxy class
106
     * @param array|Advice[] $advices List of advices to inject into class
107
     *
108
     * @return void
109
     */
110
    public static function injectJoinPoints($namespace, array $advices = [])
111
    {
112
        self::$functionAdvices[$namespace] = $advices;
113
    }
114
115
    /**
116
     * Override function with new body
117
     *
118
     * @param ReflectionFunction $function Function reflection
119
     * @param string $body New body for function
120
     *
121
     * @return $this
122
     */
123
    public function override(ReflectionFunction $function, $body)
124
    {
125
        $this->functionsCode[$function->name] = $this->getOverriddenFunction($function, $body);
126
127
        return $this;
128
    }
129
130
    /**
131
     * {@inheritDoc}
132
     */
133
    public function __toString()
134
    {
135
        $functionsCode = (
136
            "<?php\n" . // Start of file header
137
            $this->namespace->getDocComment() . "\n" . // Doc-comment for file
0 ignored issues
show
Bug introduced by
The method getDocComment cannot be called on $this->namespace (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
138
            'namespace ' . // 'namespace' keyword
139
            $this->namespace->getName() . // Name
0 ignored issues
show
Bug introduced by
The method getName cannot be called on $this->namespace (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
140
            ";\n" . // End of namespace name
141
            join("\n", $this->functionsCode) // Function definitions
142
        );
143
144
        return $functionsCode
145
            // Inject advices on call
146
            . PHP_EOL
147
            . '\\' . __CLASS__ . "::injectJoinPoints('"
148
                . $this->namespace->getName() . "',"
0 ignored issues
show
Bug introduced by
The method getName cannot be called on $this->namespace (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
149
                . var_export($this->advices, true) . ");";
150
    }
151
152
    /**
153
     * Creates a function code from Reflection
154
     *
155
     * @param ReflectionFunction $function Reflection for function
156
     * @param string $body Body of function
157
     *
158
     * @return string
159
     */
160
    protected function getOverriddenFunction(ReflectionFunction $function, $body)
161
    {
162
        static $inMemoryCache = [];
163
164
        $functionName = $function->getName();
165
        if (isset($inMemoryCache[$functionName])) {
166
            return $inMemoryCache[$functionName];
167
        }
168
169
        $code = (
170
            preg_replace('/ {4}|\t/', '', $function->getDocComment()) . "\n" . // Original doc-comment
171
            'function ' . // 'function' keyword
172
            ($function->returnsReference() ? '&' : '') . // By reference symbol
173
            $functionName . // Function name
174
            '(' . // Start of parameters
175
            join(', ', $this->getParameters($function->getParameters())) . // List of parameters
176
            ")\n" . // End of parameters
177
            "{\n" . // Start of function body
178
            $this->indent($body) . "\n" . // Body of function
179
            "}\n" // End of function body
180
        );
181
182
        $inMemoryCache[$functionName] = $code;
183
184
        return $code;
185
    }
186
187
    /**
188
     * Creates definition for trait method body
189
     *
190
     * @param ReflectionFunction $function Method reflection
191
     *
192
     * @return string new method body
193
     */
194
    protected function getJoinpointInvocationBody(ReflectionFunction $function)
195
    {
196
        $class = '\\' . __CLASS__;
197
198
        $dynamicArgs   = false;
199
        $hasOptionals  = false;
200
        $hasReferences = false;
201
202
        $argValues = array_map(function(ReflectionParameter $param) use (&$dynamicArgs, &$hasOptionals, &$hasReferences) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
203
            $byReference   = $param->isPassedByReference();
204
            $dynamicArg    = $param->name == '...';
205
            $dynamicArgs   = $dynamicArgs || $dynamicArg;
206
            $hasOptionals  = $hasOptionals || ($param->isOptional() && !$param->isDefaultValueAvailable());
207
            $hasReferences = $hasReferences || $byReference;
208
209
            return ($byReference ? '&' : '') . '$' . $param->name;
210
        }, $function->getParameters());
211
212
        if ($dynamicArgs) {
213
            // Remove last '...' argument
214
            array_pop($argValues);
215
        }
216
217
        $args = join(', ', $argValues);
218
219
        if ($dynamicArgs) {
220
            $args = $hasReferences ? "[$args] + \\func_get_args()" : '\func_get_args()';
221
        } elseif ($hasOptionals) {
222
            $args = "\\array_slice([$args], 0, \\func_num_args())";
223
        } else {
224
            $args = "[$args]";
225
        }
226
227
        return <<<BODY
228
static \$__joinPoint = null;
229
if (!\$__joinPoint) {
230
    \$__joinPoint = {$class}::getJoinPoint('{$function->name}', __NAMESPACE__);
231
}
232
return \$__joinPoint->__invoke($args);
233
BODY;
234
    }
235
236
}
237