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

CachePathManager   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 190
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 43.75%

Importance

Changes 0
Metric Value
wmc 24
lcom 1
cbo 1
dl 0
loc 190
ccs 28
cts 64
cp 0.4375
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 2014, 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\ClassLoading;
14
15
use Go\Aop\Features;
16
use Go\Core\AspectKernel;
17
use InvalidArgumentException;
18
19
use function function_exists;
20
21
/**
22
 * Class that manages real-code to cached-code paths mapping.
23
 * Can be extended to get a more sophisticated real-to-cached code mapping
24
 */
25
class CachePathManager
26
{
27
    /**
28
     * Name of the file with cache paths
29
     */
30
    private const CACHE_FILE_NAME = '/_transformation.cache';
31
32
    protected array $options = [];
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...
33
34
    /**
35
     * Aspect kernel instance
36
     */
37
    protected AspectKernel $kernel;
38
39
    protected ?string $cacheDir = null;
40
41
    /**
42
     * File mode
43
     */
44
    protected int $fileMode;
45
46
    protected ?string $appDir = null;
47
48
    /**
49
     * Cached metadata for transformation state for the concrete file
50
     */
51
    protected array $cacheState = [];
52
53
    /**
54
     * New metadata items, that was not present in $cacheState
55
     */
56
    protected array $newCacheState = [];
57
58
    public function __construct(AspectKernel $kernel)
59
    {
60
        $this->kernel   = $kernel;
61
        $this->options  = $kernel->getOptions();
62
        $this->appDir   = $this->options['appDir'];
63
        $this->cacheDir = $this->options['cacheDir'];
64
        $this->fileMode = $this->options['cacheFileMode'];
65
66
        if ($this->cacheDir) {
67
            if (!is_dir($this->cacheDir)) {
68
                $cacheRootDir = dirname($this->cacheDir);
69
                if (!is_writable($cacheRootDir) || !is_dir($cacheRootDir)) {
70 9
                    throw new InvalidArgumentException(
71
                        "Can not create a directory {$this->cacheDir} for the cache.
72 9
                        Parent directory {$cacheRootDir} is not writable or not exist."
73 9
                    );
74 9
                }
75 9
                mkdir($this->cacheDir, $this->fileMode, true);
76 9
            }
77
            if (!$this->kernel->hasFeature(Features::PREBUILT_CACHE) && !is_writable($this->cacheDir)) {
78 9
                throw new InvalidArgumentException("Cache directory {$this->cacheDir} is not writable");
79 8
            }
80 1
81 1
            if (file_exists($this->cacheDir . self::CACHE_FILE_NAME)) {
82
                $this->cacheState = include $this->cacheDir . self::CACHE_FILE_NAME;
83
            }
84
        }
85
    }
86 1
87
    /**
88 8
     * Returns current cache directory for aspects, can be null
89
     */
90
    public function getCacheDir(): ?string
91
    {
92 8
        return $this->cacheDir;
93
    }
94
95
    /**
96 9
     * Configures a new cache directory for aspects
97
     */
98
    public function setCacheDir(string $cacheDir): void
99
    {
100
        $this->cacheDir = $cacheDir;
101 8
    }
102
103 8
    /**
104
     * Returns cache path for requested file name
105
     *
106
     * @return bool|string
107
     */
108
    public function getCachePathForResource(string $resource)
109
    {
110
        if (!$this->cacheDir) {
111
            return false;
112
        }
113
114
        return str_replace($this->appDir, $this->cacheDir, $resource);
115
    }
116
117
    /**
118
     * Tries to return an information for queried resource
119 1
     *
120
     * @param string|null $resource Name of the file or null to get all information
121 1
     *
122
     * @return array|null Information or null if no record in the cache
123
     */
124
    public function queryCacheState(string $resource = null): ?array
125 1
    {
126
        if ($resource === null) {
127
            return $this->cacheState;
128
        }
129
130
        if (isset($this->newCacheState[$resource])) {
131
            return $this->newCacheState[$resource];
132
        }
133
134
        if (isset($this->cacheState[$resource])) {
135 1
            return $this->cacheState[$resource];
136
        }
137 1
138 1
        return null;
139
    }
140
141 1
    /**
142
     * Put a record about some resource in the cache
143
     *
144
     * This data will be persisted during object destruction
145 1
     *
146
     * @param array $metadata Miscellaneous information about resource
147
     */
148
    public function setCacheState(string $resource, array $metadata): void
149 1
    {
150
        $this->newCacheState[$resource] = $metadata;
151
    }
152
153
    /**
154
     * Automatic destructor saves all new changes into the cache
155
     *
156
     * This implementation is not thread-safe, so be care
157
     */
158
    public function __destruct()
159 1
    {
160
        $this->flushCacheState();
161 1
    }
162 1
163
    /**
164
     * Flushes the cache state into the file
165
     */
166
    public function flushCacheState(bool $force = false): void
167
    {
168
        if ((!empty($this->newCacheState) && is_writable($this->cacheDir)) || $force) {
169
            $fullCacheMap      = $this->newCacheState + $this->cacheState;
170
            $cachePath         = substr(var_export($this->cacheDir, true), 1, -1);
171
            $rootPath          = substr(var_export($this->appDir, true), 1, -1);
172
            $cacheData         = '<?php return ' . var_export($fullCacheMap, true) . ';';
173
            $cacheData         = strtr(
174
                $cacheData,
175
                [
176
                    '\'' . $cachePath => 'AOP_CACHE_DIR . \'',
177
                    '\'' . $rootPath  => 'AOP_ROOT_DIR . \''
178
                ]
179
            );
180
            $fullCacheFileName = $this->cacheDir . self::CACHE_FILE_NAME;
181
            file_put_contents($fullCacheFileName, $cacheData, LOCK_EX);
182
            // For cache files we don't want executable bits by default
183
            chmod($fullCacheFileName, $this->fileMode & (~0111));
184
185
            if (function_exists('opcache_invalidate')) {
186
                opcache_invalidate($fullCacheFileName, true);
187
            }
188
            $this->cacheState    = $this->newCacheState + $this->cacheState;
189
            $this->newCacheState = [];
190
        }
191
    }
192
193
    /**
194
     * Clear the cache state.
195
     */
196
    public function clearCacheState(): void
197
    {
198
        $this->cacheState    = [];
199
        $this->newCacheState = [];
200
201
        $this->flushCacheState(true);
202
    }
203
}
204