【PHPフレームワークFLow】Flowのserver:runコマンド実行時の動きについて

# 初めに
Flowでは`./flow server:run`というコマンドを叩くことで、開発用の簡易的なサーバを立てることができます。
今回は、このコマンドでは実際にどんなことが行われているのかを見ていきます。


# server:runコマンド
`server:run`コマンドはシンプルです。
以下のコマンドを実行するだけで、簡単にWebサーバを立てることができます。 

```sh
$ ./flow server:run
Server running. Please go to http://127.0.0.1:8081 to browse the application.
[Wed Feb 21 20:49:17 2024] PHP 8.1.25 Development Server (http://127.0.0.1:8081) started
```

ホストとポートはオプションで設定可能です。

```sh
$ ./flow server:run --host 127.0.0.2 --port 9999
Server running. Please go to http://127.0.0.2:9999 to browse the application.
[Wed Feb 21 21:09:52 2024] PHP 8.1.25 Development Server (http://127.0.0.2:9999) started
```

# 実際に何が起きてるのか
このコマンドが実行されるとき、Flowはどのような動きをしているのでしょうか。
たどってみましょう。

## コマンド実行時
`./flow server:run`のコマンド実行時、ServerCommandControllerの[runCommand()](https://github.com/neos/flow/blob/8.3.7/Classes/Command/ServerCommandController.php#L44)の処理が走ります。

```php
    public function runCommand(string $host = '127.0.0.1', int $port = 8081)
    {
        $command = Scripts::buildPhpCommand($this->settings);

        $address = sprintf('%s:%s', $host, $port);
        $command .= ' -S ' . escapeshellarg($address) . ' -t ' . escapeshellarg(FLOW_PATH_WEB) . ' ' . escapeshellarg(FLOW_PATH_FLOW . '/Scripts/PhpDevelopmentServerRouter.php');

        $this->outputLine('Server running. Please go to <b>http://' . $address . '</b> to browse the application.');
        exec($command);
    }
```

1. `Scripts::buildPhpCommand`でPHPコマンドを作成
2. PHPコマンドにオプションを付与
3. `exec()`でコマンドを実行


という流れです。
`exec()`の引数が実行されるphpコマンドです。このコマンドで何が起きているのでしょうか。


## 実際に渡されるコマンドを確認してみる
`exec()`コマンドで実行されるコマンドを実際に出力してみました。

```sh
SET FLOW_ROOTPATH=C:/Path/to/Project/&
SET FLOW_PATH_TEMPORARY_BASE=C:/Path/to/Project/Data//Temporary&
SET FLOW_CONTEXT=Development&
SET XDEBUG_CONFIG=idekey=FLOW_SUBREQUEST remote_port=9001&
"C:/php-8.1.25/php.exe" -c "C:\php-8.1.25\php.ini" -S "127.0.0.1:8081" -t "C:/Path/to/Project/Web/" "C:/Path/to/Project/Packages/Framework/Neos.Flow/Classes/Core/../..//Scripts/PhpDevelopmentServerRouter.php"
```

1. プロジェクトパスやコンテキストなどの環境変数を設定
2. phpコマンドを実行

という流れでした。
ここで実行してる`php`コマンドの詳細を調べてみました。

# `php -S`はビルドインウェブサーバを立てるコマンド
このコマンドはPHPの**ビルドインウェブサーバを立てるコマンド**のようです。


https://www.php.net/manual/ja/features.commandline.webserver.php

ビルドインウェブサーバとはPHPが提供する組み込みの開発サーバのことで、ApacheなどのWebサーバを立てることなくWebアプリケーションの動作確認をおこなうことができる機能になります。

以下のように、PHPコマンドに`-S`オプションをつけて実行することで簡単にWebサーバを立てることができます。
```sh
php -S localhost:8000 /Path/to/Router.php
```

`-S`オプションの後にサーバのホストとポート番号を指定できます。
最後の引数にはルーター(リクエストが来たときに実行するPHPファイル）のパスを指定しています。

その他主要な引数は以下です。
| オプション | 説明 |
|:-:|:-:|
| -c |  php.iniファイルを指定することができる |
| -t |  ドキュメントルートを指定することができる |



:::note warn
ビルドインウェブサーバは開発用に提供されている機能です。本番用のサーバには使用できませんのでご注意ください。（公式ドキュメントにもその旨が記載されておりますので、気になる方はご参照ください）
:::

# 改めて実行されたコマンドを見てみる

改めてコマンドを見てみましょう。

```sh
SET FLOW_ROOTPATH=C:/Path/to/Project/&
SET FLOW_PATH_TEMPORARY_BASE=C:/Path/to/Project/Data//Temporary&
SET FLOW_CONTEXT=Development&
SET XDEBUG_CONFIG=idekey=FLOW_SUBREQUEST remote_port=9001&
"C:/php-8.1.25/php.exe" -c "C:\php-8.1.25\php.ini" -S "127.0.0.1:8081" -t "C:/Path/to/Project/Web/" "C:/Path/to/Project/Packages/Framework/Neos.Flow/Classes/Core/../..//Scripts/PhpDevelopmentServerRouter.php"
```

ルーターファイルで指定されている[PhpDevelopmentServerRouter.php](https://github.com/neos/flow/blob/8.3.7/Scripts/PhpDevelopmentServerRouter.php)はFlowにおける開発用のルーターファイルになります。

リクエストが来るとまずこのファイルが実行されます。その後、最後にある`$bootstrap->run();`が実行され、最終的にControllerへと処理がたどり着く仕組みです。


```php:PhpDevelopmentServerRouter.php
<?php
/**
 * Entry Point (Router) for PHP's embedded HTTP server. Use ./flow server:run to execute.
 */

if (strpos($_SERVER['REQUEST_URI'], '_Resources/') !== false) {
    // published resources shall be served directly
    return false;
}

require(__DIR__. '/../Classes/Core/Bootstrap.php');

if (DIRECTORY_SEPARATOR !== '/' && trim(getenv('FLOW_ROOTPATH'), '"\' ') === '') {
    $absoluteRootpath = dirname(realpath(__DIR__ . '/../../../'));
    if (realpath(getcwd()) === $absoluteRootpath) {
        $_SERVER['FLOW_ROOTPATH'] = '.';
    } elseif (strlen(getcwd()) > strlen($absoluteRootpath)) {
        $amountOfPathsToSkipBack = substr_count(getcwd(), DIRECTORY_SEPARATOR) - substr_count($absoluteRootpath, DIRECTORY_SEPARATOR);
        $_SERVER['FLOW_ROOTPATH'] = implode('/', array_fill(0, $amountOfPathsToSkipBack, '..'));
    } else {
        $_SERVER['FLOW_ROOTPATH'] = substr($absoluteRootpath, strlen(getcwd()) + 1);
    }
} else {
    $_SERVER['FLOW_ROOTPATH'] = trim(getenv('FLOW_ROOTPATH'), '"\' ') ?: dirname($_SERVER['PHP_SELF']);
}

// Script filename and script name must "emulate" index.php, to not break routing
$_SERVER['SCRIPT_FILENAME'] = $_SERVER['FLOW_ROOTPATH'] . 'Web/index.php';
$_SERVER['SCRIPT_NAME'] = '/index.php';

$context = \Neos\Flow\Core\Bootstrap::getEnvironmentConfigurationSetting('FLOW_CONTEXT') ?: 'Development';
$bootstrap = new \Neos\Flow\Core\Bootstrap($context);
$bootstrap->run();
```




# 終わりに

今回はFlowの`server:run`コマンドが実行されたときの処理についてまとめました。
FW内部の処理を知るのは面白いですね。
また何か分かったことがあれば記事にする予定です。

ここまで読んでいただきありがとうございました！