Completed
Pull Request — master (#84)
by
unknown
02:04
created

Url   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 291
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 7
Bugs 0 Features 0
Metric Value
eloc 120
c 7
b 0
f 0
dl 0
loc 291
ccs 0
cts 187
cp 0
rs 3.6
wmc 60

23 Methods

Rating   Name   Duplication   Size   Complexity  
A getQuery() 0 5 1
A createDefaultParser() 0 3 1
A getParser() 0 7 2
A setFragment() 0 5 1
A getNetloc() 0 5 5
A setPath() 0 5 1
A setUrl() 0 5 1
F fromCurrent() 0 41 18
A parse() 0 3 1
A getPath() 0 5 1
A join() 0 22 5
A getUrl() 0 11 2
A getFragment() 0 5 1
A httpBuildRelativeUrl() 0 9 3
A extract() 0 11 2
A httpBuildUrl() 0 15 4
A setParser() 0 3 1
A doInitialize() 0 14 4
A isAbsolute() 0 5 2
A __construct() 0 4 1
A __toString() 0 3 1
A setQuery() 0 5 1
A set() 0 7 1

How to fix   Complexity   

Complex Class

Complex classes like Url 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 Url, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Purl;
6
7
use function array_map;
8
use function explode;
9
use function ltrim;
10
use function preg_match_all;
11
use function sprintf;
12
use function strpos;
13
14
/**
15
 * Url is a simple OO class for manipulating Urls in PHP.
16
 *
17
 * @property string $scheme
18
 * @property string $host
19
 * @property int $port
20
 * @property string $user
21
 * @property string $pass
22
 * @property Path|string $path
23
 * @property Query|string $query
24
 * @property Fragment|string $fragment
25
 * @property string $canonical
26
 * @property string $resource
27
 */
28
class Url extends AbstractPart
29
{
30
    /** @var string|null The original url string. */
31
    private ?string $url = null;
32
33
    private ?ParserInterface $parser = null;
34
35
    /** @var mixed[] */
36
    protected array $data = [
37
        'scheme'             => null,
38
        'host'               => null,
39
        'port'               => null,
40
        'user'               => null,
41
        'pass'               => null,
42
        'path'               => null,
43
        'query'              => null,
44
        'fragment'           => null,
45
        'publicSuffix'       => null,
46
        'registerableDomain' => null,
47
        'subdomain'          => null,
48
        'canonical'          => null,
49
        'resource'           => null,
50
    ];
51
52
    /** @var string[] */
53
    protected array $partClassMap = [
54
        'path' => 'Purl\Path',
55
        'query' => 'Purl\Query',
56
        'fragment' => 'Purl\Fragment',
57
    ];
58
59
    public function __construct(?string $url = null, ?ParserInterface $parser = null)
60
    {
61
        $this->url    = $url;
62
        $this->parser = $parser;
63
    }
64
65
    public static function parse(string $url): Url
66
    {
67
        return new self($url);
68
    }
69
70
    /**
71
     * @return Url[] $urls
72
     */
73
    public static function extract(string $string): array
74
    {
75
        $regex = '/(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}(\/\S*)?/';
76
77
        preg_match_all($regex, $string, $matches);
78
        $urls = [];
79
        foreach ($matches[0] as $url) {
80
            $urls[] = self::parse($url);
81
        }
82
83
        return $urls;
84
    }
85
86
    public static function fromCurrent(): Url
87
    {
88
        $scheme = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] === 443 ? 'https' : 'http';
89
90
        $host    = $_SERVER['HTTP_HOST'];
91
        $baseUrl = sprintf('%s://%s', $scheme, $host);
92
93
        $url = new self($baseUrl);
94
95
        if (isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI']) {
96
            if (strpos($_SERVER['REQUEST_URI'], '?') !== false) {
97
                [$path, $query] = explode('?', $_SERVER['REQUEST_URI'], 2);
98
            } else {
99
                $path  = $_SERVER['REQUEST_URI'];
100
                $query = '';
101
            }
102
103
            $url->set('path', $path);
104
            $url->set('query', $query);
105
        }
106
107
        // Only set port if different from default (80 or 443)
108
        if (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT']) {
109
            $port = $_SERVER['SERVER_PORT'];
110
            if (
111
                ($scheme === 'http' && $port !== 80) ||
112
                ($scheme === 'https' && $port !== 443)
113
            ) {
114
                $url->set('port', $port);
115
            }
116
        }
117
118
        // Authentication
119
        if (isset($_SERVER['PHP_AUTH_USER']) && $_SERVER['PHP_AUTH_USER']) {
120
            $url->set('user', $_SERVER['PHP_AUTH_USER']);
121
            if (isset($_SERVER['PHP_AUTH_PW']) && $_SERVER['PHP_AUTH_PW']) {
122
                $url->set('pass', $_SERVER['PHP_AUTH_PW']);
123
            }
124
        }
125
126
        return $url;
127
    }
128
129
    public function getParser(): ParserInterface
130
    {
131
        if ($this->parser === null) {
132
            $this->parser = self::createDefaultParser();
133
        }
134
135
        return $this->parser;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->parser could return the type null which is incompatible with the type-hinted return Purl\ParserInterface. Consider adding an additional type-check to rule them out.
Loading history...
136
    }
137
138
    public function setParser(ParserInterface $parser): void
139
    {
140
        $this->parser = $parser;
141
    }
142
143
    /**
144
     * @param string|Url $url
145
     */
146
    public function join($url): Url
147
    {
148
        $this->initialize();
149
        $parts = $this->getParser()->parseUrl($url);
150
151
        if ($this->data['scheme'] !== null) {
152
            $parts['scheme'] = $this->data['scheme'];
153
        }
154
155
        foreach ($parts as $key => $value) {
156
            if ($value === null) {
157
                continue;
158
            }
159
160
            $this->data[$key] = $value;
161
        }
162
163
        foreach ($this->data as $key => $value) {
164
            $this->data[$key] = $this->preparePartValue($key, $value);
165
        }
166
167
        return $this;
168
    }
169
170
    /**
171
     * @param mixed $value
172
     */
173
    public function set(string $key, $value): AbstractPart
174
    {
175
        $this->initialize();
176
177
        $this->data[$key] = $this->preparePartValue($key, $value);
178
179
        return $this;
180
    }
181
182
    public function setPath(Path $path): AbstractPart
183
    {
184
        $this->data['path'] = $path;
185
186
        return $this;
187
    }
188
189
    public function getPath(): Path
190
    {
191
        $this->initialize();
192
193
        return $this->data['path'];
194
    }
195
196
    public function setQuery(Query $query): AbstractPart
197
    {
198
        $this->data['query'] = $query;
199
200
        return $this;
201
    }
202
203
    public function getQuery(): Query
204
    {
205
        $this->initialize();
206
207
        return $this->data['query'];
208
    }
209
210
    public function setFragment(Fragment $fragment): AbstractPart
211
    {
212
        $this->data['fragment'] = $fragment;
213
214
        return $this;
215
    }
216
217
    public function getFragment(): Fragment
218
    {
219
        $this->initialize();
220
221
        return $this->data['fragment'];
222
    }
223
224
    public function getNetloc(): string
225
    {
226
        $this->initialize();
227
228
        return ($this->user !== null && $this->pass !== null ? $this->user . ($this->pass !== null ? ':' . $this->pass : '') . '@' : '') . $this->host . ($this->port !== null ? ':' . $this->port : '');
229
    }
230
231
    public function getUrl(): string
232
    {
233
        $this->initialize();
234
235
        $parts = array_map('strval', $this->data);
236
237
        if (! $this->isAbsolute()) {
238
            return self::httpBuildRelativeUrl($parts);
239
        }
240
241
        return self::httpBuildUrl($parts);
242
    }
243
244
    public function setUrl(string $url): void
245
    {
246
        $this->initialized = false;
247
        $this->data        = [];
248
        $this->url         = $url;
249
    }
250
251
    public function isAbsolute(): bool
252
    {
253
        $this->initialize();
254
255
        return $this->scheme !== null && $this->host !== null;
256
    }
257
258
    public function __toString(): string
259
    {
260
        return $this->getUrl();
261
    }
262
263
    protected function doInitialize(): void
264
    {
265
        $parts = $this->getParser()->parseUrl($this->url);
266
267
        foreach ($parts as $k => $v) {
268
            if (isset($this->data[$k])) {
269
                continue;
270
            }
271
272
            $this->data[$k] = $v;
273
        }
274
275
        foreach ($this->data as $key => $value) {
276
            $this->data[$key] = $this->preparePartValue($key, $value);
277
        }
278
    }
279
280
    /**
281
     * @param string[] $parts
282
     */
283
    private static function httpBuildUrl(array $parts): string
284
    {
285
        $relative = self::httpBuildRelativeUrl($parts);
286
287
        $pass = $parts['pass'] !== '' ? sprintf(':%s', $parts['pass']) : '';
288
        $auth = $parts['user'] !== '' ? sprintf('%s%s@', $parts['user'], $pass) : '';
289
        $port = $parts['port'] !== '' ? sprintf(':%d', $parts['port']) : '';
290
291
        return sprintf(
292
            '%s://%s%s%s%s',
293
            $parts['scheme'],
294
            $auth,
295
            $parts['host'],
296
            $port,
297
            $relative
298
        );
299
    }
300
301
    /**
302
     * @param string[] $parts
303
     */
304
    private static function httpBuildRelativeUrl(array $parts): string
305
    {
306
        $parts['path'] = ltrim($parts['path'], '/');
307
308
        return sprintf(
309
            '/%s%s%s',
310
            $parts['path'],
311
            $parts['query'] !== '' ? '?' . $parts['query'] : '',
312
            $parts['fragment'] !== '' ? '#' . $parts['fragment'] : ''
313
        );
314
    }
315
316
    private static function createDefaultParser(): Parser
317
    {
318
        return new Parser();
319
    }
320
}
321