diff --git a/framework/YiiBase.php b/framework/YiiBase.php index 213b81c..9d3698b 100644 --- a/framework/YiiBase.php +++ b/framework/YiiBase.php @@ -8,7 +8,9 @@ */ use yii\base\Exception; +use yii\logging\Logger; use yii\base\InvalidCallException; +use yii\base\InvalidConfigException; /** * Gets the application start timestamp. @@ -394,7 +396,7 @@ class YiiBase public static function trace($message, $category = 'application') { if (YII_DEBUG) { - self::getLogger()->trace($message, $category); + self::getLogger()->log($message, Logger::LEVEL_TRACE, $category); } } @@ -407,7 +409,7 @@ class YiiBase */ public static function error($message, $category = 'application') { - self::getLogger()->error($message, $category); + self::getLogger()->log($message, Logger::LEVEL_ERROR, $category); } /** @@ -419,7 +421,7 @@ class YiiBase */ public static function warning($message, $category = 'application') { - self::getLogger()->warning($message, $category); + self::getLogger()->log($message, Logger::LEVEL_WARNING, $category); } /** @@ -431,7 +433,7 @@ class YiiBase */ public static function info($message, $category = 'application') { - self::getLogger()->info($message, $category); + self::getLogger()->log($message, Logger::LEVEL_INFO, $category); } /** @@ -453,7 +455,7 @@ class YiiBase */ public static function beginProfile($token, $category = 'application') { - self::getLogger()->beginProfile($token, $category); + self::getLogger()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category); } /** @@ -465,7 +467,7 @@ class YiiBase */ public static function endProfile($token, $category = 'application') { - self::getLogger()->endProfile($token, $category); + self::getLogger()->log($token, Logger::LEVEL_PROFILE_END, $category); } /** @@ -477,13 +479,13 @@ class YiiBase if (self::$_logger !== null) { return self::$_logger; } else { - return self::$_logger = new \yii\logging\Logger; + return self::$_logger = new Logger; } } /** * Sets the logger object. - * @param \yii\logging\Logger $logger the logger object. + * @param Logger $logger the logger object. */ public static function setLogger($logger) { diff --git a/framework/base/Application.php b/framework/base/Application.php index e945160..6a20182 100644 --- a/framework/base/Application.php +++ b/framework/base/Application.php @@ -75,6 +75,8 @@ use yii\base\InvalidCallException; */ class Application extends Module { + const EVENT_BEFORE_REQUEST = 'beforeRequest'; + const EVENT_AFTER_REQUEST = 'afterRequest'; /** * @var string the application name. Defaults to 'My Application'. */ @@ -178,7 +180,7 @@ class Application extends Module */ public function beforeRequest() { - $this->trigger('beforeRequest'); + $this->trigger(self::EVENT_BEFORE_REQUEST); } /** @@ -186,7 +188,7 @@ class Application extends Module */ public function afterRequest() { - $this->trigger('afterRequest'); + $this->trigger(self::EVENT_AFTER_REQUEST); } /** diff --git a/framework/logging/DbTarget.php b/framework/logging/DbTarget.php index dc541f5..004bf21 100644 --- a/framework/logging/DbTarget.php +++ b/framework/logging/DbTarget.php @@ -9,6 +9,9 @@ namespace yii\logging; +use yii\db\Connection; +use yii\base\InvalidConfigException; + /** * DbTarget stores log messages in a database table. * @@ -23,23 +26,21 @@ namespace yii\logging; class DbTarget extends Target { /** - * @var string the ID of [[\yii\db\Connection]] application component. + * @var string the ID of [[Connection]] application component. * Defaults to 'db'. Please make sure that your database contains a table * whose name is as specified in [[tableName]] and has the required table structure. * @see tableName */ public $connectionID = 'db'; /** - * @var string the name of the DB table that stores log messages. Defaults to '{{log}}'. - * If you are using table prefix 'tbl_' (configured via [[\yii\db\Connection::tablePrefix]]), - * it means the DB table would be named as 'tbl_log'. + * @var string the name of the DB table that stores log messages. Defaults to 'tbl_log'. * - * The DB table must have the following structure: + * The DB table should have the following structure: * * ~~~ * CREATE TABLE tbl_log ( * id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, - * level VARCHAR(32), + * level INTEGER, * category VARCHAR(255), * log_time INTEGER, * message TEXT, @@ -50,42 +51,53 @@ class DbTarget extends Target * * Note that the 'id' column must be created as an auto-incremental column. * The above SQL shows the syntax of MySQL. If you are using other DBMS, you need - * to adjust it accordingly. For example, in PosgreSQL, it should be `id SERIAL PRIMARY KEY`. + * to adjust it accordingly. For example, in PostgreSQL, it should be `id SERIAL PRIMARY KEY`. * * The indexes declared above are not required. They are mainly used to improve the performance * of some queries about message levels and categories. Depending on your actual needs, you may - * want to create other indexes. + * want to create additional indexes (e.g. index on log_time). */ - public $tableName = '{{log}}'; + public $tableName = 'tbl_log'; private $_db; /** * Returns the DB connection used for saving log messages. - * @return \yii\db\Connection the DB connection instance - * @throws \yii\base\Exception if [[connectionID]] does not refer to a valid application component ID. + * @return Connection the DB connection instance + * @throws InvalidConfigException if [[connectionID]] does not point to a valid application component. */ public function getDb() { if ($this->_db === null) { - $this->_db = \Yii::$application->getComponent($this->connectionID); - if (!$this->_db instanceof \yii\db\Connection) { - throw new \yii\base\Exception('DbTarget.connectionID must refer to a valid application component ID'); + $db = \Yii::$application->getComponent($this->connectionID); + if ($db instanceof Connection) { + $this->_db = $db; + } else { + throw new InvalidConfigException("DbTarget::connectionID must refer to the ID of a DB application component."); } } return $this->_db; } /** + * Sets the DB connection used by the cache component. + * @param Connection $value the DB connection instance + */ + public function setDb($value) + { + $this->_db = $value; + } + + /** * Stores log [[messages]] to DB. * @param boolean $final whether this method is called at the end of the current application */ public function exportMessages($final) { - $sql = "INSERT INTO {$this->tableName} - (level, category, log_time, message) VALUES - (:level, :category, :log_time, :message)"; - $command = $this->getDb()->createCommand($sql); + $db = $this->getDb(); + $tableName = $db->quoteTableName($this->tableName); + $sql = "INSERT INTO $tableName (level, category, log_time, message) VALUES (:level, :category, :log_time, :message)"; + $command = $db->createCommand($sql); foreach ($this->messages as $message) { $command->bindValues(array( ':level' => $message[1], diff --git a/framework/logging/FileTarget.php b/framework/logging/FileTarget.php index 2595ed3..f4ddf44 100644 --- a/framework/logging/FileTarget.php +++ b/framework/logging/FileTarget.php @@ -8,17 +8,16 @@ */ namespace yii\logging; +use yii\base\InvalidConfigException; /** - * FileTarget records log messages in files. + * FileTarget records log messages in a file. * - * The log files are stored under [[logPath]] and their name - * is specified by [[logFile]]. If the size of the log file exceeds - * [[maxFileSize]] (in kilo-bytes), a rotation will be performed, - * which renames the current log file by suffixing the file name - * with '.1'. All existing log files are moved backwards one place, - * i.e., '.2' to '.3', '.1' to '.2', and so on. The property - * [[maxLogFiles]] specifies how many files to keep. + * The log file is specified via [[logFile]]. If the size of the log file exceeds + * [[maxFileSize]] (in kilo-bytes), a rotation will be performed, which renames + * the current log file by suffixing the file name with '.1'. All existing log + * files are moved backwards by one place, i.e., '.2' to '.3', '.1' to '.2', and so on. + * The property [[maxLogFiles]] specifies how many files to keep. * * @author Qiang Xue * @since 2.0 @@ -26,6 +25,12 @@ namespace yii\logging; class FileTarget extends Target { /** + * @var string log file path or path alias. If not set, it means the 'application.log' file under + * the application runtime directory. Please make sure the directory containing + * the log file is writable by the Web server process. + */ + public $logFile; + /** * @var integer maximum log file size, in kilo-bytes. Defaults to 1024, meaning 1MB. */ public $maxFileSize = 1024; // in KB @@ -33,14 +38,6 @@ class FileTarget extends Target * @var integer number of log files used for rotation. Defaults to 5. */ public $maxLogFiles = 5; - /** - * @var string directory storing log files. Defaults to the application runtime path. - */ - public $logPath; - /** - * @var string log file name. Defaults to 'application.log'. - */ - public $logFile = 'application.log'; /** @@ -50,11 +47,14 @@ class FileTarget extends Target public function init() { parent::init(); - if ($this->logPath === null) { - $this->logPath = \Yii::$application->getRuntimePath(); + if ($this->logFile === null) { + $this->logFile = \Yii::$application->getRuntimePath() . DIRECTORY_SEPARATOR . 'application.log'; + } else { + $this->logFile = \Yii::getAlias($this->logFile); } - if (!is_dir($this->logPath) || !is_writable($this->logPath)) { - throw new \yii\base\Exception("Directory '{$this->logPath}' does not exist or is not writable."); + $logPath = dirname($this->logFile); + if (!is_dir($logPath) || !is_writable($logPath)) { + throw new InvalidConfigException("Directory '$logPath' does not exist or is not writable."); } if ($this->maxLogFiles < 1) { $this->maxLogFiles = 1; @@ -70,15 +70,14 @@ class FileTarget extends Target */ public function exportMessages($final) { - $logFile = $this->logPath . DIRECTORY_SEPARATOR . $this->logFile; - if (@filesize($logFile) > $this->maxFileSize * 1024) { + if (@filesize($this->logFile) > $this->maxFileSize * 1024) { $this->rotateFiles(); } $messages = array(); foreach ($this->messages as $message) { $messages[] = $this->formatMessage($message); } - @file_put_contents($logFile, implode('', $messages), FILE_APPEND | LOCK_EX); + @file_put_contents($this->logFile, implode('', $messages), FILE_APPEND | LOCK_EX); } /** @@ -86,7 +85,7 @@ class FileTarget extends Target */ protected function rotateFiles() { - $file = $this->logPath . DIRECTORY_SEPARATOR . $this->logFile; + $file = $this->logFile; for ($i = $this->maxLogFiles; $i > 0; --$i) { $rotateFile = $file . '.' . $i; if (is_file($rotateFile)) { diff --git a/framework/logging/Logger.php b/framework/logging/Logger.php index b6fc7da..e1f809f 100644 --- a/framework/logging/Logger.php +++ b/framework/logging/Logger.php @@ -10,6 +10,7 @@ namespace yii\logging; use yii\base\Event; +use yii\base\Exception; /** * Logger records logged messages in memory. @@ -18,8 +19,6 @@ use yii\base\Event; * call [[flush()]] to send logged messages to different log targets, such as * file, email, Web. * - * Logger provides a set of events for further customization: - * * @author Qiang Xue * @since 2.0 */ @@ -30,12 +29,40 @@ class Logger extends \yii\base\Component */ const EVENT_FLUSH = 'flush'; - const LEVEL_ERROR = 'error'; - const LEVEL_WARNING = 'warning'; - const LEVEL_INFO = 'info'; - const LEVEL_TRACE = 'trace'; - const LEVEL_PROFILE_BEGIN = 'profile-begin'; - const LEVEL_PROFILE_END = 'profile-end'; + /** + * Error message level. An error message is one that indicates the abnormal termination of the + * application and may require developer's handling. + */ + const LEVEL_ERROR = 0x01; + /** + * Warning message level. A warning message is one that indicates some abnormal happens but + * the application is able to continue to run. Developers should pay attention to this message. + */ + const LEVEL_WARNING = 0x02; + /** + * Informational message level. An informational message is one that includes certain information + * for developers to review. + */ + const LEVEL_INFO = 0x04; + /** + * Tracing message level. An tracing message is one that reveals the code execution flow. + */ + const LEVEL_TRACE = 0x08; + /** + * Profiling message level. This indicates the message is for profiling purpose. + */ + const LEVEL_PROFILE = 0x40; + /** + * Profiling message level. This indicates the message is for profiling purpose. It marks the + * beginning of a profiling block. + */ + const LEVEL_PROFILE_BEGIN = 0x50; + /** + * Profiling message level. This indicates the message is for profiling purpose. It marks the + * end of a profiling block. + */ + const LEVEL_PROFILE_END = 0x60; + /** * @var integer how many messages should be logged before they are flushed from memory and sent to targets. @@ -52,7 +79,7 @@ class Logger extends \yii\base\Component * ~~~ * array( * [0] => message (mixed) - * [1] => level (string) + * [1] => level (integer) * [2] => category (string) * [3] => timestamp (float, obtained by microtime(true)) * ) @@ -61,85 +88,13 @@ class Logger extends \yii\base\Component public $messages = array(); /** - * Logs an error message. - * An error message is typically logged when an unrecoverable error occurs - * during the execution of an application. - * @param mixed $message the message to be logged. - * @param string $category the category of the message. - */ - public function error($message, $category = 'application') - { - $this->log($message, self::LEVEL_ERROR, $category); - } - - /** - * Logs a trace message. - * Trace messages are logged mainly for development purpose to see - * the execution work flow of some code. - * @param mixed $message the message to be logged. - * @param string $category the category of the message. - */ - public function trace($message, $category = 'application') - { - $this->log($message, self::LEVEL_TRACE, $category); - } - - /** - * Logs a warning message. - * A warning message is typically logged when an error occurs while the execution - * can still continue. - * @param mixed $message the message to be logged. - * @param string $category the category of the message. - */ - public function warning($message, $category = 'application') - { - $this->log($message, self::LEVEL_WARNING, $category); - } - - /** - * Logs an informative message. - * An informative message is typically logged by an application to keep record of - * something important (e.g. an administrator logs in). - * @param mixed $message the message to be logged. - * @param string $category the category of the message. - */ - public function info($message, $category = 'application') - { - $this->log($message, self::LEVEL_INFO, $category); - } - - /** - * Marks the beginning of a code block for profiling. - * This has to be matched with a call to [[endProfile()]] with the same category name. - * The begin- and end- calls must also be properly nested. - * @param string $token token for the code block - * @param string $category the category of this log message - * @see endProfile - */ - public function beginProfile($token, $category = 'application') - { - $this->log($token, self::LEVEL_PROFILE_BEGIN, $category); - } - - /** - * Marks the end of a code block for profiling. - * This has to be matched with a previous call to [[beginProfile()]] with the same category name. - * @param string $token token for the code block - * @param string $category the category of this log message - * @see beginProfile - */ - public function endProfile($token, $category = 'application') - { - $this->log($token, self::LEVEL_PROFILE_END, $category); - } - - /** * Logs a message with the given type and category. * If `YII_DEBUG` is true and `YII_TRACE_LEVEL` is greater than 0, then additional * call stack information about application code will be appended to the message. * @param string $message the message to be logged. - * @param string $level the level of the message. This must be one of the following: - * 'trace', 'info', 'warning', 'error', 'profile'. + * @param integer $level the level of the message. This must be one of the following: + * `Logger::LEVEL_ERROR`, `Logger::LEVEL_WARNING`, `Logger::LEVEL_INFO`, `Logger::LEVEL_TRACE`, + * `Logger::LEVEL_PROFILE_BEGIN`, `Logger::LEVEL_PROFILE_END`. * @param string $category the category of the message. */ public function log($message, $level, $category = 'application') @@ -242,14 +197,14 @@ class Logger extends \yii\base\Component $stack = array(); foreach ($this->messages as $log) { - if ($log[1] === self::LEVEL_PROFILE_BEGIN) { + list($token, $level, $category, $timestamp) = $log; + if ($level == self::LEVEL_PROFILE_BEGIN) { $stack[] = $log; - } elseif ($log[1] === self::LEVEL_PROFILE_END) { - list($token, $level, $category, $timestamp) = $log; + } elseif ($level == self::LEVEL_PROFILE_END) { if (($last = array_pop($stack)) !== null && $last[0] === $token) { $timings[] = array($token, $category, $timestamp - $last[3]); } else { - throw new \yii\base\Exception("Unmatched profiling block: $token"); + throw new Exception("Unmatched profiling block: $token"); } } } diff --git a/framework/logging/Router.php b/framework/logging/Router.php index ba04b46..aaac8f8 100644 --- a/framework/logging/Router.php +++ b/framework/logging/Router.php @@ -9,6 +9,10 @@ namespace yii\logging; +use Yii; +use yii\base\Component; +use yii\base\Application; + /** * Router manages [[Target|log targets]] that record log messages in different media. * @@ -48,17 +52,17 @@ namespace yii\logging; * as follows: * * ~~~ - * \Yii::$application->log->targets['file']->enabled = false; + * Yii::$application->log->targets['file']->enabled = false; * ~~~ * * @author Qiang Xue * @since 2.0 */ -class Router extends \yii\base\ApplicationComponent +class Router extends Component { /** * @var Target[] list of log target objects or configurations. If the latter, target objects will - * be created in [[init()]] by calling [[\Yii::createObject()]] with the corresponding object configuration. + * be created in [[init()]] by calling [[Yii::createObject()]] with the corresponding object configuration. */ public $targets = array(); @@ -74,28 +78,28 @@ class Router extends \yii\base\ApplicationComponent foreach ($this->targets as $name => $target) { if (!$target instanceof Target) { - $this->targets[$name] = \Yii::createObject($target); + $this->targets[$name] = Yii::createObject($target); } } - \Yii::getLogger()->on('flush', array($this, 'processMessages')); - if (\Yii::$application !== null) { - \Yii::$application->on('afterRequest', array($this, 'processMessages')); + Yii::getLogger()->on(Logger::EVENT_FLUSH, array($this, 'processMessages')); + if (Yii::$application !== null) { + Yii::$application->on(Application::EVENT_AFTER_REQUEST, array($this, 'processMessages')); } } /** * Retrieves and processes log messages from the system logger. * This method mainly serves the event handler to [[Logger::onFlush]] - * and [[\yii\base\Application::onEndRequest]] events. - * It will retrieve the available log messages from the [[\Yii::getLogger|system logger]] + * and [[Application::onEndRequest]] events. + * It will retrieve the available log messages from the [[Yii::getLogger|system logger]] * and invoke the registered [[targets|log targets]] to do the actual processing. * @param \yii\base\Event $event event parameter */ public function processMessages($event) { - $messages = \Yii::getLogger()->messages; - $final = $event->name !== 'flush'; + $messages = Yii::getLogger()->messages; + $final = $event->name === Application::EVENT_AFTER_REQUEST; foreach ($this->targets as $target) { if ($target->enabled) { $target->processMessages($messages, $final); diff --git a/framework/logging/Target.php b/framework/logging/Target.php index cda12df..a4e7714 100644 --- a/framework/logging/Target.php +++ b/framework/logging/Target.php @@ -9,6 +9,8 @@ namespace yii\logging; +use yii\base\InvalidConfigException; + /** * Target is the base class for all log target classes. * @@ -20,6 +22,8 @@ namespace yii\logging; * satisfying both filter conditions will be handled. Additionally, you * may specify [[except]] to exclude messages of certain categories. * + * @property integer $levels the message levels that this target is interested in. + * * @author Qiang Xue * @since 2.0 */ @@ -30,10 +34,6 @@ abstract class Target extends \yii\base\Component */ public $enabled = true; /** - * @var array list of message levels that this target is interested in. Defaults to empty, meaning all levels. - */ - public $levels = array(); - /** * @var array list of message categories that this target is interested in. Defaults to empty, meaning all categories. * You can use an asterisk at the end of a category so that the category may be used to * match those categories sharing the same common prefix. For example, 'yii\db\*' will match @@ -70,17 +70,19 @@ abstract class Target extends \yii\base\Component */ public $logVars = array('_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER'); /** - * @var boolean whether this target should export the collected messages to persistent storage - * (e.g. DB, email) whenever [[processMessages()]] is called. Defaults to true. If false, - * the collected messages will be stored in [[messages]] without any further processing. + * @var integer how many messages should be accumulated before they are exported. + * Defaults to 1000. Note that messages will always be exported when the application terminates. + * Set this property to be 0 if you don't want to export messages until the application terminates. */ - public $autoExport = true; + public $exportInterval = 1000; /** * @var array the messages that are retrieved from the logger so far by this log target. * @see autoExport */ public $messages = array(); + private $_levels = 0; + /** * Exports log messages to a specific destination. * Child classes must implement this method. Note that you may need @@ -102,7 +104,8 @@ abstract class Target extends \yii\base\Component $messages = $this->filterMessages($messages); $this->messages = array_merge($this->messages, $messages); - if (!empty($this->messages) && ($this->autoExport || $final)) { + $count = count($this->messages); + if ($count > 0 && ($final || $this->exportInterval > 0 && $count >= $this->exportInterval)) { $this->prepareExport($final); $this->exportMessages($final); $this->messages = array(); @@ -160,6 +163,58 @@ abstract class Target extends \yii\base\Component } /** + * @return integer the message levels that this target is interested in. This is a bitmap of + * level values. Defaults to 0, meaning all available levels. + */ + public function getLevels() + { + return $this->_levels; + } + + /** + * Sets the message levels that this target is interested in. + * + * The parameter can be either an array of interested level names or an integer representing + * the bitmap of the interested level values. Valid level names include: 'error', + * 'warning', 'info', 'trace' and 'profile'; valid level values include: + * [[Logger::LEVEL_ERROR]], [[Logger::LEVEL_WARNING]], [[Logger::LEVEL_INFO]], + * [[Logger::LEVEL_TRACE]] and [[Logger::LEVEL_PROFILE]]. + * + * For example, + * + * ~~~ + * array('error', 'warning') + * // which is equivalent to: + * Logger::LEVEL_ERROR | Logger::LEVEL_WARNING + * ~~~ + * + * @param array|integer $levels message levels that this target is interested in. + * @throws InvalidConfigException if an unknown level name is given + */ + public function setLevels($levels) + { + static $levelMap = array( + 'error' => Logger::LEVEL_ERROR, + 'warning' => Logger::LEVEL_WARNING, + 'info' => Logger::LEVEL_INFO, + 'trace' => Logger::LEVEL_TRACE, + 'profile' => Logger::LEVEL_PROFILE, + ); + if (is_array($levels)) { + $this->_levels = 0; + foreach ($levels as $level) { + if (isset($levelMap[$level])) { + $this->_levels |= $levelMap[$level]; + } else { + throw new InvalidConfigException("Unrecognized level: $level"); + } + } + } else { + $this->_levels = $levels; + } + } + + /** * Filters the given messages according to their categories and levels. * @param array $messages messages to be filtered * @return array the filtered messages. @@ -168,8 +223,10 @@ abstract class Target extends \yii\base\Component */ protected function filterMessages($messages) { + $levels = $this->getLevels(); + foreach ($messages as $i => $message) { - if (!empty($this->levels) && !in_array($message[1], $this->levels)) { + if ($levels && !($levels & $message[1])) { unset($messages[$i]); continue; } @@ -210,7 +267,19 @@ abstract class Target extends \yii\base\Component */ public function formatMessage($message) { - $s = is_string($message[0]) ? $message[0] : var_export($message[0], true); - return date('Y/m/d H:i:s', $message[3]) . " [{$message[1]}] [{$message[2]}] $s\n"; + static $levels = array( + Logger::LEVEL_ERROR => 'error', + Logger::LEVEL_WARNING => 'warning', + Logger::LEVEL_INFO => 'info', + Logger::LEVEL_TRACE => 'trace', + Logger::LEVEL_PROFILE_BEGIN => 'profile begin', + Logger::LEVEL_PROFILE_END => 'profile end', + ); + list($text, $level, $category, $timestamp) = $message; + $level = isset($levels[$level]) ? $levels[$level] : 'unknown'; + if (!is_string($text)) { + $text = var_export($text, true); + } + return date('Y/m/d H:i:s', $timestamp) . " [$level] [$category] $text\n"; } } diff --git a/todo.md b/todo.md index 777c57b..0c21fe2 100644 --- a/todo.md +++ b/todo.md @@ -1,6 +1,18 @@ +- db + * pgsql, sql server, oracle, db2 drivers + * write a guide on creating own schema definitions + * document-based (should allow storage-specific methods additionally to generic ones) + * mongodb (put it under framework/db/mongodb) + * key-value-based (should allow storage-specific methods additionally to generic ones) + * redis (put it under framework/db/redis or perhaps framework/caching?) - logging * WebTarget * ProfileTarget +- caching + * a console command to clear cached data + +--- + - base * module - Module should be able to define its own configuration including routes. Application should be able to overwrite it. @@ -16,17 +28,6 @@ * support for markdown syntax * support for [[name]] * consider to be released as a separate tool for user app docs -- caching - * a way to invalidate/clear cached data - * a command to clear cached data -- db - * pgsql, sql server, oracle, db2 drivers - * write a guide on creating own schema definitions - * document-based (should allow storage-specific methods additionally to generic ones) - * mongodb - * key-value-based (should allow storage-specific methods additionally to generic ones) - * redis - * memcachedb - i18n * consider using PHP built-in support and data * message translations, choice format