* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\PhpExecutableFinder; use Symfony\Component\Process\Process; /** * Runs PHP's built-in web server in a background process. * * @author Christian Flothmann */ class ServerStartCommand extends ServerCommand { /** * {@inheritdoc} */ protected function configure() { $this ->setDefinition(array( new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1:8000'), new InputOption('docroot', 'd', InputOption::VALUE_REQUIRED, 'Document root', null), new InputOption('router', 'r', InputOption::VALUE_REQUIRED, 'Path to custom router script'), )) ->setName('server:start') ->setDescription('Starts PHP built-in web server in the background') ->setHelp(<<%command.name% runs PHP's built-in web server: php %command.full_name% To change the default bind address and the default port use the address argument: php %command.full_name% 127.0.0.1:8080 To change the default document root directory use the --docroot option: php %command.full_name% --docroot=htdocs/ If you have a custom document root directory layout, you can specify your own router script using the --router option: php %command.full_name% --router=app/config/router.php Specifying a router script is required when the used environment is not "dev" or "prod". See also: http://www.php.net/manual/en/features.commandline.webserver.php EOF ) ; } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { if (!extension_loaded('pcntl')) { $output->writeln('This command needs the pcntl extension to run.'); $output->writeln('You can either install it or use the server:run command instead to run the built-in web server.'); if ($this->getHelper('question')->ask($input, $output, new ConfirmationQuestion('Do you want to start server:run immediately? [Yn] ', true))) { $command = $this->getApplication()->find('server:run'); return $command->run($input, $output); } return 1; } $documentRoot = $input->getOption('docroot'); if (null === $documentRoot) { $documentRoot = $this->getContainer()->getParameter('kernel.root_dir').'/../web'; } if (!is_dir($documentRoot)) { $output->writeln(sprintf('The given document root directory "%s" does not exist', $documentRoot)); return 1; } $env = $this->getContainer()->getParameter('kernel.environment'); if (false === $router = $this->determineRouterScript($input->getOption('router'), $env, $output)) { return 1; } $address = $input->getArgument('address'); if (false === strpos($address, ':')) { $output->writeln('The address has to be of the form bind-address:port.'); return 1; } if ($this->isOtherServerProcessRunning($address)) { $output->writeln(sprintf('A process is already listening on http://%s.', $address)); return 1; } if ('prod' === $env) { $output->writeln('Running PHP built-in server in production environment is NOT recommended!'); } $pid = pcntl_fork(); if ($pid < 0) { $output->writeln('Unable to start the server process'); return 1; } if ($pid > 0) { $output->writeln(sprintf('Web server listening on http://%s', $address)); return; } if (posix_setsid() < 0) { $output->writeln('Unable to set the child process as session leader'); return 1; } if (null === $process = $this->createServerProcess($output, $address, $documentRoot, $router)) { return 1; } $process->disableOutput(); $process->start(); $lockFile = $this->getLockFile($address); touch($lockFile); if (!$process->isRunning()) { $output->writeln('Unable to start the server process'); unlink($lockFile); return 1; } // stop the web server when the lock file is removed while ($process->isRunning()) { if (!file_exists($lockFile)) { $process->stop(); } sleep(1); } } /** * Determine the absolute file path for the router script, using the environment to choose a standard script * if no custom router script is specified. * * @param string|null $router File path of the custom router script, if set by the user; otherwise null * @param string $env The application environment * @param OutputInterface $output An OutputInterface instance * * @return string|bool The absolute file path of the router script, or false on failure */ private function determineRouterScript($router, $env, OutputInterface $output) { if (null === $router) { $router = $this ->getContainer() ->get('kernel') ->locateResource(sprintf('@FrameworkBundle/Resources/config/router_%s.php', $env)) ; } if (false === $path = realpath($router)) { $output->writeln(sprintf('The given router script "%s" does not exist', $router)); return false; } return $path; } /** * Creates a process to start PHP's built-in web server. * * @param OutputInterface $output A OutputInterface instance * @param string $address IP address and port to listen to * @param string $documentRoot The application's document root * @param string $router The router filename * * @return Process The process */ private function createServerProcess(OutputInterface $output, $address, $documentRoot, $router) { $finder = new PhpExecutableFinder(); if (false === $binary = $finder->find()) { $output->writeln('Unable to find PHP binary to start server'); return; } $script = implode(' ', array_map(array('Symfony\Component\Process\ProcessUtils', 'escapeArgument'), array( $binary, '-S', $address, $router, ))); return new Process('exec '.$script, $documentRoot, null, null, null); } }