Completed
Pull Request — master (#6)
by Mikaël
10:07
created

Translatable::translatingRelation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace BBSLab\NovaTranslation\Models\Traits;
4
5
use BBSLab\NovaTranslation\Models\Locale;
6
use BBSLab\NovaTranslation\Models\Observers\TranslatableObserver;
7
use BBSLab\NovaTranslation\Models\Relations\BelongsToMany;
8
use BBSLab\NovaTranslation\Models\Translation;
9
use BBSLab\NovaTranslation\Models\TranslationRelation;
10
use Illuminate\Database\Eloquent\Builder;
11
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
12
use Illuminate\Database\Eloquent\Model;
13
use Illuminate\Database\Eloquent\Relations\MorphOne;
14
use Illuminate\Database\Query\JoinClause;
15
16
/**
17
 * @property \BBSLab\NovaTranslation\Models\Translation $translation
18
 * @property \Illuminate\Database\Eloquent\Collection|\BBSLab\NovaTranslation\Models\Translation[] $translations
19
 * @method static \Illuminate\Database\Eloquent\Builder locale(?string $iso = null)
20
 */
21
trait Translatable
22
{
23
    protected $_deleting_translation = false;
24
    protected $_translating_relation = false;
25
26
    /**
27
     * {@inheritdoc}
28
     */
29
    public static function bootTranslatable()
30
    {
31
        static::observe(TranslatableObserver::class);
32
    }
33
34
    /**
35
     * Get the list of non translatable fields.
36
     *
37
     * @return array
38
     */
39
    public function getNonTranslatable(): array
40
    {
41
        return isset($this->nonTranslatable) ? $this->nonTranslatable : [];
0 ignored issues
show
Bug introduced by
The property nonTranslatable does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
42
    }
43
44
    /**
45
     * Get the list of fields to duplicate on create.
46
     * (Other fields MUST BE nullable in database).
47
     *
48
     * @return array
49
     */
50
    public function getOnCreateTranslatable(): array
51
    {
52
        return isset($this->onCreateTranslatable) ? $this->onCreateTranslatable : $this->getNonTranslatable();
0 ignored issues
show
Bug introduced by
The property onCreateTranslatable does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
53
    }
54
55
    /**
56
     * Translation relationship.
57
     *
58
     * @return \Illuminate\Database\Eloquent\Relations\MorphOne
59
     */
60
    public function translation(): MorphOne
61
    {
62
        return $this->morphOne(Translation::class, 'translatable');
0 ignored issues
show
Bug introduced by
It seems like morphOne() 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...
63
    }
64
65
    /**
66
     * Return current item translations.
67
     *
68
     * @return \BBSLab\NovaTranslation\Models\TranslationRelation
69
     */
70
    public function translations(): TranslationRelation
71
    {
72
        return new TranslationRelation($this);
73
    }
74
75
    /**
76
     * Create and return a translation entry for given locale ID.
77
     *
78
     * @param  int  $localeId
79
     * @param  int  $sourceId
80
     * @param  int  $translationId
81
     * @return \BBSLab\NovaTranslation\Models\Translation
82
     */
83
    public function upsertTranslationEntry(int $localeId, int $sourceId, int $translationId = 0): Translation
84
    {
85
        /** @var \BBSLab\NovaTranslation\Models\Translation $translation */
86
        $translation = Translation::query()
87
            ->firstOrCreate([
88
                'locale_id' => $localeId,
89
                'translation_id' => ! empty($translationId) ? $translationId : $this->freshTranslationId(),
90
                'translatable_id' => $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?

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...
91
                'translatable_type' => static::class,
92
                'translatable_source' => $sourceId,
93
            ]);
94
95
        return $translation;
96
    }
97
98
    /**
99
     * Return next fresh translation ID.
100
     *
101
     * @return int
102
     */
103
    public function freshTranslationId(): int
104
    {
105
        $lastTranslation = Translation::query()
106
            ->where('translatable_type', '=', static::class)
107
            ->max('translation_id') ?? 0;
108
109
        return $lastTranslation + 1;
110
    }
111
112
    /**
113
     * Scope a query to only retrieve items from given locale.
114
     *
115
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
116
     * @param  string  $iso
117
     * @return \Illuminate\Database\Eloquent\Builder
118
     */
119
    public function scopeLocale(EloquentBuilder $builder, string $iso = null)
120
    {
121
        return $builder->join('translations', function (JoinClause $join) {
122
            $join->on($this->getQualifiedKeyName(), '=', 'translations.translatable_id')
0 ignored issues
show
Bug introduced by
It seems like getQualifiedKeyName() 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...
123
                ->where('translations.translatable_type', '=', static::class);
124
        })
125
            ->join('locales', function (JoinClause $join) use ($iso) {
126
                $join->on('locales.id', '=', 'translations.locale_id')
127
                    ->where('locales.iso', '=', $iso ?? app()->getLocale());
128
            })
129
            ->select($this->qualifyColumn('*'));
0 ignored issues
show
Bug introduced by
It seems like qualifyColumn() 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...
130
    }
131
132
    /**
133
     * Translate model to given locale and return translated model.
134
     *
135
     * @param  \BBSLab\NovaTranslation\Models\Locale  $locale
136
     * @return \BBSLab\NovaTranslation\Models\Contracts\IsTranslatable
137
     */
138
    public function translate(Locale $locale)
139
    {
140
        /** @var \BBSLab\NovaTranslation\Models\Contracts\IsTranslatable $translated */
141
        $translated = optional(
142
            $this->translations()
0 ignored issues
show
Documentation Bug introduced by
The method where does not exist on object<BBSLab\NovaTransl...ls\TranslationRelation>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
143
                ->where('locale_id', '=', $locale->getKey())
144
                ->with('translatable')
145
                ->first()
146
        )->translatable;
147
148
        return $translated ?? static::withoutEvents(function () use ($locale) {
149
            /** @var self $translated */
150
            $translated = $this->newQuery()->create(
0 ignored issues
show
Bug introduced by
It seems like newQuery() 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...
151
                $this->only(
0 ignored issues
show
Bug introduced by
It seems like only() 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...
152
                    $this->getOnCreateTranslatable()
153
                )
154
            );
155
156
            $translated->upsertTranslationEntry(
157
                $locale->getKey(), $this->getKey(), $this->translation->translation_id
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?

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...
158
            );
159
160
            return $translated;
161
        });
162
    }
163
164
    /**
165
     * Set deleting translation state.
166
     *
167
     * @return void
168
     */
169
    public function deletingTranslation(): void
170
    {
171
        $this->_deleting_translation = true;
172
    }
173
174
    /**
175
     * Determine is the model currently in a delete translation process.
176
     *
177
     * @return bool
178
     */
179
    public function isDeletingTranslation(): bool
180
    {
181
        return $this->_deleting_translation;
182
    }
183
184
    /**
185
     * Set translating relation state.
186
     *
187
     * @return void
188
     */
189
    public function translatingRelation(): void
190
    {
191
        $this->_translating_relation = true;
192
    }
193
194
    /**
195
     * Determine is the model currently translating a relation.
196
     *
197
     * @return bool
198
     */
199
    public function isTranslatingRelation(): bool
200
    {
201
        return $this->_translating_relation;
202
    }
203
}
204