| Total Complexity | 48 |
| Total Lines | 268 |
| Duplicated Lines | 0 % |
| Changes | 0 | ||
Complex classes like RadioService 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 RadioService, and based on these observations, apply Extract Interface, too.
| 1 | <?php declare(strict_types=1); |
||
| 22 | class RadioService { |
||
| 23 | |||
| 24 | private $logger; |
||
| 25 | |||
| 26 | public function __construct(Logger $logger) { |
||
| 27 | $this->logger = $logger; |
||
| 28 | } |
||
| 29 | |||
| 30 | /** |
||
| 31 | * Loop through the array and try to find the given key. On match, return the |
||
| 32 | * text in the array cell following the key. Whitespace is trimmed from the result. |
||
| 33 | */ |
||
| 34 | private static function findStrFollowing(array $data, string $key) : ?string { |
||
| 35 | foreach ($data as $value) { |
||
| 36 | $find = \strstr($value, $key); |
||
| 37 | if ($find !== false) { |
||
| 38 | return \trim(\substr($find, \strlen($key))); |
||
| 39 | } |
||
| 40 | } |
||
| 41 | return null; |
||
| 42 | } |
||
| 43 | |||
| 44 | private static function parseStreamUrl(string $url) : array { |
||
| 45 | $ret = []; |
||
| 46 | $parse_url = \parse_url($url); |
||
| 47 | |||
| 48 | $ret['port'] = 80; |
||
| 49 | if (isset($parse_url['port'])) { |
||
| 50 | $ret['port'] = $parse_url['port']; |
||
| 51 | } else if ($parse_url['scheme'] == "https") { |
||
| 52 | $ret['port'] = 443; |
||
| 53 | } |
||
| 54 | |||
| 55 | $ret['scheme'] = $parse_url['scheme']; |
||
| 56 | $ret['hostname'] = $parse_url['host']; |
||
| 57 | $ret['pathname'] = $parse_url['path']; |
||
| 58 | |||
| 59 | if (isset($parse_url['query'])) { |
||
| 60 | $ret['pathname'] .= "?" . $parse_url['query']; |
||
| 61 | } |
||
| 62 | |||
| 63 | if ($parse_url['scheme'] == "https") { |
||
| 64 | $ret['sockAddress'] = "ssl://" . $ret['hostname']; |
||
| 65 | } else { |
||
| 66 | $ret['sockAddress'] = $ret['hostname']; |
||
| 67 | } |
||
| 68 | |||
| 69 | return $ret; |
||
| 70 | } |
||
| 71 | |||
| 72 | private static function parseTitleFromStreamMetadata($fp) : ?string { |
||
| 73 | $meta_length = \ord(\fread($fp, 1)) * 16; |
||
| 74 | if ($meta_length) { |
||
| 75 | $metadatas = \explode(';', \fread($fp, $meta_length)); |
||
| 76 | $title = self::findStrFollowing($metadatas, "StreamTitle="); |
||
| 77 | if ($title) { |
||
| 78 | return Util::truncate(\trim($title, "'"), 256); |
||
| 79 | } |
||
| 80 | } |
||
| 81 | return null; |
||
| 82 | } |
||
| 83 | |||
| 84 | private function readMetadata(string $metaUrl, callable $parseResult) : ?array { |
||
| 93 | } |
||
| 94 | } |
||
| 95 | |||
| 96 | public function readShoutcastV1Metadata(string $streamUrl) : ?array { |
||
| 97 | // cut the URL from the last '/' and append 7.html |
||
| 98 | $lastSlash = \strrpos($streamUrl, '/'); |
||
| 99 | $metaUrl = \substr($streamUrl, 0, $lastSlash) . '/7.html'; |
||
| 100 | |||
| 101 | return $this->readMetadata($metaUrl, function ($content) { |
||
| 102 | $content = \strip_tags($content); // get rid of the <html><body>...</html></body> decorations |
||
| 103 | $data = \explode(',', $content); |
||
| 104 | return [ |
||
| 105 | 'type' => 'shoutcast-v1', |
||
| 106 | 'title' => \count($data) > 6 ? \trim($data[6]) : null, // the title field is optional |
||
| 107 | 'bitrate' => $data[5] ?? null |
||
| 108 | ]; |
||
| 109 | }); |
||
| 110 | } |
||
| 111 | |||
| 112 | public function readShoutcastV2Metadata(string $streamUrl) : ?array { |
||
| 113 | // cut the URL from the last '/' and append 'stats' |
||
| 114 | $lastSlash = \strrpos($streamUrl, '/'); |
||
| 115 | $metaUrl = \substr($streamUrl, 0, $lastSlash) . '/stats'; |
||
| 116 | |||
| 117 | return $this->readMetadata($metaUrl, function ($content) { |
||
| 118 | $rootNode = \simplexml_load_string($content, \SimpleXMLElement::class, LIBXML_NOCDATA); |
||
| 119 | return [ |
||
| 120 | 'type' => 'shoutcast-v2', |
||
| 121 | 'title' => (string)$rootNode->SONGTITLE, |
||
| 122 | 'station' => (string)$rootNode->SERVERTITLE, |
||
| 123 | 'homepage' => (string)$rootNode->SERVERURL, |
||
| 124 | 'genre' => (string)$rootNode->SERVERGENRE, |
||
| 125 | 'bitrate' => (string)$rootNode->BITRATE |
||
| 126 | ]; |
||
| 127 | }); |
||
| 128 | } |
||
| 129 | |||
| 130 | public function readIcecastMetadata(string $streamUrl) : ?array { |
||
| 164 | ]; |
||
| 165 | } |
||
| 166 | }); |
||
| 167 | } |
||
| 168 | |||
| 169 | public function readIcyMetadata(string $streamUrl, int $maxattempts, int $maxredirect) : ?array { |
||
| 234 | } |
||
| 235 | |||
| 236 | /** |
||
| 237 | * Sometimes the URL given as stream URL points to a playlist which in turn contains the actual |
||
| 238 | * URL to be streamed. This function resolves such indirections. |
||
| 239 | */ |
||
| 240 | public function resolveStreamUrl(string $url) : array { |
||
| 290 | ]; |
||
| 291 | } |
||
| 292 | } |
||
| 293 | } |
||
| 294 |