Cmd::run()   A
last analyzed

Complexity

Conditions 4
Paths 6

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 17
c 0
b 0
f 0
nc 6
nop 1
dl 0
loc 25
ccs 0
cts 16
cp 0
crap 20
rs 9.7
1
<?php
2
/**
3
 * phpbu
4
 *
5
 * Copyright (c) 2014 - 2017 Sebastian Feldmann <[email protected]>
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
 * of this software and associated documentation files (the "Software"), to deal
9
 * in the Software without restriction, including without limitation the rights
10
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 * copies of the Software, and to permit persons to whom the Software is
12
 * furnished to do so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in
15
 * all copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
 * THE SOFTWARE.
24
 *
25
 * @package    phpbu
26
 * @author     Sebastian Feldmann <[email protected]>
27
 * @copyright  Sebastian Feldmann
28
 * @license    https://opensource.org/licenses/MIT The MIT License (MIT)
29
 * @link       https://phpbu.de/
30
 * @since      Class available since Release 1.0.0
31
 */
32
namespace phpbu\App;
33
34
use Phar;
35
use phpbu\App\Cmd\Args;
36
use phpbu\App\Configuration\Bootstrapper;
37
use phpbu\App\Util\Arr;
38
use function fgets;
39
use function file_put_contents;
40
use function getcwd;
41
use function sprintf;
42
use function trim;
43
use const PHP_EOL;
44
use const STDIN;
45
46
/**
47
 * Main application class.
48
 *
49
 * @package    phpbu
50
 * @author     Sebastian Feldmann <[email protected]>
51
 * @copyright  Sebastian Feldmann <[email protected]>
52
 * @license    https://opensource.org/licenses/MIT The MIT License (MIT)
53
 * @link       https://phpbu.de/
54
 * @since      Class available since Release 1.0.0
55
 */
