Binary   B
last analyzed

Complexity

Total Complexity 48

Size/Duplication

Total Lines 296
Duplicated Lines 0 %

Importance

Changes 3
Bugs 2 Features 0
Metric Value
wmc 48
eloc 144
c 3
b 2
f 0
dl 0
loc 296
rs 8.5599

13 Methods

Rating   Name   Duplication   Size   Complexity  
B getInfoFromPecl() 0 31 8
B findInLinks() 0 31 6
A __construct() 0 9 2
A copyFiles() 0 19 5
A setProgress() 0 3 1
A fetchZipName() 0 21 4
A uncompress() 0 9 1
A install() 0 9 1
A updateIni() 0 4 1
C download() 0 55 14
A setOutput() 0 3 1
A extensionPeclExists() 0 7 2
A getExtDllPaths() 0 9 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
/*
4
 * Pickle
5
 *
6
 *
7
 * @license
8
 *
9
 * New BSD License
10
 *
11
 * Copyright © 2015-2015, Pickle community. All rights reserved.
12
 *
13
 * Redistribution and use in source and binary forms, with or without
14
 * modification, are permitted provided that the following conditions are met:
15
 *     * Redistributions of source code must retain the above copyright
16
 *       notice, this list of conditions and the following disclaimer.
17
 *     * Redistributions in binary form must reproduce the above copyright
18
 *       notice, this list of conditions and the following disclaimer in the
19
 *       documentation and/or other materials provided with the distribution.
20
 *     * Neither the name of the Hoa nor the names of its contributors may be
21
 *       used to endorse or promote products derived from this software without
22
 *       specific prior written permission.
23
 *
24
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
28
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34
 * POSSIBILITY OF SUCH DAMAGE.
35
 */
