GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

HasDuplicates::duplicateRelationWithUnique()   B
last analyzed

Complexity

Conditions 7
Paths 3

Size

Total Lines 23

Duplication

Lines 10
Ratio 43.48 %

Importance

Changes 0
Metric Value
dl 10
loc 23
rs 8.6186
c 0
b 0
f 0
cc 7
nc 3
nop 2
1
<?php
2
3
namespace Neurony\Duplicate\Traits;
4
5
use Closure;
6
use Exception;
7
use Illuminate\Database\Eloquent\Model;
8
use Illuminate\Support\Arr;
9
use Illuminate\Support\Facades\DB;
10
use Neurony\Duplicate\Helpers\RelationHelper;
11
use Neurony\Duplicate\Options\DuplicateOptions;
12
13
trait HasDuplicates
14
{
15
    /**
16
     * The container for all the options necessary for this trait.
17
     * Options can be viewed in the Neurony\Duplicate\Options\DuplicateOptions file.
18
     *
19
     * @var DuplicateOptions
20
     */
21
    protected $duplicateOptions;
22
23
    /**
24
     * Set the options for the HasDuplicates trait.
25
     *
26
     * @return DuplicateOptions
27
     */
28
    abstract public function getDuplicateOptions(): DuplicateOptions;
29
30
    /**
31
     * Register a duplicating model event with the dispatcher.
32
     *
33
     * @param Closure|string  $callback
34
     * @return void
35
     */
36
    public static function duplicating($callback): void
37
    {
38
        static::registerModelEvent('duplicating', $callback);
39
    }
40
41
    /**
42
     * Register a duplicated model event with the dispatcher.
43
     *
44
     * @param Closure|string  $callback
45
     * @return void
46
     */
47
    public static function duplicated($callback): void
48
    {
49
        static::registerModelEvent('duplicated', $callback);
50
    }
51
52
    /**
53
     * Duplicate a model instance and it's relations.
54
     *
55
     * @return Model|bool
56
     * @throws Exception
57
     */
58
    public function saveAsDuplicate()
59
    {
60
        try {
61
            if ($this->fireModelEvent('duplicating') === false) {
0 ignored issues
show
Bug introduced by
It seems like fireModelEvent() 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...
62
                return false;
63
            }
64
65
            $this->initDuplicateOptions();
66
67
            $model = DB::transaction(function () {
68
                $model = $this->duplicateModel();
69
70
                if ($this->duplicateOptions->shouldDuplicateDeeply !== true) {
71
                    return $model;
72
                }
73
74
                foreach ($this->getRelationsForDuplication() as $relation => $attributes) {
75
                    if (RelationHelper::isChild($attributes['type'])) {
76
                        $this->duplicateDirectRelation($model, $relation);
77
                    }
78
79
                    if (RelationHelper::isPivoted($attributes['type'])) {
80
                        $this->duplicatePivotedRelation($model, $relation);
81
                    }
82
                }
83
84
                return $model;
85
            });
86
87
            $this->fireModelEvent('duplicated', false);
0 ignored issues
show
Bug introduced by
It seems like fireModelEvent() 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...
88
89
            return $model;
90
        } catch (Exception $e) {
91
            throw $e;
92
        }
93
    }
94
95
    /**
96
     * Get a replicated instance of the original model's instance.
97
     *
98
     * @return Model
99
     * @throws Exception
100
     */
101
    protected function duplicateModel(): Model
102
    {
103
        $model = $this->duplicateModelWithExcluding();
104
        $model = $this->duplicateModelWithUnique($model);
105
106
        $model->save();
107
108
        return $model;
109
    }
110
111
    /**
112
     * Duplicate a direct relation.
113
     * Subsequently save new relation records for the initial model instance.
114
     *
115
     * @param Model $model
116
     * @param string $relation
117
     * @return Model
118
     * @throws Exception
119
     */
120
    protected function duplicateDirectRelation(Model $model, string $relation): Model
121
    {
122
        $this->{$relation}()->get()->each(function ($rel) use ($model, $relation) {
123
            $rel = $this->duplicateRelationWithExcluding($rel, $relation);
124
            $rel = $this->duplicateRelationWithUnique($rel, $relation);
125
126
            $model->{$relation}()->save($rel);
127
        });
128
129
        return $model;
130
    }
131
132
    /**
133
     * Duplicate a pivoted relation.
134
     * Subsequently attach new pivot records corresponding to the relation for the initial model instance.
135
     *
136
     * @param Model $model
137
     * @param string $relation
138
     * @return Model
139
     * @throws Exception
140
     */
141
    protected function duplicatePivotedRelation(Model $model, string $relation): Model
142
    {
143
        $this->{$relation}()->get()->each(function ($rel) use ($model, $relation) {
144
            $attributes = $this->establishDuplicatablePivotAttributes($rel);
145
146
            $model->{$relation}()->attach($rel, $attributes);
147
        });
148
149
        return $model;
150
    }
151
152
    /**
153
     * Get the relations that should be duplicated alongside the original model.
154
     *
155
     * @return array
156
     * @throws \ReflectionException
157
     */
158
    protected function getRelationsForDuplication(): array
159
    {
160
        $relations = [];
161
        $excluded = $this->duplicateOptions->excludedRelations ?: [];
162
163
        foreach (RelationHelper::getModelRelations($this) as $relation => $attributes) {
164
            if (! in_array($relation, $excluded)) {
165
                $relations[$relation] = $attributes;
166
            }
167
        }
168
169
        return $relations;
170
    }
171
172
    /**
173
     * Replicate a model instance, excluding attributes provided in the model's getDuplicateOptions() method.
174
     *
175
     * @return Model
176
     */
177
    private function duplicateModelWithExcluding(): Model
178
    {
179
        $except = [];
180
        $excluded = $this->duplicateOptions->excludedColumns;
181
182
        if ($this->usesTimestamps()) {
0 ignored issues
show
Bug introduced by
It seems like usesTimestamps() 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...
183
            $except = array_merge($except, [
184
                $this->getCreatedAtColumn(),
0 ignored issues
show
Bug introduced by
It seems like getCreatedAtColumn() 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...
185
                $this->getUpdatedAtColumn(),
0 ignored issues
show
Bug introduced by
It seems like getUpdatedAtColumn() 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...
186
            ]);
187
        }
188
189
        if ($excluded && is_array($excluded) && ! empty($excluded)) {
190
            $except = array_merge($except, $excluded);
191
        }
192
193
        return $this->replicate($except);
0 ignored issues
show
Bug introduced by
It seems like replicate() 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...
194
    }
195
196
    /**
197
     * Update a model instance.
198
     * With unique values for the attributes provided in the model's getDuplicateOptions() method.
199
     *
200
     * @param Model $model
201
     * @return Model
202
     */
203
    private function duplicateModelWithUnique(Model $model): Model
204
    {
205
        $unique = $this->duplicateOptions->uniqueColumns;
206
207
        if (! $unique || ! is_array($unique) || empty($unique)) {
208
            return $model;
209
        }
210
211 View Code Duplication
        foreach ($unique as $column) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
212
            $i = 1;
213
            $original = $value = $model->{$column};
214
215
            while (static::withoutGlobalScopes()->where($column, $value)->first()) {
216
                $value = $original.' ('.$i++.')';
217
218
                $model->{$column} = $value;
219
            }
220
        }
221
222
        return $model;
223
    }
