Completed
Push — fix-db-exception-not-displayin... ( feb406...668afb )
by Alexander
18:34
created

CacheController::actionFlushSchema()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 7.0935

Importance

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