Completed
Push — master ( 31d990...56443c )
by Nicolas
07:04
created

TaggableTrait::scopeWhereTag()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 17
rs 9.4285
cc 3
eloc 9
nc 4
nop 3
1
<?php
2
3
namespace Modules\Tag\Traits;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Modules\Tag\Entities\Tag;
7
8
trait TaggableTrait
9
{
10
    /**
11
     * The Eloquent tags model name.
12
     *
13
     * @var string
14
     */
15
    protected static $tagsModel = Tag::class;
16
17
    /**
18
     * Returns the Eloquent tags model name.
19
     * @return string
20
     */
21
    public static function getTagsModel()
22
    {
23
        return static::$tagsModel;
24
    }
25
26
    /**
27
     * Sets the Eloquent tags model name.
28
     * @param string $model
29
     * @return void
30
     */
31
    public static function setTagsModel($model)
32
    {
33
        static::$tagsModel = $model;
34
    }
35
36
    /**
37
     * Get all the entities with the given tag(s)
38
     * Optionally specify the column on which
39
     * to perform the search operation.
40
     * @param  \Illuminate\Database\Eloquent\Builder $query
41
     * @param  string|array  $tags
42
     * @param  string  $type
43
     * @return \Illuminate\Database\Eloquent\Builder
44
     */
45
    public function scopeWhereTag(Builder $query, $tags, $type = 'slug')
46
    {
47
        if (is_string($tags) === true) {
48
            $tags = [$tags];
49
        }
50
        $query->with('translations');
51
52
        foreach ($tags as $tag) {
53
            $query->whereHas('tags', function ($query) use ($type, $tag) {
54
                $query->whereHas('translations', function ($query) use ($type, $tag) {
55
                    $query->where($type, $tag);
56
                });
57
            });
58
        }
59
60
        return $query;
61
    }
62
63
    /**
64
     * Get all the entities with one of the given tag(s)
65
     * Optionally specify the column on which
66
     * to perform the search operation.
67
     * @param  \Illuminate\Database\Eloquent\Builder  $query
68
     * @param  string|array $tags
69
     * @param  string $type
70
     * @return \Illuminate\Database\Eloquent\Builder
71
     */
72
    public function scopeWithTag(Builder $query, $tags, $type = 'slug')
73
    {
74
        if (is_string($tags) === true) {
75
            $tags = [$tags];
76
        }
77
        $query->with('translations');
78
79
        return $query->whereHas('tags', function ($query) use ($type, $tags) {
80
            $query->whereHas('translations', function ($query) use ($type, $tags) {
81
                $query->whereIn($type, $tags);
82
            });
83
        });
84
    }
85
86
    /**
87
     * Returns the entity Eloquent tag model object.
88
     * @return \Illuminate\Database\Eloquent\Relations\MorphToMany
89
     */
90
    public function tags()
91
    {
92
        return $this->morphToMany(static::$tagsModel, 'taggable', 'tag__tagged', 'taggable_id', 'tag_id');
93
    }
94
95
    /**
96
     * Creates a new model instance.
97
     * @return \Illuminate\Database\Eloquent\Model
98
     */
99
    public static function createTagsModel()
100
    {
101
        return new static::$tagsModel;
102
    }
103
104
    /**
105
     * Returns all the tags under the entity namespace.
106
     * @return \Illuminate\Database\Eloquent\Builder
107
     */
108
    public static function allTags()
109
    {
110
        $instance = new static;
111
112
        return $instance->createTagsModel()->with('translations')->whereNamespace($instance->getEntityClassName());
113
    }
114
115
    /**
116
     * Attaches or detaches the given tags.
117
     * @param  string|array $tags
118
     * @param  string $type
119
     * @return bool
120
     */
121
    public function setTags($tags, $type = 'slug')
122
    {
123
        // Get the current entity tags
124
        $entityTags = $this->tags->lists($type)->all();
125
126
        // Prepare the tags to be added and removed
127
        $tagsToAdd = array_diff($tags, $entityTags);
128
        $tagsToDel = array_diff($entityTags, $tags);
129
130
        // Detach the tags
131
        if (count($tagsToDel) > 0) {
132
            $this->untag($tagsToDel);
133
        }
134
135
        // Attach the tags
136
        if (count($tagsToAdd) > 0) {
137
            $this->tag($tagsToAdd);
138
        }
139
140
        return true;
141
    }
142
143
    /**
144
     * Attaches multiple tags to the entity.
145
     *
146
     * @param  string|array  $tags
147
     * @return bool
148
     */
149
    public function tag($tags)
150
    {
151
        foreach ($tags as $tag) {
0 ignored issues
show
Bug introduced by Nicolas Widart
The expression $tags of type string|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
152
            $this->addTag($tag);
153
        }
154
155
        return true;
156
    }
157
158
    /**
159
     * Attaches the given tag to the entity.
160
     * @param  string $name
161
     * @return void
162
     */
163
    public function addTag($name)
164
    {
165
        $tag = $this->createTagsModel()->where('namespace', $this->getEntityClassName())
166
            ->with('translations')
167
            ->whereHas('translations', function (Builder $q) use ($name) {
168
            $q->where('slug', $this->generateTagSlug($name));
169
        })->first();
170
171
        if ($tag === null) {
172
            $tag = new Tag([
173
                'namespace' => $this->getEntityClassName(),
174
                locale() => [
175
                    'slug' => $this->generateTagSlug($name),
176
                    'name' => $name,
177
                ],
178
            ]);
179
        }
180
        if ($tag->exists === false) {
181
            $tag->save();
182
        }
183
184
        if ($this->tags->contains($tag->id) === false) {
185
            $this->tags()->attach($tag);
186
        }
187
    }
188
189
    /**
190
     * {@inheritdoc}
191
     */
192
    public function untag($tags = null)
193
    {
194
        $tags = $tags ?: $this->tags->lists('name')->all();
195
196
        foreach ($tags as $tag) {
197
            $this->removeTag($tag);
198
        }
199
200
        return true;
201
    }
202
203
    /**
204
     * Detaches the given tag from the entity.
205
     * @param string $name
206
     * @return void
207
     */
208
    public function removeTag($name)
209
    {
210
        $tag = $this->createTagsModel()
211
            ->where('namespace', $this->getEntityClassName())
212
            ->with('translations')
213
            ->whereHas('translations', function (Builder $q) use ($name) {
214
                $q->orWhere('name', $this->generateTagSlug($name));
215
                $q->orWhere('slug', $this->generateTagSlug($name));
216
            })->first();
217
218
        if ($tag) {
219
            $this->tags()->detach($tag);
220
        }
221
    }
222
223
    /**
224
     * Returns the entity class name.
225
     *
226
     * @return string
227
     */
228
    protected function getEntityClassName()
229
    {
230
        if (isset(static::$entityNamespace)) {
231
            return static::$entityNamespace;
232
        }
233
234
        return $this->tags()->getMorphClass();
235
    }
236
237
    /**
238
     * Generate the tag slug using the given name.
239
     * @param string $name
240
     * @return string
241
     */
242
    protected function generateTagSlug($name)
243
    {
244
        return str_slug($name);
245
    }
246
}
247