8000 introduce new helper commands for PHP's built-in server · symfony/symfony@4fbf128 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4fbf128

Browse files
committed
introduce new helper commands for PHP's built-in server
1 parent 782ba96 commit 4fbf128

File tree

4 files changed

+319
-0
lines changed

4 files changed

+319
-0
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Command;
13+
14+
/**
15+
* Base methods for commands related to PHP's built-in web server.
16+
*
17+
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
18+
*/
19+
abstract class ServerCommand extends ContainerAwareCommand
20+
{
21+
/**
22+
* {@inheritdoc}
23+
*/
24+
public function isEnabled()
25+
{
26+
if (version_compare(phpversion(), '5.4.0', '<') || defined('HHVM_VERSION')) {
27+
return false;
28+
}
29+
30+
if (!extension_loaded('pcntl')) {
31+
return false;
32+
}
33+
34+
return parent::isEnabled();
35+
}
36+
37+
/**
38+
* Determines the name of the lock file for a particular PHP web server process.
39+
*
40+
* @param string $address An address/port tuple
41+
*
42+
* @return string The filename
43+
*/
44+
protected function getLockFile($address)
45+
{
46+
return sys_get_temp_dir().'/'.strtr($address, '.:', '--').'.pid';
47+
}
48+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Command;
13+
14+
use Symfony\Component\Console\Input\InputArgument;
15+
use Symfony\Component\Console\Input\InputInterface;
16+
use Symfony\Component\Console\Input\InputOption;
17+
use Symfony\Component\Console\Output\OutputInterface;
18+
use Symfony\Component\Process\Process;
19+
20+
/**
21+
* Runs PHP's built-in web server in a background process.
22+
*
23+
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
24+
*/
25+
class ServerStartCommand extends ServerCommand
26+
{
27+
/**
28+
* {@inheritdoc}
29+
*/
30+
protected function configure()
31+
{
32+
$this
33+
->setDefinition(array(
34+
new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1:8000'),
35+
new InputOption('docroot', 'd', InputOption::VALUE_REQUIRED, 'Document root', 'web/'),
36+
new InputOption('router', 'r', InputOption::VALUE_REQUIRED, 'Path to custom router script'),
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+
43+
<info>%command.full_name%</info>
44+
45+
To change the default bind address and the default port use the <info>address</info> argument:
46+
47+
<info>%command.full_name% 127.0.0.1:8080</info>
48+
49+
To change the default document root directory use the <info>--docroot</info> option:
50+
51+
<info>%command.full_name% --docroot=htdocs/</info>
52+
53+
If you have a custom document root directory layout, you can specify your own
54+
router script using the <info>--router</info> option:
55+
56+
<info>%command.full_name% --router=app/config/router.php</info>
57+
58+
Specifying a router script is required when the used environment is not "dev" or
59+
"prod".
60+
61+
See also: http://www.php.net/manual/en/features.commandline.webserver.php
62+
63+
EOF
64+
)
65+
;
66+
}
67+
68+
/**
69+
* {@inheritdoc}
70+
*/
71+
protected function execute(InputInterface $input, OutputInterface $output)
72+
{
73+
$env = $this->getContainer()->getParameter('kernel.environment');
74+
75+
if ('prod' === $env) {
76+
$output->writeln('<error>Running PHP built-in server in production environment is NOT recommended!</error>');
77+
}
78+
79+
$pid = pcntl_fork();
80+
81+
if ($pid < 0) {
82+
$output->writeln('<error>Unable to start the server process</error>');
83+
} elseif ($pid > 0) {
84+
$output->writeln(sprintf(
85+
'<info>Server started successfully</info>',
86+
$pid
87+
));
88+
} else {
89+
$process = $this->createServerProcess(
90+
$input->getArgument('address'),
91+
$input->getOption('docroot'),
92+
$input->getOption('router'),
93+
$env,
94+
null
95+
);
96+
$process->start();
97+
$lockFile = $this->getLockFile($input->getArgument('address'));
98+
touch($lockFile);
99+
100+
// stop the web server when the lock file is removed
101+
while ($process->isRunning()) {
102+
if (!file_exists($lockFile)) {
103+
$process->stop();
104+
}
105+
106+
sleep(1);
107+
}
108+
}
109+
}
110+
111+
/**
112+
* Creates a process to start PHP's built-in web server.
113+
*
114+
* @param string $address IP address and port to listen to
115+
* @param string $documentRoot The application's document root
116+
* @param string $router The router filename
117+
* @param string $env The application environment
118+
* @param int $timeout Process timeout
119+
*
120+
* @return \Symfony\Component\Process\Process The process
121+
*/
122+
private function createServerProcess($address, $documentRoot, $router, $env, $timeout = null)
123+
{
124+
$router = $router ?: $this
125+
->getContainer()
126+
->get('kernel')
127+
->locateResource(sprintf('@FrameworkBundle/Resources/config/router_%s.php', $env))
128+
;
129+
$script = implode(' ', array_map(array('Symfony\Component\Process\ProcessUtils', 'escapeArgument'), array(
130+
PHP_BINARY,
131+
'-S',
132+
$address,
133+
$router
134+
)));
135+
136+
return new Process('exec '.$script, $documentRoot, null, null, $timeout);
137+
}
138+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Command;
13+
use Symfony\Component\Console\Input\InputArgument;
14+
use Symfony\Component\Console\Input\InputInterface;
15+
use Symfony\Component\Console\Output\OutputInterface;
16+
17+
/**
18+
* Shows the status of a process that is running PHP's built-in web server in
19+
* the background.
20+
*
21+
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
22+
*/
23+
class ServerStatusCommand 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:8000'),
33+
))
34+
->setName('server:status')
35+
->setDescription('Outputs the status of the built-in web server for the given address')
36+
;
37+
}
38+
39+
/**
40+
* {@inheritdoc}
41+
*/
42+
protected function execute(InputInterface $input, OutputInterface $output)
43+
{
44+
$address = $input->getArgument('address');
45+
46+
// remove an orphaned lock file
47+
if (file_exists($this->getLockFile($address)) && !$this->isServerRunning($address)) {
48+
unlink($this->getLockFile($address));
49+
}
50+
51+
if (file_exists($this->getLockFile($address))) {
52+
$output->writeln(sprintf('<info>Web server still listening on %s</info>', $address));
53+
} else {
54+
$output->writeln(sprintf('<error>No web server is listening on %s</error>', $address));
55+
}
56+
}
57+
58+
private function isServerRunning($address)
59+
{
60+
list($hostname, $port) = explode(':', $address);
61+
$fp = @fsockopen($hostname, $port, $errno, $errstr, 1);
62+
fclose($fp);
63+
64+
return false !== $fp;
65+
}
66+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Command;
13+
14+
use Symfony\Component\Console\Input\InputArgument;
15+
use Symfony\Component\Console\Input\InputInterface;
16+
use Symfony\Component\Console\Output\OutputInterface;
17+
18+
/**
19+
* Stops a background process running PHP's built-in web server.
20+
*
21+
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
22+
*/
23+
class ServerStopCommand 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:8000'),
33+
))
34+
->setName('server:stop')
35+
->setDescription('Stops PHP\'s built-in web server that was started with the server:start command')
36+
->setHelp(<<<EOF
37+
The <info>%command.name%</info> stops PHP's built-in web server:
38+
39+
<info>%command.full_name%</info>
40+
41+
To change the default bind address and the default port use the <info>address</info> argument:
42+
43+
<info>%command.full_name% 127.0.0.1:8080</info>
44+
45+
EOF
46+
)
47+
;
48+
}
49+
50+
/**
51+
* {@inheritdoc}
52+
*/
53+
protected function execute(InputInterface $input, OutputInterface $output)
54+
{
55+
$address = $input->getArgument('address');
56+
$lockFile = $this->getLockFile($address);
57+
58+
if (!file_exists($lockFile)) {
59+
$output->writeln(sprintf('<error>No web server is listening on %s</error>', $address));
60+
61+
return;
62+
}
63+
64+
unlink($lockFile);
65+
$output->writeln(sprintf('<info>Stopped the web server listening on %s</info>', $address));
66+
}
67+
}

0 commit comments

Comments
 (0)
0