ServerStartCommand.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Bundle\FrameworkBundle\Command;
  11. use Symfony\Component\Console\Input\InputArgument;
  12. use Symfony\Component\Console\Input\InputInterface;
  13. use Symfony\Component\Console\Input\InputOption;
  14. use Symfony\Component\Console\Output\OutputInterface;
  15. use Symfony\Component\Console\Style\SymfonyStyle;
  16. use Symfony\Component\Process\PhpExecutableFinder;
  17. use Symfony\Component\Process\Process;
  18. /**
  19. * Runs PHP's built-in web server in a background process.
  20. *
  21. * @author Christian Flothmann <christian.flothmann@xabbuh.de>
  22. */
  23. class ServerStartCommand extends ServerCommand
  24. {
  25. /**
  26. * {@inheritdoc}
  27. */
  28. protected function configure()
  29. {
  30. $this
  31. ->setDefinition(array(
  32. new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1'),
  33. new InputOption('port', 'p', InputOption::VALUE_REQUIRED, 'Address port number', '8000'),
  34. new InputOption('docroot', 'd', InputOption::VALUE_REQUIRED, 'Document root', null),
  35. new InputOption('router', 'r', InputOption::VALUE_REQUIRED, 'Path to custom router script'),
  36. new InputOption('force', 'f', InputOption::VALUE_NONE, 'Force web server startup'),
  37. ))
  38. ->setName('server:start')
  39. ->setDescription('Starts PHP built-in web server in the background')
  40. ->setHelp(<<<'EOF'
  41. The <info>%command.name%</info> runs PHP's built-in web server:
  42. <info>php %command.full_name%</info>
  43. To change the default bind address and the default port use the <info>address</info> argument:
  44. <info>php %command.full_name% 127.0.0.1:8080</info>
  45. To change the default document root directory use the <info>--docroot</info> option:
  46. <info>php %command.full_name% --docroot=htdocs/</info>
  47. If you have a custom document root directory layout, you can specify your own
  48. router script using the <info>--router</info> option:
  49. <info>php %command.full_name% --router=app/config/router.php</info>
  50. Specifying a router script is required when the used environment is not <comment>"dev"</comment> or
  51. <comment>"prod"</comment>.
  52. See also: http://www.php.net/manual/en/features.commandline.webserver.php
  53. EOF
  54. )
  55. ;
  56. }
  57. /**
  58. * {@inheritdoc}
  59. */
  60. protected function execute(InputInterface $input, OutputInterface $output)
  61. {
  62. $io = new SymfonyStyle($input, $cliOutput = $output);
  63. if (!\extension_loaded('pcntl')) {
  64. $io->error(array(
  65. 'This command needs the pcntl extension to run.',
  66. 'You can either install it or use the "server:run" command instead to run the built-in web server.',
  67. ));
  68. if ($io->ask('Do you want to execute <info>server:run</info> immediately? [Yn] ', true)) {
  69. $command = $this->getApplication()->find('server:run');
  70. return $command->run($input, $cliOutput);
  71. }
  72. return 1;
  73. }
  74. $documentRoot = $input->getOption('docroot');
  75. if (null === $documentRoot) {
  76. $documentRoot = $this->getContainer()->getParameter('kernel.root_dir').'/../web';
  77. }
  78. if (!is_dir($documentRoot)) {
  79. $io->error(sprintf('The given document root directory "%s" does not exist.', $documentRoot));
  80. return 1;
  81. }
  82. $env = $this->getContainer()->getParameter('kernel.environment');
  83. if (false === $router = $this->determineRouterScript($input->getOption('router'), $env, $io)) {
  84. return 1;
  85. }
  86. $address = $input->getArgument('address');
  87. if (false === strpos($address, ':')) {
  88. $address .= ':'.$input->getOption('port');
  89. }
  90. if (!$input->getOption('force') && $this->isOtherServerProcessRunning($address)) {
  91. $io->error(array(
  92. sprintf('A process is already listening on http://%s.', $address),
  93. 'Use the --force option if the server process terminated unexpectedly to start a new web server process.',
  94. ));
  95. return 1;
  96. }
  97. if ('prod' === $env) {
  98. $io->error('Running PHP built-in server in production environment is NOT recommended!');
  99. }
  100. $pid = pcntl_fork();
  101. if ($pid < 0) {
  102. $io->error('Unable to start the server process.');
  103. return 1;
  104. }
  105. if ($pid > 0) {
  106. $io->success(sprintf('Web server listening on http://%s', $address));
  107. return;
  108. }
  109. if (posix_setsid() < 0) {
  110. $io->error('Unable to set the child process as session leader');
  111. return 1;
  112. }
  113. if (null === $process = $this->createServerProcess($io, $address, $documentRoot, $router)) {
  114. return 1;
  115. }
  116. $process->disableOutput();
  117. $process->start();
  118. $lockFile = $this->getLockFile($address);
  119. touch($lockFile);
  120. if (!$process->isRunning()) {
  121. $io->error('Unable to start the server process');
  122. unlink($lockFile);
  123. return 1;
  124. }
  125. // stop the web server when the lock file is removed
  126. while ($process->isRunning()) {
  127. if (!file_exists($lockFile)) {
  128. $process->stop();
  129. }
  130. sleep(1);
  131. }
  132. }
  133. /**
  134. * Determine the absolute file path for the router script, using the environment to choose a standard script
  135. * if no custom router script is specified.
  136. *
  137. * @param string|null $router File path of the custom router script, if set by the user; otherwise null
  138. * @param string $env The application environment
  139. * @param SymfonyStyle $io An SymfonyStyle instance
  140. *
  141. * @return string|bool The absolute file path of the router script, or false on failure
  142. */
  143. private function determineRouterScript($router, $env, SymfonyStyle $io)
  144. {
  145. if (null === $router) {
  146. $router = $this
  147. ->getContainer()
  148. ->get('kernel')
  149. ->locateResource(sprintf('@FrameworkBundle/Resources/config/router_%s.php', $env))
  150. ;
  151. }
  152. if (false === $path = realpath($router)) {
  153. $io->error(sprintf('The given router script "%s" does not exist.', $router));
  154. return false;
  155. }
  156. return $path;
  157. }
  158. /**
  159. * Creates a process to start PHP's built-in web server.
  160. *
  161. * @param SymfonyStyle $io A SymfonyStyle instance
  162. * @param string $address IP address and port to listen to
  163. * @param string $documentRoot The application's document root
  164. * @param string $router The router filename
  165. *
  166. * @return Process The process
  167. */
  168. private function createServerProcess(SymfonyStyle $io, $address, $documentRoot, $router)
  169. {
  170. $finder = new PhpExecutableFinder();
  171. if (false === $binary = $finder->find()) {
  172. $io->error('Unable to find PHP binary to start server.');
  173. return;
  174. }
  175. $script = implode(' ', array_map(array('Symfony\Component\Process\ProcessUtils', 'escapeArgument'), array(
  176. $binary,
  177. '-S',
  178. $address,
  179. $router,
  180. )));
  181. return new Process('exec '.$script, $documentRoot, null, null, null);
  182. }
  183. }