Completed
Pull Request — master (#615)
by
unknown
02:42
created

SelfUpdateCommand   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 139
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

Changes 6
Bugs 0 Features 1
Metric Value
wmc 14
lcom 2
cbo 3
dl 0
loc 139
rs 10
c 6
b 0
f 1

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A configure() 0 13 1
B getLatestReleaseFromGithub() 0 25 2
A isEnabled() 0 4 1
B execute() 0 57 8
A _exit() 0 4 1
1
<?php
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Robo;
13
14
use Symfony\Component\Console\Command\Command;
15
use Symfony\Component\Console\Input\InputInterface;
16
use Symfony\Component\Console\Output\OutputInterface;
17
use Symfony\Component\Filesystem\Filesystem as sfFilesystem;
18
19
/**
20
 * Update the robo.phar from the latest github release
21
 *
22
 * @author Alexander Menk <[email protected]>
23
 */
24
class SelfUpdateCommand extends Command
25
{
26
    private $command;
0 ignored issues
show
Unused Code introduced by
The property $command is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
27
28
    protected $gitHubRepository;
29
30
    protected $currentVersion;
31
32
    public function __construct($name = null, $currentVersion = null, $gitHubRepository = null)
33
    {
34
        parent::__construct($name);
35
        $this->currentVersion = $currentVersion;
36
        $this->gitHubRepository = $gitHubRepository;
37
    }
38
39
40
    /**
41
     * {@inheritdoc}
42
     */
43
    protected function configure()
44
    {
45
        $this
46
            ->setName('self-update')
47
            ->setAliases(array( 'selfupdate' ))
48
            ->setDescription('Updates the robo.phar to the latest version.')
49
            ->setHelp(
50
                <<<EOT
51
The <info>self-update</info> command checks github for newer
52
versions of robo and if found, installs the latest.
53
EOT
54
            );
55
    }
56
57
    protected function getLatestReleaseFromGithub()
58
    {
59
        $opts = [
60
            'http' => [
61
                'method' => 'GET',
62
                'header' => [
63
                    'User-Agent: ' . Robo::APPLICATION_NAME  . ' (' . $this->gitHubRepository . ')' . ' Self-Update (PHP)'
64
                ]
65
            ]
66
        ];
67
68
        $context = stream_context_create($opts);
69
70
        $releases = file_get_contents('https://api.github.com/repos/' . $this->gitHubRepository . '/releases', false, $context);
71
        $releases = json_decode($releases);
72
73
        if (! isset($releases[0])) {
74
            throw new \Exception('API error - no release found at GitHub repository ' . $this->gitHubRepository);
75
        }
76
77
        $version = $releases[0]->tag_name;
78
        $url     = $releases[0]->assets[0]->browser_download_url;
79
80
        return [ $version, $url ];
81
    }
82
83
    public function isEnabled()
84
    {
85
        return !empty(\Phar::running());
86
    }
87
88
89
    /**
90
     * {@inheritdoc}
91
     */
92
    protected function execute(InputInterface $input, OutputInterface $output)
0 ignored issues
show
Coding Style introduced by
execute uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
93
    {
94
95
        $localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0];
96
        $programName   = basename($localFilename);
97
        $tempFilename  = dirname($localFilename) . '/' . basename($localFilename, '.phar') . '-temp.phar';
98
99
        // check for permissions in local filesystem before start connection process
100
        if (! is_writable($tempDirectory = dirname($tempFilename))) {
101
            throw new \Exception(
102
                $programName . ' update failed: the "' . $tempDirectory .
103
                '" directory used to download the temp file could not be written'
104
            );
105
        }
106
107
        if (! is_writable($localFilename)) {
108
            throw new \Exception(
109
                $programName . ' update failed: the "' . $localFilename . '" file could not be written (execute with sudo)'
110
            );
111
        }
112
113
        list( $latest, $downloadUrl ) = $this->getLatestReleaseFromGithub();
114
115
116
        if ($this->currentVersion == $latest) {
117
            $output->writeln('No update available');
118
            return;
119
        }
120
121
        $fs = new sfFilesystem();
122
123
        $output->writeln('Downloading ' . Robo::APPLICATION_NAME . ' (' . $this->gitHubRepository . ') ' . $latest);
124
125
        $fs->copy($downloadUrl, $tempFilename);
126
127
        $output->writeln('Download finished');
128
129
        try {
130
            \error_reporting(E_ALL); // supress notices
131
132
            @chmod($tempFilename, 0777 & ~umask());
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
133
            // test the phar validity
134
            $phar = new \Phar($tempFilename);
135
            // free the variable to unlock the file
136
            unset($phar);
137
            @rename($tempFilename, $localFilename);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
138
            $output->writeln('<info>Successfully updated ' . $programName . '</info>');
139
            $this->_exit();
140
        } catch (\Exception $e) {
141
            @unlink($tempFilename);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
142
            if (! $e instanceof \UnexpectedValueException && ! $e instanceof \PharException) {
143
                throw $e;
144
            }
145
            $output->writeln('<error>The download is corrupted (' . $e->getMessage() . ').</error>');
146
            $output->writeln('<error>Please re-run the self-update command to try again.</error>');
147
        }
148
    }
149
150
    /**
151
     * Stop execution
152
     *
153
     * This is a workaround to prevent warning of dispatcher after replacing
154
     * the phar file.
155
     *
156
     * @return void
157
     */
158
    protected function _exit()
159
    {
160
        exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method _exit() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
161
    }
162
}
163