PageRenderTime 36ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/web/core/lib/Drupal/Core/Command/ServerCommand.php

https://gitlab.com/mohamed_hussein/prodt
PHP | 277 lines | 157 code | 25 blank | 95 comment | 19 complexity | 84fcb662887b6cdfcf658c78ba7f4df1 MD5 | raw file
  1. <?php
  2. namespace Drupal\Core\Command;
  3. use Drupal\Core\Database\ConnectionNotDefinedException;
  4. use Drupal\Core\DrupalKernel;
  5. use Drupal\Core\DrupalKernelInterface;
  6. use Drupal\Core\Site\Settings;
  7. use Drupal\user\Entity\User;
  8. use Symfony\Component\Console\Command\Command;
  9. use Symfony\Component\Console\Input\InputInterface;
  10. use Symfony\Component\Console\Input\InputOption;
  11. use Symfony\Component\Console\Output\OutputInterface;
  12. use Symfony\Component\Console\Style\SymfonyStyle;
  13. use Symfony\Component\HttpFoundation\Request;
  14. use Symfony\Component\Process\PhpExecutableFinder;
  15. use Symfony\Component\Process\PhpProcess;
  16. use Symfony\Component\Process\Process;
  17. /**
  18. * Runs the PHP webserver for a Drupal site for local testing/development.
  19. *
  20. * @internal
  21. * This command makes no guarantee of an API for Drupal extensions.
  22. */
  23. class ServerCommand extends Command {
  24. /**
  25. * The class loader.
  26. *
  27. * @var object
  28. */
  29. protected $classLoader;
  30. /**
  31. * Constructs a new ServerCommand command.
  32. *
  33. * @param object $class_loader
  34. * The class loader.
  35. */
  36. public function __construct($class_loader) {
  37. parent::__construct('server');
  38. $this->classLoader = $class_loader;
  39. }
  40. /**
  41. * {@inheritdoc}
  42. */
  43. protected function configure() {
  44. $this->setDescription('Starts up a webserver for a site.')
  45. ->addOption('host', NULL, InputOption::VALUE_OPTIONAL, 'Provide a host for the server to run on.', '127.0.0.1')
  46. ->addOption('port', NULL, InputOption::VALUE_OPTIONAL, 'Provide a port for the server to run on. Will be determined automatically if none supplied.')
  47. ->addOption('suppress-login', 's', InputOption::VALUE_NONE, 'Disable opening a login URL in a browser.')
  48. ->addUsage('--host localhost --port 8080')
  49. ->addUsage('--host my-site.com --port 80');
  50. }
  51. /**
  52. * {@inheritdoc}
  53. */
  54. protected function execute(InputInterface $input, OutputInterface $output) {
  55. $io = new SymfonyStyle($input, $output);
  56. $host = $input->getOption('host');
  57. $port = $input->getOption('port');
  58. if (!$port) {
  59. $port = $this->findAvailablePort($host);
  60. }
  61. if (!$port) {
  62. $io->getErrorStyle()->error('Unable to automatically determine a port. Use the --port to hardcode an available port.');
  63. }
  64. try {
  65. $kernel = $this->boot();
  66. }
  67. catch (ConnectionNotDefinedException $e) {
  68. $io->getErrorStyle()->error("No installation found. Use the 'install' command.");
  69. return 1;
  70. }
  71. return $this->start($host, $port, $kernel, $input, $io);
  72. }
  73. /**
  74. * Boots up a Drupal environment.
  75. *
  76. * @return \Drupal\Core\DrupalKernelInterface
  77. * The Drupal kernel.
  78. *
  79. * @throws \Exception
  80. * Exception thrown if kernel does not boot.
  81. */
  82. protected function boot() {
  83. $kernel = new DrupalKernel('prod', $this->classLoader, FALSE);
  84. $kernel::bootEnvironment();
  85. $kernel->setSitePath($this->getSitePath());
  86. Settings::initialize($kernel->getAppRoot(), $kernel->getSitePath(), $this->classLoader);
  87. $kernel->boot();
  88. // Some services require a request to work. For example, CommentManager.
  89. // This is needed as generating the URL fires up entity load hooks.
  90. $kernel->getContainer()
  91. ->get('request_stack')
  92. ->push(Request::createFromGlobals());
  93. return $kernel;
  94. }
  95. /**
  96. * Finds an available port.
  97. *
  98. * @param string $host
  99. * The host to find a port on.
  100. *
  101. * @return int|false
  102. * The available port or FALSE, if no available port found,
  103. */
  104. protected function findAvailablePort($host) {
  105. $port = 8888;
  106. while ($port >= 8888 && $port <= 9999) {
  107. $connection = @fsockopen($host, $port);
  108. if (is_resource($connection)) {
  109. // Port is being used.
  110. fclose($connection);
  111. }
  112. else {
  113. // Port is available.
  114. return $port;
  115. }
  116. $port++;
  117. }
  118. return FALSE;
  119. }
  120. /**
  121. * Opens a URL in your system default browser.
  122. *
  123. * @param string $url
  124. * The URL to browser to.
  125. * @param \Symfony\Component\Console\Style\SymfonyStyle $io
  126. * The IO.
  127. */
  128. protected function openBrowser($url, SymfonyStyle $io) {
  129. $is_windows = defined('PHP_WINDOWS_VERSION_BUILD');
  130. if ($is_windows) {
  131. // Handle escaping ourselves.
  132. $cmd = 'start "web" "' . $url . '""';
  133. }
  134. else {
  135. $url = escapeshellarg($url);
  136. }
  137. $is_linux = Process::fromShellCommandline('which xdg-open')->run();
  138. $is_osx = Process::fromShellCommandline('which open')->run();
  139. if ($is_linux === 0) {
  140. $cmd = 'xdg-open ' . $url;
  141. }
  142. elseif ($is_osx === 0) {
  143. $cmd = 'open ' . $url;
  144. }
  145. if (empty($cmd)) {
  146. $io->getErrorStyle()
  147. ->error('No suitable browser opening command found, open yourself: ' . $url);
  148. return;
  149. }
  150. if ($io->isVerbose()) {
  151. $io->writeln("<info>Browser command:</info> $cmd");
  152. }
  153. // Need to escape double quotes in the command so the PHP will work.
  154. $cmd = str_replace('"', '\"', $cmd);
  155. // Sleep for 2 seconds before opening the browser. This allows the command
  156. // to start up the PHP built-in webserver in the meantime. We use a
  157. // PhpProcess so that Windows powershell users also get a browser opened
  158. // for them.
  159. $php = "<?php sleep(2); passthru(\"$cmd\"); ?>";
  160. $process = new PhpProcess($php);
  161. $process->start();
  162. }
  163. /**
  164. * Gets a one time login URL for user 1.
  165. *
  166. * @return string
  167. * The one time login URL for user 1.
  168. */
  169. protected function getOneTimeLoginUrl() {
  170. $user = User::load(1);
  171. \Drupal::moduleHandler()->load('user');
  172. return user_pass_reset_url($user);
  173. }
  174. /**
  175. * Starts up a webserver with a running Drupal.
  176. *
  177. * @param string $host
  178. * The hostname of the webserver.
  179. * @param int $port
  180. * The port to start the webserver on.
  181. * @param \Drupal\Core\DrupalKernelInterface $kernel
  182. * The Drupal kernel.
  183. * @param \Symfony\Component\Console\Input\InputInterface $input
  184. * The input.
  185. * @param \Symfony\Component\Console\Style\SymfonyStyle $io
  186. * The IO.
  187. *
  188. * @return int
  189. * The exit status of the PHP in-built webserver command.
  190. */
  191. protected function start($host, $port, DrupalKernelInterface $kernel, InputInterface $input, SymfonyStyle $io) {
  192. $finder = new PhpExecutableFinder();
  193. $binary = $finder->find();
  194. if ($binary === FALSE) {
  195. throw new \RuntimeException('Unable to find the PHP binary.');
  196. }
  197. $io->writeln("<info>Drupal development server started:</info> <http://{$host}:{$port}>");
  198. $io->writeln('<info>This server is not meant for production use.</info>');
  199. $one_time_login = "http://$host:$port{$this->getOneTimeLoginUrl()}/login";
  200. $io->writeln("<info>One time login url:</info> <$one_time_login>");
  201. $io->writeln('Press Ctrl-C to quit the Drupal development server.');
  202. if (!$input->getOption('suppress-login')) {
  203. if ($this->openBrowser("$one_time_login?destination=" . urlencode("/"), $io) === 1) {
  204. $io->error('Error while opening up a one time login URL');
  205. }
  206. }
  207. // Use the Process object to construct an escaped command line.
  208. $process = new Process([
  209. $binary,
  210. '-S',
  211. $host . ':' . $port,
  212. '.ht.router.php',
  213. ], $kernel->getAppRoot(), [], NULL, NULL);
  214. if ($io->isVerbose()) {
  215. $io->writeln("<info>Server command:</info> {$process->getCommandLine()}");
  216. }
  217. // Carefully manage output so we can display output only in verbose mode.
  218. $descriptors = [];
  219. $descriptors[0] = STDIN;
  220. $descriptors[1] = ['pipe', 'w'];
  221. $descriptors[2] = ['pipe', 'w'];
  222. $server = proc_open($process->getCommandLine(), $descriptors, $pipes, $kernel->getAppRoot());
  223. if (is_resource($server)) {
  224. if ($io->isVerbose()) {
  225. // Write a blank line so that server output and the useful information are
  226. // visually separated.
  227. $io->writeln('');
  228. }
  229. $server_status = proc_get_status($server);
  230. while ($server_status['running']) {
  231. if ($io->isVerbose()) {
  232. fpassthru($pipes[2]);
  233. }
  234. sleep(1);
  235. $server_status = proc_get_status($server);
  236. }
  237. }
  238. return proc_close($server);
  239. }
  240. /**
  241. * Gets the site path.
  242. *
  243. * Defaults to 'sites/default'. For testing purposes this can be overridden
  244. * using the DRUPAL_DEV_SITE_PATH environment variable.
  245. *
  246. * @return string
  247. * The site path to use.
  248. */
  249. protected function getSitePath() {
  250. return getenv('DRUPAL_DEV_SITE_PATH') ?: 'sites/default';
  251. }
  252. }