Passed
Push — feature/config-file ( 80a3c9...c343d6 )
by Arnaud
11:33 queued 07:45
created

Serve::execute()   C

Complexity

Conditions 10
Paths 244

Size

Total Lines 84
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 10
eloc 59
nc 244
nop 2
dl 0
loc 84
rs 5.9212
c 4
b 0
f 0

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
 * This file is part of the Cecil/Cecil package.
4
 *
5
 * Copyright (c) Arnaud Ligny <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Cecil\Command;
12
13
use Cecil\Util;
14
use Symfony\Component\Console\Input\ArrayInput;
15
use Symfony\Component\Console\Input\InputArgument;
16
use Symfony\Component\Console\Input\InputDefinition;
17
use Symfony\Component\Console\Input\InputInterface;
18
use Symfony\Component\Console\Input\InputOption;
19
use Symfony\Component\Console\Output\OutputInterface;
20
use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
21
use Symfony\Component\Finder\Finder;
22
use Symfony\Component\Process\Exception\ProcessFailedException;
23
use Symfony\Component\Process\Process;
24
use Yosymfony\ResourceWatcher\Crc32ContentHash;
25
use Yosymfony\ResourceWatcher\ResourceCacheMemory;
26
use Yosymfony\ResourceWatcher\ResourceWatcher;
27
28
/**
29
 * Starts the built-in server.
30
 */
