Completed
Pull Request — 2.x (#349)
by Alexander
02:20
created

getJoinpointInvocationBody()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 0
cts 24
cp 0
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 18
nc 3
nop 1
crap 20
1
<?php
2
declare(strict_types = 1);
3
/*
4
 * Go! AOP framework
5
 *
6
 * @copyright Copyright 2013, Lisachenko Alexander <[email protected]>
7
 *
8
 * This source file is subject to the license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Go\Proxy;
13
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\ParserReflection\ReflectionFileNamespace;
19
use Go\Proxy\Part\FunctionCallArgumentListGenerator;
20
use Go\Proxy\Part\InterceptedFunctionGenerator;
21
use ReflectionFunction;
22
use Zend\Code\Generator\FileGenerator;
23
use Zend\Code\Generator\ValueGenerator;
24
25
/**
26
 * Function proxy builder that is used to generate a proxy-function from the list of joinpoints
27
 */
28
class FunctionProxyGenerator
29
{
30
    /**
31
     * List of advices that are used for generation of child
32
     *
33
     * @var array
34
     */
35
    protected $advices = [];
36
37
    /**
38
     * @var FileGenerator
39
     */
40
    protected $fileGenerator;
41
42
    /**
43
     * Constructs functions stub class from namespace Reflection
44
     *
45
     * @param ReflectionFileNamespace $namespace            Reflection of namespace
46
     * @param string[][]              $advices              List of function advices
47
     * @param bool                    $useParameterWidening Enables usage of parameter widening feature
48
     *
49
     * @throws \ReflectionException If there is an advice for unknown function
50
     */
51
    public function __construct(
52
        ReflectionFileNamespace $namespace,
53
        array $advices = [],
54
        bool $useParameterWidening = false
55
    ) {
56
        $this->advices       = $advices;
57
        $this->fileGenerator = new FileGenerator();
58
        $this->fileGenerator->setNamespace($namespace->getName());
59
60
        $functionsContent = [];
61
        $functionAdvices  = $advices[AspectContainer::FUNCTION_PREFIX] ?? [];
62
        foreach (array_keys($functionAdvices) as $functionName) {
63
            $functionReflection  = new ReflectionFunction($functionName);
64
            $functionBody        = $this->getJoinpointInvocationBody($functionReflection);
65
            $interceptedFunction = new InterceptedFunctionGenerator($functionReflection, $functionBody, $useParameterWidening);
66
            $functionsContent[]  = $interceptedFunction->generate();
67
        }
68
69
        $this->fileGenerator->setBody(implode("\n", $functionsContent));
70
    }
71
72
    /**
73
     * Returns a joinpoint for specific function in the namespace
74
     *
75
     * @param string $joinPointName Special joinpoint name
76
     * @param array  $advices       List of advices
77
     *
78
     * @return FunctionInvocation
79
     */
80
    public static function getJoinPoint(string $joinPointName, array $advices): FunctionInvocation
81
    {
82
        static $accessor;
83
84
        if ($accessor === null) {
85
            $accessor = AspectKernel::getInstance()->getContainer()->get('aspect.advisor.accessor');
86
        }
87
88
        $filledAdvices = [];
89
        foreach ($advices as $advisorName) {
90
            $filledAdvices[] = $accessor->$advisorName;
91
        }
92
93
        return new ReflectionFunctionInvocation($joinPointName, $filledAdvices);
94
    }
95
96
    /**
97
     * Generates the source code of function proxies in given namespace
98
     */
99
    public function generate(): string
100
    {
101
        return $this->fileGenerator->generate();
102
    }
103
104
    /**
105
     * Creates definition for trait method body
106
     *
107
     * @param ReflectionFunction $function Method reflection
108
     *
109
     * @return string new method body
110
     */
111
    protected function getJoinpointInvocationBody(ReflectionFunction $function): string
112
    {
113
        $class = '\\' . __CLASS__;
114
115
        $argumentList = new FunctionCallArgumentListGenerator($function);
116
        $argumentCode = $argumentList->generate();
117
118
        $return = 'return ';
119
        if (PHP_VERSION_ID >= 70100 && $function->hasReturnType()) {
120
            $returnType = (string) $function->getReturnType();
121
            if ($returnType === 'void') {
122
                // void return types should not return anything
123
                $return = '';
124
            }
125
        }
126
127
        $functionAdvices = $this->advices[AspectContainer::FUNCTION_PREFIX][$function->name];
128
        $advicesArray    = new ValueGenerator($functionAdvices, ValueGenerator::TYPE_ARRAY_SHORT);
129
        $advicesArray->setArrayDepth(1);
130
        $advicesCode = $advicesArray->generate();
131
132
        return <<<BODY
133
static \$__joinPoint;
134
if (\$__joinPoint === null) {
135
    \$__joinPoint = {$class}::getJoinPoint('{$function->name}', {$advicesCode});
136
}
137
{$return}\$__joinPoint->__invoke($argumentCode);
138
BODY;
139
    }
140
}
141