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

CachePathManager   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 258
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 24.71%

Importance

Changes 0
Metric Value
wmc 29
lcom 1
cbo 1
dl 0
loc 258
ccs 21
cts 85
cp 0.2471
rs 10
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
D __construct() 0 30 9
A getCacheDir() 0 4 1
A setCacheDir() 0 4 1
A getCachePathForResource() 0 8 2
A queryCacheState() 0 13 3
A queryClassMap() 0 6 1
A setCacheState() 0 5 1
A addClassMap() 0 5 1
A __destruct() 0 4 1
C flushCacheState() 0 41 8
A clearCacheState() 0 7 1
1
<?php
2
declare(strict_types = 1);
3
/*
4
 * Go! AOP framework
5
 *
6
 * @copyright Copyright 2014, 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\ClassLoading;
13
use Go\Aop\Features;
14
use Go\Core\AspectKernel;
15
16
/**
17
 * Class that manages real-code to cached-code paths mapping.
18
 * Can be extended to get a more sophisticated real-to-cached code mapping
19
 */
20
class CachePathManager
21
{
22
    /**
23
     * Name of the file with cache paths
24
     */
25
    const CACHE_FILE_NAME = '/_transformation.cache';
26
27
    /**
28
     * Name of the file with class maps
29
     */
30
    const CACHE_MAP_FILE_NAME = '/_classmap.cache';
31
32
    /**
33
     * @var array
34
     */
35
    protected $options = [];
36
37
    /**
38
     * @var \Go\Core\AspectKernel
39
     */
40
    protected $kernel;
41
42
    /**
43
     * @var string|null
44
     */
45
    protected $cacheDir;
46
47
    /**
48
     * File mode
49
     *
50
     * @var integer
51
     */
52
    protected $fileMode;
53
54
    /**
55
     * @var string|null
56
     */
57
    protected $appDir;
58
59
    /**
60
     * Cached metadata for transformation state for the concrete file
61
     *
62
     * @var array
63
     */
64
    protected $cacheState = [];
65
66
    /**
67
     * New metadata items, that was not present in $cacheState
68
     *
69
     * @var array
70
     */
71
    protected $newCacheState = [];
72
73
    /**
74
     * Map of classes to the filenames
75
     *
76
     * @var array
77
     */
78
    protected $classMap = [];
79
80
    /**
81
     * New classmap items, that was not present in $classMap
82
     *
83
     * @var array
84
     */
85
    protected $newClassMap = [];
86
87 8
    public function __construct(AspectKernel $kernel)
88
    {
89 8
        $this->kernel   = $kernel;
90 8
        $this->options  = $kernel->getOptions();
91 8
        $this->appDir   = $this->options['appDir'];
92 8
        $this->cacheDir = $this->options['cacheDir'];
93 8
        $this->fileMode = $this->options['cacheFileMode'];
94
95 8
        if ($this->cacheDir) {
96 7
            if (!is_dir($this->cacheDir)) {
97
                $cacheRootDir = dirname($this->cacheDir);
98
                if (!is_writable($cacheRootDir) || !is_dir($cacheRootDir)) {
99
                    throw new \InvalidArgumentException(
100
                        "Can not create a directory {$this->cacheDir} for the cache.
101
                        Parent directory {$cacheRootDir} is not writable or not exist.");
102
                }
103
                mkdir($this->cacheDir, $this->fileMode, true);
104
            }
105 7
            if (!$this->kernel->hasFeature(Features::PREBUILT_CACHE) && !is_writable($this->cacheDir)) {
106
                throw new \InvalidArgumentException("Cache directory {$this->cacheDir} is not writable");
107
            }
108
109 7
            if (file_exists($this->cacheDir . self::CACHE_FILE_NAME)) {
110
                $this->cacheState = include $this->cacheDir . self::CACHE_FILE_NAME;
111
            }
112 7
            if (file_exists($this->cacheDir . self::CACHE_MAP_FILE_NAME)) {
113
                $this->classMap = include $this->cacheDir . self::CACHE_MAP_FILE_NAME;
114
            }
115
        }
116 8
    }
117
118
    /**
119
     * Returns current cache directory for aspects, can be bull
120
     *
121
     * @return null|string
122
     */
123 7
    public function getCacheDir()
124
    {
125 7
        return $this->cacheDir;
126
    }
127
128
    /**
129
     * Configures a new cache directory for aspects
130
     *
131
     * @param string $cacheDir New cache directory
132
     */
133
    public function setCacheDir(string $cacheDir)
134
    {
135
        $this->cacheDir = $cacheDir;
136
    }
137
138
    /**
139
     * @param string $resource
140
     * @return bool|string
141
     */
142 5
    public function getCachePathForResource(string $resource)
143
    {
144 5
        if (!$this->cacheDir) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->cacheDir of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
145
            return false;
146
        }
147
148 5
        return str_replace($this->appDir, $this->cacheDir, $resource);
149
    }
150
151
    /**
152
     * Tries to return an information for queried resource
153
     *
154
     * @param string|null $resource Name of the file or null to get all information
155
     *
156
     * @return array|null Information or null if no record in the cache
157
     */
158
    public function &queryCacheState(string $resource = null)
159
    {
160
        $result = [];
161
        if ($resource === null) {
162
            $result = &$this->cacheState;
163
        }
164
165
        if (isset($this->cacheState[$resource])) {
166
            $result = &$this->cacheState[$resource];
167
        }
168
169
        return $result;
170
    }
171
172
    /**
173
     * Returns an information about class mapping
174
     */
175
    public function &queryClassMap(): array
176
    {
177
        $result = &$this->classMap;
178
179
        return $result;
180
    }
181
182
    /**
183
     * Put a record about some resource in the cache
184
     *
185
     * This data will be persisted during object destruction
186
     *
187
     * @param string $resource Name of the file
188
     * @param array $metadata Miscellaneous information about resource
189
     */
190
    public function setCacheState(string $resource, array $metadata)
191
    {
192
        $this->newCacheState[$resource] = $metadata;
193
        $this->cacheState[$resource]    = $metadata;
194
    }
195
196
    /**
197
     * Put a mapping for the class
198
     *
199
     * This data will be persisted during object destruction
200
     *
201
     * @param string $class         Name of the class
202
     * @param array  $classFileName Miscellaneous information about resource
203
     */
204 5
    public function addClassMap(string $class, $classFileName)
205
    {
206 5
        $this->newClassMap[$class] = $classFileName;
207 5
        $this->classMap[$class]    = $classFileName;
208 5
    }
209
210
    /**
211
     * Automatic destructor saves all new changes into the cache
212
     *
213
     * This implementation is not thread-safe, so be care
214
     */
215
    public function __destruct()
216
    {
217
        $this->flushCacheState();
218
    }
219
220
    /**
221
     * Flushes the cache state into the file
222
     *
223
     * @var bool $force Should be flushed regardless of its state.
224
     */
225
    public function flushCacheState($force = false)
226
    {
227
228
        if ((!empty($this->newCacheState) && is_writable($this->cacheDir)) || $force) {
229
            $fullCacheMap = $this->newCacheState + $this->cacheState;
230
            $cachePath    = substr(var_export($this->cacheDir, true), 1, -1);
231
            $rootPath     = substr(var_export($this->appDir, true), 1, -1);
232
            $cacheData    = '<?php return ' . var_export($fullCacheMap, true) . ';';
233
            $cacheData    = strtr($cacheData, [
234
                '\'' . $cachePath => 'AOP_CACHE_DIR . \'',
235
                '\'' . $rootPath  => 'AOP_ROOT_DIR . \''
236
            ]);
237
            $fullCacheFileName = $this->cacheDir . self::CACHE_FILE_NAME;
238
            file_put_contents($fullCacheFileName, $cacheData, LOCK_EX);
239
            // For cache files we don't want executable bits by default
240
            chmod($fullCacheFileName, $this->fileMode & (~0111));
241
242
            if (function_exists('opcache_invalidate')) {
243
                opcache_invalidate($fullCacheFileName, true);
244
            }
245
            $this->newCacheState = [];
246
        }
247
        if (!empty($this->newClassMap) && is_writable($this->cacheDir)) {
248
            $cachePath = substr(var_export($this->cacheDir, true), 1, -1);
249
            $rootPath  = substr(var_export($this->appDir, true), 1, -1);
250
            $cacheData = '<?php return ' . var_export($this->classMap, true) . ';';
251
            $cacheData = strtr($cacheData, array(
252
                '\'' . $cachePath => 'AOP_CACHE_DIR . \'',
253
                '\'' . $rootPath  => 'AOP_ROOT_DIR . \''
254
            ));
255
            $fullCacheFileName = $this->cacheDir . self::CACHE_MAP_FILE_NAME;
256
            file_put_contents($fullCacheFileName, $cacheData, LOCK_EX);
257
            // For cache files we don't want executable bits by default
258
            chmod($fullCacheFileName, $this->fileMode & (~0111));
259
260
            if (function_exists('opcache_invalidate')) {
261
                opcache_invalidate($fullCacheFileName, true);
262
            }
263
            $this->newClassMap = [];
264
        }
265
    }
266
267
    /**
268
     * Clear the cache state.
269
     */
270
    public function clearCacheState()
271
    {
272
        $this->cacheState       = [];
273
        $this->newCacheState    = [];
274
275
        $this->flushCacheState(true);
276
    }
277
}
278