36
37
namespace Pickle\Package\PHP\Command\Install\Windows;
38
39
use DOMDocument;
40
use Exception;
41
use Pickle\Base\Archive;
42
use Pickle\Base\Pecl\WebsiteFactory;
43
use Pickle\Base\Util;
44
use Pickle\Base\Util\FileOps;
45
use Pickle\Engine;
46
use Symfony\Component\Console\Output\OutputInterface as OutputInterface;
47
48
class Binary
49
{
50
    use FileOps;
51
52
    private $php;
53
54
    private $extName;
55
56
    private $extVersion;
57
58
    private $progress;
59
60
    private $output;
61
62
    private $extDll;
63
64
    /**
65
     * @param string $ext
66
     */
67
    public function __construct($ext)
68
    {
69
        // used only if only the extension name is given
70
        if (strpos('//', $ext) !== false) {
71
            $this->extensionPeclExists();
72
        }
73
74
        $this->extName = $ext;
75
        $this->php = Engine::factory();
76
    }
77
78
    public function setProgress($progress)
79
    {
80
        $this->progress = $progress;
81
    }
82
83
    public function setOutput(OutputInterface $output)
84
    {
85
        $this->output = $output;
86
    }
87
88
    /**
89
     *  1. check if ext exists
90
     *  2. check if given version requested
91
     *  2.1 yes? check if builds available
92
     *  2.2 no? get latest version+build.
93
     *
94
     * @throws Exception
95
     */
96
    public function install()
97
    {
98
        [$this->extName, $this->extVersion] = $this->getInfoFromPecl();
99
        $url = $this->fetchZipName();
100
        $pathArchive = $this->download($url);
101
        $this->uncompress($pathArchive);
102
        $this->copyFiles();
103
        $this->cleanup();
104
        $this->updateIni();
105
    }
106
107
    public function getExtDllPaths()
108
    {
109
        $ret = [];
110
111
        foreach ($this->extDll as $dll) {
112
            $ret[] = $this->php->getExtensionDir() . DIRECTORY_SEPARATOR . $dll;
113
        }
114
115
        return $ret;
116
    }
117
118
    private function extensionPeclExists()
119
    {
120
        $url = WebsiteFactory::getWebsite()->getBaseUrl() . '/get/' . $this->extName;
121
        $headers = get_headers($url, 1);
122
        $status = $headers[0];
123
        if (strpos($status, '404')) {
124
            throw new Exception("Extension <{$this->extName}> cannot be found");
125
        }
126
    }
127
128
    /**
129
     * @param string $url
130
     */
131
    private function findInLinks($url, $toFind)
132
    {
133
        $page = @file_get_contents($url);
0 ignored issues
show
Unused Code introduced by
The assignment to $page is dead and can be removed.
Loading history...
134
        $opts = [
135
            'http' => [
136
                'header' => 'User-Agent: pickle',
137
            ],
138
        ];
139
        $context = stream_context_create($opts);
140
        $page = @file_get_contents($url, false, $context);
141
        if (!$page) {
142
            return false;
143
        }
144
        $dom = new DOMDocument();
145
        $dom->loadHTML($page);
146
        $links = $dom->getElementsByTagName('a');
147
        if (!$links) {
0 ignored issues
show
introduced by
$links is of type DOMNodeList, thus it always evaluated to true.
Loading history...
148
            return false;
149
        }
150
151
        foreach ($links as $link) {
152
            if ($link->nodeValue[0] == '[') {
153
                continue;
154
            }
155
            $value = trim($link->nodeValue);
156
            if ($toFind == $value) {
157
                return $value;
158
            }
159
        }
160
161
        return false;
162
    }
163
164
    /**
165
     * @throws Exception
166
     *
167
     * @return string
168
     */
169
    private function fetchZipName()
170
    {
171
        $phpVc = $this->php->getCompiler();
172
        $phpArch = $this->php->getArchitecture();
173
        $phpZts = $this->php->getZts() ? '-ts' : '-nts';
174
        $phpVersion = $this->php->getMajorVersion() . '.' . $this->php->getMinorVersion();
175
        $pkgVersion = $this->extVersion;
176
        $extName = strtolower($this->extName);
177
        $baseUrl = 'https://windows.php.net/downloads/pecl/releases/';
178
179
        if ($this->findInLinks($baseUrl . $extName, $pkgVersion) === false) {
180
            throw new Exception('Binary for <' . $extName . '-' . $pkgVersion . '> cannot be found');
181
        }
182
183
        $fileToFind = 'php_' . $extName . '-' . $pkgVersion . '-' . $phpVersion . $phpZts . '-' . $phpVc . '-' . $phpArch . '.zip';
184
        $fileUrl = $this->findInLinks($baseUrl . $extName . '/' . $pkgVersion, $fileToFind);
185
186
        if (!$fileUrl) {
187
            throw new Exception('Binary for <' . $fileToFind . '> cannot be found');
188
        }
189
        return $baseUrl . $extName . '/' . $pkgVersion . '/' . $fileToFind;
190
    }
191
192
    /**
193
     * @param string $zipFile
194
     *
195
     * @throws Exception
196
     */
197
    private function uncompress($zipFile)
198
    {
199
        $this->createTempDir($this->extName);
200
        $this->cleanup();
201
        $zipClass = Archive\Factory::getUnzipperClassName();
202
        $zipArchive = $zipClass($zipFile);
203
        /** @var \Pickle\Base\Interfaces\Archive\Unzipper $zipArchive */
204
        $this->output->writeln('Extracting archives...');
205
        $zipArchive->extractTo($this->tempDir);
206
    }
207
208
    /**
209
     * @param string $url
210
     *
211
     * @throws Exception
212
     *
213
     * @return string
214
     */
215
    private function download($url)
216
    {
217
        $progress = $this->progress;
218
        $progress->setOverwrite(true);
219
        $ctx = stream_context_create(
220
            [
221
                'http' => [
222
                    'header' => 'User-Agent: pickle',
223
                ],
224
            ],
225
            [
226
                'notification' => function ($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax) use ($progress) {
227
                    switch ($notificationCode) {
228
                        case STREAM_NOTIFY_RESOLVE:
229
                        case STREAM_NOTIFY_AUTH_REQUIRED:
230
                        case STREAM_NOTIFY_COMPLETED:
231
                        case STREAM_NOTIFY_FAILURE:
232
                        case STREAM_NOTIFY_AUTH_RESULT:
233
                            break;
234
235
                        case STREAM_NOTIFY_REDIRECTED:
236
                            break;
237
238
                        case STREAM_NOTIFY_CONNECT:
239
                            break;
240
241
                        case STREAM_NOTIFY_FILE_SIZE_IS:
242
                            $progress->start($bytesMax);
243
                            break;
244
245
                        case STREAM_NOTIFY_MIME_TYPE_IS:
246
                            break;
247
248
                        case STREAM_NOTIFY_PROGRESS:
249
                            $progress->setProgress($bytesTransferred);
250
                            break;
251
                    }
252
                },
253
            ]
254
        );
255
        if ($this->output) {
256
            $this->output->writeln("downloading {$url} ");
257
        }
258
        $fileContents = file_get_contents($url, false, $ctx);
259
        $progress->finish();
260
        if (!$fileContents) {
261
            throw new Exception('Cannot fetch <' . $url . '>');
262
        }
263
        $tmpdir = Util\TmpDir::get();
264
        $path = $tmpdir . '/' . $this->extName . '.zip';
265
        if (!file_put_contents($path, $fileContents)) {
266
            throw new Exception('Cannot save temporary file <' . $path . '>');
267
        }
268
269
        return $path;
270
    }
271
272
    /**
273
     * @throws Exception
274
     */
275
    private function copyFiles()
276
    {
277
        $DLLs = glob($this->tempDir . '/*.dll');
278
        $this->extDll = [];
279
        foreach ($DLLs as $dll) {
280
            $dll = realpath($dll);
281
            $basename = basename($dll);
282
            $dest = $this->php->getExtensionDir() . DIRECTORY_SEPARATOR . $basename;
283
            if (substr($basename, 0, 4) == 'php_') {
284
                $this->extDll[] = $basename;
285
                $this->output->writeln("copying {$dll} to " . $dest . "\n");
286
                $success = copy($dll, $this->php->getExtensionDir() . '/' . $basename);
287
                if (!$success) {
288
                    throw new Exception('Cannot copy DLL <' . $dll . '> to <' . $dest . '>');
289
                }
290
            } else {
291
                $success = copy($dll, dirname($this->php->getPath()) . '/' . $basename);
292
                if (!$success) {
293
                    throw new Exception('Cannot copy DLL <' . $dll . '> to <' . $dest . '>');
294
                }
295
            }
296
        }
297
    }
298
299
    /**
300
     * @throws Exception
301
     */
302
    private function updateIni()
303
    {
304
        $ini = \Pickle\Engine\Ini::factory($this->php);
305
        $ini->updatePickleSection($this->extDll);
306
    }
307
308
    /**
309
     * @throws Exception
310
     *
311
     * @return array
312
     */
313
    private function getInfoFromPecl()
314
    {
315
        $baseUrl = WebsiteFactory::getWebsite()->getBaseUrl();
316
        $url = $baseUrl . '/get/' . $this->extName;
317
        $headers = get_headers($url);
318
319
        if (strpos($headers[0], '404') !== false) {
320
            throw new Exception('Cannot find extension <' . $this->extName . '>');
321
        }
322
        $headerPkg = null;
323
        foreach ($headers as $header) {
324
            if (strpos($header, 'tgz') !== false) {
325
                $headerPkg = $header;
326
                break;
327
            }
328
        }
329
        if ($headerPkg === null) {
330
            throw new Exception('Cannot find extension <' . $this->extName . '>');
331
        }
332
        $m = null;
333
        if (!preg_match('|=(.*)\\.[a-z0-9]{2,3}$|', $headerPkg, $m)) {
334
            throw new Exception("Invalid response from {$baseUrl}");
335
        }
336
        $packageFullname = $m[1];
337
338
        [$name, $version] = explode('-', $packageFullname);
339
        if ($name == '' || $version == '') {
340
            throw new Exception("Invalid response from {$baseUrl}");
341
        }
342
343
        return [$name, $version];
344
    }
345
}
346
347
/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
348