224
225
    /**
226
     * Replicate a model relation instance, excluding attributes provided in the model's getDuplicateOptions() method.
227
     *
228
     * @param Model $model
229
     * @param string $relation
230
     * @return Model
231
     */
232
    private function duplicateRelationWithExcluding(Model $model, string $relation): Model
233
    {
234
        $attributes = null;
235
        $excluded = $this->duplicateOptions->excludedRelationColumns;
236
237
        if ($excluded && is_array($excluded) && ! empty($excluded)) {
238
            if (array_key_exists($relation, $excluded)) {
239
                $attributes = $excluded[$relation];
240
            }
241
        }
242
243
        return $model->replicate($attributes);
244
    }
245
246
    /**
247
     * Update a relation for the model instance.
248
     * With unique values for the attributes attributes provided in the model's getDuplicateOptions() method.
249
     *
250
     * @param Model $model
251
     * @param string $relation
252
     * @return Model
253
     */
254
    private function duplicateRelationWithUnique(Model $model, string $relation): Model
255
    {
256
        $unique = $this->duplicateOptions->uniqueRelationColumns;
257
258
        if (! $unique || ! is_array($unique) || empty($unique)) {
259
            return $model;
260
        }
261
262
        if (array_key_exists($relation, $unique)) {
263 View Code Duplication
            foreach ($unique[$relation] as $column) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
264
                $i = 1;
265
                $original = $value = $model->{$column};
266
267
                while ($model->where($column, $value)->first()) {
268
                    $value = $original.' ('.$i++.')';
269
270
                    $model->{$column} = $value;
271
                }
272
            }
273
        }
274
275
        return $model;
276
    }
277
278
    /**
279
     * Get additional pivot attributes that should be saved when duplicating a pivoted relation.
280
     * Usually, these are attributes coming from the withPivot() method defined on the relation.
281
     *
282
     * @param Model $model
283
     * @return array
284
     */
285
    protected function establishDuplicatablePivotAttributes(Model $model): array
286
    {
287
        $pivot = $model->pivot;
288
289
        return Arr::except($pivot->getAttributes(), [
290
            $pivot->getKeyName(),
291
            $pivot->getForeignKey(),
292
            $pivot->getOtherKey(),
293
            $pivot->getCreatedAtColumn(),
294
            $pivot->getUpdatedAtColumn(),
295
        ]);
296
    }
297
298
    /**
299
     * Instantiate the duplicate options.
300
     *
301
     * @return void
302
     */
303
    protected function initDuplicateOptions(): void
304
    {
305
        if ($this->duplicateOptions === null) {
306
            $this->duplicateOptions = $this->getDuplicateOptions();
307
        }
308
    }
309
}
310