diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ServerCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ServerCommand.php new file mode 100644 index 0000000000000..784eb53b4b767 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ServerCommand.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +/** + * Base methods for commands related to PHP's built-in web server. + * + * @author Christian Flothmann + */ +abstract class ServerCommand extends ContainerAwareCommand +{ + /** + * {@inheritdoc} + */ + public function isEnabled() + { + if (version_compare(phpversion(), '5.4.0', '<') || defined('HHVM_VERSION')) { + return false; + } + + if (!extension_loaded('pcntl')) { + return false; + } + + return parent::isEnabled(); + } + + /** + * Determines the name of the lock file for a particular PHP web server process. + * + * @param string $address An address/port tuple + * + * @return string The filename + */ + protected function getLockFile($address) + { + return sys_get_temp_dir().'/'.strtr($address, '.:', '--').'.pid'; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStartCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStartCommand.php new file mode 100644 index 0000000000000..50649e4d22da3 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStartCommand.php @@ -0,0 +1,155 @@ + + * + * 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\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +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', 'web/'), + 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: + + %command.full_name% + +To change the default bind address and the default port use the address argument: + + %command.full_name% 127.0.0.1:8080 + +To change the default document root directory use the --docroot option: + + %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: + + %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) + { + $env = $this->getContainer()->getParameter('kernel.environment'); + + 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('Server started successfully'); + + return; + } + + if (posix_setsid() < 0) { + $output->writeln('Unable to set the child process as session leader'); + + return 1; + } + + $process = $this->createServerProcess( + $input->getArgument('address'), + $input->getOption('docroot'), + $input->getOption('router'), + $env, + null + ); + $process->disableOutput(); + $process->start(); + $lockFile = $this->getLockFile($input->getArgument('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); + } + } + + /** + * Creates a process to start PHP's built-in web server. + * + * @param string $address IP address and port to listen to + * @param string $documentRoot The application's document root + * @param string $router The router filename + * @param string $env The application environment + * @param int $timeout Process timeout + * + * @return Process The process + */ + private function createServerProcess($address, $documentRoot, $router, $env, $timeout = null) + { + $router = $router ?: $this + ->getContainer() + ->get('kernel') + ->locateResource(sprintf('@FrameworkBundle/Resources/config/router_%s.php', $env)) + ; + $script = implode(' ', array_map(array('Symfony\Component\Process\ProcessUtils', 'escapeArgument'), array( + PHP_BINARY, + '-S', + $address, + $router, + ))); + + return new Process('exec '.$script, $documentRoot, null, null, $timeout); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStatusCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStatusCommand.php new file mode 100644 index 0000000000000..9d48fc4bbd64a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStatusCommand.php @@ -0,0 +1,71 @@ + + * + * 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\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Shows the status of a process that is running PHP's built-in web server in + * the background. + * + * @author Christian Flothmann + */ +class ServerStatusCommand extends ServerCommand +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setDefinition(array( + new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1:8000'), + )) + ->setName('server:status') + ->setDescription('Outputs the status of the built-in web server for the given address') + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $address = $input->getArgument('address'); + + // remove an orphaned lock file + if (file_exists($this->getLockFile($address)) && !$this->isServerRunning($address)) { + unlink($this->getLockFile($address)); + } + + if (file_exists($this->getLockFile($address))) { + $output->writeln(sprintf('Web server still listening on %s', $address)); + } else { + $output->writeln(sprintf('No web server is listening on %s', $address)); + } + } + + private function isServerRunning($address) + { + list($hostname, $port) = explode(':', $address); + + if (false !== $fp = @fsockopen($hostname, $port, $errno, $errstr, 1)) { + fclose($fp); + + return true; + } + + return false; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStopCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStopCommand.php new file mode 100644 index 0000000000000..c1b3a91cd1859 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStopCommand.php @@ -0,0 +1,67 @@ + + * + * 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\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Stops a background process running PHP's built-in web server. + * + * @author Christian Flothmann + */ +class ServerStopCommand extends ServerCommand +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setDefinition(array( + new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1:8000'), + )) + ->setName('server:stop') + ->setDescription('Stops PHP\'s built-in web server that was started with the server:start command') + ->setHelp(<<%command.name% stops PHP's built-in web server: + + %command.full_name% + +To change the default bind address and the default port use the address argument: + + %command.full_name% 127.0.0.1:8080 + +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $address = $input->getArgument('address'); + $lockFile = $this->getLockFile($address); + + if (!file_exists($lockFile)) { + $output->writeln(sprintf('No web server is listening on %s', $address)); + + return 1; + } + + unlink($lockFile); + $output->writeln(sprintf('Stopped the web server listening on %s', $address)); + } +}