Completed
Push — master ( efe256...811ea7 )
by Alexander
11s
created

wrapReflectionGetFileName()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 3

Importance

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