UpdateCommand   A
last analyzed

Complexity

Total Complexity 17

Size/Duplication

Total Lines 142
Duplicated Lines 0 %

Importance

Changes 8
Bugs 1 Features 1
Metric Value
eloc 68
c 8
b 1
f 1
dl 0
loc 142
rs 10
wmc 17

5 Methods

Rating   Name   Duplication   Size   Complexity  
A rollback() 0 23 3
A updateTo() 0 38 4
A execute() 0 26 5
A onConstruct() 0 10 1
A getPharPathFor() 0 17 4
1
<?php
2
3
/*
4
 * This file is part of the PHINT package.
5
 *
6
 * (c) Jitendra Adhikari <[email protected]>
7
 *     <https://github.com/adhocore>
8
 *
9
 * Licensed under MIT license.
10
 */
11
12
namespace Ahc\Phint\Console;
13
14
use Ahc\Cli\Exception\RuntimeException;
15
use Ahc\Cli\Input\Command;
16
use Ahc\Phint\Util\Composer;
17
18
/**
19
 * Some ideas related to phar taken from `composer selfupdate`.
20
 */
21
class UpdateCommand extends BaseCommand
22
{
23
    const PHAR_URL = 'https://github.com/adhocore/phint/releases/download/{version}/phint.phar';
24
25
    /** @var string Command name */
26
    protected $_name = 'update';
27
28
    /** @var string Command description */
29
    protected $_desc = 'Update Phint to lastest version';
30
31
    protected function onConstruct()
32
    {
33
        $this
34
            ->option('-r --rollback', 'Rollback to earlier version', 'boolval', false)
35
                ->on([$this, 'rollback'])
36
            ->usage(
37
                '<bold>  phint update</end>        Updates to latest version<eol/>' .
38
                '<bold>  phint u</end>             Also updates to latest version<eol/>' .
39
                '<bold>  phint update</end> <comment>-r</end>     Rolls back to prev version<eol/>' .
40
                '<bold>  phint u</end> <comment>--rollback</end>  Also rolls back to prev version<eol/>'
41
            );
42
    }
43
44
    /**
45
     * Execute the command action.
46
     */
47
    public function execute()
48
    {
49
        $io = $this->app()->io();
50
51
        $io->cyan("Current version {$this->_version}", true);
52
        $io->comment('Fetching latest version ...', true);
53
54
        $release = \shell_exec('curl -sSL https://api.github.com/repos/adhocore/phint/releases/latest');
55
        $release = \json_decode($release);
56
57
        if (\JSON_ERROR_NONE !== \json_last_error() || empty($release->assets[0])) {
58
            $io->bgRed('Problem getting latest release', true);
59
            $io->red($release->message ?? 'Unknown error', true);
60
61
            return;
62
        }
63
64
        $latest = $release->tag_name;
65
66
        if (!\version_compare(\str_replace('v', '', $this->_version), \str_replace('v', '', $latest), '<')) {
67
            $io->bgGreen('You seem to have latest version already', true);
68
        } else {
69
            $this->updateTo($latest, $release->assets[0]->size);
70
71
            if (\is_file($this->getPharPathFor(null) . '.old')) {
72
                $io->colors('You can run <comment>phint update --rollback</end> to revert<eol/>');
73
            }
74
        }
75
    }
76
77
    /**
78
     * Perform rollback.
79
     */
80
    public function rollback()
81
    {
82
        $io = $this->app()->io();
83
84
        $io->cyan("Current version {$this->_version}", true);
85
        $io->comment('Rolling back to earlier version ...', true);
86
87
        $thisPhar = $this->getPharPathFor(null);
88
        $oldPhar  = $thisPhar . '.old';
89
90
        if (!\is_file($oldPhar)) {
91
            throw new RuntimeException('No old version locally available');
92
        }
93
94
        $oldPerms = \fileperms($thisPhar);
95
96
        if (@\rename($oldPhar, $thisPhar)) {
97
            $io->ok('Done', true);
98
        }
99
100
        @\chmod($thisPhar, $oldPerms);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

100
        /** @scrutinizer ignore-unhandled */ @\chmod($thisPhar, $oldPerms);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
101
102
        $this->emit('_exit');
103
    }
104
105
    protected function updateTo(string $version, int $size)
106
    {
107
        $io = $this->app()->io();
108
109
        $currentPhar  = $this->getPharPathFor(null);
110
        $versionPhar  = $this->getPharPathFor($version);
111
        $sourceUrl    = \str_replace('{version}', $version, static::PHAR_URL);
112
113
        $io->comment("Downloading phar $version ...", true);
114
115
        // Create new $version phar
116
        $saved = @\file_put_contents($versionPhar, \shell_exec("curl -sSL $sourceUrl"));
117
118
        if ($saved < $size) {
119
            @\unlink($versionPhar);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

119
            /** @scrutinizer ignore-unhandled */ @\unlink($versionPhar);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
120
121
            throw new RuntimeException("Couldnt download the phar for $version");
122
        }
123
124
        $io->comment("Updating to $version ...", true);
125
126
        try {
127
            @\chmod($versionPhar, \fileperms($currentPhar));
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

127
            /** @scrutinizer ignore-unhandled */ @\chmod($versionPhar, \fileperms($currentPhar));

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
128
129
            if (!\ini_get('phar.readonly')) {
130
                $phar = new \Phar($versionPhar);
131
                unset($phar);
132
            }
133
134
            // Take backup of current
135
            @\copy($currentPhar, $currentPhar . '.old');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for copy(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

135
            /** @scrutinizer ignore-unhandled */ @\copy($currentPhar, $currentPhar . '.old');

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
136
137
            // Replace current with new $version
138
            @\rename($versionPhar, $currentPhar);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for rename(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

138
            /** @scrutinizer ignore-unhandled */ @\rename($versionPhar, $currentPhar);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
139
140
            $io->ok('Done', true);
141
        } catch (\Throwable $e) {
142
            $io->error('Couldnt update to ' . $version, true);
143
        }
144
    }
145
146
    protected function getPharPathFor(string $version = null): string
147
    {
148
        if (false === $thisPhint = \realpath($_SERVER['argv'][0])) {
149
            $thisPhint = $this->_pathUtil->getPhintPath('phint.phar');
150
        }
151
152
        if (empty($thisPhint)) {
153
            throw new RuntimeException('Couldnt locate phint path, make sure you have HOME in the env vars');
154
        }
155
156
        if (empty($version)) {
157
            return $thisPhint;
158
        }
159
160
        $pathTemplate = '%s.%s.phar';
161
162
        return \sprintf($pathTemplate, \str_replace('.phar', '', $thisPhint), $version);
163
    }
164
}
165