* @author Mark Jebri * @since 2.0 */ class CacheController extends Controller { /** * Lists the caches that can be flushed. */ public function actionIndex() { $caches = $this->findCaches(); if (!empty($caches)) { $this->notifyCachesCanBeFlushed($caches); } else { $this->notifyNoCachesFound(); } } /** * Flushes given cache components. * * For example, * * ``` * # flushes caches specified by their id: "first", "second", "third" * yii cache/flush first second third * ``` */ public function actionFlush() { $cachesInput = func_get_args(); if (empty($cachesInput)) { throw new Exception('You should specify cache components names'); } $caches = $this->findCaches($cachesInput); $cachesInfo = []; $foundCaches = array_keys($caches); $notFoundCaches = array_diff($cachesInput, array_keys($caches)); if ($notFoundCaches) { $this->notifyNotFoundCaches($notFoundCaches); } if (!$foundCaches) { $this->notifyNoCachesFound(); return ExitCode::OK; } if (!$this->confirmFlush($foundCaches)) { return ExitCode::OK; } foreach ($caches as $name => $class) { $cachesInfo[] = [ 'name' => $name, 'class' => $class, 'is_flushed' => $this->canBeFlushed($class) ? Yii::$app->get($name)->flush() : false, ]; } $this->notifyFlushed($cachesInfo); } /** * Flushes all caches registered in the system. */ public function actionFlushAll() { $caches = $this->findCaches(); $cachesInfo = []; if (empty($caches)) { $this->notifyNoCachesFound(); return ExitCode::OK; } foreach ($caches as $name => $class) { $cachesInfo[] = [ 'name' => $name, 'class' => $class, 'is_flushed' => $this->canBeFlushed($class) ? Yii::$app->get($name)->flush() : false, ]; } $this->notifyFlushed($cachesInfo); } /** * Clears DB schema cache for a given connection component. * * ``` * # clears cache schema specified by component id: "db" * yii cache/flush-schema db * ``` * * @param string $db id connection component * @return int exit code * @throws Exception * @throws \yii\base\InvalidConfigException * * @since 2.0.1 */ public function actionFlushSchema($db = 'db') { $connection = Yii::$app->get($db, false); if ($connection === null) { $this->stdout("Unknown component \"$db\".\n", Console::FG_RED); return ExitCode::UNSPECIFIED_ERROR; } if (!$connection instanceof \yii\db\Connection) { $this->stdout("\"$db\" component doesn't inherit \\yii\\db\\Connection.\n", Console::FG_RED); return ExitCode::UNSPECIFIED_ERROR; } elseif (!$this->confirm("Flush cache schema for \"$db\" connection?")) { return ExitCode::OK; } try { $schema = $connection->getSchema(); $schema->refresh(); $this->stdout("Schema cache for component \"$db\", was flushed.\n\n", Console::FG_GREEN); } catch (\Exception $e) { $this->stdout($e->getMessage() . "\n\n", Console::FG_RED); } } /** * Notifies user that given caches are found and can be flushed. * @param array $caches array of cache component classes */ private function notifyCachesCanBeFlushed($caches) { $this->stdout("The following caches were found in the system:\n\n", Console::FG_YELLOW); foreach ($caches as $name => $class) { if ($this->canBeFlushed($class)) { $this->stdout("\t* $name ($class)\n", Console::FG_GREEN); } else { $this->stdout("\t* $name ($class) - can not be flushed via console\n", Console::FG_YELLOW); } } $this->stdout("\n"); } /** * Notifies user that there was not found any cache in the system. */ private function notifyNoCachesFound() { $this->stdout("No cache components were found in the system.\n", Console::FG_RED); } /** * Notifies user that given cache components were not found in the system. * @param array $cachesNames */ private function notifyNotFoundCaches($cachesNames) { $this->stdout("The following cache components were NOT found:\n\n", Console::FG_RED); foreach ($cachesNames as $name) { $this->stdout("\t* $name \n", Console::FG_GREEN); } $this->stdout("\n"); } /** * @param array $caches */ private function notifyFlushed($caches) { $this->stdout("The following cache components were processed:\n\n", Console::FG_YELLOW); foreach ($caches as $cache) { $this->stdout("\t* " . $cache['name'] . ' (' . $cache['class'] . ')', Console::FG_GREEN); if (!$cache['is_flushed']) { $this->stdout(" - not flushed\n", Console::FG_RED); } else { $this->stdout("\n"); } } $this->stdout("\n"); } /** * Prompts user with confirmation if caches should be flushed. * @param array $cachesNames * @return bool */ private function confirmFlush($cachesNames) { $this->stdout("The following cache components will be flushed:\n\n", Console::FG_YELLOW); foreach ($cachesNames as $name) { $this->stdout("\t* $name \n", Console::FG_GREEN); } return $this->confirm("\nFlush above cache components?"); } /** * Returns array of caches in the system, keys are cache components names, values are class names. * @param array $cachesNames caches to be found * @return array */ private function findCaches(array $cachesNames = []) { $caches = []; $components = Yii::$app->getComponents(); $findAll = ($cachesNames === []); foreach ($components as $name => $component) { if (!$findAll && !in_array($name, $cachesNames, true)) { continue; } if ($component instanceof CacheInterface) { $caches[$name] = get_class($component); } elseif (is_array($component) && isset($component['class']) && $this->isCacheClass($component['class'])) { $caches[$name] = $component['class']; } elseif (is_string($component) && $this->isCacheClass($component)) { $caches[$name] = $component; } elseif ($component instanceof \Closure) { $cache = Yii::$app->get($name); if ($this->isCacheClass($cache)) { $cacheClass = get_class($cache); $caches[$name] = $cacheClass; } } } return $caches; } /** * Checks if given class is a Cache class. * @param string $className class name. * @return bool */ private function isCacheClass($className) { return is_subclass_of($className, 'yii\caching\CacheInterface'); } /** * Checks if cache of a certain class can be flushed. * @param string $className class name. * @return bool */ private function canBeFlushed($className) { return !is_a($className, ApcCache::className(), true) || php_sapi_name() !== 'cli'; } }