Completed
Push — master ( 7f45a1...6afdf1 )
by Andrey
01:18
created

MultilanguageTrait::afterSave()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
cc 3
nc 3
nop 2
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
Bug introduced by
It seems like hasMany() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

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) {
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
Bug introduced by
It seems like hasOne() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

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
Documentation introduced by
The property id does not exist on object<Itstructure\Admin...els\MultilanguageTrait>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

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;
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<Itstructure\Admin...els\MultilanguageTrait>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
234
            $translateModel->{MultilanguageMigration::getKeyToLanguageTable()} = $language->id;
235
        }
236
237
        return $translateModel;
238
    }
239
}
240