Passed
Pull Request — master (#5)
by
unknown
13:11
created

CloneService::clone()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 5
ccs 1
cts 1
cp 1
crap 1
rs 10
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
                $isCollection = $item->getRelation($relation) instanceOf Collection;
45
46
                $item->setRelation(
47
                    $relation,
48
                    $isCollection ? $collection : $collection->first()
49
                );
50
            });
51
        });
52 20
53
        return $model;
54 20
    }
55
56
    /**
57
     * Gets a fresh cloned instance of the model
58
     * which is stripped of the original models unique attributes
59
     *
60
     * @param object $model
61
     * @param object $parent
62
     * @return Collection
63
     */
64
    private function getFreshInstance($model, $parent = null) : Collection
65 20
    {
66
        return Collection::wrap($model)->map(function ($original) use ($parent) {
67
            return tap(new $original, function ($instance) use ($original, $parent) {
68
                // Ensure we can get hold of the new ID relative to the original
69
                $instance->saved(function () use ($original, $instance) {
70 20
                    $this->pushToKeyMap($original, $instance);
71 20
                });
72 20
73 20
                $filter = [
74
                    $original->getForeignKey(),
75
                    $original->getKeyName(),
76 20
                    $original->getCreatedAtColumn(),
77 20
                    $original->getUpdatedAtColumn(),
78
                ];
79
80 20
                if ($parent && ! is_a($instance, Pivot::class)) {
81 20
                    array_push($filter, $parent->getForeignKey());
82 20
                }
83
84
                $attributes = Arr::except(
85 20
                    $original->getAttributes(),
86 20
                    $filter
87 20
                );
88 20
89
                $instance->setRawAttributes($attributes);
90
                $instance->setRelations($original->getRelations());
91
            });
92
        });
93
    }
94
95
    /**
96
     * Get the key map Collection.
97
     *
98
     * @return Collection
99
     */
100
    public function getKeyMap(): Collection
101
    {
102
        return $this->originalKeyToClonedKeyMap;
103
    }
104
105
    /**
106
     * Add an old to new object key to the map.
107
     *
108
     * @param Model $original The original model.
109
     * @param Model $cloned The model cloned from the original.
110
     * @return void
111
     */
112
    public function pushToKeyMap(Model $original, Model $cloned): void
113
    {
114
        $class = get_class($original);
115
116
        $this->originalKeyToClonedKeyMap->get($class, function () use ($class) {
117
            return tap(new Collection, function ($collection) use ($class) {
118
                $this->originalKeyToClonedKeyMap->put($class, $collection);
119
            });
120
        })->put($original->getKey(), $cloned->getKey());
121
    }
122
}
123