Completed
Push — master ( 090f96...5ad5e6 )
by Alexander
02:26
created

CachingTransformer::processTransformers()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 0
cts 19
cp 0
rs 8.7624
c 0
b 0
f 0
cc 6
eloc 13
nc 10
nop 1
crap 42
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 Go\Instrument\ClassLoading\CachePathManager;
16
17
/**
18
 * Caching transformer that is able to take the transformed source from a cache
19
 */
20
class CachingTransformer extends BaseSourceTransformer
21
{
22
    /**
23
     * Mask of permission bits for cache files.
24
     * By default, permissions are affected by the umask system setting
25
     *
26
     * @var integer|null
27
     */
28
    protected $cacheFileMode;
29
30
    /**
31
     * @var array|callable|SourceTransformer[]
32
     */
33
    protected $transformers = [];
34
35
    /**
36
     * @var CachePathManager|null
37
     */
38
    protected $cacheManager;
39
40
    /**
41
     * Class constructor
42
     *
43
     * @param AspectKernel $kernel Instance of aspect kernel
44
     * @param array|callable $transformers Source transformers or callable that should return transformers
45
     * @param CachePathManager $cacheManager Cache manager
46
     */
47
    public function __construct(AspectKernel $kernel, $transformers, CachePathManager $cacheManager)
48
    {
49
        parent::__construct($kernel);
50
        $this->cacheManager  = $cacheManager;
51
        $this->cacheFileMode = $this->options['cacheFileMode'];
52
        $this->transformers  = $transformers;
53
    }
54
55
    /**
56
     * This method may transform the supplied source and return a new replacement for it
57
     *
58
     * @param StreamMetaData $metadata Metadata for source
59
     * @return string See RESULT_XXX constants in the interface
60
     */
61
    public function transform(StreamMetaData $metadata): string
62
    {
63
        // Do not create a cache
64
        if (!$this->cacheManager->getCacheDir()) {
65
            return $this->processTransformers($metadata);
66
        }
67
68
        $originalUri      = $metadata->uri;
69
        $processingResult = self::RESULT_ABSTAIN;
70
        $cacheUri         = $this->cacheManager->getCachePathForResource($originalUri);
71
        // Guard to disable overwriting of original files
72
        if ($cacheUri === $originalUri) {
73
            return self::RESULT_ABORTED;
74
        }
75
76
        $lastModified  = filemtime($originalUri);
77
        $cacheState    = $this->cacheManager->queryCacheState($originalUri);
78
        $cacheModified = $cacheState ? $cacheState['filemtime'] : 0;
79
80
        if ($cacheModified < $lastModified
81
            || (isset($cacheState['cacheUri']) && $cacheState['cacheUri'] !== $cacheUri)
82
            || !$this->container->isFresh($cacheModified)
83
        ) {
84
            $processingResult = $this->processTransformers($metadata);
85
            if ($processingResult === self::RESULT_TRANSFORMED) {
86
                $parentCacheDir = dirname($cacheUri);
87
                if (!is_dir($parentCacheDir)) {
88
                    mkdir($parentCacheDir, $this->cacheFileMode, true);
89
                }
90
                file_put_contents($cacheUri, $metadata->source, LOCK_EX);
91
                // For cache files we don't want executable bits by default
92
                chmod($cacheUri, $this->cacheFileMode & (~0111));
93
            }
94
            $this->cacheManager->setCacheState($originalUri, [
95
                'filemtime' => $_SERVER['REQUEST_TIME'] ?? time(),
96
                'cacheUri'  => ($processingResult === self::RESULT_TRANSFORMED) ? $cacheUri : null
97
            ]);
98
99
            return $processingResult;
100
        }
101
102
        if ($cacheState) {
103
            $processingResult = isset($cacheState['cacheUri']) ? self::RESULT_TRANSFORMED : self::RESULT_ABORTED;
104
        }
105
        if ($processingResult === self::RESULT_TRANSFORMED) {
106
            $metadata->source = file_get_contents($cacheUri);
107
        }
108
109
        return $processingResult;
110
    }
111
112
    /**
113
     * Iterates over transformers
114
     *
115
     * @param StreamMetaData $metadata Metadata for source code
116
     * @return string See RESULT_XXX constants in the interface
117
     */
118
    private function processTransformers(StreamMetaData $metadata): string
119
    {
120
        $overallResult = self::RESULT_ABSTAIN;
121
        if (is_callable($this->transformers)) {
122
            $delayedTransformers = $this->transformers;
123
            $this->transformers  = $delayedTransformers();
124
        }
125
        foreach ($this->transformers as $transformer) {
126
            $transformationResult = $transformer->transform($metadata);
127
            if ($overallResult === self::RESULT_ABSTAIN && $transformationResult === self::RESULT_TRANSFORMED) {
128
                $overallResult = self::RESULT_TRANSFORMED;
129
            }
130
            // transformer reported about termination, next transformers will be skipped
131
            if ($transformationResult === self::RESULT_ABORTED) {
132
                $overallResult = self::RESULT_ABORTED;
133
                break;
134
            }
135
        }
136
137
        return $overallResult;
138
    }
139
}
140