Completed
Push — master ( 4dc673...306aa4 )
by Patrick
02:10
created

CascadesDeletes   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 184
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 27
lcom 1
cbo 1
dl 0
loc 184
ccs 55
cts 55
cp 1
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
C bootCascadesDeletes() 0 69 9
A getCascadeDeletes() 0 4 2
A setCascadeDeletes() 0 4 1
A getCascadeDeletesRelationNames() 0 6 2
A getCascadeDeletesRelations() 0 10 3
A getInvalidCascadeDeletesRelations() 0 4 2
A getCascadeDeletesRelationQuery() 0 18 4
A isCascadeDeletesForceDeleting() 0 13 4
1
<?php
2
3
namespace ShiftOneLabs\LaravelCascadeDeletes;
4
5
use LogicException;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Database\Eloquent\SoftDeletingScope;
8
use Illuminate\Database\Eloquent\Relations\Relation;
9
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
10
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
11
12
trait CascadesDeletes
13
{
14
    /**
15
     * Use the boot function to setup model event listeners.
16
     *
17
     * @return void
18
     */
19 17
    public static function bootCascadesDeletes()
20
    {
21
        // Setup the 'deleting' event listener.
22
        static::deleting(function ($model) {
1 ignored issue
show
Bug introduced by
The method deleting() does not exist on ShiftOneLabs\LaravelCascadeDeletes\CascadesDeletes. Did you maybe mean isCascadeDeletesForceDeleting()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
23
24
            // Wrap all of the cascading deletes inside of a transaction to make this an
25
            // all or nothing operation. Any exceptions thrown inside the transaction
26
            // need to bubble up to make sure all transactions will be rolled back.
27
            $model->getConnectionResolver()->transaction(function () use ($model) {
28
29 16
                $relations = $model->getCascadeDeletesRelations();
30
31 16
                if ($invalidRelations = $model->getInvalidCascadeDeletesRelations($relations)) {
32 1
                    throw new LogicException(sprintf('[%s]: invalid relationship(s) for cascading deletes. Relationship method(s) [%s] must return an object of type Illuminate\Database\Eloquent\Relations\Relation.', static::class, implode(', ', $invalidRelations)));
33
                }
34
35 15
                $deleteMethod = $model->isCascadeDeletesForceDeleting() ? 'forceDelete' : 'delete';
36
37 15
                foreach ($relations as $relationName => $relation) {
38 15
                    $expected = 0;
1 ignored issue
show
Unused Code introduced by
$expected is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
39 15
                    $deleted = 0;
40
41 15
                    if ($relation instanceof BelongsToMany) {
42
                        // Process the many-to-many relationships on the model.
43
                        // These relationships should not delete the related
44
                        // record, but should just detach from each other.
45
46 13
                        $expected = $model->getCascadeDeletesRelationQuery($relationName)->count();
47
48 13
                        $deleted = $model->getCascadeDeletesRelationQuery($relationName)->detach();
49 15
                    } elseif ($relation instanceof HasOneOrMany) {
50
                        // Process the one-to-one and one-to-many relationships
51
                        // on the model. These relationships should actually
52
                        // delete the related records from the database.
53
54 14
                        $children = $model->getCascadeDeletesRelationQuery($relationName)->get();
55
56
                        // To protect against potential relationship defaults,
57
                        // filter out any children that may not actually be
58
                        // Model instances, or that don't actually exist.
59
                        $children = $children->filter(function ($child) {
60 14
                            return $child instanceof Model && $child->exists;
61 14
                        })->all();
62
63 14
                        $expected = count($children);
64
65 14
                        foreach ($children as $child) {
66
                            // Delete the record using the proper method.
67 14
                            $child->$deleteMethod();
68
69
                            // forceDelete doesn't return anything until Laravel 5.2. Check
70
                            // exists property to determine if the delete was successful
71
                            // since that is the last thing set before delete returns.
72 14
                            $deleted += !$child->exists;
73 14
                        }
74 14
                    } else {
75
                        // Not all relationship types make sense for cascading. As an
76
                        // example, for a BelongsTo relationship, it does not make
77
                        // sense to delete the parent when the child is deleted.
78 4
                        throw new LogicException(sprintf('[%s]: error occurred deleting [%s]. Relation type [%s] not handled.', static::class, $relationName, get_class($relation)));
79
                    }
80
81 14
                    if ($deleted < $expected) {
82 1
                        throw new LogicException(sprintf('[%s]: error occurred deleting [%s]. Only deleted [%d] out of [%d] records.', static::class, $relationName, $deleted, $expected));
83
                    }
84 13
                }
85 16
            });
86 17
        });
87 17
    }
88
89
    /**
90
     * Get the value of the cascadeDeletes attribute, if it exists.
91
     *
92
     * @return mixed
93
     */
94 34
    public function getCascadeDeletes()
95
    {
96 34
        return property_exists($this, 'cascadeDeletes') ? $this->cascadeDeletes : [];
0 ignored issues
show
Bug introduced by
The property cascadeDeletes 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...
97
    }
98
99
    /**
100
     * Set the cascadeDeletes attribute.
101
     *
102
     * @param  mixed  $cascadeDeletes
103
     *
104
     * @return void
105
     */
106 15
    public function setCascadeDeletes($cascadeDeletes)
107
    {
108 15
        $this->cascadeDeletes = $cascadeDeletes;
109 15
    }
110
111
    /**
112
     * Get an array of cascading relation names.
113
     *
114
     * @return array
115
     */
116 32
    public function getCascadeDeletesRelationNames()
117
    {
118 32
        $deletes = $this->getCascadeDeletes();
119
120 32
        return array_filter(is_array($deletes) ? $deletes : [$deletes]);
121
    }
122
123
    /**
124
     * Get an array of the cascading relation names mapped to their relation types.
125
     *
126
     * @return array
127
     */
128 21
    public function getCascadeDeletesRelations()
129
    {
130 21
        $names = $this->getCascadeDeletesRelationNames();
131
132 21
        return array_combine($names, array_map(function ($name) {
133 21
            $relation = method_exists($this, $name) ? $this->$name() : null;
134
135 21
            return $relation instanceof Relation ? $relation : null;
136 21
        }, $names));
137
    }
138
139
    /**
140
     * Get an array of the invalid cascading relation names.
141
     *
142
     * @param  array|null  $relations
143
     *
144
     * @return array
145
     */
146 18
    public function getInvalidCascadeDeletesRelations(array $relations = null)
147
    {
148 18
        return array_keys($relations ?: $this->getCascadeDeletesRelations(), null);
149
    }
150
151
    /**
152
     * Get the relationship query to use for the specified relation.
153
     *
154
     * @param  string  $relation
155
     *
156
     * @return \Illuminate\Database\Eloquent\Relations\Relation
157
     */
158 17
    public function getCascadeDeletesRelationQuery($relation)
159
    {
160 17
        $query = $this->$relation();
161
162
        // If this is a force delete and the related model is using soft deletes,
163
        // we need to use the withTrashed() scope on the relationship query to
164
        // ensure all related records, plus soft deleted, are force deleted.
165 17
        if ($this->isCascadeDeletesForceDeleting()) {
166
            // Laravel 4.1 has the withTrashed method directly on the Eloquent
167
            // query builder, however Laravel 4.2+ uses a withTrashed macro
168
            // on the Eloquent query builder.
169 5
            if (!class_exists(SoftDeletingScope::class) || !is_null($query->getMacro('withTrashed'))) {
170 5
                $query = $query->withTrashed();
171 5
            }
172 5
        }
173
174 17
        return $query;
175
    }
176
177
    /**
178
     * Check if this cascading delete is a force delete.
179
     *
180
     * @return boolean
181
     */
182 20
    public function isCascadeDeletesForceDeleting()
183
    {
184
        // Laravel 4.1 uses the softDelete property, and the only
185
        // indication that the delete should be forced is when
186
        // the softDelete property is set to false.
187 20
        if (property_exists($this, 'softDelete') && !class_exists(SoftDeletingScope::class)) {
188
            // @codeCoverageIgnoreStart
189
            return !$this->softDelete;
0 ignored issues
show
Bug introduced by
The property softDelete 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...
190
            // @codeCoverageIgnoreEnd
191
        }
192
193 20
        return property_exists($this, 'forceDeleting') && $this->forceDeleting;
0 ignored issues
show
Bug introduced by
The property forceDeleting 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...
194
    }
195
}
196