DbMessageSource.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. <?php
  2. /**
  3. * @link https://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license https://www.yiiframework.com/license/
  6. */
  7. namespace yii\i18n;
  8. use yii\base\InvalidConfigException;
  9. use yii\caching\CacheInterface;
  10. use yii\db\Connection;
  11. use yii\db\Expression;
  12. use yii\db\Query;
  13. use yii\di\Instance;
  14. use yii\helpers\ArrayHelper;
  15. /**
  16. * DbMessageSource extends [[MessageSource]] and represents a message source that stores translated
  17. * messages in database.
  18. *
  19. * The database must contain the following two tables: source_message and message.
  20. *
  21. * The `source_message` table stores the messages to be translated, and the `message` table stores
  22. * the translated messages. The name of these two tables can be customized by setting [[sourceMessageTable]]
  23. * and [[messageTable]], respectively.
  24. *
  25. * The database connection is specified by [[db]]. Database schema could be initialized by applying migration:
  26. *
  27. * ```
  28. * yii migrate --migrationPath=@yii/i18n/migrations/
  29. * ```
  30. *
  31. * If you don't want to use migration and need SQL instead, files for all databases are in migrations directory.
  32. *
  33. * @author resurtm <resurtm@gmail.com>
  34. * @since 2.0
  35. */
  36. class DbMessageSource extends MessageSource
  37. {
  38. /**
  39. * Prefix which would be used when generating cache key.
  40. * @deprecated This constant has never been used and will be removed in 2.1.0.
  41. */
  42. const CACHE_KEY_PREFIX = 'DbMessageSource';
  43. /**
  44. * @var Connection|array|string the DB connection object or the application component ID of the DB connection.
  45. *
  46. * After the DbMessageSource object is created, if you want to change this property, you should only assign
  47. * it with a DB connection object.
  48. *
  49. * Starting from version 2.0.2, this can also be a configuration array for creating the object.
  50. */
  51. public $db = 'db';
  52. /**
  53. * @var CacheInterface|array|string the cache object or the application component ID of the cache object.
  54. * The messages data will be cached using this cache object.
  55. * Note, that to enable caching you have to set [[enableCaching]] to `true`, otherwise setting this property has no effect.
  56. *
  57. * After the DbMessageSource object is created, if you want to change this property, you should only assign
  58. * it with a cache object.
  59. *
  60. * Starting from version 2.0.2, this can also be a configuration array for creating the object.
  61. * @see cachingDuration
  62. * @see enableCaching
  63. */
  64. public $cache = 'cache';
  65. /**
  66. * @var string the name of the source message table.
  67. */
  68. public $sourceMessageTable = '{{%source_message}}';
  69. /**
  70. * @var string the name of the translated message table.
  71. */
  72. public $messageTable = '{{%message}}';
  73. /**
  74. * @var int the time in seconds that the messages can remain valid in cache.
  75. * Use 0 to indicate that the cached data will never expire.
  76. * @see enableCaching
  77. */
  78. public $cachingDuration = 0;
  79. /**
  80. * @var bool whether to enable caching translated messages
  81. */
  82. public $enableCaching = false;
  83. /**
  84. * Initializes the DbMessageSource component.
  85. * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
  86. * Configured [[cache]] component would also be initialized.
  87. * @throws InvalidConfigException if [[db]] is invalid or [[cache]] is invalid.
  88. */
  89. public function init()
  90. {
  91. parent::init();
  92. $this->db = Instance::ensure($this->db, Connection::className());
  93. if ($this->enableCaching) {
  94. $this->cache = Instance::ensure($this->cache, 'yii\caching\CacheInterface');
  95. }
  96. }
  97. /**
  98. * Loads the message translation for the specified language and category.
  99. * If translation for specific locale code such as `en-US` isn't found it
  100. * tries more generic `en`.
  101. *
  102. * @param string $category the message category
  103. * @param string $language the target language
  104. * @return array the loaded messages. The keys are original messages, and the values
  105. * are translated messages.
  106. */
  107. protected function loadMessages($category, $language)
  108. {
  109. if ($this->enableCaching) {
  110. $key = [
  111. __CLASS__,
  112. $category,
  113. $language,
  114. ];
  115. $messages = $this->cache->get($key);
  116. if ($messages === false) {
  117. $messages = $this->loadMessagesFromDb($category, $language);
  118. $this->cache->set($key, $messages, $this->cachingDuration);
  119. }
  120. return $messages;
  121. }
  122. return $this->loadMessagesFromDb($category, $language);
  123. }
  124. /**
  125. * Loads the messages from database.
  126. * You may override this method to customize the message storage in the database.
  127. * @param string $category the message category.
  128. * @param string $language the target language.
  129. * @return array the messages loaded from database.
  130. */
  131. protected function loadMessagesFromDb($category, $language)
  132. {
  133. $mainQuery = (new Query())->select(['message' => 't1.message', 'translation' => 't2.translation'])
  134. ->from(['t1' => $this->sourceMessageTable, 't2' => $this->messageTable])
  135. ->where([
  136. 't1.id' => new Expression('[[t2.id]]'),
  137. 't1.category' => $category,
  138. 't2.language' => $language,
  139. ]);
  140. $fallbackLanguage = substr($language, 0, 2);
  141. $fallbackSourceLanguage = substr($this->sourceLanguage, 0, 2);
  142. if ($fallbackLanguage !== $language) {
  143. $mainQuery->union($this->createFallbackQuery($category, $language, $fallbackLanguage), true);
  144. } elseif ($language === $fallbackSourceLanguage) {
  145. $mainQuery->union($this->createFallbackQuery($category, $language, $fallbackSourceLanguage), true);
  146. }
  147. $messages = $mainQuery->createCommand($this->db)->queryAll();
  148. return ArrayHelper::map($messages, 'message', 'translation');
  149. }
  150. /**
  151. * The method builds the [[Query]] object for the fallback language messages search.
  152. * Normally is called from [[loadMessagesFromDb]].
  153. *
  154. * @param string $category the message category
  155. * @param string $language the originally requested language
  156. * @param string $fallbackLanguage the target fallback language
  157. * @return Query
  158. * @see loadMessagesFromDb
  159. * @since 2.0.7
  160. */
  161. protected function createFallbackQuery($category, $language, $fallbackLanguage)
  162. {
  163. return (new Query())->select(['message' => 't1.message', 'translation' => 't2.translation'])
  164. ->from(['t1' => $this->sourceMessageTable, 't2' => $this->messageTable])
  165. ->where([
  166. 't1.id' => new Expression('[[t2.id]]'),
  167. 't1.category' => $category,
  168. 't2.language' => $fallbackLanguage,
  169. ])->andWhere([
  170. 'NOT IN', 't2.id', (new Query())->select('[[id]]')->from($this->messageTable)->where(['language' => $language]),
  171. ]);
  172. }
  173. }