StackOverflowWebPageHandler   B
last analyzed

Complexity

Total Complexity 49

Size/Duplication

Total Lines 210
Duplicated Lines 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
eloc 84
dl 0
loc 210
rs 8.48
c 4
b 1
f 0
wmc 49

12 Methods

Rating   Name   Duplication   Size   Complexity  
A read() 0 13 2
A getResults() 0 3 1
A processListResults() 0 10 4
A processDivBlock() 0 13 3
A processImageBlock() 0 12 4
A processChildNodes() 0 22 6
A setBasicAttributes() 0 21 6
A setCompanyAndLocation() 0 13 4
B processH2AndH3Elements() 0 22 7
A processImageDivBlocks() 0 14 4
A refineCompanyName() 0 12 3
A processExtraChildNodes() 0 21 5

How to fix   Complexity   

Complex Class

Complex classes like StackOverflowWebPageHandler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use StackOverflowWebPageHandler, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Coco\SourceWatcher\Vendors\StackOverflow;
4
5
use Coco\SourceWatcher\Watcher\Handler\WebPageHandler;
6
use DOMElement;
7
use DOMNamedNodeMap;
8
use DOMNodeList;
9
use DOMXPath;
10
11
/**
12
 * Class StackOverflowWebPageHandler
13
 *
14
 * @package Coco\SourceWatcher\Vendors\StackOverflow
15
 */
