Completed
Push — 1.x ( c183a4...05b51a )
by Alexander
8s
created

CachePathManager::flushCacheState()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

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