new helper commands for PHP's built-in server

This commit is contained in:
Christian Flothmann 2014-07-05 10:07:52 +02:00
parent c51f3f36ab
commit b601454448
4 changed files with 341 additions and 0 deletions

View File

@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <christian.flothmann@xabbuh.de>
*/
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';
}
}

View File

@ -0,0 +1,155 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <christian.flothmann@xabbuh.de>
*/
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(<<<EOF
The <info>%command.name%</info> runs PHP's built-in web server:
<info>%command.full_name%</info>
To change the default bind address and the default port use the <info>address</info> argument:
<info>%command.full_name% 127.0.0.1:8080</info>
To change the default document root directory use the <info>--docroot</info> option:
<info>%command.full_name% --docroot=htdocs/</info>
If you have a custom document root directory layout, you can specify your own
router script using the <info>--router</info> option:
<info>%command.full_name% --router=app/config/router.php</info>
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('<error>Running PHP built-in server in production environment is NOT recommended!</error>');
}
$pid = pcntl_fork();
if ($pid < 0) {
$output->writeln('<error>Unable to start the server process</error>');
return 1;
}
if ($pid > 0) {
$output->writeln('<info>Server started successfully</info>');
return;
}
if (posix_setsid() < 0) {
$output->writeln('<error>Unable to set the child process as session leader</error>');
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('<error>Unable to start the server process</error>');
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);
}
}

View File

@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <christian.flothmann@xabbuh.de>
*/
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('<info>Web server still listening on %s</info>', $address));
} else {
$output->writeln(sprintf('<error>No web server is listening on %s</error>', $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;
}
}

View File

@ -0,0 +1,67 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <christian.flothmann@xabbuh.de>
*/
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(<<<EOF
The <info>%command.name%</info> stops PHP's built-in web server:
<info>%command.full_name%</info>
To change the default bind address and the default port use the <info>address</info> argument:
<info>%command.full_name% 127.0.0.1:8080</info>
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$address = $input->getArgument('address');
$lockFile = $this->getLockFile($address);
if (!file_exists($lockFile)) {
$output->writeln(sprintf('<error>No web server is listening on %s</error>', $address));
return 1;
}
unlink($lockFile);
$output->writeln(sprintf('<info>Stopped the web server listening on %s</info>', $address));
}
}