Categorizable::syncCategories()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Rinvex\Categories\Traits;
6
7
use Illuminate\Support\Arr;
8
use Illuminate\Database\Eloquent\Model;
9
use Illuminate\Database\Eloquent\Builder;
10
use Illuminate\Database\Eloquent\Collection;
11
use Illuminate\Support\Collection as BaseCollection;
12
use Illuminate\Database\Eloquent\Relations\MorphToMany;
13
14
trait Categorizable
15
{
16
    /**
17
     * Register a saved model event with the dispatcher.
18
     *
19
     * @param \Closure|string $callback
20
     *
21
     * @return void
22
     */
23
    abstract public static function saved($callback);
24
25
    /**
26
     * Register a deleted model event with the dispatcher.
27
     *
28
     * @param \Closure|string $callback
29
     *
30
     * @return void
31
     */
32
    abstract public static function deleted($callback);
33
34
    /**
35
     * Define a polymorphic many-to-many relationship.
36
     *
37
     * @param string $related
38
     * @param string $name
39
     * @param string $table
0 ignored issues
show
Documentation introduced by
Should the type for parameter $table not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
40
     * @param string $foreignPivotKey
0 ignored issues
show
Documentation introduced by
Should the type for parameter $foreignPivotKey not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
41
     * @param string $relatedPivotKey
0 ignored issues
show
Documentation introduced by
Should the type for parameter $relatedPivotKey not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
42
     * @param string $parentKey
0 ignored issues
show
Documentation introduced by
Should the type for parameter $parentKey not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
43
     * @param string $relatedKey
0 ignored issues
show
Documentation introduced by
Should the type for parameter $relatedKey not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
44
     * @param bool   $inverse
45
     *
46
     * @return \Illuminate\Database\Eloquent\Relations\MorphToMany
47
     */
48
    abstract public function morphToMany(
49
        $related,
50
        $name,
51
        $table = null,
52
        $foreignPivotKey = null,
53
        $relatedPivotKey = null,
54
        $parentKey = null,
55
        $relatedKey = null,
56
        $inverse = false
57
    );
58
59
    /**
60
     * Get all attached categories to the model.
61
     *
62
     * @return \Illuminate\Database\Eloquent\Relations\MorphToMany
63
     */
64
    public function categories(): MorphToMany
65
    {
66
        return $this->morphToMany(config('rinvex.categories.models.category'), 'categorizable', config('rinvex.categories.tables.categorizables'), 'categorizable_id', 'category_id')
67
                    ->withTimestamps();
68
    }
69
70
    /**
71
     * Attach the given category(ies) to the model.
72
     *
73
     * @param int|string|array|\ArrayAccess|\Rinvex\Categories\Models\Category $categories
74
     *
75
     * @return void
76
     */
77
    public function setCategoriesAttribute($categories): void
78
    {
79
        static::saved(function (self $model) use ($categories) {
80
            $model->syncCategories($categories);
81
        });
82
    }
83
84
    /**
85
     * Boot the categorizable trait for the model.
86
     *
87
     * @return void
88
     */
89
    public static function bootCategorizable()
90
    {
91
        static::deleted(function (self $model) {
92
            $model->categories()->detach();
93
        });
94
    }
95
96
    /**
97
     * Scope query with all the given categories.
98
     *
99
     * @param \Illuminate\Database\Eloquent\Builder $builder
100
     * @param mixed                                 $categories
101
     *
102
     * @return \Illuminate\Database\Eloquent\Builder
103
     */
104
    public function scopeWithAllCategories(Builder $builder, $categories): Builder
105
    {
106
        $categories = $this->prepareCategoryIds($categories);
107
108
        collect($categories)->each(function ($category) use ($builder) {
109
            $builder->whereHas('categories', function (Builder $builder) use ($category) {
110
                return $builder->where('id', $category);
111
            });
112
        });
113
114
        return $builder;
115
    }
116
117
    /**
118
     * Scope query with any of the given categories.
119
     *
120
     * @param \Illuminate\Database\Eloquent\Builder $builder
121
     * @param mixed                                 $categories
122
     *
123
     * @return \Illuminate\Database\Eloquent\Builder
124
     */
125
    public function scopeWithAnyCategories(Builder $builder, $categories): Builder
126
    {
127
        $categories = $this->prepareCategoryIds($categories);
128
129
        return $builder->whereHas('categories', function (Builder $builder) use ($categories) {
130
            $builder->whereIn('id', $categories);
131
        });
132
    }
133
134
    /**
135
     * Scope query with any of the given categories.
136
     *
137
     * @param \Illuminate\Database\Eloquent\Builder $builder
138
     * @param mixed                                 $categories
139
     *
140
     * @return \Illuminate\Database\Eloquent\Builder
141
     */
142
    public function scopeWithCategories(Builder $builder, $categories): Builder
143
    {
144
        return static::scopeWithAnyCategories($builder, $categories);
145
    }
146
147
    /**
148
     * Scope query without any of the given categories.
149
     *
150
     * @param \Illuminate\Database\Eloquent\Builder $builder
151
     * @param mixed                                 $categories
152
     *
153
     * @return \Illuminate\Database\Eloquent\Builder
154
     */
155
    public function scopeWithoutCategories(Builder $builder, $categories): Builder
156
    {
157
        $categories = $this->prepareCategoryIds($categories);
158
159
        return $builder->whereDoesntHave('categories', function (Builder $builder) use ($categories) {
160
            $builder->whereIn('id', $categories);
161
        });
162
    }
163
164
    /**
165
     * Scope query without any categories.
166
     *
167
     * @param \Illuminate\Database\Eloquent\Builder $builder
168
     *
169
     * @return \Illuminate\Database\Eloquent\Builder
170
     */
171
    public function scopeWithoutAnyCategories(Builder $builder): Builder
172
    {
173
        return $builder->doesntHave('categories');
174
    }
175
176
    /**
177
     * Determine if the model has any of the given categories.
178
     *
179
     * @param mixed $categories
180
     *
181
     * @return bool
182
     */
183
    public function hasCategories($categories): bool
184
    {
185
        $categories = $this->prepareCategoryIds($categories);
186
187
        return ! $this->categories->pluck('id')->intersect($categories)->isEmpty();
0 ignored issues
show
Bug introduced by
The property categories 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...
188
    }
189
190
    /**
191
     * Determine if the model has any the given categories.
192
     *
193
     * @param mixed $categories
194
     *
195
     * @return bool
196
     */
197
    public function hasAnyCategories($categories): bool
198
    {
199
        return static::hasCategories($categories);
200
    }
201
202
    /**
203
     * Determine if the model has all of the given categories.
204
     *
205
     * @param mixed $categories
206
     *
207
     * @return bool
208
     */
209
    public function hasAllCategories($categories): bool
210
    {
211
        $categories = $this->prepareCategoryIds($categories);
212
213
        return collect($categories)->diff($this->categories->pluck('id'))->isEmpty();
214
    }
215
216
    /**
217
     * Sync model categories.
218
     *
219
     * @param mixed $categories
220
     * @param bool  $detaching
221
     *
222
     * @return $this
223
     */
224
    public function syncCategories($categories, bool $detaching = true)
225
    {
226
        // Find categories
227
        $categories = $this->prepareCategoryIds($categories);
228
229
        // Sync model categories
230
        $this->categories()->sync($categories, $detaching);
231
232
        return $this;
233
    }
234
235
    /**
236
     * Attach model categories.
237
     *
238
     * @param mixed $categories
239
     *
240
     * @return $this
241
     */
242
    public function attachCategories($categories)
243
    {
244
        return $this->syncCategories($categories, false);
245
    }
246
247
    /**
248
     * Detach model categories.
249
     *
250
     * @param mixed $categories
251
     *
252
     * @return $this
253
     */
254
    public function detachCategories($categories = null)
255
    {
256
        $categories = ! is_null($categories) ? $this->prepareCategoryIds($categories) : null;
257
258
        // Sync model categories
259
        $this->categories()->detach($categories);
260
261
        return $this;
262
    }
263
264
    /**
265
     * Prepare category IDs.
266
     *
267
     * @param mixed $categories
268
     *
269
     * @return array
270
     */
271
    protected function prepareCategoryIds($categories): array
272
    {
273
        // Convert collection to plain array
274
        if ($categories instanceof BaseCollection && is_string($categories->first())) {
275
            $categories = $categories->toArray();
276
        }
277
278
        // Find categories by their ids
279
        if (is_numeric($categories) || (is_array($categories) && is_numeric(Arr::first($categories)))) {
0 ignored issues
show
Documentation introduced by
$categories is of type array, but the function expects a object<Illuminate\Support\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
280
            return array_map('intval', (array) $categories);
281
        }
282
283
        // Find categories by their slugs
284
        if (is_string($categories) || (is_array($categories) && is_string(Arr::first($categories)))) {
0 ignored issues
show
Documentation introduced by
$categories is of type array, but the function expects a object<Illuminate\Support\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
285
            $categories = app('rinvex.categories.category')->whereIn('slug', (array) $categories)->get()->pluck('id');
286
        }
287
288
        if ($categories instanceof Model) {
289
            return [$categories->getKey()];
290
        }
291
292
        if ($categories instanceof Collection) {
293
            return $categories->modelKeys();
294
        }
295
296
        if ($categories instanceof BaseCollection) {
297
            return $categories->toArray();
298
        }
299
300
        return (array) $categories;
301
    }
302
}
303