Completed
Pull Request — master (#70)
by
unknown
03:52
created

InnerNode   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 320
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 7
Bugs 4 Features 2
Metric Value
wmc 38
c 7
b 4
f 2
lcom 1
cbo 5
dl 0
loc 320
rs 8.3999

15 Methods

Rating   Name   Duplication   Size   Complexity  
A propagateEncoding() 0 11 2
A hasChildren() 0 4 1
A getChild() 0 8 2
A getChildren() 0 15 3
A countChildren() 0 4 1
B addChild() 0 39 5
B removeChild() 0 24 4
A isChild() 0 10 3
A nextChild() 0 7 1
A previousChild() 0 7 1
B replaceChild() 0 25 5
A firstChild() 0 7 1
A lastChild() 0 7 1
B isDescendant() 0 19 6
A setParent() 0 9 2
1
<?php
2
namespace PHPHtmlParser\Dom;
3
4
use PHPHtmlParser\Exceptions\ChildNotFoundException;
5
use PHPHtmlParser\Exceptions\CircularException;
6
use stringEncode\Encode;
7
8
/**
9
 * Inner node of the html tree, might have children.
10
 *
11
 * @package PHPHtmlParser\Dom
12
 */
13
abstract class InnerNode extends ArrayNode
14
{
15
16
    /**
17
     * An array of all the children.
18
     *
19
     * @var array
20
     */
21
    protected $children = [];
22
23
    /**
24
     * Sets the encoding class to this node and propagates it
25
     * to all its children.
26
     *
27
     * @param Encode $encode
28
     * @return void
29
     */
30
    public function propagateEncoding(Encode $encode)
31
    {
32
        $this->encode = $encode;
33
        $this->tag->setEncoding($encode);
34
        // check children
35
        foreach ($this->children as $id => $child) {
36
            /** @var AbstractNode $node */
37
            $node = $child['node'];
38
            $node->propagateEncoding($encode);
39
        }
40
    }
41
42
    /**
43
     * Checks if this node has children.
44
     *
45
     * @return bool
46
     */
47
    public function hasChildren()
48
    {
49
        return ! empty($this->children);
50
    }
51
52
    /**
53
     * Returns the child by id.
54
     *
55
     * @param int $id
56
     * @return AbstractNode
57
     * @throws ChildNotFoundException
58
     */
59
    public function getChild($id)
60
    {
61
        if ( ! isset($this->children[$id])) {
62
            throw new ChildNotFoundException("Child '$id' not found in this node.");
63
        }
64
65
        return $this->children[$id]['node'];
66
    }
67
68
    /**
69
     * Returns a new array of child nodes
70
     *
71
     * @return array
72
     */
73
    public function getChildren()
74
    {
75
        $nodes = [];
76
        try {
77
            $child = $this->firstChild();
78
            do {
79
                $nodes[] = $child;
80
                $child   = $this->nextChild($child->id());
81
            } while ( ! is_null($child));
82
        } catch (ChildNotFoundException $e) {
83
            // we are done looking for children
84
        }
85
86
        return $nodes;
87
    }
88
89
    /**
90
     * Counts children
91
     *
92
     * @return int
93
     */
94
    public function countChildren()
95
    {
96
        return count($this->children);
97
    }
98
99
    /**
100
     * Adds a child node to this node and returns the id of the child for this
101
     * parent.
102
     *
103
     * @param AbstractNode $child
104
     * @return bool
105
     * @throws CircularException
106
     */
107
    public function addChild(AbstractNode $child)
108
    {
109
        $key = null;
110
111
        // check integrity
112
        if ($this->isAncestor($child->id())) {
113
            throw new CircularException('Can not add child. It is my ancestor.');
114
        }
115
116
        // check if child is itself
117
        if ($child->id() == $this->id) {
118
            throw new CircularException('Can not set itself as a child.');
119
        }
120
121
        if ($this->hasChildren()) {
122
            if (isset($this->children[$child->id()])) {
123
                // we already have this child
124
                return false;
125
            }
126
            $sibling                      = $this->lastChild();
127
            $key                          = $sibling->id();
128
            $this->children[$key]['next'] = $child->id();
129
        }
130
131
        // add the child
132
        $this->children[$child->id()] = [
133
            'node' => $child,
134
            'next' => null,
135
            'prev' => $key,
136
        ];
137
138
        // tell child I am the new parent
139
        $child->setParent($this);
140
141
        //clear any cache
142
        $this->clear();
143
144
        return true;
145
    }
146
147
    /**
148
     * Removes the child by id.
149
     *
150
     * @param int $id
151
     * @return $this
152
     */
153
    public function removeChild($id)
154
    {
155
        if ( ! isset($this->children[$id])) {
156
            return $this;
157
        }
158
159
        // handle moving next and previous assignments.
160
        $next = $this->children[$id]['next'];
161
        $prev = $this->children[$id]['prev'];
162
        if ( ! is_null($next)) {
163
            $this->children[$next]['prev'] = $prev;
164
        }
165
        if ( ! is_null($prev)) {
166
            $this->children[$prev]['next'] = $next;
167
        }
168
169
        // remove the child
170
        unset($this->children[$id]);
171
172
        //clear any cache
173
        $this->clear();
174
175
        return $this;
176
    }
177
178
    /**
179
     * Attempts to get the next child.
180
     *
181
     * @param int $id
182
     * @return AbstractNode
183
     * @uses $this->getChild()
184
     * @throws ChildNotFoundException
185
     */
186
    public function nextChild($id)
187
    {
188
        $child = $this->getChild($id);
189
        $next  = $this->children[$child->id()]['next'];
190
191
        return $this->getChild($next);
192
    }
193
194
    /**
195
     * Attempts to get the previous child.
196
     *
197
     * @param int $id
198
     * @return AbstractNode
199
     * @uses $this->getChild()
200
     * @throws ChildNotFoundException
201
     */
202
    public function previousChild($id)
203
    {
204
        $child = $this->getchild($id);
205
        $next  = $this->children[$child->id()]['prev'];
206
207
        return $this->getChild($next);
208
    }
209
210
    /**
211
     * Checks if the given node id is a child of the
212
     * current node.
213
     *
214
     * @param int $id
215
     * @return bool
216
     */
217
    public function isChild($id)
218
    {
219
        foreach ($this->children as $childId => $child) {
220
            if ($id == $childId) {
221
                return true;
222
            }
223
        }
224
225
        return false;
226
    }
227
228
    /**
229
     * Removes the child with id $childId and replace it with the new child
230
     * $newChild.
231
     *
232
     * @param int $childId
233
     * @param AbstractNode $newChild
234
     * @throws ChildNotFoundException
235
     */
236
    public function replaceChild($childId, AbstractNode $newChild)
237
    {
238
        $oldChild = $this->children[$childId];
239
240
        $newChild->prev = $oldChild['prev'];
0 ignored issues
show
Bug introduced by
The property prev does not seem to exist in PHPHtmlParser\Dom\AbstractNode.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
241
        $newChild->next = $oldChild['next'];
0 ignored issues
show
Bug introduced by
The property next does not seem to exist in PHPHtmlParser\Dom\AbstractNode.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
242
243
        $keys = array_keys($this->children);
244
        $index = array_search($childId, $keys, true);
245
        $keys[$index] = $newChild->id();
246
        $this->children = array_combine($keys, $this->children);
247
        $this->children[$newChild->id()] = array(
248
            'prev' => $oldChild['prev'],
249
            'node' => $newChild,
250
            'next' => $oldChild['next']
251
        );
252
253
        if ($oldChild['prev'] && isset($this->children[$newChild->prev])) {
0 ignored issues
show
Documentation introduced by
The property prev does not exist on object<PHPHtmlParser\Dom\AbstractNode>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
254
            $this->children[$oldChild['prev']]['next'] = $newChild->id();
255
        }
256
257
        if ($oldChild['next'] && isset($this->children[$newChild->next])) {
0 ignored issues
show
Documentation introduced by
The property next does not exist on object<PHPHtmlParser\Dom\AbstractNode>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
258
            $this->children[$oldChild['next']]['prev'] = $newChild->id();
259
        }
260
    }
261
262
    /**
263
     * Shortcut to return the first child.
264
     *
265
     * @return AbstractNode
266
     * @uses $this->getChild()
267
     */
268
    public function firstChild()
269
    {
270
        reset($this->children);
271
        $key = key($this->children);
272
273
        return $this->getChild($key);
274
    }
275
276
    /**
277
     * Attempts to get the last child.
278
     *
279
     * @return AbstractNode
280
     */
281
    public function lastChild()
282
    {
283
        end($this->children);
284
        $key = key($this->children);
285
286
        return $this->getChild($key);
287
    }
288
289
    /**
290
     * Checks if the given node id is a descendant of the
291
     * current node.
292
     *
293
     * @param int $id
294
     * @return bool
295
     */
296
    public function isDescendant($id)
297
    {
298
        if ($this->isChild($id)) {
299
            return true;
300
        }
301
302
        foreach ($this->children as $childId => $child) {
303
            /** @var InnerNode $node */
304
            $node = $child['node'];
305
            if ($node instanceof InnerNode &&
306
                $node->hasChildren() &&
307
                $node->isDescendant($id)
308
            ) {
309
                return true;
310
            }
311
        }
312
313
        return false;
314
    }
315
316
    /**
317
     * Sets the parent node.
318
     *
319
     * @param InnerNode $parent
320
     * @return $this
321
     * @throws CircularException
322
     */
323
    public function setParent(InnerNode $parent)
324
    {
325
        // check integrity
326
        if ($this->isDescendant($parent->id())) {
327
            throw new CircularException('Can not add descendant "'.$parent->id().'" as my parent.');
328
        }
329
330
        return parent::setParent($parent);
331
    }
332
}