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

InnerNode::replaceChild()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 2 Features 1
Metric Value
c 4
b 2
f 1
dl 0
loc 18
rs 8.8571
cc 5
eloc 12
nc 4
nop 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
        $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...
240
        $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...
241
        unset($oldChild['node']);
242
        $oldChild['node'] = $newChild;
243
        unset($this->children[$childId]);
244
        $this->children[$newChild->id()] = $oldChild;
245
246
        if ($newChild->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...
247
            $this->children[$newChild->prev]['next'] = $newChild->id();
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...
248
        }
249
250
        if ($newChild->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...
251
            $this->children[$newChild->next]['prev'] = $newChild->id();
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...
252
        }
253
    }
254
255
    /**
256
     * Shortcut to return the first child.
257
     *
258
     * @return AbstractNode
259
     * @uses $this->getChild()
260
     */
261
    public function firstChild()
262
    {
263
        reset($this->children);
264
        $key = key($this->children);
265
266
        return $this->getChild($key);
267
    }
268
269
    /**
270
     * Attempts to get the last child.
271
     *
272
     * @return AbstractNode
273
     */
274
    public function lastChild()
275
    {
276
        end($this->children);
277
        $key = key($this->children);
278
279
        return $this->getChild($key);
280
    }
281
282
    /**
283
     * Checks if the given node id is a descendant of the
284
     * current node.
285
     *
286
     * @param int $id
287
     * @return bool
288
     */
289
    public function isDescendant($id)
290
    {
291
        if ($this->isChild($id)) {
292
            return true;
293
        }
294
295
        foreach ($this->children as $childId => $child) {
296
            /** @var InnerNode $node */
297
            $node = $child['node'];
298
            if ($node instanceof InnerNode &&
299
                $node->hasChildren() &&
300
                $node->isDescendant($id)
301
            ) {
302
                return true;
303
            }
304
        }
305
306
        return false;
307
    }
308
309
    /**
310
     * Sets the parent node.
311
     *
312
     * @param InnerNode $parent
313
     * @return $this
314
     * @throws CircularException
315
     */
316
    public function setParent(InnerNode $parent)
317
    {
318
        // check integrity
319
        if ($this->isDescendant($parent->id())) {
320
            throw new CircularException('Can not add descendant "'.$parent->id().'" as my parent.');
321
        }
322
323
        return parent::setParent($parent);
324
    }
325
}