56
final class Cmd
57
{
58
    const EXIT_SUCCESS   = 0;
59
    const EXIT_FAILURE   = 1;
60
    const EXIT_EXCEPTION = 2;
61
62
    /**
63
     * Is cmd executed from phar
64
     *
65
     * @var bool
66
     */
67
    private $isPhar;
68
69
    /**
70
     * Is version string printed already
71
     *
72
     * @var bool
73
     */
74
    private $isVersionStringPrinted = false;
75
76
    /**
77
     * List of given arguments
78
     *
79
     * @var array
80
     */
81
    private $arguments = [];
82
83
    /**
84
     * Runs the application
85
     *
86
     * @param array $args
87
     */
88
    public function run(array $args) : void
89
    {
90
        $this->isPhar = defined('__PHPBU_PHAR__');
91
        $this->handleOpt($args);
92
93
        $ret        = self::EXIT_FAILURE;
94
        $factory    = new Factory();
95
        $runner     = new Runner($factory);
96
        $configFile = $this->findConfiguration();
97
98
        try {
99
            $this->printVersionString();
100
            $result = $runner->run($this->createConfiguration($configFile, $factory));
101
102
            if ($result->wasSuccessful()) {
103
                $ret = self::EXIT_SUCCESS;
104
            } elseif ($result->errorCount() > 0) {
105
                $ret = self::EXIT_EXCEPTION;
106
            }
107
        } catch (\Exception $e) {
108
            echo $e->getMessage() . PHP_EOL;
109
            $ret = self::EXIT_EXCEPTION;
110
        }
111
112
        exit($ret);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
113
    }
114
115
    /**
116
     * Check arguments and load configuration file
117
     *
118
     * @param array $args
119
     */
120
    protected function handleOpt(array $args) : void
121
    {
122
        try {
123
            $parser  = new Args($this->isPhar);
124
            $options = $parser->getOptions($args);
125
            $this->handleArgs($options);
126
        } catch (Exception $e) {
127
            $this->printError($e->getMessage(), true);
128
        }
129
    }
130
131
    /**
132
     * Handle the parsed command line options
133
     *
134
     * @param  array $options
135
     * @return void
136
     */
137
    protected function handleArgs(array $options) : void
138
    {
139
        foreach ($options as $option => $argument) {
140
            switch ($option) {
141
                case '-V':
142
                case '--version':
143
                    $this->printVersionString();
144
                    exit(self::EXIT_SUCCESS);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
145
                case '--self-update':
146
                    $this->handleSelfUpdate();
147
                    exit(self::EXIT_SUCCESS);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
148
                case '--generate-configuration':
149
                    $this->handleConfigGeneration();
150
                    exit(self::EXIT_SUCCESS);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
151
                case '--version-check':
152
                    $this->handleVersionCheck();
153
                    exit(self::EXIT_SUCCESS);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
154
                case '-h':
155
                case '--help':
156
                    $this->printHelp();
157
                    exit(self::EXIT_SUCCESS);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
158
                case '-v':
159
                    $this->arguments['verbose'] = true;
160
                    break;
161
                default:
162
                    $this->arguments[trim($option, '-')] = $argument;
163
                    break;
164
            }
165
        }
166
    }
167
168
    /**
169
     * Try to find a configuration file
170
     */
171
    protected function findConfiguration() : string
172
    {
173
        $configOption = $this->arguments['configuration'] ?? '';
174
175
        try {
176
            $finder = new Configuration\Finder();
177
            return $finder->findConfiguration($configOption);
178
        } catch (\Exception $e) {
179
            // config option given but still no config found
180
            if (!empty($configOption)) {
181
                $this->printError('Can\'t find configuration file.');
182
            }
183
            $this->printHelp();
184
            exit(self::EXIT_EXCEPTION);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
185
        }
186
    }
187
188
    /**
189
     * Create a application configuration
190
     *
191
     * @param  string             $configurationFile
192
     * @param  \phpbu\App\Factory $factory
193
     * @return \phpbu\App\Configuration
194
     * @throws \phpbu\App\Exception
195
     */
196
    protected function createConfiguration(string $configurationFile, Factory $factory) : Configuration
197
    {
198
        // setup bootstrapper with --bootstrap option that has precedence over the config file value
199
        $bootstrapper  = new Bootstrapper(Arr::getValue($this->arguments, 'bootstrap', ''));
200
        $configLoader  = Configuration\Loader\Factory::createLoader($configurationFile, $bootstrapper);
201
202
        if ($configLoader->hasValidationErrors()) {
203
            echo "  Warning - The configuration file did not pass validation!" . PHP_EOL .
204
                 "  The following problems have been detected:" . PHP_EOL;
205
206
            foreach ($configLoader->getValidationErrors() as $line => $errors) {
207
                echo sprintf("\n  Line %d:\n", $line);
208
                foreach ($errors as $msg) {
209
                    echo sprintf("  - %s\n", $msg);
210
                }
211
            }
212
            echo PHP_EOL;
213
        }
214
215
        $configuration = $configLoader->getConfiguration($factory);
216
217
        // command line arguments overrule the config file settings
218
        $this->overrideConfigWithArguments($configuration);
219
220
        // check for command line limit option
221
        $limitOption = Arr::getValue($this->arguments, 'limit');
222
        $configuration->setLimit(!empty($limitOption) ? explode(',', $limitOption) : []);
223
224
        // add a cli printer for some output
225
        $configuration->addLogger(
226
            new Result\PrinterCli(
227
                $configuration->getVerbose(),
228
                $configuration->getColors(),
229
                ($configuration->getDebug() || $configuration->isSimulation())
230
            )
231
        );
232
        return $configuration;
233
    }
234
235
    /**
236
     * Override configuration settings with command line arguments
237
     *
238
     * @param \phpbu\App\Configuration $configuration
239
     */
240
    protected function overrideConfigWithArguments(Configuration $configuration) : void
241
    {
242
        $settingsToOverride = ['verbose', 'colors', 'debug', 'simulate', 'restore'];
243
        foreach ($settingsToOverride as $arg) {
244
            $value = Arr::getValue($this->arguments, $arg);
245
            if (!empty($value)) {
246
                $setter = 'set' . ucfirst($arg);
247
                $configuration->{$setter}($value);
248
            }
249
        }
250
    }
251
252
    /**
253
     * Handle the phar self-update
254
     */
255
    protected function handleSelfUpdate() : void
256
    {
257
        $this->printVersionString();
258
259
        // check if upgrade is necessary
260
        $latestVersion = $this->getLatestVersion();
261
        if (!$this->isPharOutdated($latestVersion)) {
262
            echo 'You already have the latest version of phpbu installed.' . PHP_EOL;
263
            exit(self::EXIT_SUCCESS);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
264
        }
265
266
        $remoteFilename = 'http://phar.phpbu.de/phpbu.phar';
267
        $localFilename  = realpath($_SERVER['argv'][0]);
268
        $tempFilename   = basename($localFilename, '.phar') . '-temp.phar';
269
270
        echo 'Updating the phpbu PHAR to version ' . $latestVersion . ' ... ';
271
272
        $old  = error_reporting(0);
273
        $phar = file_get_contents($remoteFilename);
274
        error_reporting($old);
275
        if (!$phar) {
276
            echo ' failed' . PHP_EOL . 'Could not reach phpbu update site' . PHP_EOL;
277
            exit(self::EXIT_EXCEPTION);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
278
        }
279
        file_put_contents($tempFilename, $phar);
280
281
        chmod($tempFilename, 0777 & ~umask());
282
283
        // check downloaded phar
284
        try {
285
            $phar = new Phar($tempFilename);
286
            unset($phar);
287
            // replace current phar with the new one
288
            rename($tempFilename, $localFilename);
289
        } catch (\Exception $e) {
290
            // cleanup crappy phar
291
            unlink($tempFilename);
292
            echo 'failed' . PHP_EOL . $e->getMessage() . PHP_EOL;
293
            exit(self::EXIT_EXCEPTION);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
294
        }
295
296
        echo 'done' . PHP_EOL;
297
    }
298
299
    /**
300
     * Handle phar version-check
301
     */
302
    protected function handleVersionCheck() : void
303
    {
304
        $this->printVersionString();
305
306
        $latestVersion = $this->getLatestVersion();
307
        if ($this->isPharOutdated($latestVersion)) {
308
            print 'You are not using the latest version of phpbu.' . PHP_EOL
309
                . 'Use "phpbu --self-update" to install phpbu ' . $latestVersion . PHP_EOL;
310
        } else {
311
            print 'You are using the latest version of phpbu.' . PHP_EOL;
312
        }
313
    }
314
315
    /**
316
     * Create a configuration file by asking the user some questions
317
     *
318
     * @return void
319
     */
320
    private function handleConfigGeneration() : void
321
    {
322
        $this->printVersionString();
323
324
        print 'Configuration file format: xml|json (default: xml): ';
325
        $format = trim(fgets(STDIN)) === 'json' ? 'json' : 'xml';
326
        $file   = 'phpbu.' . $format;
327
328
        if (file_exists($file)) {
329
            echo '  FAILED: The configuration file already exists.' . PHP_EOL;
330
            exit(self::EXIT_EXCEPTION);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
331
        }
332
333
        print PHP_EOL . 'Generating ' . $file . ' in ' . getcwd() . PHP_EOL . PHP_EOL;
334
335
        print 'Bootstrap script (relative to path shown above; e.g: vendor/autoload.php): ';
336
        $bootstrapScript = trim(fgets(STDIN));
337
338
        $generator = new Configuration\Generator;
339
340
        file_put_contents(
341
            $file,
342
            $generator->generateConfigurationSkeleton(
343
                Version::minor(),
344
                $format,
345
                $bootstrapScript
346
            )
347
        );
348
349
        print PHP_EOL . 'Generated ' . $file . ' in ' . getcwd() . PHP_EOL . PHP_EOL .
350
            'ATTENTION:' . PHP_EOL .
351
            'The created configuration is just a skeleton. You have to finish the configuration manually.' . PHP_EOL;
352
    }
353
354
    /**
355
     * Returns latest released phpbu version
356
     *
357
     * @return string
358
     * @throws \RuntimeException
359
     */
360
    protected function getLatestVersion() : string
361
    {
362
        $old     = error_reporting(0);
363
        $version = file_get_contents('https://phar.phpbu.de/latest-version-of/phpbu');
364
        error_reporting($old);
365
        if (!$version) {
366
            echo 'Network-Error: Could not check latest version.' . PHP_EOL;
367
            exit(self::EXIT_EXCEPTION);
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
368
        }
369
        return $version;
370
    }
371
372
    /**
373
     * Check if current phar is outdated
374
     *
375
     * @param  string $latestVersion
376
     * @return bool
377
     */
378
    protected function isPharOutdated(string $latestVersion) : bool
379
    {
380
        return version_compare($latestVersion, Version::id(), '>');
381
    }
382
383
    /**
384
     * Shows the current application version.
385
     */
386
    protected function printVersionString() : void
387
    {
388
        if ($this->isVersionStringPrinted) {
389
            return;
390
        }
391
392
        echo Version::getVersionString() . PHP_EOL . PHP_EOL;
393
        $this->isVersionStringPrinted = true;
394
    }
395
396
    /**
397
     * Show the help message
398
     */
399
    protected function printHelp() : void
400
    {
401
        $this->printVersionString();
402
        echo <<<EOT
403
Usage: phpbu [option]
404
405
  --bootstrap=<file>       A "bootstrap" PHP file that is included before the backup.
406
  --configuration=<file>   A phpbu configuration file.
407
  --colors                 Use colors in output.
408
  --debug                  Display debugging information during backup generation.
409
  --generate-configuration Create a new configuration skeleton.
410
  --limit=<subset>         Limit backup execution to a subset.
411
  --simulate               Perform a trial run with no changes made.
412
  --restore                Print a restore guide.
413
  -h, --help               Print this usage information.
414
  -v, --verbose            Output more verbose information.
415
  -V, --version            Output version information and exit.
416
417
EOT;
418
        if ($this->isPhar) {
419
            echo '  --version-check        Check whether phpbu is up to date.' . PHP_EOL;
420
            echo '  --self-update          Upgrade phpbu to the latest version.' . PHP_EOL;
421
        }
422
    }
423
424
    /**
425
     * Shows some given error message
426
     *
427
     * @param string $message
428
     * @param bool   $hint
429
     */
430
    private function printError($message, $hint = false) : void
431
    {
432
        $help = $hint ? ', use "phpbu -h" for help' : '';
433
        $this->printVersionString();
434
        echo $message . $help . PHP_EOL;
435
        exit(self::EXIT_EXCEPTION);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
436
    }
437
438
    /**
439
     * Main method, is called by phpbu command and the phar file
440
     */
441
    public static function main() : void
442
    {
443
        $app = new static();
444
        $app->run($_SERVER['argv']);
445
    }
446
}
447