*/ class SelfUpdateCommand extends Command { const SELF_UPDATE_COMMAND_NAME = 'self:update'; protected $gitHubRepository; protected $currentVersion; protected $applicationName; public function __construct($applicationName = null, $currentVersion = null, $gitHubRepository = null) { $this->applicationName = $applicationName; $this->currentVersion = $currentVersion; $this->gitHubRepository = $gitHubRepository; parent::__construct(self::SELF_UPDATE_COMMAND_NAME); } /** * {@inheritdoc} */ protected function configure() { $app = $this->applicationName; $this ->setAliases(array('update')) ->setDescription("Updates $app to the latest version.") ->setHelp( <<self-update command checks github for newer versions of $app and if found, installs the latest. EOT ); } protected function getLatestReleaseFromGithub() { $opts = [ 'http' => [ 'method' => 'GET', 'header' => [ 'User-Agent: ' . $this->applicationName . ' (' . $this->gitHubRepository . ')' . ' Self-Update (PHP)' ] ] ]; $context = stream_context_create($opts); $releases = file_get_contents('https://api.github.com/repos/' . $this->gitHubRepository . '/releases', false, $context); $releases = json_decode($releases); if (! isset($releases[0])) { throw new \Exception('API error - no release found at GitHub repository ' . $this->gitHubRepository); } $version = $releases[0]->tag_name; $url = $releases[0]->assets[0]->browser_download_url; return [ $version, $url ]; } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { if (empty(\Phar::running())) { throw new \Exception(self::SELF_UPDATE_COMMAND_NAME . ' only works when running the phar version of ' . $this->applicationName . '.'); } $localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0]; $programName = basename($localFilename); $tempFilename = dirname($localFilename) . '/' . basename($localFilename, '.phar') . '-temp.phar'; // check for permissions in local filesystem before start connection process if (! is_writable($tempDirectory = dirname($tempFilename))) { throw new \Exception( $programName . ' update failed: the "' . $tempDirectory . '" directory used to download the temp file could not be written' ); } if (! is_writable($localFilename)) { throw new \Exception( $programName . ' update failed: the "' . $localFilename . '" file could not be written (execute with sudo)' ); } list( $latest, $downloadUrl ) = $this->getLatestReleaseFromGithub(); if ($this->currentVersion == $latest) { $output->writeln('No update available'); return; } $fs = new sfFilesystem(); $output->writeln('Downloading ' . $this->applicationName . ' (' . $this->gitHubRepository . ') ' . $latest); $fs->copy($downloadUrl, $tempFilename); $output->writeln('Download finished'); try { \error_reporting(E_ALL); // supress notices @chmod($tempFilename, 0777 & ~umask()); // test the phar validity $phar = new \Phar($tempFilename); // free the variable to unlock the file unset($phar); @rename($tempFilename, $localFilename); $output->writeln('Successfully updated ' . $programName . ''); $this->_exit(); } catch (\Exception $e) { @unlink($tempFilename); if (! $e instanceof \UnexpectedValueException && ! $e instanceof \PharException) { throw $e; } $output->writeln('The download is corrupted (' . $e->getMessage() . ').'); $output->writeln('Please re-run the self-update command to try again.'); } } /** * Stop execution * * This is a workaround to prevent warning of dispatcher after replacing * the phar file. * * @return void */ protected function _exit() { exit; } }