31
class Serve extends AbstractCommand
32
{
33
    /**
34
     * {@inheritdoc}
35
     */
36
    protected function configure()
37
    {
38
        $this
39
            ->setName('serve')
40
            ->setDescription('Starts the built-in server')
41
            ->setDefinition(
42
                new InputDefinition([
43
                    new InputArgument('path', InputArgument::OPTIONAL, 'Use the given path as working directory'),
44
                    new InputOption('config', 'c', InputOption::VALUE_REQUIRED, 'Specific configuration file ("config.yml")'),
45
                    new InputOption('drafts', 'd', InputOption::VALUE_NONE, 'Include drafts'),
46
                    new InputOption('open', 'o', InputOption::VALUE_NONE, 'Open browser automatically'),
47
                    new InputOption('host', null, InputOption::VALUE_REQUIRED, 'Server host'),
48
                    new InputOption('port', null, InputOption::VALUE_REQUIRED, 'Server port'),
49
                    new InputOption(
50
                        'postprocess',
51
                        null,
52
                        InputOption::VALUE_OPTIONAL,
53
                        'Post-process output (disable with "no")',
54
                        false
55
                    ),
56
                    new InputOption('clear-cache', null, InputOption::VALUE_NONE, 'Clear cache after build'),
57
                ])
58
            )
59
            ->setHelp('Starts the live-reloading-built-in web server');
60
    }
61
62
    /**
63
     * {@inheritdoc}
64
     */
65
    protected function execute(InputInterface $input, OutputInterface $output)
66
    {
67
        $drafts = $input->getOption('drafts');
68
        $open = $input->getOption('open');
69
        $host = $input->getOption('host') ?? 'localhost';
70
        $port = $input->getOption('port') ?? '8000';
71
        $postprocess = $input->getOption('postprocess');
72
73
        $this->setUpServer($host, $port);
74
        $command = sprintf(
75
            'php -S %s:%d -t %s %s',
76
            $host,
77
            $port,
78
            $this->getPath().'/'.(string) $this->getBuilder()->getConfig()->get('output.dir'),
79
            Util::joinFile($this->getPath(), self::TMP_DIR, 'router.php')
80
        );
81
        $process = Process::fromShellCommandline($command);
82
83
        // (re)builds before serve
84
        $buildCommand = $this->getApplication()->find('build');
85
        $buildImputArray = [
86
            'command'       => 'build',
87
            'path'          => $this->getPath(),
88
            '--drafts'      => $drafts,
89
            '--postprocess' => $postprocess,
90
        ];
91
        if ($this->getConfigFile() !== null) {
1 ignored issue
show
introduced by
The condition $this->getConfigFile() !== null is always true.
Loading history...
92
            $buildImputArray = array_merge($buildImputArray, [
93
                '--config' => $this->getConfigFile(),
94
            ]);
95
        }
96
        $buildInput = new ArrayInput($buildImputArray);
97
        if ($buildCommand->run($buildInput, $this->output) != 0) {
98
            return 1;
99
        }
100
101
        // handles process
102
        if (!$process->isStarted()) {
103
            // set resource watcher
104
            $finder = new Finder();
105
            $finder->files()
106
                ->in($this->getPath())
107
                ->exclude($this->getBuilder()->getConfig()->getOutputPath());
108
            if (file_exists(Util::joinFile($this->getPath(), '.gitignore'))) {
109
                $finder->ignoreVCSIgnored(true);
110
            }
111
            $hashContent = new Crc32ContentHash();
112
            $resourceCache = new ResourceCacheMemory();
113
            $resourceWatcher = new ResourceWatcher($resourceCache, $finder, $hashContent);
114
            $resourceWatcher->initialize();
115
            // starts server
116
            try {
117
                if (function_exists('\pcntl_signal')) {
118
                    \pcntl_async_signals(true);
119
                    \pcntl_signal(SIGINT, [$this, 'tearDownServer']);
120
                    \pcntl_signal(SIGTERM, [$this, 'tearDownServer']);
121
                }
122
                $output->writeln(
123
                    sprintf('Starting server (<href=http://%s:%d>%s:%d</>)...', $host, $port, $host, $port)
124
                );
125
                $process->start();
126
                if ($open) {
127
                    $output->writeln('Opening web browser...');
128
                    Util\Plateform::openBrowser(sprintf('http://%s:%s', $host, $port));
129
                }
130
                while ($process->isRunning()) {
131
                    $result = $resourceWatcher->findChanges();
132
                    if ($result->hasChanges()) {
133
                        // re-builds
134
                        $output->writeln('<comment>Changes detected.</comment>');
135
                        $output->writeln('');
136
                        $buildCommand->run($buildInput, $output);
137
                        $output->writeln('<info>Server is runnning...</info>');
138
                    }
139
                    usleep(1000000); // waits 1s
140
                }
141
            } catch (ProcessFailedException $e) {
142
                $this->tearDownServer();
143
144
                throw new \Exception(sprintf($e->getMessage()));
145
            }
146
        }
147
148
        return 0;
149
    }
150
151
    /**
152
     * Prepares server's files.
153
     *
154
     * @param string $host
155
     * @param string $port
156
     *
157
     * @throws \Exception
158
     *
159
     * @return void
160
     */
161
    private function setUpServer(string $host, string $port): void
162
    {
163
        try {
164
            $root = Util::joinFile(__DIR__, '../../');
165
            if (Util\Plateform::isPhar()) {
166
                $root = Util\Plateform::getPharPath().'/';
167
            }
168
            // copying router
169
            $this->fs->copy(
170
                $root.'/res/server/router.php',
171
                Util::joinFile($this->getPath(), self::TMP_DIR, 'router.php'),
172
                true
173
            );
174
            // copying livereload JS
175
            $this->fs->copy(
176
                $root.'/res/server/livereload.js',
177
                Util::joinFile($this->getPath(), self::TMP_DIR, 'livereload.js'),
178
                true
179
            );
180
            // copying baseurl text file
181
            $this->fs->dumpFile(
182
                Util::joinFile($this->getPath(), self::TMP_DIR, 'baseurl'),
183
                sprintf(
184
                    '%s;%s',
185
                    (string) $this->getBuilder()->getConfig()->get('baseurl'),
186
                    sprintf('http://%s:%s/', $host, $port)
187
                )
188
            );
189
        } catch (IOExceptionInterface $e) {
190
            throw new \Exception(sprintf('An error occurred while copying server\'s files to "%s"', $e->getPath()));
191
        }
192
        if (!is_file(Util::joinFile($this->getPath(), self::TMP_DIR, 'router.php'))) {
193
            throw new \Exception(sprintf('Router not found: "%s"', Util::joinFile(self::TMP_DIR, 'router.php')));
194
        }
195
    }
196
197
    /**
198
     * Removes temporary directory.
199
     *
200
     * @throws \Exception
201
     *
202
     * @return void
203
     */
204
    private function tearDownServer(): void
205
    {
206
        $this->output->writeln('');
207
        $this->output->writeln('<comment>Server stopped.</comment>');
208
209
        try {
210
            $this->fs->remove(Util::joinFile($this->getPath(), self::TMP_DIR));
211
        } catch (IOExceptionInterface $e) {
212
            throw new \Exception($e->getMessage());
213
        }
214
    }
215
}
216