Completed
Branch CASC/base (fc1e8e)
by
unknown
20:14 queued 01:00
created

RelationNode   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 275
Duplicated Lines 2.91 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
dl 8
loc 275
rs 10
c 0
b 0
f 0
wmc 30
lcom 1
cbo 7

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
B work() 3 36 6
A allChildrenComplete() 0 9 3
A visitAlreadyDiscoveredNodes() 0 14 4
A isDiscovered() 0 4 1
A isComplete() 5 11 3
A discover() 0 4 1
A whereQueryParams() 0 19 3
A toArray() 0 12 2
A getIds() 0 16 3
A countSubNodes() 0 4 1
A __sleep() 0 15 1
A __wakeup() 0 6 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
namespace EventEspresso\core\services\orm\tree_traversal;
4
5
use EE_Base_Class;
6
use EE_Error;
7
use EE_Has_Many_Any_Relation;
8
use EE_Model_Relation_Base;
9
use EE_Registry;
10
use EEM_Base;
11
use EventEspresso\core\exceptions\InvalidDataTypeException;
12
use EventEspresso\core\exceptions\InvalidInterfaceException;
13
use EventEspresso\core\services\payment_methods\forms\PayPalSettingsForm;
14
use InvalidArgumentException;
15
use ReflectionException;
16
17
/**
18
 * Class RelationNode
19
 *
20
 * Wraps a model object and one of its model's relations; stores how many related model objects exist across that
21
 * relation, and eventually createsa a ModelObjNode for each of its related model objects.
22
 *
23
 * @package     Event Espresso
24
 * @author         Mike Nelson
25
 * @since         $VID:$
26
 *
27
 */
