Passed
Pull Request — master (#7)
by Jean Paul
01:59
created

StackOverflowWebPageHandler   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 205
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 51
eloc 89
c 1
b 0
f 0
dl 0
loc 205
rs 7.92

11 Methods

Rating   Name   Duplication   Size   Complexity  
A read() 0 13 2
A processDivBlock() 0 11 3
A processListResults() 0 11 4
A processImageBlock() 0 12 6
A __construct() 0 3 1
B setBasicAttributes() 0 21 7
A processChildNodes() 0 29 6
C processH2AndH3Elements() 0 43 12
A processImageDivBlocks() 0 11 4
A getResults() 0 3 1
A processExtraChildNodes() 0 19 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
class StackOverflowWebPageHandler extends WebPageHandler
12
{
13
    public function __construct ( string $url )
14
    {
15
        parent::__construct( $url );
16
    }
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 = array();
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
                // DOMElement or DOMText
48
                $currentChildrenNode = $children->item( $i );
49
50
                if ( $currentChildrenNode instanceof DOMElement ) {
51
                    $this->processChildNodes( $currentChildrenNode );
52
                }
53
            }
54
        }
55
    }
56
57
    private function processChildNodes ( DOMElement $currentChildrenNode ) : void
58
    {
59
        $currentJob = new StackOverflowJob();
60
61
        $currentJob = $this->setBasicAttributes( $currentChildrenNode->attributes, $currentJob );
62
63
        if ( $currentChildrenNode->hasChildNodes() ) {
64
            if ( trim( $currentChildrenNode->nodeValue ) == "You might be interested in these jobs:" ) {
65
                echo "Found job separator" . PHP_EOL;
66
            } else {
67
                $extraChildNodes = $currentChildrenNode->childNodes; // DOMNodeList
68
69
                for ( $i = 0; $i < $extraChildNodes->count(); $i++ ) {
70
                    // DOMElement or DOMText
71
                    $currentExtraChildNode = $extraChildNodes->item( $i );
72
73
                    if ( $currentExtraChildNode instanceof DOMElement ) {
74
                        $currentJob = $this->processExtraChildNodes( $currentExtraChildNode, $currentJob );
75
                    }
76
                }
77
            }
78
79
80
        }
81
82
        if ( $currentJob->allAttributesDefined() ) {
83
            array_push( $this->results, $currentJob );
84
        } else {
85
            echo "Ignoring job because of missing attributes" . PHP_EOL;
86
        }
87
    }
88
89
    private function setBasicAttributes ( DOMNamedNodeMap $attributes, StackOverflowJob $stackOverflowJob ) : StackOverflowJob
90
    {
91
        if ( $attributes != null ) {
92
            foreach ( $attributes as $currentAttribute ) {
93
                if ( $currentAttribute->name != null ) {
94
                    if ( $currentAttribute->name == "data-jobid" ) {
95
                        $stackOverflowJob->setJobId( $currentAttribute->value );
96
                    }
97
98
                    if ( $currentAttribute->name == "data-result-id" ) {
99
                        $stackOverflowJob->setResultId( $currentAttribute->value );
100
                    }
101
102
                    if ( $currentAttribute->name == "data-preview-url" ) {
103
                        $stackOverflowJob->setPreviewUrl( $currentAttribute->value );
104
                    }
105
                }
106
            }
107
        }
108
109
        return $stackOverflowJob;
110
    }
111
112
    private function processExtraChildNodes ( DOMElement $currentExtraChildNode, StackOverflowJob $stackOverflowJob ) : StackOverflowJob
113
    {
114
        if ( $currentExtraChildNode->hasChildNodes() ) {
115
            $currentExtraChildNodeChildren = $currentExtraChildNode->childNodes; // DOMNodeList
116
117
            $nodeCount = $currentExtraChildNodeChildren->count();
118
119
            if ( $nodeCount >= 6 ) {
120
                for ( $i = 0; $i < $nodeCount; $i++ ) {
121
                    $currentDeepNode = $currentExtraChildNodeChildren->item( $i ); // DOMElement or DOMText
122
123
                    if ( $currentDeepNode instanceof DOMElement ) {
124
                        $stackOverflowJob = $this->processImageDivBlocks( $currentDeepNode, $stackOverflowJob );
125
                    }
126
                }
127
            }
128
        }
129
130
        return $stackOverflowJob;
131
    }
132
133
    private function processImageDivBlocks ( DOMElement $currentDeepNode, StackOverflowJob $stackOverflowJob ) : StackOverflowJob
