Completed
Push — 2.1 ( 28b26f...4d9204 )
by Alexander
10:53
created

CacheController::actionClear()   C

Complexity

Conditions 7
Paths 11

Size

Total Lines 37
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 7.0046

Importance

Changes 0
Metric Value
dl 0
loc 37
ccs 21
cts 22
cp 0.9545
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 21
nc 11
nop 0
crap 7.0046
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\console\controllers;
9
10
use Psr\SimpleCache\CacheInterface;
11
use Yii;
12
use yii\caching\ApcCache;
13
use yii\caching\Cache;
14
use yii\console\Controller;
15
use yii\console\Exception;
16
use yii\console\ExitCode;
17
use yii\helpers\Console;
18
19
/**
20
 * Allows you to clear cache.
21
 *
22
 * see list of available components to clear:
23
 *
24
 *     yii cache
25
 *
26
 * clear particular components specified by their names:
27
 *
28
 *     yii cache/clear first second third
29
 *
30
 * clear all cache components that can be found in the system
31
 *
32
 *     yii cache/clear-all
33
 *
34
 * Note that the command uses cache components defined in your console application configuration file. If components
35
 * configured are different from web application, web application cache won't be cleared. In order to fix it please
36
 * duplicate web application cache components in console config. You can use any component names.
37
 *
38
 * APC is not shared between PHP processes so clearing cache from command line has no effect on web.
39
 * Clearing web cache could be either done by:
40
 *
41
 * - Putting a php file under web root and calling it via HTTP
42
 * - Using [Cachetool](http://gordalina.github.io/cachetool/)
43
 *
44
 * @author Alexander Makarov <[email protected]>
45
 * @author Mark Jebri <[email protected]>
46
 * @since 2.0
47
 */
48
class CacheController extends Controller
49
{
50
    /**
51
     * Lists the caches that can be cleared.
52
     */
53
    public function actionIndex()
54
    {
55
        $caches = $this->findCaches();
56
57
        if (!empty($caches)) {
58
            $this->notifyCachesCanBeCleared($caches);
59
        } else {
60
            $this->notifyNoCachesFound();
61
        }
62
    }
63
64
    /**
65
     * Clears given cache components.
66
     * For example,
67
     *
68
     * ```
69
     * # clears caches specified by their id: "first", "second", "third"
70
     * yii cache/clear first second third
71
     * ```
72
     */
73 4
    public function actionClear()
74
    {
75 4
        $cachesInput = func_get_args();
76
77 4
        if (empty($cachesInput)) {
78 1
            throw new Exception('You should specify cache components names');
79
        }
80
81 3
        $caches = $this->findCaches($cachesInput);
82 3
        $cachesInfo = [];
83
84 3
        $foundCaches = array_keys($caches);
85 3
        $notFoundCaches = array_diff($cachesInput, array_keys($caches));
86
87 3
        if ($notFoundCaches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $notFoundCaches of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
88 1
            $this->notifyNotFoundCaches($notFoundCaches);
89
        }
90
91 3
        if (!$foundCaches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $foundCaches of type array<integer|string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
92 1
            $this->notifyNoCachesFound();
93 1
            return ExitCode::OK;
94
        }
95
96 2
        if (!$this->confirmClear($foundCaches)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->confirmClear($foundCaches) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
97
            return ExitCode::OK;
98
        }
99
100 2
        foreach ($caches as $name => $class) {
101 2
            $cachesInfo[] = [
102 2
                'name' => $name,
103 2
                'class' => $class,
104 2
                'is_cleared' => $this->canBeCleared($class) ? Yii::$app->get($name)->clear() : false,
105
            ];
106
        }
107
108 2
        $this->notifyCleared($cachesInfo);
109 2
    }
110
111
    /**
112
     * Clears all caches registered in the system.
113
     */
114 1
    public function actionClearAll()
115
    {
116 1
        $caches = $this->findCaches();
117 1
        $cachesInfo = [];
118
119 1
        if (empty($caches)) {
120
            $this->notifyNoCachesFound();
121
            return ExitCode::OK;
122
        }
123
124 1
        foreach ($caches as $name => $class) {
125 1
            $cachesInfo[] = [
126 1
                'name' => $name,
127 1
                'class' => $class,
128 1
                'is_cleared' => $this->canBeCleared($class) ? Yii::$app->get($name)->clear() : false,
129
            ];
130
        }
131
132 1
        $this->notifyCleared($cachesInfo);
133 1
    }
134
135
    /**
136
     * Clears DB schema cache for a given connection component.
137
     *
138
     * ```
139
     * # clears cache schema specified by component id: "db"
140
     * yii cache/clear-schema db
141
     * ```
142
     *
143
     * @param string $db id connection component
144
     * @return int exit code
145
     * @throws Exception
146
     * @throws \yii\base\InvalidConfigException
147
     *
148
     * @since 2.0.1
149
     */
150 1
    public function actionClearSchema($db = 'db')
151
    {
152 1
        $connection = Yii::$app->get($db, false);
153 1
        if ($connection === null) {
154
            $this->stdout("Unknown component \"$db\".\n", Console::FG_RED);
155
            return ExitCode::UNSPECIFIED_ERROR;
156
        }
157
158 1
        if (!$connection instanceof \yii\db\Connection) {
159
            $this->stdout("\"$db\" component doesn't inherit \\yii\\db\\Connection.\n", Console::FG_RED);
160
            return ExitCode::UNSPECIFIED_ERROR;
161 1
        } elseif (!$this->confirm("Clear cache schema for \"$db\" connection?")) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->confirm("Clear ca...\"{$db}\" connection?") of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
162
            return ExitCode::OK;
163
        }
164
165
        try {
166 1
            $schema = $connection->getSchema();
167 1
            $schema->refresh();
168 1
            $this->stdout("Schema cache for component \"$db\", was cleared.\n\n", Console::FG_GREEN);
169
        } catch (\Exception $e) {
170
            $this->stdout($e->getMessage() . "\n\n", Console::FG_RED);
171
        }
172 1
    }
