Completed
Push — fix-unique-exists-expressions ( b0719b...8467f4 )
by Alexander
07:52
created

CacheController::actionFlush()   C

Complexity

Conditions 7
Paths 11

Size

Total Lines 37
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 37
ccs 0
cts 22
cp 0
rs 6.7272
cc 7
eloc 21
nc 11
nop 0
crap 56
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 Yii;
11
use yii\caching\ApcCache;
12
use yii\caching\CacheInterface;
13
use yii\console\Controller;
14
use yii\console\Exception;
15
use yii\helpers\Console;
16
17
/**
18
 * Allows you to flush cache.
19
 *
20
 * see list of available components to flush:
21
 *
22
 *     yii cache
23
 *
24
 * flush particular components specified by their names:
25
 *
26
 *     yii cache/flush first second third
27
 *
28
 * flush all cache components that can be found in the system
29
 *
30
 *     yii cache/flush-all
31
 *
32
 * Note that the command uses cache components defined in your console application configuration file. If components
33
 * configured are different from web application, web application cache won't be cleared. In order to fix it please
34
 * duplicate web application cache components in console config. You can use any component names.
35
 *
36
 * APC is not shared between PHP processes so flushing cache from command line has no effect on web.
37
 * Flushing web cache could be either done by:
38
 *
39
 * - Putting a php file under web root and calling it via HTTP
40
 * - Using [Cachetool](http://gordalina.github.io/cachetool/)
41
 *
42
 * @author Alexander Makarov <[email protected]>
43
 * @author Mark Jebri <[email protected]>
44
 * @since 2.0
45
 */
46
class CacheController extends Controller
47
{
48
    /**
49
     * Lists the caches that can be flushed.
50
     */
51
    public function actionIndex()
52
    {
53
        $caches = $this->findCaches();
54
55
        if (!empty($caches)) {
56
            $this->notifyCachesCanBeFlushed($caches);
57
        } else {
58
            $this->notifyNoCachesFound();
59
        }
60
    }
61
62
    /**
63
     * Flushes given cache components.
64
     * For example,
65
     *
66
     * ```
67
     * # flushes caches specified by their id: "first", "second", "third"
68
     * yii cache/flush first second third
69
     * ```
70
     */
71
    public function actionFlush()
72
    {
73
        $cachesInput = func_get_args();
74
75
        if (empty($cachesInput)) {
76
            throw new Exception('You should specify cache components names');
77
        }
78
79
        $caches = $this->findCaches($cachesInput);
80
        $cachesInfo = [];
81
82
        $foundCaches = array_keys($caches);
83
        $notFoundCaches = array_diff($cachesInput, array_keys($caches));
84
85
        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...
86
            $this->notifyNotFoundCaches($notFoundCaches);
87
        }
88
89
        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...
90
            $this->notifyNoCachesFound();
91
            return static::EXIT_CODE_NORMAL;
92
        }
93
94
        if (!$this->confirmFlush($foundCaches)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->confirmFlush($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...
95
            return static::EXIT_CODE_NORMAL;
96
        }
97
98
        foreach ($caches as $name => $class) {
99
            $cachesInfo[] = [
100
                'name' => $name,
101
                'class' => $class,
102
                'is_flushed' => $this->canBeFlushed($class) ? Yii::$app->get($name)->flush() : false,
103
            ];
104
        }
105
106
        $this->notifyFlushed($cachesInfo);
107
    }
108
109
    /**
110
     * Flushes all caches registered in the system.
111
     */
112
    public function actionFlushAll()
113
    {
114
        $caches = $this->findCaches();
115
        $cachesInfo = [];
116
117
        if (empty($caches)) {
118
            $this->notifyNoCachesFound();
119
            return static::EXIT_CODE_NORMAL;
120
        }
121
122
        foreach ($caches as $name => $class) {
123
            $cachesInfo[] = [
124
                'name' => $name,
125
                'class' => $class,
126
                'is_flushed' => $this->canBeFlushed($class) ? Yii::$app->get($name)->flush() : false,
127
            ];
128
        }
129
130
        $this->notifyFlushed($cachesInfo);
131
    }
132
133
    /**
134
     * Clears DB schema cache for a given connection component.
135
     *
136
     * ```
137
     * # clears cache schema specified by component id: "db"
138
     * yii cache/flush-schema db
139
     * ```
140
     *
141
     * @param string $db id connection component
142
     * @return int exit code
143
     * @throws Exception
144
     * @throws \yii\base\InvalidConfigException
145
     *
146
     * @since 2.0.1
147
     */
148
    public function actionFlushSchema($db = 'db')
