Passed
Pull Request — master (#20134)
by Wilmer
14:25 queued 05:46
created

CacheController::actionFlush()   B

Complexity

Conditions 7
Paths 11

Size

Total Lines 36
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 7.0046

Importance

Changes 0
Metric Value
cc 7
eloc 20
nc 11
nop 0
dl 0
loc 36
ccs 21
cts 22
cp 0.9545
crap 7.0046
rs 8.6666
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @link https://www.yiiframework.com/
5
 * @copyright Copyright (c) 2008 Yii Software LLC
6
 * @license https://www.yiiframework.com/license/
7
 */
8
9
namespace yii\console\controllers;
10
11
use Yii;
12
use yii\caching\ApcCache;
13
use yii\caching\CacheInterface;
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 flush cache.
21
 *
22
 * see list of available components to flush:
23
 *
24
 *     yii cache
25
 *
26
 * flush particular components specified by their names:
27
 *
28
 *     yii cache/flush first second third
29
 *
30
 * flush all cache components that can be found in the system
31
 *
32
 *     yii cache/flush-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 flushing cache from command line has no effect on web.
39
 * Flushing web cache could be either done by:
40
 *
41
 * - Putting a php file under web root and calling it via HTTP
42
 * - Using [Cachetool](https://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 flushed.
52
     */
53
    public function actionIndex()
54
    {
55
        $caches = $this->findCaches();
56
57
        if (!empty($caches)) {
58
            $this->notifyCachesCanBeFlushed($caches);
59
        } else {
60
            $this->notifyNoCachesFound();
61
        }
62
    }
63
64
    /**
65
     * Flushes given cache components.
66
     *
67
     * For example,
68
     *
69
     * ```
70
     * # flushes caches specified by their id: "first", "second", "third"
71
     * yii cache/flush first second third
72
     * ```
73
     */
74 4
    public function actionFlush()
75
    {
76 4
        $cachesInput = func_get_args();
77
78 4
        if (empty($cachesInput)) {
79 1
            throw new Exception('You should specify cache components names');
80
        }
81
82 3
        $caches = $this->findCaches($cachesInput);
83 3
        $cachesInfo = [];
84
85 3
        $foundCaches = array_keys($caches);
86 3
        $notFoundCaches = array_diff($cachesInput, array_keys($caches));
87
88 3
        if ($notFoundCaches !== []) {
89 1
            $this->notifyNotFoundCaches($notFoundCaches);
90
        }
91
92 3
        if ($foundCaches === []) {
93 1
            $this->notifyNoCachesFound();
94 1
            return ExitCode::OK;
95
        }
96
97 2
        if (!$this->confirmFlush($foundCaches)) {
98
            return ExitCode::OK;
99
        }
100
101 2
        foreach ($caches as $name => $class) {
102 2
            $cachesInfo[] = [
103 2
                'name' => $name,
104 2
                'class' => $class,
105 2
                'is_flushed' => $this->canBeFlushed($class) ? Yii::$app->get($name)->flush() : false,
106 2
            ];
107
        }
108
109 2
        $this->notifyFlushed($cachesInfo);
110
    }
111
112
    /**
113
     * Flushes all caches registered in the system.
114
     */
115 1
    public function actionFlushAll()
116
    {
117 1
        $caches = $this->findCaches();
118 1
        $cachesInfo = [];
119
120 1
        if (empty($caches)) {
121
            $this->notifyNoCachesFound();
122
            return ExitCode::OK;
123
        }
124
125 1
        foreach ($caches as $name => $class) {
126 1
            $cachesInfo[] = [
127 1
                'name' => $name,
128 1
                'class' => $class,
129 1
                'is_flushed' => $this->canBeFlushed($class) ? Yii::$app->get($name)->flush() : false,
130 1
            ];
131
        }
132
133 1
        $this->notifyFlushed($cachesInfo);
134
    }
135
136
    /**
137
     * Clears DB schema cache for a given connection component.
138
     *
139
     * ```
140
     * # clears cache schema specified by component id: "db"
141
     * yii cache/flush-schema db
142
     * ```
143
     *
144
     * @param string $db id connection component
145
     * @return int exit code
146
     * @throws Exception
147
     * @throws \yii\base\InvalidConfigException
148
     *
149
     * @since 2.0.1
150
     */
151 1
    public function actionFlushSchema($db = 'db')
152
    {
153 1
        $connection = Yii::$app->get($db, false);
154 1
        if ($connection === null) {
155
            $this->stdout("Unknown component \"$db\".\n", Console::FG_RED);
156
            return ExitCode::UNSPECIFIED_ERROR;
157
        }
158
159 1
        if (!$connection instanceof \yii\db\Connection) {
160
            $this->stdout("\"$db\" component doesn't inherit \\yii\\db\\Connection.\n", Console::FG_RED);
161
            return ExitCode::UNSPECIFIED_ERROR;
162 1
        } elseif (!$this->confirm("Flush cache schema for \"$db\" connection?")) {
163
            return ExitCode::OK;
164
        }
165
166
        try {
167 1
            $schema = $connection->getSchema();
168 1
            $schema->refresh();
169 1
            $this->stdout("Schema cache for component \"$db\", was flushed.\n\n", Console::FG_GREEN);
170
        } catch (\Exception $e) {
171
            $this->stdout($e->getMessage() . "\n\n", Console::FG_RED);
172
        }
173
    }
174
175
    /**
176
     * Notifies user that given caches are found and can be flushed.
177
     * @param array $caches array of cache component classes
178
     */
179
    private function notifyCachesCanBeFlushed($caches)
180
    {
181
        $this->stdout("The following caches were found in the system:\n\n", Console::FG_YELLOW);
182
183
        foreach ($caches as $name => $class) {
184
            if ($this->canBeFlushed($class)) {
185
                $this->stdout("\t* $name ($class)\n", Console::FG_GREEN);
186
            } else {
187
                $this->stdout("\t* $name ($class) - can not be flushed via console\n", Console::FG_YELLOW);
188
            }
189
        }
190
191
        $this->stdout("\n");
192
    }
193
194
    /**
195
     * Notifies user that there was not found any cache in the system.
196
     */
197 1
    private function notifyNoCachesFound()
198
    {
199 1
        $this->stdout("No cache components were found in the system.\n", Console::FG_RED);
200
    }
201
202
    /**
203
     * Notifies user that given cache components were not found in the system.
204
     * @param array $cachesNames
205
     */
206 1
    private function notifyNotFoundCaches($cachesNames)
207
    {
208 1
        $this->stdout("The following cache components were NOT found:\n\n", Console::FG_RED);
209
210 1
        foreach ($cachesNames as $name) {
211 1
            $this->stdout("\t* $name \n", Console::FG_GREEN);
212
        }
213
214 1
        $this->stdout("\n");
215
    }
216
217
    /**
218
     * @param array $caches
219
     */
220 3
    private function notifyFlushed($caches)
221
    {
222 3
        $this->stdout("The following cache components were processed:\n\n", Console::FG_YELLOW);
223
224 3
        foreach ($caches as $cache) {
225 3
            $this->stdout("\t* " . $cache['name'] . ' (' . $cache['class'] . ')', Console::FG_GREEN);
226
227 3
            if (!$cache['is_flushed']) {
228
                $this->stdout(" - not flushed\n", Console::FG_RED);
229
            } else {
230 3
                $this->stdout("\n");
231
            }
232
        }
233
234 3
        $this->stdout("\n");
235
    }
236
237
    /**
238
     * Prompts user with confirmation if caches should be flushed.
239
     * @param array $cachesNames
240
     * @return bool
241
     */
242 2
    private function confirmFlush($cachesNames)
243
    {
244 2
        $this->stdout("The following cache components will be flushed:\n\n", Console::FG_YELLOW);
245
246 2
        foreach ($cachesNames as $name) {
247 2
            $this->stdout("\t* $name \n", Console::FG_GREEN);
248
        }
249
250 2
        return $this->confirm("\nFlush above cache components?");
251
    }
252
253
    /**
254
     * Returns array of caches in the system, keys are cache components names, values are class names.
255
     * @param array $cachesNames caches to be found
256
     * @return array
257
     */
258 4
    private function findCaches(array $cachesNames = [])
259
    {
260 4
        $caches = [];
261 4
        $components = Yii::$app->getComponents();
262 4
        $findAll = ($cachesNames === []);
263
264 4
        foreach ($components as $name => $component) {
265 4
            if (!$findAll && !in_array($name, $cachesNames, true)) {
266 3
                continue;
267
            }
268
269 3
            if ($component instanceof CacheInterface) {
270
                $caches[$name] = get_class($component);
271 3
            } elseif (is_array($component) && isset($component['class']) && $this->isCacheClass($component['class'])) {
272
                $caches[$name] = $component['class'];
273 3
            } elseif (is_string($component) && $this->isCacheClass($component)) {
274 3
                $caches[$name] = $component;
275 2
            } elseif ($component instanceof \Closure) {
276 2
                $cache = Yii::$app->get($name);
277 2
                if ($this->isCacheClass($cache)) {
0 ignored issues
show
Bug introduced by
It seems like $cache can also be of type mixed and object; however, parameter $className of yii\console\controllers\...troller::isCacheClass() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

277
                if ($this->isCacheClass(/** @scrutinizer ignore-type */ $cache)) {
Loading history...
278 2
                    $cacheClass = get_class($cache);
0 ignored issues
show
Bug introduced by
It seems like $cache can also be of type mixed and null; however, parameter $object of get_class() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

278
                    $cacheClass = get_class(/** @scrutinizer ignore-type */ $cache);
Loading history...
279 2
                    $caches[$name] = $cacheClass;
280
                }
281
            }
282
        }
283
284 4
        return $caches;
285
    }
286
287
    /**
288
     * Checks if given class is a Cache class.
289
     * @param string $className class name.
290
     * @return bool
291
     */
292 3
    private function isCacheClass($className)
293
    {
294 3
        return is_subclass_of($className, 'yii\caching\CacheInterface') || $className === 'yii\caching\CacheInterface';
295
    }
296
297
    /**
298
     * Checks if cache of a certain class can be flushed.
299
     * @param string $className class name.
300
     * @return bool
301
     */
302 3
    private function canBeFlushed($className)
303
    {
304 3
        return !is_a($className, ApcCache::className(), true) || PHP_SAPI !== 'cli';
0 ignored issues
show
Deprecated Code introduced by
The function yii\base\BaseObject::className() has been deprecated: since 2.0.14. On PHP >=5.5, use `::class` instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

304
        return !is_a($className, /** @scrutinizer ignore-deprecated */ ApcCache::className(), true) || PHP_SAPI !== 'cli';

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
305
    }
306
}
307