Completed
Pull Request — master (#440)
by
unknown
25:11
created

FunctionProxy::getJoinpointInvocationBody()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 9.552
c 0
b 0
f 0
cc 4
nc 3
nop 1
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 Go\ParserReflection\ReflectionFileNamespace;
20
use ReflectionFunction;
21
22
/**
23
 * Function proxy builder that is used to generate a proxy-function from the list of joinpoints
24
 */
25
class FunctionProxy extends AbstractProxy
26
{
27
28
    /**
29
     * List of advices for functions
30
     *
31
     * @var array
32
     */
33
    protected static $functionAdvices = [];
34
35
    /**
36
     * File namespace
37
     *
38
     * @var ReflectionFileNamespace
39
     */
40
    protected $namespace;
41
42
    /**
43
     * Source code for functions
44
     *
45
     * @var array Name of the function => source code for it
46
     */
47
    protected $functionsCode = [];
48
49
    /**
50
     * Constructs functions stub class from namespace Reflection
51
     *
52
     * @param ReflectionFileNamespace $namespace Reflection of namespace
53
     * @param array $advices List of function advices
54
     *
55
     * @throws \InvalidArgumentException for invalid classes
56
     */
57
    public function __construct(ReflectionFileNamespace $namespace, array $advices = [])
58
    {
59
        parent::__construct($advices);
60
        $this->namespace = $namespace;
61
62
        if (empty($advices[AspectContainer::FUNCTION_PREFIX])) {
63
            return;
64
        }
65
66
        foreach ($advices[AspectContainer::FUNCTION_PREFIX] as $pointName => $value) {
67
            $function = new ReflectionFunction($pointName);
68
            $this->override($function, $this->getJoinpointInvocationBody($function));
69
        }
70
    }
71
72
    /**
73
     * Returns a joinpoint for specific function in the namespace
74
     *
75
     * @param string $joinPointName Special joinpoint name
76
     * @param string $namespace Name of the namespace
77
     *
78
     * @return FunctionInvocation
79
     */
80
    public static function getJoinPoint($joinPointName, $namespace)
81
    {
82
        /** @var LazyAdvisorAccessor $accessor */
83
        static $accessor;
84
85
        if (!$accessor) {
86
            $accessor = AspectKernel::getInstance()->getContainer()->get('aspect.advisor.accessor');
87
        }
88
89
        $advices = self::$functionAdvices[$namespace][AspectContainer::FUNCTION_PREFIX][$joinPointName];
90
91
        $filledAdvices = [];
92
        foreach ($advices as $advisorName) {
93
            $filledAdvices[] = $accessor->$advisorName;
94
        }
95
96
        return new ReflectionFunctionInvocation($joinPointName, $filledAdvices);
97
    }
98
99
    /**
100
     * Inject advices for given trait
101
     *
102
     * NB This method will be used as a callback during source code evaluation to inject joinpoints
103
     *
104
     * @param string $namespace Aop child proxy class
105
     * @param array|Advice[] $advices List of advices to inject into class
106
     *
107
     * @return void
108
     */
109
    public static function injectJoinPoints($namespace, array $advices = [])
110
    {
111
        self::$functionAdvices[$namespace] = $advices;
112
    }
113
114
    /**
115
     * Override function with new body
116
     *
117
     * @param ReflectionFunction $function Function reflection
118
     * @param string $body New body for function
119
     *
120
     * @return $this
121
     */
122
    public function override(ReflectionFunction $function, $body)
123
    {
124
        $this->functionsCode[$function->name] = $this->getOverriddenFunction($function, $body);
125
126
        return $this;
127
    }
128
129
    /**
130
     * {@inheritDoc}
131
     */
132
    public function __toString()
133
    {
134
        $functionsCode = (
135
            "<?php\n" . // Start of file header
136
            $this->namespace->getDocComment() . "\n" . // Doc-comment for file
137
            'namespace ' . // 'namespace' keyword
138
            $this->namespace->getName() . // Name
139
            ";\n" . // End of namespace name
140
            implode("\n", $this->functionsCode) // Function definitions
141
        );
142
143
        return $functionsCode
144
            // Inject advices on call
145
            . PHP_EOL
146
            . '\\' . __CLASS__ . "::injectJoinPoints('"
147
                . $this->namespace->getName() . "',"
148
                . var_export($this->advices, true) . ');';
149
    }
150
151
    /**
152
     * Creates definition for trait method body
153
     *
154
     * @param ReflectionFunction $function Method reflection
155
     *
156
     * @return string new method body
157
     */
158
    protected function getJoinpointInvocationBody(ReflectionFunction $function)
159
    {
160
        $class = '\\' . __CLASS__;
161
162
        $args = $this->prepareArgsLine($function);
163
164
        $return = 'return ';
165
        if (PHP_VERSION_ID >= 70100 && $function->hasReturnType()) {
166
            $returnType = (string) $function->getReturnType();
167
            if ($returnType === 'void') {
168
                // void return types should not return anything
169
                $return = '';
170
            }
171
        }
172
173
        return <<<BODY
174
static \$__joinPoint;
175
if (!\$__joinPoint) {
176
    \$__joinPoint = {$class}::getJoinPoint('{$function->name}', __NAMESPACE__);
177
}
178
{$return}\$__joinPoint->__invoke($args);
179
BODY;
180
    }
181
182
}
183