Completed
Branch CASC/base (79f9d1)
by
unknown
19:48 queued 09:56
created

ModelObjNode::getIds()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 2
nop 0
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
1
<?php
2
3
namespace EventEspresso\core\services\orm\tree_traversal;
4
5
use EE_HABTM_Relation;
6
use EE_Has_Many_Relation;
7
use EE_Registry;
8
use EEM_Base;
9
use EventEspresso\core\exceptions\InvalidDataTypeException;
10
use EventEspresso\core\exceptions\InvalidInterfaceException;
11
use InvalidArgumentException;
12
use ReflectionException;
13
14
/**
15
 * Class ModelObjNode
16
 * Wraps a model object and stores which of its model's relations have already been traversed and which haven't.
17
 *
18
 * @package     Event Espresso
19
 * @author         Mike Nelson
20
 * @since         $VID:$
21
 *
22
 */
23
class ModelObjNode extends BaseNode
24
{
25
    /**
26
     * @var int|string
27
     */
28
    protected $id;
29
30
    /**
31
     * @var EEM_Base
32
     */
33
    protected $model;
34
35
    /**
36
     * @var RelationNode[]
37
     */
38
    protected $nodes;
39
40
    /**
41
     * We don't pass the model objects because this needs to serialize to something tiny for effiency.
42
     * @param $model_obj_id
43
     * @param EEM_Base $model
44
     * @param array $dont_traverse_models array of model names we DON'T want to traverse.
45
     */
46
    public function __construct($model_obj_id, EEM_Base $model, array $dont_traverse_models = [])
47
    {
48
        $this->id = $model_obj_id;
49
        $this->model = $model;
50
        $this->dont_traverse_models = $dont_traverse_models;
51
    }
52
53
    /**
54
     * Creates a relation node for each relation of this model's relations.
55
     * Does NOT call `discover` on them yet though.
56
     * @since $VID:$
57
     * @throws \EE_Error
58
     * @throws InvalidDataTypeException
59
     * @throws InvalidInterfaceException
60
     * @throws InvalidArgumentException
61
     * @throws ReflectionException
62
     */
63
    protected function discover()
64
    {
65
        $this->nodes = [];
66
        foreach ($this->model->relation_settings() as $relationName => $relation) {
67
            // Make sure this isn't one of the models we were told to not traverse into.
68
            if (in_array($relationName, $this->dont_traverse_models)) {
69
                continue;
70
            }
71
            if ($relation instanceof EE_Has_Many_Relation) {
72
                $this->nodes[ $relationName ] = new RelationNode(
73
                    $this->id,
74
                    $this->model,
75
                    $relation->get_other_model(),
0 ignored issues
show
Bug introduced by
It seems like $relation->get_other_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...
76
                    $this->dont_traverse_models
77
                );
78
            } elseif ($relation instanceof EE_HABTM_Relation &&
79
                ! in_array(
80
                    $relation->get_join_model()->get_this_model_name(),
81
                    $this->dont_traverse_models
82
                )) {
83
                $this->nodes[ $relation->get_join_model()->get_this_model_name() ] = new RelationNode(
84
                    $this->id,
85
                    $this->model,
86
                    $relation->get_join_model(),
0 ignored issues
show
Bug introduced by
It seems like $relation->get_join_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...
87
                    $this->dont_traverse_models
88
                );
89
            }
90
        }
91
        ksort($this->nodes);
92
    }
93
94
95
    /**
96
     * Whether this item has already been initialized
97
     */
98
    protected function isDiscovered()
99
    {
100
        return $this->nodes !== null && is_array($this->nodes);
101
    }
102
103
    /**
104
     * @since $VID:$
105
     * @return boolean
106
     */
107
    public function isComplete()
108
    {
109
        if ($this->complete === null) {
110
            $this->complete = false;
111
        }
112
        return $this->complete;
113
    }
114
115
    /**
116
     * Triggers working on each child relation node that has work to do.
117
     * @since $VID:$
118
     * @param $model_objects_to_identify
119
     * @return int units of work done
120
     */
121
    protected function work($model_objects_to_identify)
122
    {
123
        $num_identified = 0;
124
        // Begin assuming we'll finish all the work on this node and its children...
125
        $this->complete = true;
126
        foreach ($this->nodes as $model_name => $relation_node) {
127
            $num_identified += $relation_node->visit($model_objects_to_identify - $num_identified);
128
            // To save on space when serializing, only bother keeping a record of relation nodes that actually found
129
            // related model objects.
130
            if ($relation_node->isComplete() && $relation_node->countSubNodes() === 0) {
131
                unset($this->nodes[ $model_name ]);
132
            }
133
            if ($num_identified >= $model_objects_to_identify) {
134
                // ...but admit we're wrong if the work exceeded the budget.
135
                $this->complete = false;
136
                break;
137
            }
138
        }
139
        return $num_identified;
140
    }
141
142
    /**
143
     * @since $VID:$
144
     * @return array
145
     * @throws \EE_Error
146
     * @throws InvalidDataTypeException
147
     * @throws InvalidInterfaceException
148
     * @throws InvalidArgumentException
149
     * @throws ReflectionException
150
     */
151
    public function toArray()
152
    {
153
        $tree = [
154
            'id' => $this->id,
155
            'complete' => $this->isComplete(),
156
            'rels' => []
157
        ];
158
        if ($this->nodes === null) {
159
            $tree['rels'] = null;
160
        } else {
161
            foreach ($this->nodes as $relation_name => $relation_node) {
162
                $tree['rels'][ $relation_name ] = $relation_node->toArray();
163
            }
164
        }
165
        return $tree;
166
    }
167
168
    /**
169
     * @since $VID:$
170
     * @return array|mixed
171
     * @throws InvalidArgumentException
172
     * @throws InvalidDataTypeException
173
     * @throws InvalidInterfaceException
174
     * @throws ReflectionException
175
     * @throws \EE_Error
176
     */
177
    public function getIds()
178
    {
179
        $ids = [
180
            $this->model->get_this_model_name() => [
181
                $this->id => $this->id
182
            ]
183
        ];
184
        if ($this->nodes && is_array($this->nodes)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->nodes of type EventEspresso\core\servi...raversal\RelationNode[] 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...
185
            foreach ($this->nodes as $relation_node) {
186
                $ids = array_replace_recursive($ids, $relation_node->getIds());
187
            }
188
        }
189
        return $ids;
190
    }
191
192
    /**
193
     * Don't serialize the models. Just record their names on some dynamic properties.
194
     * @since $VID:$
195
     */
196
    public function __sleep()
197
    {
198
        $this->m = $this->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...
199
        return array_merge(
200
            [
201
                'm',
202
                'id',
203
                'nodes',
204
            ],
205
            parent::__sleep()
206
        );
207
    }
208
209
    /**
210
     * Use the dynamic properties to instantiate the models we use.
211
     * @since $VID:$
212
     * @throws EE_Error
213
     * @throws InvalidArgumentException
214
     * @throws InvalidDataTypeException
215
     * @throws InvalidInterfaceException
216
     * @throws ReflectionException
217
     */
218
    public function __wakeup()
219
    {
220
        $this->model = EE_Registry::instance()->load_model($this->m);
221
        parent::__wakeup();
222
    }
223
}
224
// End of file Visitor.php
225
// Location: EventEspresso\core\services\orm\tree_traversal/Visitor.php
226