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

MagicConstantTransformer   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 102
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 6

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 10
lcom 2
cbo 6
dl 0
loc 102
ccs 44
cts 44
cp 1
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 2012, 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\Instrument\Transformer;
14
15
use Go\Core\AspectKernel;
16
use PhpParser\Node\Expr\MethodCall;
17
use PhpParser\Node\Scalar\MagicConst;
18
use PhpParser\Node\Scalar\MagicConst\Dir;
19
use PhpParser\Node\Scalar\MagicConst\File;
20
use PhpParser\NodeTraverser;
21
use PhpParser\Node\Identifier;
22
23
/**
24
 * Transformer that replaces magic __DIR__ and __FILE__ constants in the source code
25
 *
26
 * Additionally, ReflectionClass->getFileName() is also wrapped into normalizer method call
27
 */
28
class MagicConstantTransformer extends BaseSourceTransformer
29
{
30
    /**
31
     * Root path of application
32
     */
33
    protected static string $rootPath = '';
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_STRING, expecting T_FUNCTION or T_CONST
Loading history...
34
35
    /**
36
     * Path to rewrite to (cache directory)
37
     */
38
    protected static string $rewriteToPath = '';
39
40
    /**
41
     * Class constructor
42
     */
43
    public function __construct(AspectKernel $kernel)
44
    {
45 7
        parent::__construct($kernel);
46
        self::$rootPath      = $this->options['appDir'];
47 7
        self::$rewriteToPath = $this->options['cacheDir'];
48 7
    }
49 7
50 7
    /**
51
     * This method may transform the supplied source and return a new replacement for it
52
     *
53
     * @return string See RESULT_XXX constants in the interface
54
     */
55
    public function transform(StreamMetaData $metadata): string
56
    {
57 6
        $this->replaceMagicDirFileConstants($metadata);
58
        $this->wrapReflectionGetFileName($metadata);
59 6
60 6
        // We should always vote abstain, because if there is only changes for magic constants, we can drop them
61
        return self::RESULT_ABSTAIN;
62
    }
63 6
64
    /**
65
     * Resolves file name from the cache directory to the real application root dir
66
     */
67
    public static function resolveFileName(string $fileName): string
68
    {
69 2
        $suffix = '.php';
70
        $pathParts = explode($suffix, str_replace(
71 2
            [self::$rewriteToPath, DIRECTORY_SEPARATOR . '_proxies'],
72 2
            [self::$rootPath, ''],
73 2
            $fileName
74 2
        ));
75 2
        // throw away namespaced path from actual filename
76
        return $pathParts[0] . $suffix;
77
    }
78 2
79
    /**
80
     * Wraps all possible getFileName() methods from ReflectionFile
81
     */
82
    private function wrapReflectionGetFileName(StreamMetaData $metadata): void
83
    {
84 6
        $methodCallFinder = new NodeFinderVisitor([MethodCall::class]);
85
        $traverser        = new NodeTraverser();
86 6
        $traverser->addVisitor($methodCallFinder);
87 6
        $traverser->traverse($metadata->syntaxTree);
88 6
89 6
        /** @var MethodCall[] $methodCalls */
90
        $methodCalls = $methodCallFinder->getFoundNodes();
91
        foreach ($methodCalls as $methodCallNode) {
92 6
            if (($methodCallNode->name instanceof Identifier) && ($methodCallNode->name->toString() === 'getFileName')) {
93 6
                $startPosition    = $methodCallNode->getAttribute('startTokenPos');
94 2
                $endPosition      = $methodCallNode->getAttribute('endTokenPos');
95 1
                $expressionPrefix = '\\' . self::class . '::resolveFileName(';
96
97 2
                $metadata->tokenStream[$startPosition][1] = $expressionPrefix . $metadata->tokenStream[$startPosition][1];
98 2
                $metadata->tokenStream[$endPosition][1] .= ')';
99 2
            }
100
101 2
        }
102 2
    }
103
104 6
    /**
105
     * Replaces all magic __DIR__ and __FILE__ constants in the file with calculated value
106
     */
107
    private function replaceMagicDirFileConstants(StreamMetaData $metadata): void
108
    {
109 6
        $magicConstFinder = new NodeFinderVisitor([Dir::class, File::class]);
110
        $traverser        = new NodeTraverser();
111 6
        $traverser->addVisitor($magicConstFinder);
112 6
        $traverser->traverse($metadata->syntaxTree);
113 6
114 6
        /** @var MagicConst[] $magicConstants */
115
        $magicConstants = $magicConstFinder->getFoundNodes();
116
        $magicFileValue = $metadata->uri;
117 6
        $magicDirValue  = dirname($magicFileValue);
118 6
        foreach ($magicConstants as $magicConstantNode) {
119 6
            $tokenPosition = $magicConstantNode->getAttribute('startTokenPos');
120 6
            $replacement   = $magicConstantNode instanceof Dir ? $magicDirValue : $magicFileValue;
121 2
122 2
            $metadata->tokenStream[$tokenPosition][1] = "'{$replacement}'";
123
        }
124 2
    }
125
}
126