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