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

TraitProxyGenerator::getJoinpointInvocationBody()   C

Complexity

Conditions 7
Paths 24

Size

Total Lines 32
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 0
cts 27
cp 0
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 21
nc 24
nop 1
crap 56
1
<?php
2
declare(strict_types = 1);
3
/*
4
 * Go! AOP framework
5
 *
6
 * @copyright Copyright 2012, 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\Intercept\MethodInvocation;
15
use Go\Core\AspectContainer;
16
use Go\Core\AspectKernel;
17
use Go\Proxy\Part\FunctionCallArgumentListGenerator;
18
use ReflectionClass;
19
use ReflectionMethod;
20
use Zend\Code\Generator\DocBlockGenerator;
21
use Zend\Code\Generator\TraitGenerator;
22
use Zend\Code\Generator\ValueGenerator;
23
use Zend\Code\Reflection\DocBlockReflection;
24
25
/**
26
 * Trait proxy builder that is used to generate a trait from the list of joinpoints
27
 */
28
class TraitProxyGenerator extends ClassProxyGenerator
29
{
30
31
    /**
32
     * Generates an child code by original class reflection and joinpoints for it
33
     *
34
     * @param ReflectionClass $originalTrait        Original class reflection
35
     * @param string          $parentTraitName      Parent trait name to use
36
     * @param string[][]      $traitAdvices         List of advices for class
37
     * @param bool            $useParameterWidening Enables usage of parameter widening feature
38
     */
39
    public function __construct(
40
        ReflectionClass $originalTrait,
41
        string $parentTraitName,
42
        array $traitAdvices,
43
        bool $useParameterWidening
44
    ) {
45
        $this->advices        = $traitAdvices;
46
        $dynamicMethodAdvices = $traitAdvices[AspectContainer::METHOD_PREFIX] ?? [];
47
        $staticMethodAdvices  = $traitAdvices[AspectContainer::STATIC_METHOD_PREFIX] ?? [];
48
        $interceptedMethods   = array_keys($dynamicMethodAdvices + $staticMethodAdvices);
49
        $generatedMethods     = $this->interceptMethods($originalTrait, $interceptedMethods);
50
51
        $this->generator = new TraitGenerator(
52
            $originalTrait->getShortName(),
53
            $originalTrait->getNamespaceName(),
54
            null,
55
            null,
56
            [],
57
            [],
58
            $generatedMethods,
59
            DocBlockGenerator::fromReflection(new DocBlockReflection($originalTrait->getDocComment()))
60
        );
61
62
        // Normalize FQDN
63
        $namespaceParts       = explode('\\', $parentTraitName);
64
        $parentNormalizedName = end($namespaceParts);
65
        $this->generator->addTrait($parentNormalizedName);
66
67
        foreach ($interceptedMethods as $methodName) {
68
            $fullName = $parentNormalizedName . '::' . $methodName;
69
            $this->generator->addTraitAlias($fullName, $methodName . '➩', ReflectionMethod::IS_PROTECTED);
70
        }
71
    }
72
73
    /**
74
     * Returns a joinpoint for the specific trait
75
     *
76
     * @param string $className     Name of the class
77
     * @param string $joinPointType Type of joinpoint (static or dynamic method)
78
     * @param string $methodName    Name of the method
79
     * @param array  $advices       List of advices for this trait method
80
     *
81
     * @return MethodInvocation
82
     */
83
    public static function getJoinPoint(
84
        string $className,
85
        string $joinPointType,
86
        string $methodName,
87
        array $advices
88
    ): MethodInvocation {
89
        static $accessor;
90
91
        if ($accessor === null) {
92
            $aspectKernel = AspectKernel::getInstance();
93
            $accessor     = $aspectKernel->getContainer()->get('aspect.advisor.accessor');
94
        }
95
96
        $filledAdvices = [];
97
        foreach ($advices as $advisorName) {
98
            $filledAdvices[] = $accessor->$advisorName;
99
        }
100
101
        $joinPoint = new self::$invocationClassMap[$joinPointType]($className, $methodName . '➩', $filledAdvices);
102
103
        return $joinPoint;
104
    }
105
106
    /**
107
     * Creates definition for trait method body
108
     *
109
     * @param ReflectionMethod $method Method reflection
110
     *
111
     * @return string new method body
112
     */
113
    protected function getJoinpointInvocationBody(ReflectionMethod $method): string
114
    {
115
        $isStatic = $method->isStatic();
116
        $class    = '\\' . __CLASS__;
117
        $scope    = $isStatic ? 'static::class' : '$this';
118
        $prefix   = $isStatic ? AspectContainer::STATIC_METHOD_PREFIX : AspectContainer::METHOD_PREFIX;
119
120
        $argumentList = new FunctionCallArgumentListGenerator($method);
121
        $argumentCode = $argumentList->generate();
122
        $argumentCode = $scope . ($argumentCode ? ", $argumentCode" : '');
123
124
        $return = 'return ';
125
        if (PHP_VERSION_ID >= 70100 && $method->hasReturnType()) {
126
            $returnType = (string) $method->getReturnType();
127
            if ($returnType === 'void') {
128
                // void return types should not return anything
129
                $return = '';
130
            }
131
        }
132
133
        $advicesArray = new ValueGenerator($this->advices[$prefix][$method->name], ValueGenerator::TYPE_ARRAY_SHORT);
134
        $advicesArray->setArrayDepth(1);
135
        $advicesCode = $advicesArray->generate();
136
137
        return <<<BODY
138
static \$__joinPoint;
139
if (\$__joinPoint === null) {
140
    \$__joinPoint = {$class}::getJoinPoint(__CLASS__, '{$prefix}', '{$method->name}', {$advicesCode});
141
}
142
{$return}\$__joinPoint->__invoke($argumentCode);
143
BODY;
144
    }
145
146
    /**
147
     * {@inheritDoc}
148
     */
149
    public function generate(): string
150
    {
151
        return $this->generator->generate();
152
    }
153
}
154