134
    {
135
        if ( $currentDeepNode->tagName == "img" ) {
136
            $stackOverflowJob = $this->processImageBlock( $currentDeepNode->attributes, $stackOverflowJob );
137
        }
138
139
        if ( $currentDeepNode->tagName == "div" && $currentDeepNode->hasChildNodes() ) {
140
            $stackOverflowJob = $this->processDivBlock( $currentDeepNode->childNodes, $stackOverflowJob );
141
        }
142
143
        return $stackOverflowJob;
144
    }
145
146
    private function processImageBlock ( DOMNamedNodeMap $currentDeepNodeAttributes, StackOverflowJob $stackOverflowJob ) : StackOverflowJob
147
    {
148
        if ( $currentDeepNodeAttributes != null && sizeof( $currentDeepNodeAttributes ) == 2 ) {
149
            $attr1 = $currentDeepNodeAttributes[0]; // DOMAttr
150
            $attr2 = $currentDeepNodeAttributes[1]; // DOMAttr
151
152
            if ( $attr1->name == "class" && $attr1->value == "grid--cell fl-shrink0 w48 h48 bar-sm mr12" && $attr2->name == "src" ) {
153
                $stackOverflowJob->setLogo( $attr2->value );
154
            }
155
        }
156
157
        return $stackOverflowJob;
158
    }
159
160
    private function processDivBlock ( DOMNodeList $currentDeepNodeChildren, StackOverflowJob $stackOverflowJob ) : StackOverflowJob
161
    {
162
        for ( $i = 0; $i < $currentDeepNodeChildren->count(); $i++ ) {
163
            $currentDeepNodeChildrenElement = $currentDeepNodeChildren->item( $i ); // DOMElement or DOMText
164
165
            if ( $currentDeepNodeChildrenElement instanceof DOMElement ) {
166
                $stackOverflowJob = $this->processH2AndH3Elements( $currentDeepNodeChildrenElement, $stackOverflowJob );
167
            }
168
        }
169
170
        return $stackOverflowJob;
171
    }
172
173
    private function processH2AndH3Elements ( DOMElement $currentDeepNodeChildrenElement, StackOverflowJob $stackOverflowJob ) : StackOverflowJob
174
    {
175
        if ( $currentDeepNodeChildrenElement->tagName == "h2" ) {
176
            $stackOverflowJob->setTitle( trim( $currentDeepNodeChildrenElement->nodeValue ) );
177
        }
178
179
        if ( $currentDeepNodeChildrenElement->tagName == "h3" ) {
180
            if ( $currentDeepNodeChildrenElement->hasChildNodes() ) {
181
                $companyAndLocationDomNodeList = $currentDeepNodeChildrenElement->childNodes; // DOMNodeList
182
183
                for ( $i = 0; $i < $companyAndLocationDomNodeList->count(); $i++ ) {
184
                    $currentCompanyAndLocationElement = $companyAndLocationDomNodeList->item( $i ); // DOMElement or DOMText
185
186
                    if ( $currentCompanyAndLocationElement instanceof DOMElement ) {
187
                        if ( $currentCompanyAndLocationElement->nodeName == "span" ) {
188
                            if ( $currentCompanyAndLocationElement->attributes->count() == 0 ) {
189
                                $companyName = trim( $currentCompanyAndLocationElement->nodeValue );
190
191
                                if ( strpos( $companyName, "\r\n" ) !== false ) {
192
                                    $companyNameParts = explode( "\r\n", $companyName );
193
194
                                    foreach ( $companyNameParts as $index => $currentCompanyNamePart ) {
195
                                        $companyNameParts[$index] = trim( $currentCompanyNamePart );
196
                                    }
197
198
                                    $stackOverflowJob->setCompany( implode( " ", $companyNameParts ) );
199
                                } else {
200
                                    $stackOverflowJob->setCompany( $companyName );
201
                                }
202
                            }
203
204
                            if ( $currentCompanyAndLocationElement->attributes->count() == 1 ) {
205
                                if ( $currentCompanyAndLocationElement->getAttribute( "class" ) == "fc-black-500" ) {
206
                                    $stackOverflowJob->setLocation( trim( $currentCompanyAndLocationElement->nodeValue ) );
207
                                }
208
                            }
209
                        }
210
                    }
211
                }
212
            }
213
        }
214
215
        return $stackOverflowJob;
216
    }
217
}
218