/src/Symfony/Bundle/WebServerBundle/WebServer.php

https://github.com/deviantintegral/symfony · PHP · 169 lines · 118 code · 34 blank · 17 comment · 18 complexity · 2855f9bf20adce2211062ca9077d1b39 MD5 · raw file

  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\WebServerBundle;
  11. use Symfony\Component\Process\PhpExecutableFinder;
  12. use Symfony\Component\Process\Process;
  13. use Symfony\Component\Process\Exception\RuntimeException;
  14. /**
  15. * Manages a local HTTP web server.
  16. *
  17. * @author Fabien Potencier <fabien@symfony.com>
  18. */
  19. class WebServer
  20. {
  21. const STARTED = 0;
  22. const STOPPED = 1;
  23. public function run(WebServerConfig $config, $disableOutput = true, callable $callback = null)
  24. {
  25. if ($this->isRunning()) {
  26. throw new \RuntimeException(sprintf('A process is already listening on http://%s.', $config->getAddress()));
  27. }
  28. $process = $this->createServerProcess($config);
  29. if ($disableOutput) {
  30. $process->disableOutput();
  31. $callback = null;
  32. } else {
  33. try {
  34. $process->setTty(true);
  35. $callback = null;
  36. } catch (RuntimeException $e) {
  37. }
  38. }
  39. $process->run($callback);
  40. if (!$process->isSuccessful()) {
  41. $error = 'Server terminated unexpectedly.';
  42. if ($process->isOutputDisabled()) {
  43. $error .= ' Run the command again with -v option for more details.';
  44. }
  45. throw new \RuntimeException($error);
  46. }
  47. }
  48. public function start(WebServerConfig $config, $pidFile = null)
  49. {
  50. $pidFile = $pidFile ?: $this->getDefaultPidFile();
  51. if ($this->isRunning($pidFile)) {
  52. throw new \RuntimeException(sprintf('A process is already listening on http://%s.', $config->getAddress()));
  53. }
  54. $pid = pcntl_fork();
  55. if ($pid < 0) {
  56. throw new \RuntimeException('Unable to start the server process.');
  57. }
  58. if ($pid > 0) {
  59. return self::STARTED;
  60. }
  61. if (posix_setsid() < 0) {
  62. throw new \RuntimeException('Unable to set the child process as session leader.');
  63. }
  64. $process = $this->createServerProcess($config);
  65. $process->disableOutput();
  66. $process->start();
  67. if (!$process->isRunning()) {
  68. throw new \RuntimeException('Unable to start the server process.');
  69. }
  70. file_put_contents($pidFile, $config->getAddress());
  71. // stop the web server when the lock file is removed
  72. while ($process->isRunning()) {
  73. if (!file_exists($pidFile)) {
  74. $process->stop();
  75. }
  76. sleep(1);
  77. }
  78. return self::STOPPED;
  79. }
  80. public function stop($pidFile = null)
  81. {
  82. $pidFile = $pidFile ?: $this->getDefaultPidFile();
  83. if (!file_exists($pidFile)) {
  84. throw new \RuntimeException('No web server is listening.');
  85. }
  86. unlink($pidFile);
  87. }
  88. public function getAddress($pidFile = null)
  89. {
  90. $pidFile = $pidFile ?: $this->getDefaultPidFile();
  91. if (!file_exists($pidFile)) {
  92. return false;
  93. }
  94. return file_get_contents($pidFile);
  95. }
  96. public function isRunning($pidFile = null)
  97. {
  98. $pidFile = $pidFile ?: $this->getDefaultPidFile();
  99. if (!file_exists($pidFile)) {
  100. return false;
  101. }
  102. $address = file_get_contents($pidFile);
  103. $pos = strrpos($address, ':');
  104. $hostname = substr($address, 0, $pos);
  105. $port = substr($address, $pos + 1);
  106. if (false !== $fp = @fsockopen($hostname, $port, $errno, $errstr, 1)) {
  107. fclose($fp);
  108. return true;
  109. }
  110. unlink($pidFile);
  111. return false;
  112. }
  113. /**
  114. * @return Process The process
  115. */
  116. private function createServerProcess(WebServerConfig $config)
  117. {
  118. $finder = new PhpExecutableFinder();
  119. if (false === $binary = $finder->find(false)) {
  120. throw new \RuntimeException('Unable to find the PHP binary.');
  121. }
  122. $process = new Process(array_merge(array($binary), $finder->findArguments(), array('-dvariables_order=EGPCS', '-S', $config->getAddress(), $config->getRouter())));
  123. $process->setWorkingDirectory($config->getDocumentRoot());
  124. $process->setTimeout(null);
  125. if (in_array('APP_ENV', explode(',', getenv('SYMFONY_DOTENV_VARS')))) {
  126. $process->setEnv(array('APP_ENV' => false));
  127. $process->inheritEnvironmentVariables();
  128. }
  129. return $process;
  130. }
  131. private function getDefaultPidFile()
  132. {
  133. return getcwd().'/.web-server-pid';
  134. }
  135. }