Passed
Pull Request — master (#95)
by Wilmer
02:57
created

Serve::execute()   B

Complexity

Conditions 10
Paths 28

Size

Total Lines 59
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 10.0296

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 29
c 2
b 0
f 0
dl 0
loc 59
ccs 28
cts 30
cp 0.9333
rs 7.6666
cc 10
nc 28
nop 2
crap 10.0296

How to fix   Long Method    Complexity   

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
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Console\Command;
6
7
use Symfony\Component\Console\Command\Command;
8
use Symfony\Component\Console\Input\InputArgument;
9
use Symfony\Component\Console\Input\InputInterface;
10
use Symfony\Component\Console\Input\InputOption;
11
use Symfony\Component\Console\Output\OutputInterface;
12
use Symfony\Component\Console\Style\SymfonyStyle;
13
use Yiisoft\Yii\Console\ExitCode;
14
15
class Serve extends Command
16
{
17
    public const EXIT_CODE_NO_DOCUMENT_ROOT = 2;
18
    public const EXIT_CODE_NO_ROUTING_FILE = 3;
19
    public const EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_PROCESS = 5;
20
21
    private const DEFAULT_PORT = 8080;
22
    private const DEFAULT_DOCROOT = 'public';
23
    private const DEFAULT_ROUTER = 'public/index.php';
24
25
    protected static $defaultName = 'serve';
26
27 5
    public function configure(): void
28
    {
29
        $this
30 5
            ->setDescription('Runs PHP built-in web server')
31 5
            ->setHelp('In order to access server from remote machines use 0.0.0.0:8000. That is especially useful when running server in a virtual machine.')
32 5
            ->addArgument('address', InputArgument::OPTIONAL, 'Host to serve at', 'localhost')
33 5
            ->addOption('port', 'p', InputOption::VALUE_OPTIONAL, 'Port to serve at', self::DEFAULT_PORT)
34 5
            ->addOption('docroot', 't', InputOption::VALUE_OPTIONAL, 'Document root to serve from', self::DEFAULT_DOCROOT)
35 5
            ->addOption('router', 'r', InputOption::VALUE_OPTIONAL, 'Path to router script', self::DEFAULT_ROUTER)
36 5
            ->addOption('env', 'e', InputOption::VALUE_OPTIONAL, 'It is only used for testing.');
37 5
    }
38
39 5
    protected function execute(InputInterface $input, OutputInterface $output): int
40
    {
41 5
        $io = new SymfonyStyle($input, $output);
42
43
        /** @var string $address */
44 5
        $address = $input->getArgument('address');
45
46
        /** @var string $router */
47 5
        $router = $input->getOption('router');
48
49
        /** @var string $port */
50 5
        $port = $input->getOption('port');
51
52
        /** @var string $docroot */
53 5
        $docroot = $input->getOption('docroot');
54
55 5
        if ($router === self::DEFAULT_ROUTER && !file_exists(self::DEFAULT_ROUTER)) {
56 3
            $router = null;
57
        }
58
59 5
        $env = $input->getOption('env');
60
61 5
        $documentRoot = getcwd() . '/' . $docroot; // TODO: can we do it better?
62
63 5
        if (strpos($address, ':') === false) {
64 4
            $address .= ':' . $port;
65
        }
66
67 5
        if (!is_dir($documentRoot)) {
68 1
            $io->error("Document root \"$documentRoot\" does not exist.");
69 1
            return self::EXIT_CODE_NO_DOCUMENT_ROOT;
70
        }
71
72 4
        if ($this->isAddressTaken($address)) {
73 1
            $io->error("http://$address is taken by another process.");
74 1
            return self::EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_PROCESS;
75
        }
76
77 3
        if ($router !== null && !file_exists($router)) {
78 1
            $io->error("Routing file \"$router\" does not exist.");
79 1
            return self::EXIT_CODE_NO_ROUTING_FILE;
80
        }
81
82 2
        $output->writeLn("Server started on <href=http://{$address}/>http://{$address}/</>");
83 2
        $output->writeLn("Document root is \"{$documentRoot}\"");
84
85 2
        if ($router) {
86 1
            $output->writeLn("Routing file is \"$router\"");
87
        }
88
89 2
        $output->writeLn('Quit the server with CTRL-C or COMMAND-C.');
90
91 2
        if ($env === 'test') {
92 2
            return ExitCode::OK;
93
        }
94
95
        passthru('"' . PHP_BINARY . '"' . " -S {$address} -t \"{$documentRoot}\" $router");
96
97
        return ExitCode::OK;
98
    }
99
100
    /**
101
     * @param string $address server address
102
     *
103
     * @return bool if address is already in use
104
     */
105 4
    private function isAddressTaken(string $address): bool
106
    {
107 4
        [$hostname, $port] = explode(':', $address);
108 4
        $fp = @fsockopen($hostname, (int)$port, $errno, $errstr, 3);
109 4
        if ($fp === false) {
110 3
            return false;
111
        }
112 1
        fclose($fp);
113 1
        return true;
114
    }
115
}
116