Completed
Pull Request — master (#3)
by
unknown
03:33
created

CloneService::cloneRecursive()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 7
nc 1
nop 1
dl 0
loc 14
ccs 3
cts 3
cp 1
crap 1
rs 10
c 1
b 0
f 0
1
<?php
2
3
namespace Anfischer\Cloner;
4
5
use Illuminate\Database\Eloquent\Model;
6
use Illuminate\Database\Eloquent\Relations\Pivot;
7
use Illuminate\Support\Arr;
8
use Illuminate\Support\Collection;
9
10
class CloneService implements CloneServiceInterface
11
{
12
    protected $originalKeyToClonedKeyMap;
13
14
    public function __construct()
15
    {
16
        $this->originalKeyToClonedKeyMap = new Collection();
17
    }
18 24
19
    /**
20 24
     * Clones a model and its relationships
21
     *
22
     * @param Model $model
23
     * @return Model
24
     */
25
    public function clone(Model $model) : Model
26
    {
27
        return $this->cloneRecursive(
28
            $this->getFreshInstance($model)
29 24
        )->first();
30
    }
31
32
    /**
33 20
     * Recursively clones a model and its relationships
34
     *
35 20
     * @param $model
36 20
     * @return mixed
37 20
     */
38
    private function cloneRecursive($model)
39 24
    {
40 24
        Collection::wrap($model)->each(function ($item) {
41
            Collection::wrap($item->getRelations())->each(function ($method, $relation) use ($item) {
42 24
                $collection = $this->getFreshInstance($this->cloneRecursive($method), $item);
43
44
                $item->setRelation(
45
                    $relation,
46
                    $this->getItemOrCollection($collection)
47
                );
48
            });
49
        });
50
51
        return $model;
52 20
    }
53
54 20
    /**
55
     * Gets the first item of the collection if the collection
56
     * only contains one item, otherwise it returns the collection
57
     *
58
     * @param Collection $collection
59
     * @return Collection|mixed
60
     */
61
    private function getItemOrCollection(Collection $collection)
62
    {
63
        return $collection->count() > 1 ? $collection : $collection->first();
64
    }
65 20
66
    /**
67
     * Gets a fresh cloned instance of the model
68
     * which is stripped of the original models unique attributes
69
     *
70 20
     * @param object $model
71 20
     * @param object $parent
72 20
     * @return Collection
73 20
     */
74
    private function getFreshInstance($model, $parent = null) : Collection
75
    {
76 20
        return Collection::wrap($model)->map(function ($original) use ($parent) {
77 20
            return tap(new $original, function ($instance) use ($original, $parent) {
78
                // Ensure we can get hold of the new ID relative to the original
79
                $instance->saved(function () use ($original, $instance) {
80 20
                    $this->pushToKeyMap($original, $instance);
81 20
                });
82 20
83
                $filter = [
84
                    $original->getForeignKey(),
85 20
                    $original->getKeyName(),
86 20
                    $original->getCreatedAtColumn(),
87 20
                    $original->getUpdatedAtColumn(),
88 20
                ];
89
90
                if ($parent && ! is_a($instance, Pivot::class)) {
91
                    array_push($filter, $parent->getForeignKey());
92
                }
93
94
                $attributes = Arr::except(
95
                    $original->getAttributes(),
96
                    $filter
97
                );
98
99
                $instance->setRawAttributes($attributes);
100
                $instance->setRelations($original->getRelations());
101
            });
102
        });
103
    }
104
105
    /**
106
     * Get the key map Collection.
107
     *
108
     * @return Collection
109
     */
110
    public function getKeyMap(): Collection
111
    {
112
        return $this->originalKeyToClonedKeyMap;
113
    }
114
115
    /**
116
     * Add an old to new object key to the map.
117
     *
118
     * @param Model $original The original model.
119
     * @param Model $cloned The model cloned from the original.
120
     * @return void
121
     */
122
    public function pushToKeyMap(Model $original, Model $cloned): void
123
    {
124
        $class = get_class($original);
125
126
        $this->originalKeyToClonedKeyMap->get($class, function () use ($class) {
127
            return tap(new Collection, function ($collection) use ($class) {
128
                $this->originalKeyToClonedKeyMap->put($class, $collection);
129
            });
130
        })->put($original->getKey(), $cloned->getKey());
131
    }
132
}
133