Test Failed
Pull Request — master (#4)
by
unknown
11:21
created

PivotAwareTrait::pivots()   D

Complexity

Conditions 14
Paths 39

Size

Total Lines 104
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 104
rs 4.9516
c 0
b 0
f 0
cc 14
eloc 53
nc 39
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Charcoal\Relation\Traits;
4
5
use InvalidArgumentException;
6
use RuntimeException;
7
8
// From 'charcoal-core'
9
use Charcoal\Model\ModelInterface;
10
11
// From 'charcoal-cms'
12
use Charcoal\Admin\Widget\RelationWidget;
13
use Charcoal\Relation\Interfaces\PivotableInterface;
14
use Charcoal\Relation\Pivot;
15
16
// From 'mcaskill/charcoal-support'
17
use Charcoal\Support\Model\Collection;
18
19
/**
20
 * Provides support for pivots on objects.
21
 *
22
 * Used by source objects that need a pivot to a target object.
23
 *
24
 * Abstract methods need to be implemented.
25
 *
26
 * Implementation of {@see \Charcoal\Relation\Interfaces\PivotAwareInterface}
27
 *
28
 * ## Required Services
29
 *
30
 * - 'model/factory' — {@see \Charcoal\Model\ModelFactory}
31
 * - 'model/collection/loader' — {@see \Charcoal\Loader\CollectionLoader}
32
 */
33
trait PivotAwareTrait
34
{
35
    /**
36
     * A store of cached pivots, by ID.
37
     *
38
     * @var Pivot[] $pivotCache
39
     */
40
    protected static $pivotCache = [];
41
42
    /**
43
     * Store a collection of node objects.
44
     *
45
     * @var Collection|Pivot[]
46
     */
47
    protected $pivots = [];
48
49
    /**
50
     * Store the widget instance currently displaying relations.
51
     *
52
     * @var RelationWidget
53
     */
54
    protected $relationWidget;
55
56
    /**
57
     * Available target object types.
58
     *
59
     * @var array
60
     */
61
    protected $targetObjectTypes = [];
62
63
    /**
64
     * Retrieve the objects associated to the current object.
65
     *
66
     * @param  string|null   $group    Filter the pivots by a group identifier.
67
     * @param  string|null   $type     Filter the pivots by type.
68
     * @param  callable|null $callback Optional routine to apply to every object.
69
     * @throws InvalidArgumentException If the $group is invalid.
70
     * @throws InvalidArgumentException If the $type is invalid.
71
     * @return Collection[]
72
     */
73
    public function pivots($group = null, $type = null, callable $callback = null)
0 ignored issues
show
Unused Code introduced by
The parameter $callback is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
74
    {
75
        if ($group === null) {
76
            $group = 0;
77
        } elseif (!is_string($group)) {
78
            throw new InvalidArgumentException('The $group must be a string.');
79
        }
80
81
        if ($type === null) {
82
            $type = 0;
83
        } else {
84
            if (!is_string($type)) {
85
                throw new InvalidArgumentException('The $type must be a string.');
86
            }
87
88
            $type = preg_replace('/([a-z])([A-Z])/', '$1-$2', $type);
89
            $type = strtolower(str_replace('\\', '/', $type));
90
        }
91
92
        if (isset($this->pivots[$group][$type])) {
93
            return $this->pivots[$group][$type];
94
        }
95
96
        $sourceObjectType = $this->objType();
97
        $sourceObjectId = $this->id();
98
99
        $pivotProto = $this->modelFactory()->get(Pivot::class);
100
        $pivotTable = $pivotProto->source()->table();
101
102
        if (!$pivotProto->source()->tableExists()) {
103
            return [];
104
        }
105
106
        $widget = $this->relationWidget();
107
108
        $targetObjectTypes = $this->targetObjectTypes($group);
109
110
        if (!is_array($targetObjectTypes) || empty($targetObjectTypes)) {
111
            throw new RuntimeException('No target object types are set for this object.');
112
        }
113
114
        $cases = [];
0 ignored issues
show
Unused Code introduced by
$cases 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...
115
        $joins = [];
0 ignored issues
show
Unused Code introduced by
$joins 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...
116
        $collection = new Collection;
117
        foreach ($targetObjectTypes as $targetObjectType => $metadata) {
118
            $parts = explode('/', str_replace('-', '_', $targetObjectType));
119
            $targetObjectIdent = end($parts).'_obj';
0 ignored issues
show
Unused Code introduced by
$targetObjectIdent 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...
120
121
            $targetObjectProto = $this->modelFactory()->get($targetObjectType);
122
            $targetObjectTable = $targetObjectProto->source()->table();
123
124
            if (!$targetObjectProto->source()->tableExists() || !$targetObjectProto instanceof PivotableInterface) {
125
                continue;
126
            }
127
128
            $query = '
129
                SELECT
130
                    target_obj.*,
131
                    pivot_obj.id as pivotObjId,
132
                    pivot_obj.position as position
133
                FROM
134
                    `'.$targetObjectTable.'` AS target_obj
135
                LEFT JOIN
136
                    `'.$pivotTable.'` AS pivot_obj
137
                ON
138
                    pivot_obj.target_object_id = target_obj.id
139
                WHERE
140
                    1 = 1';
141
142
            // Disable `active` check in admin
143
            if (!$widget instanceof RelationWidget) {
144
                $query .= '
145
                AND
146
                    target_obj.active = 1';
147
            }
148
149
            $query .= '
150
                AND
151
                    pivot_obj.source_object_type = "'.$sourceObjectType.'"
152
                AND
153
                    pivot_obj.source_object_id = "'.$sourceObjectId.'"
154
                AND
155
                    pivot_obj.target_object_type = "'.$targetObjectType.'"';
156
157
            if ($group) {
158
                $query .= '
159
                AND
160
                    pivot_obj.group = "'.$group.'"';
161
            }
162
163
            $query .= '
164
                ORDER BY pivot_obj.position';
165
166
            $loader = $this->collectionLoader();
167
            $loader->setModel($targetObjectProto);
0 ignored issues
show
Documentation introduced by
$targetObjectProto is of type object<Charcoal\Relation...ces\PivotableInterface>, but the function expects a string|object<Charcoal\Model\ModelInterface>.

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...
168
169
            $targetCollection = $loader->loadFromQuery($query);
170
            $collection->merge($targetCollection);
171
        }
172
173
        $this->pivots[$group][$type] = $collection->sortBy('position');
174
175
        return $this->pivots[$group][$type];
176
    }
177
178
    /**
179
     * Determine if the current object has any nodes.
180
     *
181
     * @return boolean Whether $this has any nodes (TRUE) or not (FALSE).
182
     */
183
    public function hasPivots()
184
    {
185
        return !!($this->numPivots());
186
    }
187
188
    /**
189
     * Count the number of nodes associated to the current object.
190
     *
191
     * @return integer
192
     */
193
    public function numPivots()
194
    {
195
        return count($this->pivots());
196
    }
197
198
    /**
199
     * Attach an node to the current object.
200
     *
201
     * @param PivotableInterface|ModelInterface $obj An object.
202
     * @return boolean|self
203
     */
204
    public function addPivot($obj)
205
    {
206
        if (!$obj instanceof PivotableInterface && !$obj instanceof ModelInterface) {
207
            return false;
208
        }
209
210
        $model = $this->modelFactory()->create(Pivot::class);
211
212
        $sourceObjectId = $this->id();
213
        $sourceObjectType = $this->objType();
214
        $pivotId = $obj->id();
215
216
        $model->setPivotId($pivotId);
217
        $model->setObjId($sourceObjectId);
218
        $model->setObjType($sourceObjectType);
219
220
        $model->save();
221
222
        return $this;
223
    }
224
225
    /**
226
     * Remove all pivots linked to a specific object.
227
     *
228
     * @return boolean
229
     */
230 View Code Duplication
    public function removePivots()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
231
    {
232
        $pivotProto = $this->modelFactory()->get(Pivot::class);
233
234
        $loader = $this->collectionLoader();
235
        $loader
236
            ->setModel($pivotProto)
237
            ->addFilter('source_object_type', $this->objType())
238
            ->addFilter('source_object_id', $this->id());
239
240
        $collection = $loader->load();
241
242
        foreach ($collection as $obj) {
243
            $obj->delete();
244
        }
245
246
        return true;
247
    }
248
249
    /**
250
     * Retrieve the relation widget.
251
     *
252
     * @return RelationWidget
253
     */
254
    protected function relationWidget()
255
    {
256
        return $this->relationWidget;
257
    }
258
259
    /**
260
     * Set the relation widget.
261
     *
262
     * @param  RelationWidget $widget The widget displaying pivots.
263
     * @return string
264
     */
265
    protected function setRelationWidget(RelationWidget $widget)
266
    {
267
        $this->relationWidget = $widget;
268
269
        return $this;
270
    }
271
272
    /**
273
     * Retrieve the target object types.
274
     *
275
     * @param  string $group The pivot group ident.
276
     * @return array
277
     */
278
    public function targetObjectTypes($group = 'generic')
279
    {
280
        if (!isset($this->targetObjectTypes[$group])) {
281
            $this->targetObjectTypes[$group] = $this->metadata()->get('relation.'.$group.'.target_object_types');
0 ignored issues
show
Bug introduced by
It seems like metadata() 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...
282
        }
283
284
        return $this->targetObjectTypes[$group];
285
    }
286
287
    /**
288
     * Retrieve the object's type identifier.
289
     *
290
     * @return string
291
     */
292
    abstract public function objType();
293
294
    /**
295
     * Retrieve the object's unique ID.
296
     *
297
     * @return mixed
298
     */
299
    abstract public function id();
300
301
    /**
302
     * Retrieve the object model factory.
303
     *
304
     * @return \Charcoal\Factory\FactoryInterface
305
     */
306
    abstract public function modelFactory();
307
308
    /**
309
     * Retrieve the model collection loader.
310
     *
311
     * @return \Charcoal\Loader\CollectionLoader
312
     */
313
    abstract public function collectionLoader();
314
}
315