Completed
Pull Request — master (#56)
by
unknown
04:05
created

AbstractNode::addChild()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 39
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 1
Metric Value
c 6
b 0
f 1
dl 0
loc 39
rs 8.439
cc 5
eloc 19
nc 5
nop 1
1
<?php
2
namespace PHPHtmlParser\Dom;
3
4
use PHPHtmlParser\Selector;
5
use PHPHtmlParser\Exceptions\ChildNotFoundException;
6
use PHPHtmlParser\Exceptions\CircularException;
7
use PHPHtmlParser\Exceptions\ParentNotFoundException;
8
use stringEncode\Encode;
9
10
/**
11
 * Dom node object.
12
 *
13
 * @property string outerhtml
14
 * @property string innerhtml
15
 * @property string text
16
 */
17
abstract class AbstractNode
18
{
19
20
    /**
21
     * Contains the tag name/type
22
     *
23
     * @var \PHPHtmlParser\Dom\Tag
24
     */
25
    protected $tag;
26
27
    /**
28
     * Contains a list of attributes on this tag.
29
     *
30
     * @var array
31
     */
32
    protected $attr = [];
33
34
    /**
35
     * An array of all the children.
36
     *
37
     * @var array
38
     */
39
    protected $children = [];
40
41
    /**
42
     * Contains the parent Node.
43
     *
44
     * @var AbstractNode
45
     */
46
    protected $parent = null;
47
48
    /**
49
     * The unique id of the class. Given by PHP.
50
     *
51
     * @var string
52
     */
53
    protected $id;
54
55
    /**
56
     * The encoding class used to encode strings.
57
     *
58
     * @var mixed
59
     */
60
    protected $encode;
61
62
    /**
63
     * Creates a unique spl hash for this node.
64
     */
65
    public function __construct()
66
    {
67
        $this->id = spl_object_hash($this);
68
    }
69
70
    /**
71
     * Magic get method for attributes and certain methods.
72
     *
73
     * @param string $key
74
     * @return mixed
75
     */
76
    public function __get($key)
77
    {
78
        // check attribute first
79
        if ( ! is_null($this->getAttribute($key))) {
80
            return $this->getAttribute($key);
81
        }
82
        switch (strtolower($key)) {
83
            case 'outerhtml':
84
                return $this->outerHtml();
85
            case 'innerhtml':
86
                return $this->innerHtml();
87
            case 'text':
88
                return $this->text();
89
        }
90
91
        return null;
92
    }
93
94
    /**
95
     * Attempts to clear out any object references.
96
     */
97
    public function __destruct()
98
    {
99
        $this->tag      = null;
100
        $this->attr     = [];
101
        $this->parent   = null;
102
        $this->children = [];
103
    }
104
105
    /**
106
     * Simply calls the outer text method.
107
     *
108
     * @return string
109
     */
110
    public function __toString()
111
    {
112
        return $this->outerHtml();
113
    }
114
115
    /**
116
     * Returns the id of this object.
117
     */
118
    public function id()
119
    {
120
        return $this->id;
121
    }
122
123
    /**
124
     * Returns the parent of node.
125
     *
126
     * @return AbstractNode
127
     */
128
    public function getParent()
129
    {
130
        return $this->parent;
131
    }
132
133
    /**
134
     * Sets the parent node.
135
     *
136
     * @param AbstractNode $parent
137
     * @return $this
138
     * @throws CircularException
139
     */
140
    public function setParent(AbstractNode $parent)
141
    {
142
        // check integrity
143
        if ($this->isDescendant($parent->id())) {
144
            throw new CircularException('Can not add descendant "'.$parent->id().'" as my parent.');
145
        }
146
147
        // remove from old parent
148
        if ( ! is_null($this->parent)) {
149
            if ($this->parent->id() == $parent->id()) {
150
                // already the parent
151
                return $this;
152
            }
153
154
            $this->parent->removeChild($this->id);
155
        }
156
157
        $this->parent = $parent;
158
159
        // assign child to parent
160
        $this->parent->addChild($this);
161
162
        //clear any cache
163
        $this->clear();
164
165
        return $this;
166
    }
167
168
    /**
169
     * Sets the encoding class to this node and propagates it
170
     * to all its children.
171
     *
172
     * @param Encode $encode
173
     */
174
    public function propagateEncoding(Encode $encode)
175
    {
176
        $this->encode = $encode;
177
        $this->tag->setEncoding($encode);
178
        // check children
179
        foreach ($this->children as $id => $child) {
180
            /** @var AbstractNode $node */
181
            $node = $child['node'];
182
            $node->propagateEncoding($encode);
183
        }
184
    }
185
186
    /**
187
     * Checks if this node has children.
188
     *
189
     * @return bool
190
     */
191
    public function hasChildren()
192
    {
193
        return ! empty($this->children);
194
    }
195
196
    /**
197
     * Returns the child by id.
198
     *
199
     * @param int $id
200
     * @return AbstractNode
201
     * @throws ChildNotFoundException
202
     */
203
    public function getChild($id)
204
    {
205
        if ( ! isset($this->children[$id])) {
206
            throw new ChildNotFoundException("Child '$id' not found in this node.");
207
        }
208
209
        return $this->children[$id]['node'];
210
    }
211
212
    /**
213
     * Returns a new array of child nodes
214
     *
215
     * @return array
216
     */
217
    public function getChildren()
218
    {
219
        $nodes = [];
220
        try {
221
            $child = $this->firstChild();
222
            do {
223
                $nodes[] = $child;
224
                $child   = $this->nextChild($child->id());
225
            } while ( ! is_null($child));
226
        } catch (ChildNotFoundException $e) {
227
            // we are done looking for children
228
        }
229
230
        return $nodes;
231
    }
232
233
    /**
234
     * Counts children
235
     *
236
     * @return int
237
     */
238
    public function countChildren()
239
    {
240
        return count($this->children);
241
    }
242
243
    /**
244
     * Adds a child node to this node and returns the id of the child for this
245
     * parent.
246
     *
247
     * @param AbstractNode $child
248
     * @return bool
249
     * @throws CircularException
250
     */
251
    public function addChild(AbstractNode $child)
252
    {
253
        $key = null;
254
255
        // check integrity
256
        if ($this->isAncestor($child->id())) {
257
            throw new CircularException('Can not add child. It is my ancestor.');
258
        }
259
260
        // check if child is itself
261
        if ($child->id() == $this->id) {
262
            throw new CircularException('Can not set itself as a child.');
263
        }
264
265
        if ($this->hasChildren()) {
266
            if (isset($this->children[$child->id()])) {
267
                // we already have this child
268
                return false;
269
            }
270
            $sibling                      = $this->lastChild();
271
            $key                          = $sibling->id();
272
            $this->children[$key]['next'] = $child->id();
273
        }
274
275
        // add the child
276
        $this->children[$child->id()] = [
277
            'node' => $child,
278
            'next' => null,
279
            'prev' => $key,
280
        ];
281
282
        // tell child I am the new parent
283
        $child->setParent($this);
284
285
        //clear any cache
286
        $this->clear();
287
288
        return true;
289
    }
290
291
    /**
292
     * Removes the child by id.
293
     *
294
     * @param int $id
295
     * @return $this
296
     */
297
    public function removeChild($id)
298
    {
299
        if ( ! isset($this->children[$id])) {
300
            return $this;
301
        }
302
303
        // handle moving next and previous assignments.
304
        $next = $this->children[$id]['next'];
305
        $prev = $this->children[$id]['prev'];
306
        if ( ! is_null($next)) {
307
            $this->children[$next]['prev'] = $prev;
308
        }
309
        if ( ! is_null($prev)) {
310
            $this->children[$prev]['next'] = $next;
311
        }
312
313
        // remove the child
314
        unset($this->children[$id]);
315
316
        //clear any cache
317
        $this->clear();
318
319
        return $this;
320
    }
321
322
    /**
323
     * Attempts to get the next child.
324
     *
325
     * @param int $id
326
     * @return AbstractNode
327
     * @uses $this->getChild()
328
     */
329 View Code Duplication
    public function nextChild($id)
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...
330
    {
331
        $child = $this->getChild($id);
332
        $next  = $this->children[$child->id()]['next'];
333
334
        return $this->getChild($next);
335
    }
336
337
    /**
338
     * Attempts to get the previous child.
339
     *
340
     * @param int $id
341
     * @return AbstractNode
342
     * @uses $this->getChild()
343
     */
344 View Code Duplication
    public function previousChild($id)
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...
345
    {
346
        $child = $this->getchild($id);
347
        $next  = $this->children[$child->id()]['prev'];
348
349
        return $this->getChild($next);
350
    }
351
352
    /**
353
     * Checks if the given node id is a child of the
354
     * current node.
355
     *
356
     * @param int $id
357
     * @return bool
358
     */
359
    public function isChild($id)
360
    {
361
        foreach ($this->children as $childId => $child) {
362
            if ($id == $childId) {
363
                return true;
364
            }
365
        }
366
367
        return false;
368
    }
369
370
    /**
371
     * Checks if the given node id is a descendant of the
372
     * current node.
373
     *
374
     * @param int $id
375
     * @return bool
376
     */
377
    public function isDescendant($id)
378
    {
379
        if ($this->isChild($id)) {
380
            return true;
381
        }
382
383
        foreach ($this->children as $childId => $child) {
384
            /** @var AbstractNode $node */
385
            $node = $child['node'];
386
            if ($node->hasChildren() &&
387
                $node->isDescendant($id)
388
            ) {
389
                return true;
390
            }
391
        }
392
393
        return false;
394
    }
395
396
    /**
397
     * Checks if the given node id is an ancestor of
398
     * the current node.
399
     *
400
     * @param int $id
401
     * @return bool
402
     */
403 View Code Duplication
    public function isAncestor($id)
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...
404
    {
405
        if ( ! is_null($this->parent)) {
406
            if ($this->parent->id() == $id) {
407
                return true;
408
            }
409
410
            return $this->parent->isAncestor($id);
411
        }
412
413
        return false;
414
    }
415
416
    /**
417
     * Attempts to get an ancestor node by the given id.
418
     *
419
     * @param int $id
420
     * @return null|AbstractNode
421
     */
422 View Code Duplication
    public function getAncestor($id)
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...
423
    {
424
        if ( ! is_null($this->parent)) {
425
            if ($this->parent->id() == $id) {
426
                return $this->parent;
427
            }
428
429
            return $this->parent->getAncestor($id);
430
        }
431
432
        return null;
433
    }
434
435
    /**
436
     * Shortcut to return the first child.
437
     *
438
     * @return AbstractNode
439
     * @uses $this->getChild()
440
     */
441
    public function firstChild()
442
    {
443
        reset($this->children);
444
        $key = key($this->children);
445
446
        return $this->getChild($key);
447
    }
448
449
    /**
450
     * Attempts to get the last child.
451
     *
452
     * @return AbstractNode
453
     */
454
    public function lastChild()
455
    {
456
        end($this->children);
457
        $key = key($this->children);
458
459
        return $this->getChild($key);
460
    }
461
462
    /**
463
     * Attempts to get the next sibling.
464
     *
465
     * @return AbstractNode
466
     * @throws ParentNotFoundException
467
     */
468
    public function nextSibling()
469
    {
470
        if (is_null($this->parent)) {
471
            throw new ParentNotFoundException('Parent is not set for this node.');
472
        }
473
474
        return $this->parent->nextChild($this->id);
475
    }
476
477
    /**
478
     * Attempts to get the previous sibling
479
     *
480
     * @return AbstractNode
481
     * @throws ParentNotFoundException
482
     */
483
    public function previousSibling()
484
    {
485
        if (is_null($this->parent)) {
486
            throw new ParentNotFoundException('Parent is not set for this node.');
487
        }
488
489
        return $this->parent->previousChild($this->id);
490
    }
491
492
    /**
493
     * Gets the tag object of this node.
494
     *
495
     * @return Tag
496
     */
497
    public function getTag()
498
    {
499
        return $this->tag;
500
    }
501
502
    /**
503
     * A wrapper method that simply calls the getAttribute method
504
     * on the tag of this node.
505
     *
506
     * @return array
507
     */
508
    public function getAttributes()
509
    {
510
        $attributes = $this->tag->getAttributes();
511
        foreach ($attributes as $name => $info) {
512
            $attributes[$name] = $info['value'];
513
        }
514
515
        return $attributes;
516
    }
517
518
    /**
519
     * A wrapper method that simply calls the getAttribute method
520
     * on the tag of this node.
521
     *
522
     * @param string $key
523
     * @return mixed
524
     */
525
    public function getAttribute($key)
526
    {
527
        $attribute = $this->tag->getAttribute($key);
528
        if ( ! is_null($attribute)) {
529
            $attribute = $attribute['value'];
530
        }
531
532
        return $attribute;
533
    }
534
535
    /**
536
     * A wrapper method that simply calls the setAttribute method
537
     * on the tag of this node.
538
     *
539
     * @param string $key
540
     * @param string $value
541
     * @return $this
542
     */
543
    public function setAttribute($key, $value)
544
    {
545
        $this->tag->setAttribute($key, $value);
546
547
        return $this;
548
    }
549
550
    /**
551
     * A wrapper method that simply calls the removeAttribute method
552
     * on the tag of this node.
553
     *
554
     * @param string $key
555
     * @return $this
556
     */
557
    public function removeAttribute($key)
558
    {
559
        $this->tag->removeAttribute($key);
560
561
        return $this;
562
    }
563
564
    /**
565
     * A wrapper method that simply calls the removeAttributes method
566
     * on the tag of this node.
567
     *
568
     * @param array $keys
569
     * @return $this
570
     */
571
    public function removeAttributes($keys)
572
    {
573
        $this->tag->removeAttributes($keys);
574
575
        return $this;
576
    }
577
578
    /**
579
     * Function to locate a specific ancestor tag in the path to the root.
580
     *
581
     * @param  string $tag
582
     * @return AbstractNode
583
     * @throws ParentNotFoundException
584
     */
585
    public function ancestorByTag($tag)
586
    {
587
        // Start by including ourselves in the comparison.
588
        $node = $this;
589
590
        while ( ! is_null($node)) {
591
            if ($node->tag->name() == $tag) {
592
                return $node;
593
            }
594
595
            $node = $node->getParent();
596
        }
597
598
        throw new ParentNotFoundException('Could not find an ancestor with "'.$tag.'" tag');
599
    }
600
601
    /**
602
     * Find elements by css selector
603
     *
604
     * @param string $selector
605
     * @param int $nth
606
     * @return array|AbstractNode
607
     */
608
    public function find($selector, $nth = null)
609
    {
610
        $selector = new Selector($selector);
611
        $nodes    = $selector->find($this);
612
613
        if ( ! is_null($nth)) {
614
            // return nth-element or array
615
            if (isset($nodes[$nth])) {
616
                return $nodes[$nth];
617
            }
618
619
            return null;
620
        }
621
622
        return $nodes;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $nodes; (PHPHtmlParser\Dom\Collection) is incompatible with the return type documented by PHPHtmlParser\Dom\AbstractNode::find of type array|PHPHtmlParser\Dom\AbstractNode.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
623
    }
624
625
    /**
626
     * Function to try a few tricks to determine the displayed size of an img on the page.
627
     * NOTE: This will ONLY work on an IMG tag. Returns FALSE on all other tag types.
628
     *
629
     * Future enhancement:
630
     * Look in the tag to see if there is a class or id specified that has a height or width attribute to it.
631
     *
632
     * Far future enhancement
633
     * Look at all the parent tags of this image to see if they specify a class or id that has an img selector that specifies a height or width
634
     * Note that in this case, the class or id will have the img sub-selector for it to apply to the image.
635
     *
636
     * ridiculously far future development
637
     * If the class or id is specified in a SEPARATE css file that's not on the page, go get it and do what we were just doing for the ones on the page.
638
     *
639
     * @author John Schlick
640
     * @return array an array containing the 'height' and 'width' of the image on the page or -1 if we can't figure it out.
641
     */
642
    public function get_display_size()
643
    {
644
        $width  = -1;
645
        $height = -1;
646
647
        if ($this->tag->name() != 'img') {
648
            return false;
649
        }
650
651
        // See if there is a height or width attribute in the tag itself.
652
        if ( ! is_null($this->tag->getAttribute('width'))) {
653
            $width = $this->tag->getAttribute('width');
654
        }
655
656
        if ( ! is_null($this->tag->getAttribute('height'))) {
657
            $height = $this->tag->getAttribute('height');
658
        }
659
660
        // Now look for an inline style.
661
        if ( ! is_null($this->tag->getAttribute('style'))) {
662
            // Thanks to user 'gnarf' from stackoverflow for this regular expression.
663
            $attributes = [];
664
            preg_match_all("/([\w-]+)\s*:\s*([^;]+)\s*;?/", $this->tag->getAttribute('style'), $matches,
665
                PREG_SET_ORDER);
666
            foreach ($matches as $match) {
0 ignored issues
show
Bug introduced by
The expression $matches of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
667
                $attributes[$match[1]] = $match[2];
668
            }
669
670
            // If there is a width in the style attributes:
671 View Code Duplication
            if (isset($attributes['width']) and $width == -1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
672
                // check that the last two characters are px (pixels)
673
                if (strtolower(substr($attributes['width'], -2)) == 'px') {
674
                    $proposed_width = substr($attributes['width'], 0, -2);
675
                    // Now make sure that it's an integer and not something stupid.
676
                    if (filter_var($proposed_width, FILTER_VALIDATE_INT)) {
677
                        $width = $proposed_width;
678
                    }
679
                }
680
            }
681
682
            // If there is a width in the style attributes:
683 View Code Duplication
            if (isset($attributes['height']) and $height == -1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
684
                // check that the last two characters are px (pixels)
685
                if (strtolower(substr($attributes['height'], -2)) == 'px') {
686
                    $proposed_height = substr($attributes['height'], 0, -2);
687
                    // Now make sure that it's an integer and not something stupid.
688
                    if (filter_var($proposed_height, FILTER_VALIDATE_INT)) {
689
                        $height = $proposed_height;
690
                    }
691
                }
692
            }
693
694
        }
695
696
        $result = [
697
            'height' => $height,
698
            'width'  => $width,
699
        ];
700
701
        return $result;
702
    }
703
704
    /**
705
     * Gets the inner html of this node.
706
     *
707
     * @return string
708
     */
709
    abstract public function innerHtml();
710
711
    /**
712
     * Gets the html of this node, including it's own
713
     * tag.
714
     *
715
     * @return string
716
     */
717
    abstract public function outerHtml();
718
719
    /**
720
     * Gets the text of this node (if there is any text).
721
     *
722
     * @return string
723
     */
724
    abstract public function text();
725
726
    /**
727
     * Call this when something in the node tree has changed. Like a child has been added
728
     * or a parent has been changed.
729
     *
730
     * @return void
731
     */
732
    abstract protected function clear();
733
}
734