Issues (35)

src/models/MultilanguageTrait.php (4 issues)

1
<?php
2
3
namespace Itstructure\AdminModule\models;
4
5
use yii\db\ActiveQuery;
6
use Itstructure\AdminModule\components\MultilanguageMigration;
7
8
/**
9
 * Trait MultilanguageTrait
10
 *
11
 * @property \yii\db\ActiveRecord $this
12
 * @property Language[] $translateList list of related translated records.
13
 * @property array $storage Container for temporary storage of translation data.
14
 *
15
 * @package Itstructure\AdminModule\models
16
 *
17
 * @author Andrey Girnik <[email protected]>
18
 */
19
trait MultilanguageTrait
20
{
21
    /**
22
     * Container for temporary storage of translation data.
23
     *
24
     * @var array
25
     */
26
    private $storage = [];
27
28
    /**
29
     * Return key name of relation between main table and translations table.
30
     *
31
     * @return string
32
     */
33
    public static function getKeyToMainModel()
34
    {
35
        return static::tableName() . '_id';
36
    }
37
38
    /**
39
     * Return translations table name.
40
     *
41
     * @return string
42
     */
43
    public static function getTranslateTablelName()
44
    {
45
        return static::tableName() . '_' . MultilanguageMigration::TRANSLATE_TABLE_POSTFIX;
46
    }
47
48
    /**
49
     * Return related translate model name.
50
     *
51
     * @return string
52
     */
53
    public static function getTranslateModelName()
54
    {
55
        $class = new \ReflectionClass(static::class);
56
        return $class->getNamespaceName() . '\\' . $class->getShortName() .
57
            ucfirst(MultilanguageMigration::TRANSLATE_TABLE_POSTFIX);
58
    }
59
60
    /**
61
     * Return related translated records.
62
     *
63
     * @return ActiveQuery
64
     */
65
    public function getTranslateList()
66
    {
67
        return $this->hasMany(static::getTranslateModelName(), [
0 ignored issues
show
It seems like hasMany() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

67
        return $this->/** @scrutinizer ignore-call */ hasMany(static::getTranslateModelName(), [
Loading history...
68
            static::getKeyToMainModel() => 'id',
69
        ]);
70
    }
71
72
    /**
73
     * Override model magic getter. Return translate for field.
74
     * Example: if we try $model->title_en, we will get 'title' in english.
75
     *
76
     * @param string $name field name.
77
     *
78
     * @return mixed|null
79
     */
80
    public function __get($name)
81
    {
82
        if (false === $this->isMultiLanguageField($name)) {
83
            return parent::__get($name);
84
        }
85
86
        $nameArray = explode('_', $name);
87
        $lang = array_pop($nameArray);
88
        $field = implode('_', $nameArray);
89
90
        foreach ($this->translateList as $translate) {
91
            if ($translate->language->shortName === $lang) {
0 ignored issues
show
Bug Best Practice introduced by
The property language does not exist on Itstructure\AdminModule\models\Language. Since you implemented __get, consider adding a @property annotation.
Loading history...
92
                return $translate->{$field};
93
            }
94
        }
95
96
        return null;
97
    }
98
99
    /**
100
     * Override model magic setter. Set translation for the field.
101
     * For example $model->title_en  will save title field in translate model where
102
     * language_id => record in language with 'en' locale.
103
     *
104
     * @param string $name  name of field.
105
     * @param mixed  $value value to be stored in field.
106
     *
107
     * @return void
108
     */
109
    public function __set($name, $value)
110
    {
111
        if (false === $this->isMultiLanguageField($name)) {
112
            parent::__set($name, $value);
113
            return;
114
        }
115
116
        $nameArray = explode('_', $name);
117
        $lang = array_pop($nameArray);
118
        $field = implode('_', $nameArray);
119
120
        $this->storage[$lang][$field] = $value;
121
    }
122
123
    /**
124
     * Override model method to save all translations after main model saved.
125
     *
126
     * @return void
127
     */
128
    public function afterSave($insert, $changedAttributes)
129
    {
130
        parent::afterSave($insert, $changedAttributes);
131
132
        foreach ($this->storage as $lang => $fields) {
133
134
            foreach ($fields as $field => $value){
135
136
                $langModel = $this->findOrCreateTranslateModel($lang);
137
                $langModel->{$field} = $value;
138
                $langModel->save();
139
            }
140
        }
141
    }
142
143
    /**
144
     * Returns default translate. If field name is given, we can take an alternative
145
     * translate when default translate value is empty.
146
     *
147
     * @param string $field
148
     *
149
     * @return mixed
150
     */
151
    public function getDefaultTranslate($field = null)
152
    {
153
        $mainRequest = $this->hasOne(static::getTranslateModelName(),
0 ignored issues
show
It seems like hasOne() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

153
        /** @scrutinizer ignore-call */ 
154
        $mainRequest = $this->hasOne(static::getTranslateModelName(),
Loading history...
154
            [
155
                static::getKeyToMainModel() => 'id'
156
            ]
157
        );
158
159
        $defaultTranslate = $mainRequest->andWhere([
160
            MultilanguageMigration::getKeyToLanguageTable() => Language::findOne([
161
                'default' => 1
162
            ])->id
163
        ]);
164
165
        if ($field !== null &&
166
            $defaultTranslate->andWhere([
167
                '!=', $field, ''
168
            ])->count() == 0) {
169
170
            $result = $mainRequest->where([
171
                '!=', $field, ''
172
            ])->one();
173
174
            return $result == null ? '-' : $result->{$field};
175
        }
176
177
        return $field === null ? $defaultTranslate->one() : $defaultTranslate->one()->{$field};
178
    }
179
180
    /**
181
     * Check for multi-language mode of field.
182
     *
183
     * @param string $name name of field to be checked.
184
     *
185
     * @return boolean
186
     */
187
    private function isMultiLanguageField($name): bool
188
    {
189
        if (false === strpos($name, '_')) {
190
            return false;
191
        }
192
193
        $nameArray = explode('_', $name);
194
        $lang = array_pop($nameArray);
195
196
        if (null === $lang) {
197
            return false;
198
        }
199
200
        if (false === in_array($lang, Language::getShortLanguageList(), true)) {
201
            return false;
202
        }
203
204
        return true;
205
    }
206
207
    /**
208
     * Find or create related model with translates.
209
     *
210
     * @param string $lang language short name.
211
     *
212
     * @return mixed
213
     */
214
    private function findOrCreateTranslateModel($lang)
215
    {
216
        $language = Language::findOne([
217
            'shortName' => $lang
218
        ]);
219
220
        $translateModel = call_user_func([
221
            static::getTranslateModelName(),
222
            'find',
223
        ]);
224
        $translateModel = $translateModel->where([
225
            static::getKeyToMainModel() => $this->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on Itstructure\AdminModule\models\MultilanguageTrait. Since you implemented __get, consider adding a @property annotation.
Loading history...
226
        ])->andWhere([
227
            MultilanguageMigration::getKeyToLanguageTable() => $language->id,
228
        ])->one();
229
230
        if (null === $translateModel) {
231
            $translateModelName = static::getTranslateModelName();
232
            $translateModel = new $translateModelName;
233
            $translateModel->{static::getKeyToMainModel()} = $this->id;
234
            $translateModel->{MultilanguageMigration::getKeyToLanguageTable()} = $language->id;
235
        }
236
237
        return $translateModel;
238
    }
239
}
240