Completed
Push — master ( 4964bc...b068c9 )
by Freek
01:27
created

HasTags::tagsTranslated()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
3
namespace Spatie\Tags;
4
5
use InvalidArgumentException;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Database\Eloquent\Builder;
8
use Illuminate\Database\Eloquent\Collection;
9
use Illuminate\Database\Eloquent\Relations\MorphToMany;
10
11
trait HasTags
12
{
13
    protected $queuedTags = [];
14
15
    public static function getTagClassName(): string
16
    {
17
        return Tag::class;
18
    }
19
20
    public static function bootHasTags()
21
    {
22
        static::created(function (Model $taggableModel) {
23
            $taggableModel->attachTags($taggableModel->queuedTags);
24
25
            $taggableModel->queuedTags = [];
26
        });
27
28
        static::deleted(function (Model $deletedModel) {
29
            $tags = $deletedModel->tags()->get();
30
31
            $deletedModel->detachTags($tags);
32
        });
33
    }
34
35
    public function tags(): MorphToMany
36
    {
37
        return $this
0 ignored issues
show
Bug introduced by
It seems like morphToMany() 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...
38
            ->morphToMany(self::getTagClassName(), 'taggable')
39
            ->orderBy('order_column');
40
    }
41
42
    /**
43
     * @param string $locale
44
     */
45
    public function tagsTranslated($locale = null): MorphToMany
46
    {
47
        $locale = ! is_null($locale) ? $locale : app()->getLocale();
48
49
        return $this
0 ignored issues
show
Bug introduced by
It seems like morphToMany() 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...
50
            ->morphToMany(self::getTagClassName(), 'taggable')
51
            ->select('*')
52
            ->selectRaw("JSON_UNQUOTE(JSON_EXTRACT(name, '$.\"{$locale}\"')) as name_translated")
53
            ->selectRaw("JSON_UNQUOTE(JSON_EXTRACT(slug, '$.\"{$locale}\"')) as slug_translated")
54
            ->orderBy('order_column');
55
    }
56
57
    /**
58
     * @param string|array|\ArrayAccess|\Spatie\Tags\Tag $tags
59
     */
60
    public function setTagsAttribute($tags)
61
    {
62
        if (! $this->exists) {
0 ignored issues
show
Bug introduced by
The property exists 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...
63
            $this->queuedTags = $tags;
64
65
            return;
66
        }
67
68
        $this->attachTags($tags);
69
    }
70
71
    /**
72
     * @param \Illuminate\Database\Eloquent\Builder $query
73
     * @param array|\ArrayAccess|\Spatie\Tags\Tag $tags
74
     *
75
     * @return \Illuminate\Database\Eloquent\Builder
76
     */
77 View Code Duplication
    public function scopeWithAllTags(Builder $query, $tags, string $type = null): Builder
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
78
    {
79
        $tags = static::convertToTags($tags, $type);
80
81
        collect($tags)->each(function ($tag) use ($query) {
82
            $query->whereIn("{$this->getTable()}.{$this->getKeyName()}", function ($query) use ($tag) {
0 ignored issues
show
Bug introduced by
It seems like getTable() 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...
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?

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...
83
                $query->from('taggables')
84
                    ->select('taggables.taggable_id')
85
                    ->where('taggables.tag_id', $tag ? $tag->id : 0);
86
            });
87
        });
88
89
        return $query;
90
    }
91
92
    /**
93
     * @param \Illuminate\Database\Eloquent\Builder $query
94
     * @param array|\ArrayAccess|\Spatie\Tags\Tag $tags
95
     *
96
     * @return \Illuminate\Database\Eloquent\Builder
97
     */
98 View Code Duplication
    public function scopeWithAnyTags(Builder $query, $tags, string $type = null): Builder
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
99
    {
100
        $tags = static::convertToTags($tags, $type);
101
102
        return $query->whereHas('tags', function (Builder $query) use ($tags) {
103
            $tagIds = collect($tags)->pluck('id');
104
105
            $query->whereIn('tags.id', $tagIds);
106
        });
107
    }
108
109 View Code Duplication
    public function scopeWithAllTagsOfAnyType(Builder $query, $tags): Builder
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
110
    {
111
        $tags = static::convertToTagsOfAnyType($tags);
112
113
        collect($tags)->each(function ($tag) use ($query) {
114
            $query->whereIn("{$this->getTable()}.{$this->getKeyName()}", function ($query) use ($tag) {
0 ignored issues
show
Bug introduced by
It seems like getTable() 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...
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?

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...
115
                $query->from('taggables')
116
                    ->select('taggables.taggable_id')
117
                    ->where('taggables.tag_id', $tag ? $tag->id : 0);
118
            });
119
        });
120
121
        return $query;
122
    }
123
124 View Code Duplication
    public function scopeWithAnyTagsOfAnyType(Builder $query, $tags): Builder
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
125
    {
126
        $tags = static::convertToTagsOfAnyType($tags);
127
128
        return $query->whereHas('tags', function (Builder $query) use ($tags) {
129
            $tagIds = collect($tags)->pluck('id');
130
131
            $query->whereIn('tags.id', $tagIds);
132
        });
133
    }
134
135
    public function tagsWithType(string $type = null): Collection
136
    {
137
        return $this->tags->filter(function (Tag $tag) use ($type) {
0 ignored issues
show
Bug introduced by
The property tags 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...
138
            return $tag->type === $type;
139
        });
140
    }
141
142
    /**
143
     * @param array|\ArrayAccess|\Spatie\Tags\Tag $tags
144
     *
145
     * @return $this
146
     */
147 View Code Duplication
    public function attachTags($tags)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
148
    {
149
        $className = static::getTagClassName();
150
151
        $tags = collect($className::findOrCreate($tags));
152
153
        $this->tags()->syncWithoutDetaching($tags->pluck('id')->toArray());
154
155
        return $this;
156
    }
157
158
    /**
159
     * @param string|\Spatie\Tags\Tag $tag
160
     *
161
     * @return $this
162
     */
163
    public function attachTag($tag)
164
    {
165
        return $this->attachTags([$tag]);
166
    }
167
168
    /**
169
     * @param array|\ArrayAccess $tags
170
     *
171
     * @return $this
172
     */
173
    public function detachTags($tags)
174
    {
175
        $tags = static::convertToTags($tags);
176
177
        collect($tags)
178
            ->filter()
179
            ->each(function (Tag $tag) {
180
                $this->tags()->detach($tag);
181
            });
182
183
        return $this;
184
    }
185
186
    /**
187
     * @param string|\Spatie\Tags\Tag $tag
188
     *
189
     * @return $this
190
     */
191
    public function detachTag($tag)
192
    {
193
        return $this->detachTags([$tag]);
194
    }
195
196
    /**
197
     * @param array|\ArrayAccess $tags
198
     *
199
     * @return $this
200
     */
201 View Code Duplication
    public function syncTags($tags)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
202
    {
203
        $className = static::getTagClassName();
204
205
        $tags = collect($className::findOrCreate($tags));
206
207
        $this->tags()->sync($tags->pluck('id')->toArray());
208
209
        return $this;
210
    }
211
212
    /**
213
     * @param array|\ArrayAccess $tags
214
     * @param string|null $type
215
     *
216
     * @return $this
217
     */
218 View Code Duplication
    public function syncTagsWithType($tags, string $type = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
219
    {
220
        $className = static::getTagClassName();
221
222
        $tags = collect($className::findOrCreate($tags, $type));
223
224
        $this->syncTagIds($tags->pluck('id')->toArray(), $type);
225
226
        return $this;
227
    }
228
229
    protected static function convertToTags($values, $type = null, $locale = null)
230
    {
231
        return collect($values)->map(function ($value) use ($type, $locale) {
232
            if ($value instanceof Tag) {
233
                if (isset($type) && $value->type != $type) {
234
                    throw new InvalidArgumentException("Type was set to {$type} but tag is of type {$value->type}");
235
                }
236
237
                return $value;
238
            }
239
240
            $className = static::getTagClassName();
241
242
            return $className::findFromString($value, $type, $locale);
243
        });
244
    }
245
246
    protected static function convertToTagsOfAnyType($values, $locale = null)
247
    {
248
        return collect($values)->map(function ($value) use ($locale) {
249
            if ($value instanceof Tag) {
250
                return $value;
251
            }
252
253
            $className = static::getTagClassName();
254
255
            return $className::findFromStringOfAnyType($value, $locale);
256
        });
257
    }
258
259
    /**
260
     * Use in place of eloquent's sync() method so that the tag type may be optionally specified.
261
     *
262
     * @param $ids
263
     * @param string|null $type
264
     * @param bool $detaching
265
     */
266
    protected function syncTagIds($ids, string $type = null, $detaching = true)
267
    {
268
        $isUpdated = false;
269
270
        // Get a list of tag_ids for all current tags
271
        $current = $this->tags()
272
            ->newPivotStatement()
273
            ->where('taggable_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...
274
            ->where('taggable_type', $this->getMorphClass())
0 ignored issues
show
Bug introduced by
It seems like getMorphClass() 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...
275
            ->when($type !== null, function ($query) use ($type) {
276
                $tagModel = $this->tags()->getRelated();
277
278
                return $query->join(
279
                    $tagModel->getTable(),
280
                    'taggables.tag_id',
281
                    '=',
282
                    $tagModel->getTable().'.'.$tagModel->getKeyName()
283
                )
284
                    ->where('tags.type', $type);
285
            })
286
            ->pluck('tag_id')
287
            ->all();
288
289
        // Compare to the list of ids given to find the tags to remove
290
        $detach = array_diff($current, $ids);
291
        if ($detaching && count($detach) > 0) {
292
            $this->tags()->detach($detach);
293
            $isUpdated = true;
294
        }
295
296
        // Attach any new ids
297
        $attach = array_diff($ids, $current);
298
        if (count($attach) > 0) {
299
            collect($attach)->each(function ($id) {
300
                $this->tags()->attach($id, []);
301
            });
302
            $isUpdated = true;
303
        }
304
305
        // Once we have finished attaching or detaching the records, we will see if we
306
        // have done any attaching or detaching, and if we have we will touch these
307
        // relationships if they are configured to touch on any database updates.
308
        if ($isUpdated) {
309
            $this->tags()->touchIfTouching();
310
        }
311
    }
312
}
313