Completed
Branch master (634f14)
by Sebastian
02:37
created

SelfUpdateCommand::execute()   B

Complexity

Conditions 8
Paths 13

Size

Total Lines 53
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 53
rs 7.1199
cc 8
eloc 45
nc 13
nop 2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * SelfUpdateCommand.php
4
 *
5
 * MIT LICENSE
6
 *
7
 * LICENSE: This source file is subject to the MIT license.
8
 * A copy of the licenses text was distributed alongside this
9
 * file (usually the repository or package root). The text can also
10
 * be obtained through one of the following sources:
11
 * * http://opensource.org/licenses/MIT
12
 * * https://github.com/suralc/pvra/blob/master/LICENSE
13
 *
14
 * @author     suralc <[email protected]>
15
 * @license    http://opensource.org/licenses/MIT  MIT
16
 */
17
namespace Pvra\Console\Commands;
18
19
20
use Phar;
21
use Symfony\Component\Console\Command\Command;
22
use Symfony\Component\Console\Input\InputInterface;
23
use Symfony\Component\Console\Input\InputOption;
24
use Symfony\Component\Console\Output\OutputInterface;
25
use Symfony\Component\Console\Question\ConfirmationQuestion;
26
27
/**
28
 * Class SelfUpdateCommand
29
 *
30
 * @package Pvra\Console\Commands
31
 */
32
class SelfUpdateCommand extends Command
33
{
34
    const DEFAULT_API_ROOT = 'https://api.github.com/';
35
    const DEFAULT_REPO_SLUG = 'suralc/pvra';
36
37
    private $apiRoot = self::DEFAULT_API_ROOT;
38
    private $slug = self::DEFAULT_REPO_SLUG;
39
40
    /**
41
     * @inheritdoc
42
     */
43
    protected function configure()
44
    {
45
        $this->setName('self-update')
46
            ->setAliases(['selfupdate', 'update'])
47
            ->setDescription('Checks for a newer tagged version in the github repository.');
48
        $this->addOption('repo-slug', 'r', InputOption::VALUE_REQUIRED, 'Source repository', self::DEFAULT_REPO_SLUG)
49
            ->addOption('api-root', 'a', InputOption::VALUE_REQUIRED, 'Api root url', self::DEFAULT_API_ROOT);
50
    }
51
52
    protected function initialize(InputInterface $in, OutputInterface $out)
53
    {
54
        $this->apiRoot = $in->getOption('api-root');
55
        $this->slug = $in->getOption('repo-slug');
56
    }
57
58
    /**
59
     * @inheritdoc
60
     */
61
    protected function execute(InputInterface $in, OutputInterface $out)
62
    {
63
        if (Phar::running() === '') {
64
            $out->writeln('<error>This command can only be used to upgrade the phar. Use composer if you run pvra from source</error>');
65
            return 2;
66
        }
67
        $out->writeln('Checking version for updates...');
68
        $out->writeln('Attempting to connect to repository: ' . $this->slug);
69
        list($ghReleases, $header) = $this->getReleases();
70
        if ($in->getOption('verbose')) {
71
            $headers = $this->parseHeader($header);
72
            $out->writeln('Github api limit: ' . $headers['X-RateLimit-Limit']);
73
            $out->writeln('Github api limit remaining: ' . $headers['X-RateLimit-Remaining']);
74
        }
75
        $ghReleasesContent = json_decode($ghReleases, true);
76
        unset($ghReleases);
77
        if (!empty($ghReleasesContent)) {
78
            $remoteVersion = substr($ghReleasesContent[0]['tag_name'], 1);
79
            $out->writeln('Your version: ' . $this->getApplication()->getVersion()
80
                . '(' . $this->getComparableVersion() . ')');
81
            $out->writeln('Current remote version is: ' . $remoteVersion);
82
            $compared = version_compare($this->getComparableVersion(), $remoteVersion);
83
            if ($compared < 0) {
84
                $out->writeln('A newer version is available.');
85
                $question = new ConfirmationQuestion('Do you wish to upgrade to ' . $remoteVersion . ' ? [y/N]: ');
86
                if ($this->getHelper('question')->ask($in, $out, $question)) {
87
                    $out->writeln('Upgrading...');
88
                    $temp = tmpfile();
89
                    stream_copy_to_stream(fopen($this->getPharUrlFromAssetArray($ghReleasesContent[0]['assets']), 'rb'),
90
                        $temp);
91
                    rewind($temp);
92
                    $running = Phar::running(false);
93
                    if (!is_writable($running)) {
94
                        throw new \RuntimeException('The current process needs to be able to delete ' . $running);
95
                    }
96
                    unlink($running);
97
                    $target = fopen($running, 'w+');
98
                    stream_copy_to_stream($temp, $target);
99
                    fclose($target);
100
                    fclose($temp);
101
                    $out->writeln($running . ' has  been successfully updated.');
102
                }
103
                return 0;
104
            } elseif ($compared > 0) {
105
                $out->writeln('The local version is ahead of the remote version');
106
                return 0;
107
            }
108
            $out->writeln('Remote and local version are equal');
109
            return 0;
110
        }
111
        $out->writeln('No releases were found. The phar can\'t be updated automatically.');
112
        return 0;
113
    }
114
115
    private function getNewDefaultStreamContext()
116
    {
117
        $version = $this->getApplication()->getVersion();
118
        return stream_context_create([
119
            'http' => [
120
                'header' => "User-Agent: Php-Version-Requirement-Analyser(pvra) by @suralc V{$version}\r\n" .
121
                    "Accept: application/vnd.github.v3+json\r\n",
122
            ],
123
        ]);
124
    }
125
126
    private function getReleases($target = '')
127
    {
128
        $response = file_get_contents(sprintf('%srepos/%s/releases%s', $this->apiRoot, $this->slug, $target), 0,
129
            $this->getNewDefaultStreamContext());
130
        return [$response, $http_response_header];
131
    }
132
133
    private function parseHeader(array $headerArray)
134
    {
135
        $headers = [];
136
        foreach ($headerArray as $num => $headerLine) {
137
            if ($num === 0) {
138
                // status code
139
                continue;
140
            }
141
            list($key, $value) = explode(':', $headerLine);
142
            $headers[ $key ] = $value;
143
        }
144
145
        return $headers;
146
    }
147
148
    private function getComparableVersion()
149
    {
150
        static $comparableVersion;
151
        if ($comparableVersion === null) {
152
            $localVersion = ltrim($this->getApplication()->getVersion(), 'v');
153
            preg_match('/(?P<major>[\\d]+).(?P<minor>[\\d]+).(?P<patch>[\\d]+)-(?P<commitCount>[\\d]+)-(g)?(?P<hash>[0-9a-f]{5,40})/i',
154
                $localVersion, $matches);
155
            $comparableVersion = sprintf('%d.%d.%d', $matches['major'], $matches['minor'], $matches['patch']);
156
            if (isset($matches['commitCount'])) {
157
                $comparableVersion .= '.' . $matches['commitCount'];
158
            }
159
        }
160
        return $comparableVersion;
161
    }
162
163
    /**
164
     * @param array $assets
165
     * @return string
166
     * @throws \Exception
167
     */
168
    private function getPharUrlFromAssetArray(array $assets)
169
    {
170
        if (!empty($assets)) {
171
            foreach ($assets as $asset) {
172
                if ($asset['name'] === 'pvra.phar') {
173
                    return $asset['browser_download_url'];
174
                }
175
            }
176
        }
177
        throw new \Exception('No valid asset found.');
178
    }
179
}
180