Passed
Pull Request — master (#4)
by
unknown
10:40
created

Translatable::qualifyColumn()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Laraplus\Data;
4
5
use Illuminate\Database\Eloquent\Relations\HasMany;
6
7
trait Translatable
8
{
9
    protected $overrideLocale = null;
10
11
    protected $overrideFallbackLocale = null;
12
13
    protected $overrideOnlyTranslated = null;
14
15
    protected $overrideWithFallback = null;
16
17
    protected $localeChanged = false;
18
19
    /**
20
     * Translated attributes cache.
21
     *
22
     * @var array
23
     */
24
    protected static $i18nAttributes = [];
25
26
    /**
27
     * Boot the trait.
28
     */
29
    public static function bootTranslatable()
30
    {
31
        static::addGlobalScope(new TranslatableScope);
32
    }
33
34
    /**
35
     * Save a new model and return the instance.
36
     *
37
     * @param array $attributes
38
     * @param array|string $translations
39
     *
40
     * @return static
41
     */
42
    public static function create(array $attributes = [], $translations = [])
43
    {
44
        $model = new static($attributes);
0 ignored issues
show
Unused Code introduced by
The call to Laraplus\Data\Translatable::__construct() has too many arguments starting with $attributes. ( Ignorable by Annotation )

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

44
        $model = /** @scrutinizer ignore-call */ new static($attributes);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
45
46
        if ($model->save() && is_array($translations)) {
0 ignored issues
show
Bug introduced by
It seems like save() 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

46
        if ($model->/** @scrutinizer ignore-call */ save() && is_array($translations)) {
Loading history...
47
            $model->saveTranslations($translations);
48
        }
49
50
        return $model;
51
    }
52
53
    /**
54
     * Save a new model in provided locale and return the instance.
55
     *
56
     * @param string $locale
57
     * @param array $attributes
58
     * @param array|string $translations
59
     *
60
     * @return Translatable|string
61
     */
62
    public static function createInLocale($locale, array $attributes = [], $translations = [])
63
    {
64
        $model = (new static($attributes))->setLocale($locale);
0 ignored issues
show
Unused Code introduced by
The call to Laraplus\Data\Translatable::__construct() has too many arguments starting with $attributes. ( Ignorable by Annotation )

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

64
        $model = (/** @scrutinizer ignore-call */ new static($attributes))->setLocale($locale);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
65
66
        if ($model->save() && is_array($translations)) {
67
            $model->saveTranslations($translations);
68
        }
69
70
        return $model;
71
    }
72
73
    /**
74
     * Save a new model and return the instance. Allow mass-assignment.
75
     *
76
     * @param array $attributes
77
     * @param array|string $translations
78
     *
79
     * @return static
80
     */
81
    public static function forceCreate(array $attributes, $translations = [])
82
    {
83
        $model = new static;
84
85
        return static::unguarded(function () use ($model, $attributes, $translations) {
86
            return $model->create($attributes, $translations);
87
        });
88
    }
89
90
    /**
91
     * Save a new model in provided locale and return the instance. Allow mass-assignment.
92
     *
93
     * @param array $attributes
94
     * @param array|string $translations
95
     *
96
     * @return static
97
     */
98
    public static function forceCreateInLocale($locale, array $attributes, $translations = [])
99
    {
100
        $model = new static;
101
102
        return static::unguarded(function () use ($locale, $model, $attributes, $translations) {
103
            return $model->createInLocale($locale, $attributes, $translations);
104
        });
105
    }
106
107
    /**
108
     * Reload a fresh model instance from the database.
109
     *
110
     * @param array|string $with
111
     *
112
     * @return static|null
113
     */
114
    public function fresh($with = [])
115
    {
116
        if (! $this->exists) {
117
            return null;
118
        }
119
120
        $query = static::newQueryWithoutScopes()
121
            ->with(is_string($with) ? func_get_args() : $with)
122
            ->where($this->getKeyName(), $this->getKey());
0 ignored issues
show
Bug introduced by
It seems like getKey() 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

122
            ->where($this->getKeyName(), $this->/** @scrutinizer ignore-call */ getKey());
Loading history...
Bug introduced by
It seems like getKeyName() 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

122
            ->where($this->/** @scrutinizer ignore-call */ getKeyName(), $this->getKey());
Loading history...
123
124
        (new TranslatableScope)->apply($query, $this);
0 ignored issues
show
Bug introduced by
$this of type Laraplus\Data\Translatable is incompatible with the type Illuminate\Database\Eloquent\Model expected by parameter $model of Laraplus\Data\TranslatableScope::apply(). ( Ignorable by Annotation )

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

124
        (new TranslatableScope)->apply($query, /** @scrutinizer ignore-type */ $this);
Loading history...
125
126
        return $query->first();
127
    }
128
129
    /**
130
     * Save the translations.
131
     *
132
     * @param array $translations
133
     *
134
     * @return bool
135
     */
136
    public function saveTranslations(array $translations)
137
    {
138
        $success = true;
139
        $fresh = parent::fresh();
140
141
        foreach ($translations as $locale => $attributes) {
142
            $model = clone $fresh;
143
144
            $model->setLocale($locale);
145
            $model->fill($attributes);
146
147
            $success &= $model->save();
148
        }
149
150
        return $success;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $success also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
151
    }
152
153
    /**
154
     * Force saving the translations.
155
     *
156
     * @param array $translations
157
     *
158
     * @return bool
159
     */
160
    public function forceSaveTranslations(array $translations)
161
    {
162
        return static::unguarded(function () use ($translations) {
163
            return $this->saveTranslations($translations);
164
        });
165
    }
166
167
    /**
168
     * Save the translation.
169
     *
170
     * @param $locale
171
     * @param array $attributes
172
     *
173
     * @return bool
174
     */
175
    public function saveTranslation($locale, array $attributes)
176
    {
177
        return $this->saveTranslations([
178
            $locale => $attributes,
179
        ]);
180
    }
181
182
    /**
183
     * Force saving the translation.
184
     *
185
     * @param $locale
186
     * @param array $attributes
187
     *
188
     * @return bool
189
     */
190
    public function forceSaveTranslation($locale, array $attributes)
191
    {
192
        return static::unguarded(function () use ($locale, $attributes) {
193
            return $this->saveTranslation($locale, $attributes);
194
        });
195
    }
196
197
    /**
198
     * Populate the translations.
199
     *
200
     * @param array $attributes
201
     *
202
     * @return $this
203
     *
204
     * @throws \Illuminate\Database\Eloquent\MassAssignmentException
205
     */
206
    public function fill(array $attributes)
207
    {
208
        if (! isset(static::$i18nAttributes[$this->getTable()])) {
0 ignored issues
show
Bug introduced by
The method getTable() does not exist on Laraplus\Data\Translatable. Did you maybe mean getI18nTable()? ( Ignorable by Annotation )

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

208
        if (! isset(static::$i18nAttributes[$this->/** @scrutinizer ignore-call */ getTable()])) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
209
            $this->initTranslatableAttributes();
210
        }
211
212
        return parent::fill($attributes);
213
    }
214
215
    /**
216
     * Initialize translatable attributes.
217
     */
218
    protected function initTranslatableAttributes()
219
    {
220
        if (property_exists($this, 'translatable')) {
221
            $attributes = $this->translatable;
222
        } else {
223
            $attributes = $this->getTranslatableAttributesFromSchema();
224
        }
225
226
        static::$i18nAttributes[$this->getTable()] = $attributes;
227
    }
228
229
    /**
230
     * Return an array of translatable attributes from schema.
231
     *
232
     * @return array
233
     */
234
    protected function getTranslatableAttributesFromSchema()
235
    {
236
        if ((! $con = $this->getConnection()) || (! $builder = $con->getSchemaBuilder())) {
0 ignored issues
show
Bug introduced by
It seems like getConnection() 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

236
        if ((! $con = $this->/** @scrutinizer ignore-call */ getConnection()) || (! $builder = $con->getSchemaBuilder())) {
Loading history...
237
            return [];
238
        }
239
240
        if ($columns = TranslatableConfig::cacheGet($this->getI18nTable())) {
241
            return $columns;
242
        }
243
244
        $columns = $builder->getColumnListing($this->getI18nTable());
245
246
        unset($columns[array_search($this->getForeignKey(), $columns)]);
0 ignored issues
show
Bug introduced by
It seems like getForeignKey() 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

246
        unset($columns[array_search($this->/** @scrutinizer ignore-call */ getForeignKey(), $columns)]);
Loading history...
247
248
        TranslatableConfig::cacheSet($this->getI18nTable(), $columns);
249
250
        return $columns;
251
    }
252
253
    /**
254
     * Return a collection of translated attributes in a given locale.
255
     *
256
     * @param $locale
257
     * @return \Laraplus\Data\TranslationModel|null
258
     */
259
    public function translate($locale)
260
    {
261
        $found = $this->translations->where($this->getLocaleKey(), $locale)->first();
262
263
        if (! $found && $this->shouldFallback($locale)) {
264
            return $this->translate($this->getFallbackLocale());
265
        }
266
267
        return $found;
268
    }
269
270
    /**
271
     * Return a collection of translated attributes in a given locale or create a new one.
272
     *
273
     * @param $locale
274
     *
275
     * @return \Laraplus\Data\TranslationModel
276
     */
277
    public function translateOrNew($locale)
278
    {
279
        if (is_null($instance = $this->translate($locale))) {
280
            return $this->newModelInstance();
0 ignored issues
show
Bug introduced by
It seems like newModelInstance() 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

280
            return $this->/** @scrutinizer ignore-call */ newModelInstance();
Loading history...
281
        }
282
283
        return $instance;
284
    }
285
286
    /**
287
     * Translations relationship.
288
     *
289
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
290
     */
291
    public function translations()
292
    {
293
        $localKey = $this->getKeyName();
294
        $foreignKey = $this->getForeignKey();
295
        $instance = $this->translationModel();
296
297
        return new HasMany($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey);
0 ignored issues
show
Bug introduced by
$this of type Laraplus\Data\Translatable is incompatible with the type Illuminate\Database\Eloquent\Model expected by parameter $parent of Illuminate\Database\Eloq...\HasMany::__construct(). ( Ignorable by Annotation )

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

297
        return new HasMany($instance->newQuery(), /** @scrutinizer ignore-type */ $this, $instance->getTable().'.'.$foreignKey, $localKey);
Loading history...
298
    }
299
300
    /**
301
     * Returns the default translation model instance.
302
     *
303
     * @return TranslationModel
304
     */
305
    public function translationModel()
306
    {
307
        $translation = new TranslationModel;
308
309
        $translation->setConnection($this->getI18nConnection());
310
        $translation->setTable($this->getI18nTable());
311
        $translation->setKeyName($this->getForeignKey());
312
        $translation->setLocaleKey($this->getLocaleKey());
313
314
        if ($attributes = $this->translatableAttributes()) {
315
            $translation->fillable(array_intersect($attributes, $this->getFillable()));
0 ignored issues
show
Bug introduced by
It seems like getFillable() 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

315
            $translation->fillable(array_intersect($attributes, $this->/** @scrutinizer ignore-call */ getFillable()));
Loading history...
316
        }
317
318
        return $translation;
319
    }
320
321
    /**
322
     * Return an array of translatable attributes.
323
     *
324
     * @return array
325
     */
326
    public function translatableAttributes()
327
    {
328
        if (! isset(static::$i18nAttributes[$this->getTable()])) {
329
            return [];
330
        }
331
332
        return static::$i18nAttributes[$this->getTable()];
333
    }
334
335
    /**
336
     * Return the name of the locale key.
337
     *
338
     * @return string
339
     */
340
    public function getLocaleKey()
341
    {
342
        return TranslatableConfig::dbKey();
343
    }
344
345
    /**
346
     * Set the current locale.
347
     *
348
     * @param $locale
349
     *
350
     * @return string
351
     */
352
    public function setLocale($locale)
353
    {
354
        $this->overrideLocale = $locale;
355
        $this->localeChanged = true;
356
357
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Laraplus\Data\Translatable which is incompatible with the documented return type string.
Loading history...
358
    }
359
360
    /**
361
     * Return the current locale.
362
     *
363
     * @return string
364
     */
365
    public function getLocale()
366
    {
367
        if ($this->overrideLocale) {
368
            return $this->overrideLocale;
369
        }
370
371
        if (property_exists($this, 'locale')) {
372
            return $this->locale;
373
        }
374
375
        return TranslatableConfig::currentLocale();
376
    }
377
378
    /**
379
     * Set the fallback locale.
380
     *
381
     * @param $locale
382
     *
383
     * @return string
384
     */
385
    public function setFallbackLocale($locale)
386
    {
387
        $this->overrideFallbackLocale = $locale;
388
389
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Laraplus\Data\Translatable which is incompatible with the documented return type string.
Loading history...
390
    }
391
392
    /**
393
     * Return the fallback locale.
394
     *
395
     * @return string
396
     */
397
    public function getFallbackLocale()
398
    {
399
        if ($this->overrideFallbackLocale) {
400
            return $this->overrideFallbackLocale;
401
        }
402
403
        if (property_exists($this, 'fallbackLocale')) {
404
            return $this->fallbackLocale;
405
        }
406
407
        return TranslatableConfig::fallbackLocale();
408
    }
409
410
    /**
411
     * Set if model should select only translated rows.
412
     *
413
     * @param bool $onlyTranslated
414
     *
415
     * @return $this
416
     */
417
    public function setOnlyTranslated($onlyTranslated)
418
    {
419
        $this->overrideOnlyTranslated = $onlyTranslated;
420
421
        return $this;
422
    }
423
424
    /**
425
     * Return only translated rows.
426
     *
427
     * @return bool
428
     */
429
    public function getOnlyTranslated()
430
    {
431
        if (! is_null($this->overrideOnlyTranslated)) {
432
            return $this->overrideOnlyTranslated;
433
        }
434
435
        if (property_exists($this, 'onlyTranslated')) {
436
            return $this->onlyTranslated;
437
        }
438
439
        return TranslatableConfig::onlyTranslated();
440
    }
441
442
    /**
443
     * Set if model should select only translated rows.
444
     *
445
     * @param bool $withFallback
446
     *
447
     * @return $this
448
     */
449
    public function setWithFallback($withFallback)
450
    {
451
        $this->overrideWithFallback = $withFallback;
452
453
        return $this;
454
    }
455
456
    /**
457
     * Return current locale with fallback.
458
     *
459
     * @return bool
460
     */
461
    public function getWithFallback()
462
    {
463
        if (! is_null($this->overrideWithFallback)) {
464
            return $this->overrideWithFallback;
465
        }
466
467
        if (property_exists($this, 'withFallback')) {
468
            return $this->withFallback;
469
        }
470
471
        return TranslatableConfig::withFallback();
472
    }
473
474
    /**
475
     * Return the i18n connection name associated with the model.
476
     *
477
     * @return string
478
     */
479
    public function getI18nConnection()
480
    {
481
        return $this->getConnectionName();
0 ignored issues
show
Bug introduced by
It seems like getConnectionName() 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

481
        return $this->/** @scrutinizer ignore-call */ getConnectionName();
Loading history...
482
    }
483
484
    /**
485
     * Return the i18n table associated with the model.
486
     *
487
     * @return string
488
     */
489
    public function getI18nTable()
490
    {
491
        return $this->getTable().$this->getTranslationTableSuffix();
492
    }
493
494
    /**
495
     * Return the i18n table suffix.
496
     *
497
     * @return string
498
     */
499
    public function getTranslationTableSuffix()
500
    {
501
        return TranslatableConfig::dbSuffix();
502
    }
503
504
    /**
505
     * Should fallback to a primary translation.
506
     *
507
     * @param string|null $locale
508
     *
509
     * @return bool
510
     */
511
    public function shouldFallback($locale = null)
512
    {
513
        if (! $this->getWithFallback() || ! $this->getFallbackLocale()) {
514
            return false;
515
        }
516
517
        $locale = $locale ?: $this->getLocale();
518
519
        return $locale != $this->getFallbackLocale();
520
    }
521
522
    /**
523
     * Create a new Eloquent query builder for the model.
524
     *
525
     * @param \Illuminate\Database\Query\Builder $query
526
     *
527
     * @return \Illuminate\Database\Eloquent\Builder|static
528
     */
529
    public function newEloquentBuilder($query)
530
    {
531
        return new Builder($query);
532
    }
533
534
    /**
535
     * Return a new query builder instance for the connection.
536
     *
537
     * @return \Illuminate\Database\Query\Builder
538
     */
539
    protected function newBaseQueryBuilder()
540
    {
541
        $conn = $this->getConnection();
542
        $grammar = $conn->getQueryGrammar();
543
        $builder = new QueryBuilder($conn, $grammar, $conn->getPostProcessor());
544
545
        return $builder->setModel($this);
0 ignored issues
show
Bug introduced by
$this of type Laraplus\Data\Translatable is incompatible with the type Illuminate\Database\Eloquent\Model expected by parameter $model of Laraplus\Data\QueryBuilder::setModel(). ( Ignorable by Annotation )

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

545
        return $builder->setModel(/** @scrutinizer ignore-type */ $this);
Loading history...
546
    }
547
548
    /**
549
     * Return the attributes that have been changed since the last sync.
550
     *
551
     * @return array
552
     */
553
    public function getDirty()
554
    {
555
        $dirty = parent::getDirty();
556
557
        if (! $this->localeChanged) {
558
            return $dirty;
559
        }
560
561
        foreach ($this->translatableAttributes() as $key) {
562
            if (isset($this->attributes[$key])) {
563
                $dirty[$key] = $this->attributes[$key];
564
            }
565
        }
566
567
        return $dirty;
568
    }
569
570
    /**
571
     * Sync the original attributes with the current.
572
     *
573
     * @return $this
574
     */
575
    public function syncOriginal()
576
    {
577
        $this->localeChanged = false;
578
579
        return parent::syncOriginal();
580
    }
581
582
    /**
583
     * Prefix column names with translation table instead of model table
584
     * if the given column is translated.
585
     * @param $column
586
     * @return string
587
     */
588
    public function qualifyColumn($column)
589
    {
590
        if (\in_array($column, $this->translatableAttributes(), true)) {
591
            return sprintf('%s.%s', $this->getI18nTable(), $column);
592
        }
593
        return parent::qualifyColumn($column);
594
    }
595
596
}
597