28
class RelationNode extends BaseNode
29
{
30
31
    /**
32
     * @var string|int
33
     */
34
    protected $id;
35
36
    /**
37
     * @var EEM_Base
38
     */
39
    protected $main_model;
40
41
    /**
42
     * @var int
43
     */
44
    protected $count;
45
46
    /**
47
     * @var EEM_Base
48
     */
49
    protected $related_model;
50
51
    /**
52
     * @var ModelObjNode[]
53
     */
54
    protected $nodes;
55
56
    public function __construct($main_model_obj_id, EEM_Base $main_model, EEM_Base $related_model)
57
    {
58
        $this->id = $main_model_obj_id;
59
        $this->main_model = $main_model;
60
        $this->related_model = $related_model;
61
        $this->nodes = [];
62
    }
63
64
65
    /**
66
     * Here is where most of the work happens. We've counted how many related model objects exist, here we identify
67
     * them (ie, learn their IDs). But its recursive, so we'll also find their related dependent model objects etc.
68
     * @since $VID:$
69
     * @param int $model_objects_to_identify
70
     * @return int
71
     * @throws EE_Error
72
     * @throws InvalidArgumentException
73
     * @throws InvalidDataTypeException
74
     * @throws InvalidInterfaceException
75
     * @throws ReflectionException
76
     */
77
    protected function work($model_objects_to_identify)
78
    {
79
        $num_identified = $this->visitAlreadyDiscoveredNodes($this->nodes, $model_objects_to_identify);
80
        if ($num_identified < $model_objects_to_identify) {
81
            $related_model_objs = $this->related_model->get_all(
82
                [
83
                    $this->whereQueryParams(),
84
                    'limit' => [
85
                        count($this->nodes),
86
                        $model_objects_to_identify - $num_identified
87
                    ]
88
                ]
89
            );
90
            $new_item_nodes = [];
91
92
            // Add entity nodes for each of the model objects we fetched.
93
            foreach ($related_model_objs as $related_model_obj) {
94
                $entity_node = new ModelObjNode($related_model_obj->ID(), $related_model_obj->get_model());
0 ignored issues
show
Bug introduced by
It seems like $related_model_obj->get_model() can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
95
                $this->nodes[ $related_model_obj->ID() ] = $entity_node;
96
                $new_item_nodes[ $related_model_obj->ID() ] = $entity_node;
97
            }
98
            $num_identified += count($new_item_nodes);
99
            if ($num_identified < $model_objects_to_identify) {
100
                // And lastly do the work.
101
                $num_identified += $this->visitAlreadyDiscoveredNodes(
102
                    $new_item_nodes,
103
                    $model_objects_to_identify - $num_identified
104
                );
105
            }
106
        }
107
108 View Code Duplication
        if (count($this->nodes) >= $this->count && $this->allChildrenComplete()) {
109
            $this->complete = true;
110
        }
111
        return $num_identified;
112
    }
113
114
    /**
115
     * Checks if all the identified child nodes are complete or not.
116
     * @since $VID:$
117
     * @return bool
118
     */
119
    protected function allChildrenComplete()
120
    {
121
        foreach ($this->nodes as $model_obj_node) {
122
            if (! $model_obj_node->isComplete()) {
123
                return false;
124
            }
125
        }
126
        return true;
127
    }
128
129
    /**
130
     * Visits the provided nodes and keeps track of how much work was done, making sure to not go over budget.
131
     * @since $VID:$
132
     * @param ModelObjNode[] $model_obj_nodes
133
     * @param $work_budget
134
     * @return int
135
     */
136
    protected function visitAlreadyDiscoveredNodes($model_obj_nodes, $work_budget)
137
    {
138
        $work_done = 0;
139
        if (! $model_obj_nodes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $model_obj_nodes of type EventEspresso\core\servi...raversal\ModelObjNode[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
140
            return 0;
141
        }
142
        foreach ($model_obj_nodes as $model_obj_node) {
143
            if ($work_done >= $work_budget) {
144
                break;
145
            }
146
            $work_done += $model_obj_node->visit($work_budget - $work_done);
147
        }
148
        return $work_done;
149
    }
150
151
    /**
152
     * Whether this item has already been initialized
153
     */
154
    protected function isDiscovered()
155
    {
156
        return $this->count !== null;
157
    }
158
159
    /**
160
     * @since $VID:$
161
     * @return boolean
162
     */
163
    public function isComplete()
164
    {
165
        if ($this->complete === null) {
166 View Code Duplication
            if (count($this->nodes) === $this->count) {
167
                $this->complete = true;
168
            } else {
169
                $this->complete = false;
170
            }
171
        }
172
        return $this->complete;
173
    }
174
175
    /**
176
     * Discovers how many related model objects exist.
177
     * @since $VID:$
178
     * @return mixed|void
179
     * @throws EE_Error
180
     * @throws InvalidArgumentException
181
     * @throws InvalidDataTypeException
182
     * @throws InvalidInterfaceException
183
     * @throws ReflectionException
184
     */
185
    protected function discover()
186
    {
187
        $this->count = $this->related_model->count([$this->whereQueryParams()]);
188
    }
189
190
    /**
191
     * @since $VID:$
192
     * @return array
193
     * @throws EE_Error
194
     * @throws InvalidDataTypeException
195
     * @throws InvalidInterfaceException
196
     * @throws InvalidArgumentException
197
     * @throws ReflectionException
198
     */
199
    protected function whereQueryParams()
200
    {
201
        $where_params =  [
202
            $this->related_model->get_foreign_key_to(
203
                $this->main_model->get_this_model_name()
204
            )->get_name() => $this->id
205
        ];
206
        try {
207
            $relation_settings = $this->main_model->related_settings_for($this->related_model->get_this_model_name());
208
        } catch (EE_Error $e) {
209
            // This will happen for has-and-belongs-to-many relations, when this node's related model is that join table
210
            // which hasn't been explicitly declared in the main model object's model's relations.
211
            $relation_settings = null;
212
        }
213
        if ($relation_settings instanceof EE_Has_Many_Any_Relation) {
214
            $where_params[ $this->related_model->get_field_containing_related_model_name()->get_name() ] = $this->main_model->get_this_model_name();
215
        }
216
        return $where_params;
217
    }
218
    /**
219
     * @since $VID:$
220
     * @return array
221
     */
222
    public function toArray()
223
    {
224
        $tree = [
225
            'count' => $this->count,
226
            'complete' => $this->isComplete(),
227
            'objs' => []
228
        ];
229
        foreach ($this->nodes as $id => $model_obj_node) {
230
            $tree['objs'][ $id ] = $model_obj_node->toArray();
231
        }
232
        return $tree;
233
    }
234
235
    /**
236
     * Gets the IDs of all the model objects to delete; indexed first by model object name.
237
     * @since $VID:$
238
     * @return array
239
     */
240
    public function getIds()
241
    {
242
        if (empty($this->nodes)) {
243
            return [];
244
        }
245
        $ids = [
246
            $this->related_model->get_this_model_name() => array_combine(
247
                array_keys($this->nodes),
248
                array_keys($this->nodes)
249
            )
250
        ];
251
        foreach ($this->nodes as $model_obj_node) {
252
            $ids = array_replace_recursive($ids, $model_obj_node->getIds());
253
        }
254
        return $ids;
255
    }
256
257
    /**
258
     * Returns the number of sub-nodes found (ie, related model objects across this relation.)
259
     * @since $VID:$
260
     * @return int
261
     */
262
    public function countSubNodes()
263
    {
264
        return count($this->nodes);
265
    }
266
267
    /**
268
     * Don't serialize the models. Just record their names on some dynamic properties.
269
     * @since $VID:$
270
     */
271
    public function __sleep()
272
    {
273
        $this->m = $this->main_model->get_this_model_name();
0 ignored issues
show
Bug introduced by
The property m 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...
274
        $this->rm = $this->related_model->get_this_model_name();
0 ignored issues
show
Bug introduced by
The property rm 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...
275
        return array_merge(
276
            [
277
                'm',
278
                'rm',
279
                'id',
280
                'count',
281
                'nodes',
282
            ],
283
            parent::__sleep()
284
        );
285
    }
286
287
    /**
288
     * Use the dynamic properties to instantiate the models we use.
289
     * @since $VID:$
290
     * @throws EE_Error
291
     * @throws InvalidArgumentException
292
     * @throws InvalidDataTypeException
293
     * @throws InvalidInterfaceException
294
     * @throws ReflectionException
295
     */
296
    public function __wakeup()
297
    {
298
        $this->main_model = EE_Registry::instance()->load_model($this->m);
299
        $this->related_model = EE_Registry::instance()->load_model($this->rm);
300
        parent::__wakeup();
301
    }
302
}
303
// End of file RelationNode.php
304
// Location: EventEspresso\core\services\orm\tree_traversal/RelationNode.php
305