Completed
Pull Request — master (#463)
by Alexander
30:17 queued 05:15
created

FunctionProxyGenerator   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 104
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 8

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 10
lcom 2
cbo 8
dl 0
loc 104
ccs 0
cts 58
cp 0
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types = 1);
4
/*
5
 * Go! AOP framework
6
 *
7
 * @copyright Copyright 2013, Lisachenko Alexander <[email protected]>
8
 *
9
 * This source file is subject to the license that is bundled
10
 * with this source code in the file LICENSE.
11
 */
12
13
namespace Go\Proxy;
14
15
use Go\Aop\Framework\ReflectionFunctionInvocation;
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 Laminas\Code\Generator\FileGenerator;
22
use Laminas\Code\Generator\ValueGenerator;
23
use ReflectionException;
24
use ReflectionFunction;
25
use ReflectionNamedType;
26
27
/**
28
 * Function proxy builder that is used to generate a proxy-function from the list of joinpoints
29
 */
30
class FunctionProxyGenerator
31
{
32
    /**
33
     * List of advices that are used for generation of child
34
     */
35
    protected array $adviceNames = [];
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_ARRAY, expecting T_FUNCTION or T_CONST
Loading history...
36
37
    /**
38
     * Instance of file generator
39
     */
40
    protected FileGenerator $fileGenerator;
41
42
    /**
43
     * Constructs functions stub class from namespace Reflection
44
     *
45
     * @param ReflectionFileNamespace $namespace            Reflection of namespace
46
     * @param string[][]              $adviceNames          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 $adviceNames = [],
54
        bool $useParameterWidening = false
55
    ) {
56
        $this->adviceNames   = $adviceNames;
57
        $this->fileGenerator = new FileGenerator();
58
        $this->fileGenerator->setNamespace($namespace->getName());
59
60
        $functionsContent = [];
61
        $functionAdvices  = $adviceNames[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 array $adviceNames List of advices
76
     */
77
    public static function getJoinPoint(string $functionName, array $adviceNames): ReflectionFunctionInvocation
78
    {
79
        static $accessor;
80
81
        if ($accessor === null) {
82
            $accessor = AspectKernel::getInstance()->getContainer()->get('aspect.advisor.accessor');
83
        }
84
85
        $filledAdvices = [];
86
        foreach ($adviceNames as $advisorName) {
87
            $filledAdvices[] = $accessor->$advisorName;
88
        }
89
90
        return new ReflectionFunctionInvocation($filledAdvices, $functionName);
91
    }
92
93
    /**
94
     * Generates the source code of function proxies in given namespace
95
     */
96
    public function generate(): string
97
    {
98
        return $this->fileGenerator->generate();
99
    }
100
101
    /**
102
     * Creates string definition for function method body by function reflection
103
     */
104
    protected function getJoinpointInvocationBody(ReflectionFunction $function): string
105
    {
106
        $class = '\\' . self::class;
107
108
        $argumentList = new FunctionCallArgumentListGenerator($function);
109
        $argumentCode = $argumentList->generate();
110
111
        $return = 'return ';
112
        if ($function->hasReturnType()) {
113
            $returnType = $function->getReturnType();
114
            if ($returnType instanceof ReflectionNamedType && $returnType->getName() === 'void') {
115
                // void return types should not return anything
116
                $return = '';
117
            }
118
        }
119
120
        $functionAdvices = $this->adviceNames[AspectContainer::FUNCTION_PREFIX][$function->name];
121
        $advicesArray    = new ValueGenerator($functionAdvices, ValueGenerator::TYPE_ARRAY_SHORT);
122
        $advicesArray->setArrayDepth(1);
123
        $advicesCode = $advicesArray->generate();
124
125
        return <<<BODY
126
static \$__joinPoint;
127
if (\$__joinPoint === null) {
128
    \$__joinPoint = {$class}::getJoinPoint('{$function->name}', {$advicesCode});
129
}
130
{$return}\$__joinPoint->__invoke($argumentCode);
131
BODY;
132
    }
133
}
134