173
174
    /**
175
     * Notifies user that given caches are found and can be cleared.
176
     * @param array $caches array of cache component classes
177
     */
178
    private function notifyCachesCanBeCleared($caches)
179
    {
180
        $this->stdout("The following caches were found in the system:\n\n", Console::FG_YELLOW);
181
182
        foreach ($caches as $name => $class) {
183
            if ($this->canBeCleared($class)) {
184
                $this->stdout("\t* $name ($class)\n", Console::FG_GREEN);
185
            } else {
186
                $this->stdout("\t* $name ($class) - can not be cleared via console\n", Console::FG_YELLOW);
187
            }
188
        }
189
190
        $this->stdout("\n");
191
    }
192
193
    /**
194
     * Notifies user that there was not found any cache in the system.
195
     */
196 1
    private function notifyNoCachesFound()
197
    {
198 1
        $this->stdout("No cache components were found in the system.\n", Console::FG_RED);
199 1
    }
200
201
    /**
202
     * Notifies user that given cache components were not found in the system.
203
     * @param array $cachesNames
204
     */
205 1
    private function notifyNotFoundCaches($cachesNames)
206
    {
207 1
        $this->stdout("The following cache components were NOT found:\n\n", Console::FG_RED);
208
209 1
        foreach ($cachesNames as $name) {
210 1
            $this->stdout("\t* $name \n", Console::FG_GREEN);
211
        }
212
213 1
        $this->stdout("\n");
214 1
    }
215
216
    /**
217
     * @param array $caches
218
     */
219 3
    private function notifyCleared($caches)
220
    {
221 3
        $this->stdout("The following cache components were processed:\n\n", Console::FG_YELLOW);
222
223 3
        foreach ($caches as $cache) {
224 3
            $this->stdout("\t* " . $cache['name'] . ' (' . $cache['class'] . ')', Console::FG_GREEN);
225
226 3
            if (!$cache['is_cleared']) {
227
                $this->stdout(" - not cleared\n", Console::FG_RED);
228
            } else {
229 3
                $this->stdout("\n");
230
            }
231
        }
232
233 3
        $this->stdout("\n");
234 3
    }
235
236
    /**
237
     * Prompts user with confirmation if caches should be cleared.
238
     * @param array $cachesNames
239
     * @return bool
240
     */
241 2
    private function confirmClear($cachesNames)
242
    {
243 2
        $this->stdout("The following cache components will be cleared:\n\n", Console::FG_YELLOW);
244
245 2
        foreach ($cachesNames as $name) {
246 2
            $this->stdout("\t* $name \n", Console::FG_GREEN);
247
        }
248
249 2
        return $this->confirm("\nClear above cache components?");
250
    }
251
252
    /**
253
     * Returns array of caches in the system, keys are cache components names, values are class names.
254
     * @param array $cachesNames caches to be found
255
     * @return array
256
     */
257 4
    private function findCaches(array $cachesNames = [])
258
    {
259 4
        $caches = [];
260 4
        $components = Yii::$app->getComponents();
261 4
        $findAll = ($cachesNames === []);
262
263 4
        foreach ($components as $name => $component) {
264 4
            if (!$findAll && !in_array($name, $cachesNames, true)) {
265 3
                continue;
266
            }
267
268 3
            if ($component instanceof CacheInterface) {
269
                $caches[$name] = get_class($component);
270 3
            } elseif (is_array($component) && isset($component['class']) && $this->isCacheClass($component['class'])) {
271
                $caches[$name] = $component['class'];
272 3
            } elseif (is_string($component) && $this->isCacheClass($component)) {
273 3
                $caches[$name] = $component;
274
            }
275
        }
276
277 4
        return $caches;
278
    }
279
280
    /**
281
     * Checks if given class is a Cache class.
282
     * @param string $className class name.
283
     * @return bool
284
     */
285 3
    private function isCacheClass($className)
286
    {
287 3
        return is_subclass_of($className, CacheInterface::class);
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \Psr\SimpleCache\CacheInterface::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
288
    }
289
290
    /**
291
     * Checks if cache of a certain class can be cleared
292
     * @param string $className class name.
293
     * @return bool
294
     */
295 3
    private function canBeCleared($className)
296
    {
297 3
        return !is_a($className, ApcCache::class, true) || php_sapi_name() !== 'cli';
298
    }
299
}
300