16
class StackOverflowWebPageHandler extends WebPageHandler
17
{
18
    private array $results = [];
19
20
    public function getResults () : array
21
    {
22
        return $this->results;
23
    }
24
25
    public function read () : void
26
    {
27
        parent::read();
28
29
        $this->results = [];
30
31
        $finder = new DomXPath( $this->dom );
32
33
        $classname = "listResults";
34
        $listResultsDom = $finder->query( "//*[contains(@class, '$classname')]" ); // DOMNodeList
35
36
        for ( $i = 0; $i < $listResultsDom->count(); $i++ ) {
37
            $this->processListResults( $listResultsDom->item( $i ) );
38
        }
39
    }
40
41
    private function processListResults ( DOMElement $currentDomNode ) : void
42
    {
43
        if ( $currentDomNode->hasChildNodes() ) {
44
            $children = $currentDomNode->childNodes; // DOMNodeList
45
46
            for ( $i = 0; $i < $children->count(); $i++ ) {
47
                $currentChildrenNode = $children->item( $i );
48
49
                if ( $currentChildrenNode instanceof DOMElement ) {
50
                    $this->processChildNodes( $currentChildrenNode );
51
                }
52
            }
53
        }
54
    }
55
56
    public static string $JOB_SEPARATOR = "You might be interested in these jobs:";
57
58
    private function processChildNodes ( DOMElement $currentChildrenNode ) : void
59
    {
60
        $currentJob = new StackOverflowJob();
61
62
        $currentJob = $this->setBasicAttributes( $currentChildrenNode->attributes, $currentJob );
0 ignored issues
show
Bug introduced by
It seems like $currentChildrenNode->attributes can also be of type null; however, parameter $attributes of Coco\SourceWatcher\Vendo...r::setBasicAttributes() does only seem to accept DOMNamedNodeMap, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

62
        $currentJob = $this->setBasicAttributes( /** @scrutinizer ignore-type */ $currentChildrenNode->attributes, $currentJob );
Loading history...
63
64
        if ( $currentChildrenNode->hasChildNodes() ) {
65
            if ( trim( $currentChildrenNode->nodeValue ) != StackOverflowWebPageHandler::$JOB_SEPARATOR ) {
66
                $extraChildNodes = $currentChildrenNode->childNodes; // DOMNodeList
67
68
                for ( $i = 0; $i < $extraChildNodes->count(); $i++ ) {
69
                    $currentExtraChildNode = $extraChildNodes->item( $i ); // DOMElement or DOMText
70
71
                    if ( $currentExtraChildNode instanceof DOMElement ) {
72
                        $currentJob = $this->processExtraChildNodes( $currentExtraChildNode, $currentJob );
73
                    }
74
                }
75
            }
76
        }
77
78
        if ( $currentJob->allAttributesDefined() ) {
79
            array_push( $this->results, $currentJob );
80
        }
81
    }
82
83
    private function setBasicAttributes (
84
        DOMNamedNodeMap $attributes,
85
        StackOverflowJob $stackOverflowJob
86
    ) : StackOverflowJob {
87
        if ( $attributes != null ) {
88
            foreach ( $attributes as $currentAttribute ) {
89
                if ( $currentAttribute->name == "data-jobid" ) {
90
                    $stackOverflowJob->setJobId( $currentAttribute->value );
91
                }
92
93
                if ( $currentAttribute->name == "data-result-id" ) {
94
                    $stackOverflowJob->setResultId( $currentAttribute->value );
95
                }
96
97
                if ( $currentAttribute->name == "data-preview-url" ) {
98
                    $stackOverflowJob->setPreviewUrl( $currentAttribute->value );
99
                }
100
            }
101
        }
102
103
        return $stackOverflowJob;
104
    }
105
106
    private function processExtraChildNodes (
107
        DOMElement $currentExtraChildNode,
108
        StackOverflowJob $stackOverflowJob
109
    ) : StackOverflowJob {
110
        if ( $currentExtraChildNode->hasChildNodes() ) {
111
            $currentExtraChildNodeChildren = $currentExtraChildNode->childNodes; // DOMNodeList
112
113
            $nodeCount = $currentExtraChildNodeChildren->count();
114
115
            if ( $nodeCount >= 6 ) {
116
                for ( $i = 0; $i < $nodeCount; $i++ ) {
117
                    $currentDeepNode = $currentExtraChildNodeChildren->item( $i ); // DOMElement or DOMText
118
119
                    if ( $currentDeepNode instanceof DOMElement ) {
120
                        $stackOverflowJob = $this->processImageDivBlocks( $currentDeepNode, $stackOverflowJob );
121
                    }
122
                }
123
            }
124
        }
125
126
        return $stackOverflowJob;
127
    }
128
129
    private function processImageDivBlocks (
130
        DOMElement $currentDeepNode,
131
        StackOverflowJob $stackOverflowJob
132
    ) : StackOverflowJob {
133
        if ( $currentDeepNode->childNodes->count() == 2 ) {
134
            $stackOverflowJob = $this->processImageBlock( $currentDeepNode->childNodes->item( 1 )->attributes,
0 ignored issues
show
Bug introduced by
It seems like $currentDeepNode->childNodes->item(1)->attributes can also be of type null; however, parameter $currentDeepNodeAttributes of Coco\SourceWatcher\Vendo...er::processImageBlock() does only seem to accept DOMNamedNodeMap, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

134
            $stackOverflowJob = $this->processImageBlock( /** @scrutinizer ignore-type */ $currentDeepNode->childNodes->item( 1 )->attributes,
Loading history...
135
                $stackOverflowJob );
136
        }
137
138
        if ( $currentDeepNode->tagName == "div" && $currentDeepNode->hasChildNodes() ) {
139
            $stackOverflowJob = $this->processDivBlock( $currentDeepNode->childNodes, $stackOverflowJob );
140
        }
141
142
        return $stackOverflowJob;
143
    }
144
145
    private function processImageBlock (
146
        DOMNamedNodeMap $currentDeepNodeAttributes,
147
        StackOverflowJob $stackOverflowJob
148
    ) : StackOverflowJob {
149
        if ( $currentDeepNodeAttributes != null && sizeof( $currentDeepNodeAttributes ) == 2 ) {
150
            $attr1 = $currentDeepNodeAttributes[0]; // DOMAttr
151
            $attr2 = $currentDeepNodeAttributes[1]; // DOMAttr
152
153
            $stackOverflowJob->setLogo( $attr1->name == "src" ? $attr1->value : $attr2->value );
154
        }
155
156
        return $stackOverflowJob;
157
    }
158
159
    private function processDivBlock (
160
        DOMNodeList $currentDeepNodeChildren,
161
        StackOverflowJob $stackOverflowJob
162
    ) : StackOverflowJob {
163
        for ( $i = 0; $i < $currentDeepNodeChildren->count(); $i++ ) {
164
            $currentDeepNodeChildrenElement = $currentDeepNodeChildren->item( $i ); // DOMElement or DOMText
165
166
            if ( $currentDeepNodeChildrenElement instanceof DOMElement ) {
167
                $stackOverflowJob = $this->processH2AndH3Elements( $currentDeepNodeChildrenElement, $stackOverflowJob );
168
            }
169
        }
170
171
        return $stackOverflowJob;
172
    }
173
174
    private function refineCompanyName ( string $companyName ) : string
175
    {
176
        if ( strpos( $companyName, "\r\n" ) !== false ) {
177
            $companyNameParts = explode( "\r\n", $companyName );
178
179
            foreach ( $companyNameParts as $index => $currentCompanyNamePart ) {
180
                $companyNameParts[$index] = trim( $currentCompanyNamePart );
181
            }
182
183
            return implode( " ", $companyNameParts );
184
        } else {
185
            return $companyName;
186
        }
187
    }
188
189
    private function setCompanyAndLocation (
190
        DOMElement $element,
191
        StackOverflowJob $stackOverflowJob
192
    ) : StackOverflowJob {
193
        if ( $element->attributes->count() == 0 ) {
0 ignored issues
show
Bug introduced by
The method count() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

193
        if ( $element->attributes->/** @scrutinizer ignore-call */ count() == 0 ) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
194
            $stackOverflowJob->setCompany( $this->refineCompanyName( trim( $element->nodeValue ) ) );
195
        }
196
197
        if ( $element->attributes->count() == 1 && $element->getAttribute( "class" ) == "fc-black-500" ) {
198
            $stackOverflowJob->setLocation( trim( $element->nodeValue ) );
199
        }
200
201
        return $stackOverflowJob;
202
    }
203
204
    private function processH2AndH3Elements (
205
        DOMElement $currentDeepNodeChildrenElement,
206
        StackOverflowJob $stackOverflowJob
207
    ) : StackOverflowJob {
208
        if ( $currentDeepNodeChildrenElement->tagName == "h2" ) {
209
            $stackOverflowJob->setTitle( trim( $currentDeepNodeChildrenElement->nodeValue ) );
210
        }
211
212
        if ( $currentDeepNodeChildrenElement->tagName == "h3" && $currentDeepNodeChildrenElement->hasChildNodes() ) {
213
            $companyAndLocationDomNodeList = $currentDeepNodeChildrenElement->childNodes; // DOMNodeList
214
215
            for ( $i = 0; $i < $companyAndLocationDomNodeList->count(); $i++ ) {
216
                $currentCompanyAndLocationElement = $companyAndLocationDomNodeList->item( $i ); // DOMElement or DOMText
217
218
                if ( $currentCompanyAndLocationElement instanceof DOMElement && $currentCompanyAndLocationElement->nodeName == "span" ) {
219
                    $stackOverflowJob = $this->setCompanyAndLocation( $currentCompanyAndLocationElement,
220
                        $stackOverflowJob );
221
                }
222
            }
223
        }
224
225
        return $stackOverflowJob;
226
    }
227
}
228