| Total Complexity | 51 |
| Total Lines | 349 |
| Duplicated Lines | 0 % |
| Changes | 10 | ||
| Bugs | 3 | Features | 0 |
Complex classes like Serve often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Serve, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 38 | class Serve extends AbstractCommand |
||
| 39 | { |
||
| 40 | /** @var boolean */ |
||
| 41 | protected $watcherEnabled; |
||
| 42 | |||
| 43 | /** |
||
| 44 | * {@inheritdoc} |
||
| 45 | */ |
||
| 46 | protected function configure() |
||
| 47 | { |
||
| 48 | $this |
||
| 49 | ->setName('serve') |
||
| 50 | ->setDescription('Starts the built-in server') |
||
| 51 | ->setDefinition([ |
||
| 52 | new InputArgument('path', InputArgument::OPTIONAL, 'Use the given path as working directory'), |
||
| 53 | new InputOption('open', 'o', InputOption::VALUE_NONE, 'Open web browser automatically'), |
||
| 54 | new InputOption('host', null, InputOption::VALUE_REQUIRED, 'Server host', 'localhost'), |
||
| 55 | new InputOption('port', null, InputOption::VALUE_REQUIRED, 'Server port', '8000'), |
||
| 56 | new InputOption('watch', 'w', InputOption::VALUE_NEGATABLE, 'Enable (or disable --no-watch) changes watcher (enabled by default)', true), |
||
| 57 | new InputOption('drafts', 'd', InputOption::VALUE_NONE, 'Include drafts'), |
||
| 58 | new InputOption('optimize', null, InputOption::VALUE_NEGATABLE, 'Enable (or disable --no-optimize) optimization of generated files'), |
||
| 59 | new InputOption('config', 'c', InputOption::VALUE_REQUIRED, 'Set the path to extra config files (comma-separated)'), |
||
| 60 | new InputOption('clear-cache', null, InputOption::VALUE_OPTIONAL, 'Clear cache before build (optional cache key as regular expression)', false), |
||
| 61 | new InputOption('page', 'p', InputOption::VALUE_REQUIRED, 'Build a specific page'), |
||
| 62 | new InputOption('no-ignore-vcs', null, InputOption::VALUE_NONE, 'Changes watcher must not ignore VCS directories'), |
||
| 63 | new InputOption('metrics', 'm', InputOption::VALUE_NONE, 'Show build metrics (duration and memory) of each step'), |
||
| 64 | new InputOption('timeout', null, InputOption::VALUE_REQUIRED, 'Sets the process timeout (max. runtime) in seconds', 7200), // default is 2 hours |
||
| 65 | new InputOption('notif', null, InputOption::VALUE_NONE, 'Send desktop notification on server start'), |
||
| 66 | ]) |
||
| 67 | ->setHelp( |
||
| 68 | <<<'EOF' |
||
| 69 | The <info>%command.name%</> command starts the live-reloading-built-in web server. |
||
| 70 | |||
| 71 | <info>%command.full_name%</> |
||
| 72 | <info>%command.full_name% path/to/the/working/directory</> |
||
| 73 | <info>%command.full_name% --open</> |
||
| 74 | <info>%command.full_name% --drafts</> |
||
| 75 | <info>%command.full_name% --no-watch</> |
||
| 76 | |||
| 77 | You can use a custom host and port by using the <info>--host</info> and <info>--port</info> options: |
||
| 78 | |||
| 79 | <info>%command.full_name% --host=127.0.0.1 --port=8080</> |
||
| 80 | |||
| 81 | To build the website with an extra configuration file, you can use the <info>--config</info> option. |
||
| 82 | This is useful during local development to <comment>override some settings</comment> without modifying the main configuration: |
||
| 83 | |||
| 84 | <info>%command.full_name% --config=config/dev.yml</> |
||
| 85 | |||
| 86 | To start the server with changes watcher <comment>not ignoring VCS</comment> directories, run: |
||
| 87 | |||
| 88 | <info>%command.full_name% --no-ignore-vcs</> |
||
| 89 | |||
| 90 | To define the process <comment>timeout</comment> (in seconds), run: |
||
| 91 | |||
| 92 | <info>%command.full_name% --timeout=7200</> |
||
| 93 | |||
| 94 | Send a desktop <comment>notification</comment> on server start, run: |
||
| 95 | |||
| 96 | <info>%command.full_name% --notif</> |
||
| 97 | EOF |
||
| 98 | ); |
||
| 99 | } |
||
| 100 | |||
| 101 | /** |
||
| 102 | * {@inheritdoc} |
||
| 103 | * |
||
| 104 | * @throws RuntimeException |
||
| 105 | */ |
||
| 106 | protected function execute(InputInterface $input, OutputInterface $output): int |
||
| 299 | } |
||
| 300 | |||
| 301 | /** |
||
| 302 | * Build success actions. |
||
| 303 | */ |
||
| 304 | private function buildSuccessActions(OutputInterface $output): void |
||
| 305 | { |
||
| 306 | // writes `changes.flag` file |
||
| 307 | if ($this->watcherEnabled) { |
||
| 308 | Util\File::getFS()->dumpFile(Util::joinFile($this->getPath(), self::TMP_DIR, 'changes.flag'), time()); |
||
| 309 | } |
||
| 310 | // writes `headers.ini` file |
||
| 311 | $headers = $this->getBuilder()->getConfig()->get('server.headers'); |
||
| 312 | if (is_iterable($headers)) { |
||
| 313 | $output->writeln('Writing headers file...'); |
||
| 314 | Util\File::getFS()->remove(Util::joinFile($this->getPath(), self::TMP_DIR, 'headers.ini')); |
||
| 315 | foreach ($headers as $entry) { |
||
| 316 | Util\File::getFS()->appendToFile(Util::joinFile($this->getPath(), self::TMP_DIR, 'headers.ini'), "[{$entry['path']}]\n"); |
||
| 317 | foreach ($entry['headers'] ?? [] as $header) { |
||
| 318 | Util\File::getFS()->appendToFile(Util::joinFile($this->getPath(), self::TMP_DIR, 'headers.ini'), "{$header['key']} = \"{$header['value']}\"\n"); |
||
| 319 | } |
||
| 320 | } |
||
| 321 | } |
||
| 322 | } |
||
| 323 | |||
| 324 | /** |
||
| 325 | * Sets up the watcher. |
||
| 326 | */ |
||
| 327 | private function setupWatcher(bool $noignorevcs = false): ResourceWatcher |
||
| 337 | } |
||
| 338 | |||
| 339 | /** |
||
| 340 | * Prepares server's files. |
||
| 341 | * |
||
| 342 | * @throws RuntimeException |
||
| 343 | */ |
||
| 344 | private function setUpServer(): void |
||
| 345 | { |
||
| 346 | try { |
||
| 347 | // copying router |
||
| 348 | Util\File::getFS()->copy( |
||
| 349 | $this->rootPath . 'resources/server/router.php', |
||
| 350 | Util::joinFile($this->getPath(), self::TMP_DIR, 'router.php'), |
||
| 351 | true |
||
| 352 | ); |
||
| 353 | // copying livereload JS for watcher |
||
| 354 | $livereloadJs = Util::joinFile($this->getPath(), self::TMP_DIR, 'livereload.js'); |
||
| 355 | if (is_file($livereloadJs)) { |
||
| 356 | Util\File::getFS()->remove($livereloadJs); |
||
| 357 | } |
||
| 358 | if ($this->watcherEnabled) { |
||
| 359 | Util\File::getFS()->copy( |
||
| 360 | $this->rootPath . 'resources/server/livereload.js', |
||
| 361 | $livereloadJs, |
||
| 362 | true |
||
| 363 | ); |
||
| 364 | } |
||
| 365 | } catch (IOExceptionInterface $e) { |
||
| 366 | throw new RuntimeException(\sprintf('An error occurred while copying server\'s files to "%s".', $e->getPath())); |
||
| 367 | } |
||
| 368 | if (!is_file(Util::joinFile($this->getPath(), self::TMP_DIR, 'router.php'))) { |
||
| 369 | throw new RuntimeException(\sprintf('Router not found: "%s".', Util::joinFile(self::TMP_DIR, 'router.php'))); |
||
| 370 | } |
||
| 371 | } |
||
| 372 | |||
| 373 | /** |
||
| 374 | * Removes temporary directory. |
||
| 375 | * |
||
| 376 | * @throws RuntimeException |
||
| 377 | */ |
||
| 378 | public function tearDownServer(): void |
||
| 387 | } |
||
| 388 | } |
||
| 390 |