149
    {
150
        $connection = Yii::$app->get($db, false);
151
        if ($connection === null) {
152
            $this->stdout("Unknown component \"$db\".\n", Console::FG_RED);
153
            return self::EXIT_CODE_ERROR;
154
        }
155
156
        if (!$connection instanceof \yii\db\Connection) {
157
            $this->stdout("\"$db\" component doesn't inherit \\yii\\db\\Connection.\n", Console::FG_RED);
158
            return self::EXIT_CODE_ERROR;
159
        } elseif (!$this->confirm("Flush cache schema for \"$db\" connection?")) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->confirm("Flush 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...
160
            return static::EXIT_CODE_NORMAL;
161
        }
162
163
        try {
164
            $schema = $connection->getSchema();
165
            $schema->refresh();
166
            $this->stdout("Schema cache for component \"$db\", was flushed.\n\n", Console::FG_GREEN);
167
        } catch (\Exception $e) {
168
            $this->stdout($e->getMessage() . "\n\n", Console::FG_RED);
169
        }
170
    }
171
172
    /**
173
     * Notifies user that given caches are found and can be flushed.
174
     * @param array $caches array of cache component classes
175
     */
176
    private function notifyCachesCanBeFlushed($caches)
177
    {
178
        $this->stdout("The following caches were found in the system:\n\n", Console::FG_YELLOW);
179
180
        foreach ($caches as $name => $class) {
181
            if ($this->canBeFlushed($class)) {
182
                $this->stdout("\t* $name ($class)\n", Console::FG_GREEN);
183
            } else {
184
                $this->stdout("\t* $name ($class) - can not be flushed via console\n", Console::FG_YELLOW);
185
            }
186
        }
187
188
        $this->stdout("\n");
189
    }
190
191
    /**
192
     * Notifies user that there was not found any cache in the system.
193
     */
194
    private function notifyNoCachesFound()
195
    {
196
        $this->stdout("No cache components were found in the system.\n", Console::FG_RED);
197
    }
198
199
    /**
200
     * Notifies user that given cache components were not found in the system.
201
     * @param array $cachesNames
202
     */
203
    private function notifyNotFoundCaches($cachesNames)
204
    {
205
        $this->stdout("The following cache components were NOT found:\n\n", Console::FG_RED);
206
207
        foreach ($cachesNames as $name) {
208
            $this->stdout("\t* $name \n", Console::FG_GREEN);
209
        }
210
211
        $this->stdout("\n");
212
    }
213
214
    /**
215
     * @param array $caches
216
     */
217
    private function notifyFlushed($caches)
218
    {
219
        $this->stdout("The following cache components were processed:\n\n", Console::FG_YELLOW);
220
221
        foreach ($caches as $cache) {
222
            $this->stdout("\t* " . $cache['name'] . ' (' . $cache['class'] . ')', Console::FG_GREEN);
223
224
            if (!$cache['is_flushed']) {
225
                $this->stdout(" - not flushed\n", Console::FG_RED);
226
            } else {
227
                $this->stdout("\n");
228
            }
229
        }
230
231
        $this->stdout("\n");
232
    }
233
234
    /**
235
     * Prompts user with confirmation if caches should be flushed.
236
     * @param array $cachesNames
237
     * @return bool
238
     */
239
    private function confirmFlush($cachesNames)
240
    {
241
        $this->stdout("The following cache components will be flushed:\n\n", Console::FG_YELLOW);
242
243
        foreach ($cachesNames as $name) {
244
            $this->stdout("\t* $name \n", Console::FG_GREEN);
245
        }
246
247
        return $this->confirm("\nFlush above cache components?");
248
    }
249
250
    /**
251
     * Returns array of caches in the system, keys are cache components names, values are class names.
252
     * @param array $cachesNames caches to be found
253
     * @return array
254
     */
255
    private function findCaches(array $cachesNames = [])
256
    {
257
        $caches = [];
258
        $components = Yii::$app->getComponents();
259
        $findAll = ($cachesNames === []);
260
261
        foreach ($components as $name => $component) {
262
            if (!$findAll && !in_array($name, $cachesNames, true)) {
263
                continue;
264
            }
265
266
            if ($component instanceof CacheInterface) {
267
                $caches[$name] = get_class($component);
268
            } elseif (is_array($component) && isset($component['class']) && $this->isCacheClass($component['class'])) {
269
                $caches[$name] = $component['class'];
270
            } elseif (is_string($component) && $this->isCacheClass($component)) {
271
                $caches[$name] = $component;
272
            }
273
        }
274
275
        return $caches;
276
    }
277
278
    /**
279
     * Checks if given class is a Cache class.
280
     * @param string $className class name.
281
     * @return bool
282
     */
283
    private function isCacheClass($className)
284
    {
285
        return is_subclass_of($className, 'yii\caching\CacheInterface');
286
    }
287
288
    /**
289
     * Checks if cache of a certain class can be flushed
290
     * @param string $className class name.
291
     * @return bool
292
     */
293
    private function canBeFlushed($className)
294
    {
295
        return !is_a($className, ApcCache::className(), true) || php_sapi_name() !== 'cli';
296
    }
297
}
298