Completed
Push — master ( 06ef8e...568e3c )
by Alexander
03:25
created

CachePathManager::queryCacheState()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

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