Upgrade to Pro — share decks privately, control downloads, hide ads and more …

いろいろなフレームワークの仕組みを index.php から読み解こう / index.php of each framework

いろいろなフレームワークの仕組みを index.php から読み解こう / index.php of each framework

2023/03/23, 24, 25 開催「PHPerKaigi 2023」(https://phpcon.php.gr.jp/2021/)の発表資料です

Shohei Okada

March 25, 2023
Tweet

More Decks by Shohei Okada

Other Decks in Programming

Transcript

  1. いろいろなフレームワークの仕組みを
    index.php から読み解こう
    2023/03/25 PHPerKaigi 2023 @okashoi

    View Slide

  2. • 発表内容の性質上コードを示すことが多く文字が小さくて読めない可能
    性があります
    • 資料の URL を共有してありますので、
    必要に応じて手元で資料を確認しながら聴講、視聴してください
    • 対象としたフレームワーク、およびバージョンは以下のとおりです
    • CakePHP 4.4
    • Laravel 10.0
    • Slim 4.11
    • Symfony 6.2
    • 各公式ドキュメントに則り、通常の Web アプリケーション開発用途で
    インストールした場合を想定しています
    おことわり

    View Slide

  3. 1. はじめに 01:00 〜 04:00
    2. FW を使った/使ってないコードの比較 04:00 〜 10:00
    3. CakePHP の index.php 10:00 〜 15:00
    4. Laravel の index.php 15:00 〜 20:00
    5. Slim の index.php 20:00 〜 25:00
    6. Symfony の index.php 25:00 〜 30:00
    7. 各 FW の共通点 〜問いへの答え〜 30:00 〜 34:00
    8. 自己紹介 34:00 〜 35:00
    目次(※時間は目安です)

    View Slide

  4. 1. はじめに 01:00 〜 04:00
    2. FW を使った/使ってないコードの比較 04:00 〜 10:00
    3. CakePHP の index.php 10:00 〜 15:00
    4. Laravel の index.php 15:00 〜 20:00
    5. Slim の index.php 20:00 〜 25:00
    6. Symfony の index.php 25:00 〜 30:00
    7. 各 FW の共通点 〜問いへの答え〜 30:00 〜 34:00
    8. 自己紹介 34:00 〜 35:00
    目次(※時間は目安です)

    View Slide

  5. • 多くの場合、我々は Web アプリケーションフレームワーク(以下、
    FW)を利用して開発している
    • 一方で単一の PHP ファイルでも Web ページを表示することはできる
    • 書き方は全く異なっているのに、どうして同じ PHP で動くのだろう?
    はじめに

    View Slide

  6. と、本題に入る前に
    基礎のおさらいをします

    View Slide

  7. Web アプリケーション(サーバサイド)がやっていること
    クライアント
    サーバ

    View Slide

  8. Web アプリケーション(サーバサイド)がやっていること
    サーバ
    GET /search-products HTTP/1.1
    Accept: text/html
    Accept-Encoding: gzip, deflate
    Accept-Language: ja,en-US;q=0.9,en;q=0.8
    Connection: keep-alive
    :
    :
    クライアント
    HTTP リクエスト

    View Slide

  9. Web アプリケーション(サーバサイド)がやっていること
    クライアント
    サーバ
    リクエスト内容に基づ
    く処理の実行

    View Slide

  10. Web アプリケーション(サーバサイド)がやっていること
    HTTP/1.1 200 OK
    Date: Sat, 25 Mar 2023 02:15:00 GMT
    Server: Apache/2.4.54 (Debian)
    X-Powered-By: PHP/8.1.16
    :
    :


    :
    :
    クライアント
    サーバ
    処理の結果に基づくレ
    スポンスを生成、送信
    HTTP レスポンス

    View Slide

  11. Web アプリケーション(サーバサイド)がやっていること
    サーバ
    GET /search-products HTTP/1.1
    Accept: text/html
    Accept-Encoding: gzip, deflate
    Accept-Language: ja,en-US;q=0.9,en;q=0.8
    Connection: keep-alive
    :
    :
    クライアント
    HTTP リクエスト
    HTTP/1.1 200 OK
    Date: Sat, 25 Mar 2023 02:15:00 GMT
    Server: Apache/2.4.54 (Debian)
    X-Powered-By: PHP/8.1.16
    :
    :


    :
    :
    HTTP レスポンス
    リクエスト内容に基づ
    く処理の実行
    処理の結果に基づくレ
    スポンスを生成、送信

    View Slide

  12. 1. はじめに 01:00 〜 04:00
    2. FW を使った/使ってないコードの比較 04:00 〜 10:00
    3. CakePHP の index.php 10:00 〜 15:00
    4. Laravel の index.php 15:00 〜 20:00
    5. Slim の index.php 20:00 〜 25:00
    6. Symfony の index.php 25:00 〜 30:00
    7. 各 FW の共通点 〜問いへの答え〜 30:00 〜 34:00
    8. 自己紹介 34:00 〜 35:00
    目次(※時間は目安です)

    View Slide

  13. こんな Web ページの表示を考える
    入力 & フォーム送信

    View Slide

  14. • 例として Slim を取り上げます
    • 他の FW でも大まかな流れは同じようなものだと思います
    • 説明の目的上、ORM は使っていません
    • 使っていようがいまいが説明したいことには関係ありません
    • 各種設定(依存関係の注入含む)は適切になされているものとします
    FW を使って実装する場合 - 前提条件

    View Slide

  15. FW を使って実装する場合(Slim の例)
    declare(strict_types=1);
    use App\Application\Actions\SearchProducts;
    use Slim\App;
    return function (App $app) {
    $app->get('/search-products', SearchProducts::class);
    };

    View Slide

  16. FW を使って実装する場合(Slim の例)
    namespace App\Application\Actions;
    use PDO;
    use Psr\Http\Message\ResponseInterface as Response;
    use Psr\Http\Message\ServerRequestInterface as Request;
    use Slim\Views\Twig;
    class SearchProducts
    {
    // PDO のインスタンスはコンストラクタに渡される
    public function __construct(private readonly PDO $pdo)
    {
    }
    public function __invoke(Request $request, Response $response): Response
    {
    // ServerRequestInterface を介して入力を受け取る
    $q = $request->getQueryParams()['q'] ?? '';
    $stmt = $this->pdo->prepare('SELECT name, price FROM products WHERE name LIKE ?');
    $stmt->execute(['%' . $q . '%']);
    $products = $stmt->fetchAll();
    // echo などは使わず、 ResponseInterface のインスタンスを返す
    return Twig::fromRequest($request)
    ->render($response, 'search-products.html', compact('q', 'products'));
    }
    }

    View Slide

  17. namespace App\Application\Actions;
    use PDO;
    use Psr\Http\Message\ResponseInterface as Response;
    use Psr\Http\Message\ServerRequestInterface as Request;
    use Slim\Views\Twig;
    class SearchProducts
    {
    // PDO のインスタンスはコンストラクタに渡される
    public function __construct(private readonly PDO $pdo)
    {
    }
    public function __invoke(Request $request, Response $response): Response
    {
    // ServerRequestInterface を介して入力を受け取る
    $q = $request->getQueryParams()['q'] ?? '';
    $stmt = $this->pdo->prepare('SELECT name, price FROM products WHERE name LIKE ?');
    $stmt->execute(['%' . $q . '%']);
    $products = $stmt->fetchAll();
    // echo などは使わず、 ResponseInterface のインスタンスを返す
    return Twig::fromRequest($request)
    ->render($response, 'search-products.html', compact('q', 'products'));
    }
    }
    FW を使って実装する場合(Slim の例)
    // PDO のインスタンスはコンストラクタに渡される
    public function __construct(private readonly PDO $pdo)
    {
    }

    View Slide

  18. namespace App\Application\Actions;
    use PDO;
    use Psr\Http\Message\ResponseInterface as Response;
    use Psr\Http\Message\ServerRequestInterface as Request;
    use Slim\Views\Twig;
    class SearchProducts
    {
    // PDO のインスタンスはコンストラクタに渡される
    public function __construct(private readonly PDO $pdo)
    {
    }
    public function __invoke(Request $request, Response $response): Response
    {
    // ServerRequestInterface を介して入力を受け取る
    $q = $request->getQueryParams()['q'] ?? '';
    $stmt = $this->pdo->prepare('SELECT name, price FROM products WHERE name LIKE ?');
    $stmt->execute(['%' . $q . '%']);
    $products = $stmt->fetchAll();
    // echo などは使わず、 ResponseInterface のインスタンスを返す
    return Twig::fromRequest($request)
    ->render($response, 'search-products.html', compact('q', 'products'));
    }
    }
    FW を使って実装する場合(Slim の例)
    // ServerRequestInterface を介して入力を受け取る
    $q = $request->getQueryParams()['q'] ?? '';

    View Slide

  19. FW を使って実装する場合(Slim の例)
    namespace App\Application\Actions;
    use PDO;
    use Psr\Http\Message\ResponseInterface as Response;
    use Psr\Http\Message\ServerRequestInterface as Request;
    use Slim\Views\Twig;
    class SearchProducts
    {
    // PDO のインスタンスはコンストラクタに渡される
    public function __construct(private readonly PDO $pdo)
    {
    }
    public function __invoke(Request $request, Response $response): Response
    {
    // ServerRequestInterface を介して入力を受け取る
    $q = $request->getQueryParams()['q'] ?? '';
    $stmt = $this->pdo->prepare('SELECT name, price FROM products WHERE name LIKE ?');
    $stmt->execute(['%' . $q . '%']);
    $products = $stmt->fetchAll();
    // echo などは使わず、 ResponseInterface のインスタンスを返す
    return Twig::fromRequest($request)
    ->render($response, 'search-products.html', compact('q', 'products'));
    }
    }
    // echo などは使わず、ResponseInterface のインスタンスを返す
    return Twig::fromRequest($request)
    ->render($response, 'search-products.html', compact('q', 'products'));

    View Slide

  20. FW を使わずに実装する場合
    // include, require 等で他のファイルのコードを読み込む
    require_once __DIR__ . '/../config/database.php';
    $pdo = new PDO(
    sprintf('mysql:host=%s;dbname=%s', MYSQL_HOST, MYSQL_DATABASE),
    MYSQL_USER,
    MYSQL_PASSWORD,
    );
    // $_GET, $_POST 等で入力を受け取る
    $q = $_GET['q'] ?? '';
    $stmt = $pdo->prepare('SELECT `name`, `price` FROM `products` WHERE `name` LIKE ?');
    $stmt->execute(['%' . $q . '%']);
    // .php ファイル内に HTML を記述したり、echo を使ったりすることで出力
    ?>









    商品名
    価格



    fetch()): ?>









    ドキュメントルート

    View Slide

  21. FW を使わずに実装する場合
    // include, require 等で他のファイルのコードを読み込む
    require_once __DIR__ . '/../config/database.php';
    $pdo = new PDO(
    sprintf('mysql:host=%s;dbname=%s', MYSQL_HOST, MYSQL_DATABASE),
    MYSQL_USER,
    MYSQL_PASSWORD,
    );
    // $_GET, $_POST 等で入力を受け取る
    $q = $_GET['q'] ?? '';
    $stmt = $pdo->prepare('SELECT `name`, `price` FROM `products` WHERE `name` LIKE ?');
    $stmt->execute(['%' . $q . '%']);
    // .php ファイル内に HTML を記述したり、echo を使ったりすることで出力
    ?>









    商品名
    価格



    fetch()): ?>









    // include, require 等で他のファイルのコードを読み込む
    require_once __DIR__ . '/../config/database.php';
    $pdo = new PDO(
    sprintf('mysql:host=%s;dbname=%s', MYSQL_HOST, MYSQL_DATABASE),
    MYSQL_USER,
    MYSQL_PASSWORD,
    );

    View Slide

  22. FW を使わずに実装する場合
    // include, require 等で他のファイルのコードを読み込む
    require_once __DIR__ . '/../config/database.php';
    $pdo = new PDO(
    sprintf('mysql:host=%s;dbname=%s', MYSQL_HOST, MYSQL_DATABASE),
    MYSQL_USER,
    MYSQL_PASSWORD,
    );
    // $_GET, $_POST 等で入力を受け取る
    $q = $_GET['q'] ?? '';
    $stmt = $pdo->prepare('SELECT `name`, `price` FROM `products` WHERE `name` LIKE ?');
    $stmt->execute(['%' . $q . '%']);
    // .php ファイル内に HTML を記述したり、echo を使ったりすることで出力
    ?>









    商品名
    価格



    fetch()): ?>









    // $_GET, $_POST 等で入力を受け取る
    $q = $_GET['q'] ?? '';

    View Slide

  23. FW を使わずに実装する場合
    // include, require 等で他のファイルのコードを読み込む
    require_once __DIR__ . '/../config/database.php';
    $pdo = new PDO(
    sprintf('mysql:host=%s;dbname=%s', MYSQL_HOST, MYSQL_DATABASE),
    MYSQL_USER,
    MYSQL_PASSWORD,
    );
    // $_GET, $_POST 等で入力を受け取る
    $q = $_GET['q'] ?? '';
    $stmt = $pdo->prepare('SELECT `name`, `price` FROM `products` WHERE `name` LIKE ?');
    $stmt->execute(['%' . $q . '%']);
    // .php ファイル内に HTML を記述したり、echo を使ったりすることで出力
    ?>









    商品名
    価格



    fetch()): ?>









    View Slide

  24. FW を使わずに実装する場合
    // include, require 等で他のファイルのコードを読み込む
    require_once __DIR__ . '/../config/database.php';
    $pdo = new PDO(
    sprintf('mysql:host=%s;dbname=%s', MYSQL_HOST, MYSQL_DATABASE),
    MYSQL_USER,
    MYSQL_PASSWORD,
    );
    // $_GET, $_POST 等で入力を受け取る
    $q = $_GET['q'] ?? '';
    $stmt = $pdo->prepare('SELECT `name`, `price` FROM `products` WHERE `name` LIKE ?');
    $stmt->execute(['%' . $q . '%']);
    // .php ファイル内に HTML を記述したり、echo を使ったりすることで出力
    ?>









    商品名
    価格



    fetch()): ?>









    // .php ファイル内に HTML を記述したり、echo を使ったりすることで出力
    ?>






    :
    :

    View Slide

  25. 異なる点
    FW あり FW なし
    URL ルーティングを記述 ファイルパスがそのまま
    依存関係 インスタンスが渡される include, require
    パラメータの受け取り ServerRequestInterface を介する $_GET, $_POST
    出力 ResponseInterface を返す HTML を直接記述, echo 等

    View Slide

  26. 異なる点 - URL
    FW あり
    FW なし
    return function (App $app) {
    $app->get('/search-products', SearchProducts::class);
    };
    ドキュメントルート
    routes.php に書いたパス
    ドキュメントルートからのファイルパス

    View Slide

  27. 異なる点 - 依存関係
    // PDO のインスタンスはコンストラクタに渡される
    public function __construct(private readonly PDO $pdo)
    {
    }
    // include, require 等で他のファイルのコードを読み込む
    require_once __DIR__ . '/../config/database.php';
    $pdo = new PDO(
    sprintf('mysql:host=%s;dbname=%s', MYSQL_HOST, MYSQL_DATABASE),
    MYSQL_USER,
    MYSQL_PASSWORD,
    );
    FW あり
    FW なし

    View Slide

  28. 異なる点 - パラメータの受け取り
    // ServerRequestInterface を介して入力を受け取る
    $q = $request->getQueryParams()['q'] ?? '';
    // $_GET, $_POST 等で入力を受け取る
    $q = $_GET['q'] ?? '';
    FW あり
    FW なし

    View Slide

  29. 異なる点 - 出力
    // echo などは使わず、ResponseInterface のインスタンスを返す
    return Twig::fromRequest($request)
    ->render($response, 'search-products.html', compact('q', 'products'
    // .php ファイル内に HTML を記述したり、echo を使ったりすることで出力
    ?>






    :
    :
    FW あり
    FW なし

    View Slide

  30. • PHP-FIG(Framework Interop Group) という団体によって PSR(PHP
    Standard Recommendation)として定められているもの
    • 仕様と interface の定義が提供されている
    (参考)ServerRequestInterface と ResponseInterface
    https://www.php-fig.org/
    https://github.com/php-fig

    View Slide

  31. 本トークでは以降、(正しい表現とは言えないが)
    • 「ServerRequestInterface を実装したクラスのインスタンス」のことを
    「Request のインスタンス」あるいは単に「Request」
    • 「ResponseInterface を実装したクラスのインスタンス」のことを
    「Response のインスタンス」あるいは単に「Response」
    と表記する/呼ぶこととします。
    (参考)ServerRequestInterface と ResponseInterface

    View Slide

  32. • URL と対応する PHP ファイルがなくても 404 エラーにならないのか
    • HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
    • $_GET, $_POST を使わずにパラメータにアクセスできるのか
    • echo 等をしていないのに HTTP レスポンスを返せるのか
    問い:FW を使って実装すると、どうして

    View Slide

  33. • URL と対応する PHP ファイルがなくても 404 エラーにならないのか
    • HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
    • $_GET, $_POST を使わずにパラメータにアクセスできるのか
    • echo 等をしていないのに HTTP レスポンスを返せるのか
    → 各 FW の index.php を読み解くことで答えが見えてくる!
    問い:FW を使って実装すると、どうして

    View Slide

  34. Apache や Nginx の設定が各ドキュメントに書かれている
    • https://book.cakephp.org/4/ja/installation.html#url-rewriting
    • https://laravel.com/docs/10.x/deployment#server-configuration
    • https://www.slimframework.com/docs/v4/start/web-servers.html
    • https://symfony.com/doc/current/setup/web_server_configuration.
    html
    すべてのリクエストを index.php でハンドリングしている

    View Slide

  35. すべてのリクエストを index.php でハンドリングしている
    Apache(.htacess)
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
    Nginx
    index index.php;
    location /
    try_files $uri /index.php$is_args$args;
    }
    location ~ \.php$ {
    include fastcgi_params;
    fastcgi_pass unix:/var/run/php/php-fpm.sock;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
    どの URL へのリクエストでも
    index.php が実行される
    → index.php を読み解くと
     FW の仕組みがわかる!

    View Slide

  36. 1. はじめに 01:00 〜 04:00
    2. FW を使った/使ってないコードの比較 04:00 〜 10:00
    3. CakePHP の index.php 10:00 〜 15:00
    4. Laravel の index.php 15:00 〜 20:00
    5. Slim の index.php 20:00 〜 25:00
    6. Symfony の index.php 25:00 〜 30:00
    7. 各 FW の共通点 〜問いへの答え〜 30:00 〜 34:00
    8. 自己紹介 34:00 〜 35:00
    目次(※時間は目安です)

    View Slide

  37. CakePHP の index.php
    https://github.com/cakephp/app/blob/4.4.2/webroot/index.php
    // Check platform requirements
    require dirname(__DIR__) . '/config/requirements.php';
    // For built-in server
    if (PHP_SAPI === 'cli-server') {
    $_SERVER['PHP_SELF'] = '/' . basename(__FILE__);
    $url = parse_url(urldecode($_SERVER['REQUEST_URI']));
    $file = __DIR__ . $url['path'];
    if (strpos($url['path'], '..') === false && strpos($url['path'], '.') !== false && is_file($file)) {
    return false;
    }
    }
    require dirname(__DIR__) . '/vendor/autoload.php';
    use App\Application;
    use Cake\Http\Server;
    // Bind your application to the server.
    $server = new Server(new Application(dirname(__DIR__) . '/config'));
    // Run the request/response through the application and emit the response.
    $server->emit($server->run());
    ※スペースの都合でライセンス表記等を省略しています

    View Slide

  38. CakePHP の index.php
    // Check platform requirements
    require dirname(__DIR__) . '/config/requirements.php';
    // For built-in server
    if (PHP_SAPI === 'cli-server') {
    $_SERVER['PHP_SELF'] = '/' . basename(__FILE__);
    $url = parse_url(urldecode($_SERVER['REQUEST_URI']));
    $file = __DIR__ . $url['path'];
    if (strpos($url['path'], '..') === false && strpos($url['pa
    return false;
    }
    }
    require dirname(__DIR__) . '/vendor/autoload.php';
    use App\Application;
    use Cake\Http\Server;
    // Bind your application to the server.
    $server = new Server(new Application(dirname(__DIR__) . '/conf
    // Run the request/response through the application and emit t
    $server->emit($server->run());
    // Check platform requirements
    require dirname(__DIR__) . '/config/requirements.php';

    View Slide

  39. CakePHP の index.php
    // Check platform requirements
    require dirname(__DIR__) . '/config/requirements.php';
    // For built-in server
    if (PHP_SAPI === 'cli-server') {
    $_SERVER['PHP_SELF'] = '/' . basename(__FILE__);
    $url = parse_url(urldecode($_SERVER['REQUEST_URI']));
    $file = __DIR__ . $url['path'];
    if (strpos($url['path'], '..') === false && strpos($url['pa
    return false;
    }
    }
    require dirname(__DIR__) . '/vendor/autoload.php';
    use App\Application;
    use Cake\Http\Server;
    // Bind your application to the server.
    $server = new Server(new Application(dirname(__DIR__) . '/conf
    // Run the request/response through the application and emit t
    $server->emit($server->run());
    require dirname(__DIR__) . '/vendor/autoload.php';

    View Slide

  40. (参考)Composer による autoloading について
    https://docs.google.com/presentation/d/1pVTSdNuq5mySj7jCIhMuv8eTPtWXBuXzk7y
    6HHJ9khM/edit?usp=sharing

    View Slide

  41. CakePHP の index.php
    // Check platform requirements
    require dirname(__DIR__) . '/config/requirements.php';
    // For built-in server
    if (PHP_SAPI === 'cli-server') {
    $_SERVER['PHP_SELF'] = '/' . basename(__FILE__);
    $url = parse_url(urldecode($_SERVER['REQUEST_URI']));
    $file = __DIR__ . $url['path'];
    if (strpos($url['path'], '..') === false && strpos($url['pa
    return false;
    }
    }
    require dirname(__DIR__) . '/vendor/autoload.php';
    use App\Application;
    use Cake\Http\Server;
    // Bind your application to the server.
    $server = new Server(new Application(dirname(__DIR__) . '/conf
    // Run the request/response through the application and emit t
    $server->emit($server->run());
    // Bind your application to the server.
    $server = new Server(new Application(dirname(__DIR__) . '/config'));
    Server Application 各種設定
    DI、ルーティング、
    ミドルウェア......

    View Slide

  42. CakePHP の index.php
    // Check platform requirements
    require dirname(__DIR__) . '/config/requirements.php';
    // For built-in server
    if (PHP_SAPI === 'cli-server') {
    $_SERVER['PHP_SELF'] = '/' . basename(__FILE__);
    $url = parse_url(urldecode($_SERVER['REQUEST_URI']));
    $file = __DIR__ . $url['path'];
    if (strpos($url['path'], '..') === false && strpos($url['pa
    return false;
    }
    }
    require dirname(__DIR__) . '/vendor/autoload.php';
    use App\Application;
    use Cake\Http\Server;
    // Bind your application to the server.
    $server = new Server(new Application(dirname(__DIR__) . '/conf
    // Run the request/response through the application and emit t
    $server->emit($server->run());
    // Run the request/response through the application and emit the response.
    $server->emit($server->run());

    View Slide

  43. public function run(
    ?ServerRequestInterface $request = null,
    ?MiddlewareQueue $middlewareQueue = null
    ): ResponseInterface {
    $this->bootstrap();
    $request = $request ?: ServerRequestFactory::fromGlobals();
    $middleware = $this->app->middleware($middlewareQueue ?? new MiddlewareQueue());
    if ($this->app instanceof PluginApplicationInterface) {
    $middleware = $this->app->pluginMiddleware($middleware);
    }
    $this->dispatchEvent('Server.buildMiddleware', ['middleware' => $middleware]);
    $response = $this->runner->run($middleware, $request, $this->app);
    if ($request instanceof ServerRequest) {
    $request->getSession()->close();
    }
    return $response;
    }
    \Cake\Http\Server::run()
    https://github.com/cakephp/cakephp/blob/4.4.11/src/Http/Server.php
    Request のインスタンスを生成

    View Slide

  44. public function run(
    ?ServerRequestInterface $request = null,
    ?MiddlewareQueue $middlewareQueue = null
    ): ResponseInterface {
    $this->bootstrap();
    $request = $request ?: ServerRequestFactory::fromGlobals();
    $middleware = $this->app->middleware($middlewareQueue ?? new MiddlewareQueue());
    if ($this->app instanceof PluginApplicationInterface) {
    $middleware = $this->app->pluginMiddleware($middleware);
    }
    $this->dispatchEvent('Server.buildMiddleware', ['middleware' => $middleware]);
    $response = $this->runner->run($middleware, $request, $this->app);
    if ($request instanceof ServerRequest) {
    $request->getSession()->close();
    }
    return $response;
    }
    \Cake\Http\Server::run()
    https://github.com/cakephp/cakephp/blob/4.4.11/src/Http/Server.php
    Request を受け取って Response を返す

    View Slide

  45. CakePHP の index.php
    // Check platform requirements
    require dirname(__DIR__) . '/config/requirements.php';
    // For built-in server
    if (PHP_SAPI === 'cli-server') {
    $_SERVER['PHP_SELF'] = '/' . basename(__FILE__);
    $url = parse_url(urldecode($_SERVER['REQUEST_URI']));
    $file = __DIR__ . $url['path'];
    if (strpos($url['path'], '..') === false && strpos($url['pa
    return false;
    }
    }
    require dirname(__DIR__) . '/vendor/autoload.php';
    use App\Application;
    use Cake\Http\Server;
    // Bind your application to the server.
    $server = new Server(new Application(dirname(__DIR__) . '/conf
    // Run the request/response through the application and emit t
    $server->emit($server->run());
    // Run the request/response through the application and emit the response.
    $server->emit($server->run());

    View Slide

  46. \Cake\Http\Server::emit()
    https://github.com/cakephp/cakephp/blob/4.4.11/src/Http/Server.php
    public function emit(ResponseInterface $response, ?EmitterInterface $emitter = null): void
    {
    if (!$emitter) {
    $emitter = new ResponseEmitter();
    }
    $emitter->emit($response);
    }

    View Slide

  47. \Cake\Http\ResponseEmitter::emit()
    https://github.com/cakephp/cakephp/blob/4.4.11/src/Http/Respons
    eEmitter.php
    public function emit(ResponseInterface $response): bool
    {
    $file = '';
    $line = 0;
    if (headers_sent($file, $line)) {
    $message = "Unable to emit headers. Headers sent in file=$file line=$line";
    trigger_error($message, E_USER_WARNING);
    }
    $this->emitStatusLine($response);
    $this->emitHeaders($response);
    $this->flush();
    $range = $this->parseContentRange($response->getHeaderLine('Content-Range'));
    if (is_array($range)) {
    $this->emitBodyRange($range, $response);
    } else {
    $this->emitBody($response);
    }
    if (function_exists('fastcgi_finish_request')) {
    fastcgi_finish_request();
    }
    return true;
    }

    View Slide

  48. \Cake\Http\ResponseEmitter::emitBody()
    https://github.com/cakephp/cakephp/blob/4.4.11/src/Http/Respons
    eEmitter.php
    protected function emitBody(ResponseInterface $response): void
    {
    if (in_array($response->getStatusCode(), [204, 304], true)) {
    return;
    }
    $body = $response->getBody();
    if (!$body->isSeekable()) {
    echo $body;
    return;
    }
    $body->rewind();
    while (!$body->eof()) {
    echo $body->read($this->maxBufferLength);
    }
    }
    奥底まで辿ると echo している!

    View Slide

  49. • サーバにインストールされている依存関係のチェック
    • Composer の autoload.php の読み込み
    • Application インスタンスの生成
    • 依存関係(DI コンテナ)、ルーティング、ミドルウェア等の反映
    • Server インスタンスの生成
    • 上記 Application を受け取る
    • 処理を実行する(run)
    • 生成された Request を受け取り Response を返す
    • レスポンスを送信する(emit)
    • Response のインスタンスをもとに
    実際に HTTP ヘッダ、ボディ(HTML, JSON 等)を出力する
    CakePHP の index.php の処理の流れまとめ

    View Slide

  50. (参考)CakePHP 公式ドキュメント
    https://book.cakephp.org/4/ja/intro.html#request-cycle

    View Slide

  51. 1. はじめに 01:00 〜 04:00
    2. FW を使った/使ってないコードの比較 04:00 〜 10:00
    3. CakePHP の index.php 10:00 〜 15:00
    4. Laravel の index.php 15:00 〜 20:00
    5. Slim の index.php 20:00 〜 25:00
    6. Symfony の index.php 25:00 〜 30:00
    7. 各 FW の共通点 〜問いへの答え〜 30:00 〜 34:00
    8. 自己紹介 34:00 〜 35:00
    目次(※時間は目安です)

    View Slide

  52. Laravel の index.php
    use Illuminate\Contracts\Http\Kernel;
    use Illuminate\Http\Request;
    define('LARAVEL_START', microtime(true));
    if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
    require $maintenance;
    }
    require __DIR__.'/../vendor/autoload.php';
    $app = require_once __DIR__.'/../bootstrap/app.php';
    $kernel = $app->make(Kernel::class);
    $response = $kernel->handle(
    $request = Request::capture()
    )->send();
    $kernel->terminate($request, $response);
    ※スペースの都合でコメントを省略しています
    https://github.com/laravel/laravel/blob/v10.0.2/public/index.php

    View Slide

  53. use Illuminate\Contracts\Http\Kernel;
    use Illuminate\Http\Request;
    define('LARAVEL_START', microtime(true));
    if (file_exists($maintenance = __DIR__.'/../storage/framework/mainten
    require $maintenance;
    }
    require __DIR__.'/../vendor/autoload.php';
    $app = require_once __DIR__.'/../bootstrap/app.php';
    $kernel = $app->make(Kernel::class);
    $response = $kernel->handle(
    $request = Request::capture()
    )->send();
    $kernel->terminate($request, $response);
    Laravel の index.php
    if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
    require $maintenance;
    }

    View Slide

  54. use Illuminate\Contracts\Http\Kernel;
    use Illuminate\Http\Request;
    define('LARAVEL_START', microtime(true));
    if (file_exists($maintenance = __DIR__.'/../storage/framework/mainten
    require $maintenance;
    }
    require __DIR__.'/../vendor/autoload.php';
    $app = require_once __DIR__.'/../bootstrap/app.php';
    $kernel = $app->make(Kernel::class);
    $response = $kernel->handle(
    $request = Request::capture()
    )->send();
    $kernel->terminate($request, $response);
    Laravel の index.php
    require __DIR__.'/../vendor/autoload.php';

    View Slide

  55. use Illuminate\Contracts\Http\Kernel;
    use Illuminate\Http\Request;
    define('LARAVEL_START', microtime(true));
    if (file_exists($maintenance = __DIR__.'/../storage/framework/mainten
    require $maintenance;
    }
    require __DIR__.'/../vendor/autoload.php';
    $app = require_once __DIR__.'/../bootstrap/app.php';
    $kernel = $app->make(Kernel::class);
    $response = $kernel->handle(
    $request = Request::capture()
    )->send();
    $kernel->terminate($request, $response);
    Laravel の index.php
    App Kernel 各種設定
    $app = require_once __DIR__.'/../bootstrap/app.php';
    $kernel = $app->make(Kernel::class);
    DI、ルーティング、
    ミドルウェア......

    View Slide

  56. use Illuminate\Contracts\Http\Kernel;
    use Illuminate\Http\Request;
    define('LARAVEL_START', microtime(true));
    if (file_exists($maintenance = __DIR__.'/../storage/framework/mainten
    require $maintenance;
    }
    require __DIR__.'/../vendor/autoload.php';
    $app = require_once __DIR__.'/../bootstrap/app.php';
    $kernel = $app->make(Kernel::class);
    $response = $kernel->handle(
    $request = Request::capture()
    )->send();
    $kernel->terminate($request, $response);
    Laravel の index.php
    $response = $kernel->handle(
    $request = Request::capture()
    )->send();
    Request のインスタンスを生成

    View Slide

  57. \Illuminate\Http\Request::capture()
    https://github.com/laravel/framework/blob/v10.0.3/src/Illuminate/
    Http/Request.php
    public static function capture()
    {
    static::enableHttpMethodParameterOverride();
    return static::createFromBase(SymfonyRequest::createFromGlobals());
    }
    \Symfony\Component\HttpFoundation\Request

    View Slide

  58. use Illuminate\Contracts\Http\Kernel;
    use Illuminate\Http\Request;
    define('LARAVEL_START', microtime(true));
    if (file_exists($maintenance = __DIR__.'/../storage/framework/mainten
    require $maintenance;
    }
    require __DIR__.'/../vendor/autoload.php';
    $app = require_once __DIR__.'/../bootstrap/app.php';
    $kernel = $app->make(Kernel::class);
    $response = $kernel->handle(
    $request = Request::capture()
    )->send();
    $kernel->terminate($request, $response);
    Laravel の index.php
    $response = $kernel->handle(
    $request = Request::capture()
    )->send();

    View Slide

  59. \Illuminate\Foundation\Http\Kernel::handle()
    https://github.com/laravel/framework/blob/v10.0.3/src/Illuminate/F
    oundation/Http/Kernel.php
    public function handle($request)
    {
    $this->requestStartedAt = Carbon::now();
    try {
    $request->enableHttpMethodParameterOverride();
    $response = $this->sendRequestThroughRouter($request);
    } catch (Throwable $e) {
    $this->reportException($e);
    $response = $this->renderException($request, $e);
    }
    $this->app['events']->dispatch(
    new RequestHandled($request, $response)
    );
    return $response;
    }
    Request を受け取って Response を返す
    \Symfony\Component\HttpFoundation\Response

    View Slide

  60. use Illuminate\Contracts\Http\Kernel;
    use Illuminate\Http\Request;
    define('LARAVEL_START', microtime(true));
    if (file_exists($maintenance = __DIR__.'/../storage/framework/mainten
    require $maintenance;
    }
    require __DIR__.'/../vendor/autoload.php';
    $app = require_once __DIR__.'/../bootstrap/app.php';
    $kernel = $app->make(Kernel::class);
    $response = $kernel->handle(
    $request = Request::capture()
    )->send();
    $kernel->terminate($request, $response);
    Laravel の index.php
    $response = $kernel->handle(
    $request = Request::capture()
    )->send();

    View Slide

  61. public function send(): static
    {
    $this->sendHeaders();
    $this->sendContent();
    if (\function_exists('fastcgi_finish_request')) {
    fastcgi_finish_request();
    } elseif (\function_exists('litespeed_finish_request')) {
    litespeed_finish_request();
    } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
    static::closeOutputBuffers(0, true);
    flush();
    }
    return $this;
    }
    \Symfony\Component\HttpFoundation\Response::send()
    https://github.com/symfony/http-foundation/blob/v6.2.6/Response.php

    View Slide

  62. public function sendContent():
    static
    {
    echo $this->content;
    return $this;
    }
    \Symfony\Component\HttpFoundation\Response::sendContent()
    https://github.com/symfony/http-foundation/blob/v6.2.6/Response.php
    奥底まで辿ると echo している!

    View Slide

  63. • メンテナンスモードのチェック
    • Composer の autoload.php の読み込み
    • Kernel インスタンスの生成
    • 依存関係(DI コンテナ)、ルーティング、ミドルウェア等の反映
    • 処理を実行する(handle)
    • 生成された Request を受け取り Response を返す
    • レスポンスを送る(send)
    • Response のインスタンスをもとに
    実際に HTTP ヘッダ、ボディ(HTML, JSON 等)を出力する
    Laravel の index.php の処理の流れまとめ

    View Slide

  64. (参考)Laravel 公式ドキュメント
    https://laravel.com/docs/10.x/lifecycle

    View Slide

  65. 1. はじめに 01:00 〜 04:00
    2. FW を使った/使ってないコードの比較 04:00 〜 10:00
    3. CakePHP の index.php 10:00 〜 15:00
    4. Laravel の index.php 15:00 〜 20:00
    5. Slim の index.php 20:00 〜 25:00
    6. Symfony の index.php 25:00 〜 30:00
    7. 各 FW の共通点 〜問いへの答え〜 30:00 〜 34:00
    8. 自己紹介 34:00 〜 35:00
    目次(※時間は目安です)

    View Slide

  66. Slim の index.php
    :

    https://github.com/slimphp/Slim-Skeleton/blob/4.5.0/public/index.php
    declare(strict_types=1);
    use App\Application\Handlers\HttpErrorHandler;
    use App\Application\Handlers\ShutdownHandler;
    use App\Application\ResponseEmitter\ResponseEmitter;
    use App\Application\Settings\SettingsInterface;
    use DI\ContainerBuilder;
    use Slim\Factory\AppFactory;
    use Slim\Factory\ServerRequestCreatorFactory;
    require __DIR__ . '/../vendor/autoload.php';
    // Instantiate PHP-DI ContainerBuilder
    $containerBuilder = new ContainerBuilder();
    if (false) { // Should be set to true in production
    $containerBuilder->enableCompilation(__DIR__ . '/../var/cache');
    }
    // Set up settings
    $settings = require __DIR__ . '/../app/settings.php';
    $settings($containerBuilder);
    // Set up dependencies
    $dependencies = require __DIR__ . '/../app/dependencies.php';
    $dependencies($containerBuilder);
    // Set up repositories
    $repositories = require __DIR__ . '/../app/repositories.php';
    $repositories($containerBuilder);
    // Build PHP-DI Container instance
    $container = $containerBuilder->build();
    // Instantiate the app
    AppFactory::setContainer($container);
    $app = AppFactory::create();
    $callableResolver = $app->getCallableResolver();
    // Register middleware
    $middleware = require __DIR__ . '/../app/middleware.php';
    $middleware($app);
    // Register routes
    $routes = require __DIR__ . '/../app/routes.php';
    $routes($app);
    /** @var SettingsInterface $settings */
    $settings = $container->get(SettingsInterface::class);
    $displayErrorDetails = $settings->get('displayErrorDetails');
    $logError = $settings->get('logError');
    $logErrorDetails = $settings->get('logErrorDetails');
    // Create Request object from globals
    $serverRequestCreator = ServerRequestCreatorFactory::create();
    $request = $serverRequestCreator->createServerRequestFromGlobals();
    // Create Error Handler
    $responseFactory = $app->getResponseFactory();
    $errorHandler = new HttpErrorHandler($callableResolver, $responseFactory);
    // Create Shutdown Handler
    $shutdownHandler = new ShutdownHandler($request, $errorHandler, $displayErrorDetails);
    register_shutdown_function($shutdownHandler);
    // Add Routing Middleware
    $app->addRoutingMiddleware();
    // Add Body Parsing Middleware
    $app->addBodyParsingMiddleware();
    // Add Error Middleware
    $errorMiddleware = $app->addErrorMiddleware($displayErrorDetails, $logError, $logErrorDetails);
    $errorMiddleware->setDefaultErrorHandler($errorHandler);
    // Run App & Emit Response
    $response = $app->handle($request);
    $responseEmitter = new ResponseEmitter();
    $responseEmitter->emit($response);
    :


    View Slide

  67. Slim の index.php
    declare(strict_types=1);
    use App\Application\Handlers\HttpErrorHandler;
    use App\Application\Handlers\ShutdownHandler;
    use App\Application\ResponseEmitter\ResponseEmitter;
    use App\Application\Settings\SettingsInterface;
    use DI\ContainerBuilder;
    use Slim\Factory\AppFactory;
    use Slim\Factory\ServerRequestCreatorFactory;
    require __DIR__ . '/../vendor/autoload.php';
    // Instantiate PHP-DI ContainerBuilder
    $containerBuilder = new ContainerBuilder();
    if (false) { // Should be set to true in production
    $containerBuilder->enableCompilation(__DIR__ . '/../var/cache');
    }
    // Set up settings
    $settings = require __DIR__ . '/../app/settings.php';
    $settings($containerBuilder);
    // Set up dependencies
    $dependencies = require __DIR__ . '/../app/dependencies.php';
    $dependencies($containerBuilder);
    // Set up repositories
    $repositories = require __DIR__ . '/../app/repositories.php';
    $repositories($containerBuilder);
    // Build PHP-DI Container instance
    $container = $containerBuilder->build();
    // Instantiate the app
    AppFactory::setContainer($container);
    $app = AppFactory::create();
    $callableResolver = $app->getCallableResolver();
    require __DIR__ . '/../vendor/autoload.php';

    View Slide

  68. Slim の index.php
    declare(strict_types=1);
    use App\Application\Handlers\HttpErrorHandler;
    use App\Application\Handlers\ShutdownHandler;
    use App\Application\ResponseEmitter\ResponseEmitter;
    use App\Application\Settings\SettingsInterface;
    use DI\ContainerBuilder;
    use Slim\Factory\AppFactory;
    use Slim\Factory\ServerRequestCreatorFactory;
    require __DIR__ . '/../vendor/autoload.php';
    // Instantiate PHP-DI ContainerBuilder
    $containerBuilder = new ContainerBuilder();
    if (false) { // Should be set to true in production
    $containerBuilder->enableCompilation(__DIR__ . '/../var/cache');
    }
    // Set up settings
    $settings = require __DIR__ . '/../app/settings.php';
    $settings($containerBuilder);
    // Set up dependencies
    $dependencies = require __DIR__ . '/../app/dependencies.php';
    $dependencies($containerBuilder);
    // Set up repositories
    $repositories = require __DIR__ . '/../app/repositories.php';
    $repositories($containerBuilder);
    // Build PHP-DI Container instance
    $container = $containerBuilder->build();
    // Instantiate the app
    AppFactory::setContainer($container);
    $app = AppFactory::create();
    $callableResolver = $app->getCallableResolver();
    Container
    DI コンテナ

    View Slide

  69. // Instantiate PHP-DI ContainerBuilder
    $containerBuilder = new ContainerBuilder();
    Slim の index.php
    declare(strict_types=1);
    use App\Application\Handlers\HttpErrorHandler;
    use App\Application\Handlers\ShutdownHandler;
    use App\Application\ResponseEmitter\ResponseEmitter;
    use App\Application\Settings\SettingsInterface;
    use DI\ContainerBuilder;
    use Slim\Factory\AppFactory;
    use Slim\Factory\ServerRequestCreatorFactory;
    require __DIR__ . '/../vendor/autoload.php';
    // Instantiate PHP-DI ContainerBuilder
    $containerBuilder = new ContainerBuilder();
    if (false) { // Should be set to true in production
    $containerBuilder->enableCompilation(__DIR__ . '/../var/cache');
    }
    // Set up settings
    $settings = require __DIR__ . '/../app/settings.php';
    $settings($containerBuilder);
    // Set up dependencies
    $dependencies = require __DIR__ . '/../app/dependencies.php';
    $dependencies($containerBuilder);
    // Set up repositories
    $repositories = require __DIR__ . '/../app/repositories.php';
    $repositories($containerBuilder);
    // Build PHP-DI Container instance
    $container = $containerBuilder->build();
    // Instantiate the app
    AppFactory::setContainer($container);
    $app = AppFactory::create();
    $callableResolver = $app->getCallableResolver();
    Container
    DI コンテナ

    View Slide

  70. Slim の index.php
    declare(strict_types=1);
    use App\Application\Handlers\HttpErrorHandler;
    use App\Application\Handlers\ShutdownHandler;
    use App\Application\ResponseEmitter\ResponseEmitter;
    use App\Application\Settings\SettingsInterface;
    use DI\ContainerBuilder;
    use Slim\Factory\AppFactory;
    use Slim\Factory\ServerRequestCreatorFactory;
    require __DIR__ . '/../vendor/autoload.php';
    // Instantiate PHP-DI ContainerBuilder
    $containerBuilder = new ContainerBuilder();
    if (false) { // Should be set to true in production
    $containerBuilder->enableCompilation(__DIR__ . '/../var/cache');
    }
    // Set up settings
    $settings = require __DIR__ . '/../app/settings.php';
    $settings($containerBuilder);
    // Set up dependencies
    $dependencies = require __DIR__ . '/../app/dependencies.php';
    $dependencies($containerBuilder);
    // Set up repositories
    $repositories = require __DIR__ . '/../app/repositories.php';
    $repositories($containerBuilder);
    // Build PHP-DI Container instance
    $container = $containerBuilder->build();
    // Instantiate the app
    AppFactory::setContainer($container);
    $app = AppFactory::create();
    $callableResolver = $app->getCallableResolver();
    Container
    DI コンテナ

    View Slide

  71. Slim の index.php
    declare(strict_types=1);
    use App\Application\Handlers\HttpErrorHandler;
    use App\Application\Handlers\ShutdownHandler;
    use App\Application\ResponseEmitter\ResponseEmitter;
    use App\Application\Settings\SettingsInterface;
    use DI\ContainerBuilder;
    use Slim\Factory\AppFactory;
    use Slim\Factory\ServerRequestCreatorFactory;
    require __DIR__ . '/../vendor/autoload.php';
    // Instantiate PHP-DI ContainerBuilder
    $containerBuilder = new ContainerBuilder();
    if (false) { // Should be set to true in production
    $containerBuilder->enableCompilation(__DIR__ . '/../var/cache');
    }
    // Set up settings
    $settings = require __DIR__ . '/../app/settings.php';
    $settings($containerBuilder);
    // Set up dependencies
    $dependencies = require __DIR__ . '/../app/dependencies.php';
    $dependencies($containerBuilder);
    // Set up repositories
    $repositories = require __DIR__ . '/../app/repositories.php';
    $repositories($containerBuilder);
    // Build PHP-DI Container instance
    $container = $containerBuilder->build();
    // Instantiate the app
    AppFactory::setContainer($container);
    $app = AppFactory::create();
    $callableResolver = $app->getCallableResolver();
    // Instantiate the app
    AppFactory::setContainer($container);
    $app = AppFactory::create();
    $callableResolver = $app->getCallableResolver();
    App Container
    DI コンテナ

    View Slide

  72. // Register middleware
    $middleware = require __DIR__ . '/../app/middleware.php';
    $middleware($app);
    // Register routes
    $routes = require __DIR__ . '/../app/routes.php';
    $routes($app);
    /** @var SettingsInterface $settings */
    $settings = $container->get(SettingsInterface::class);
    $displayErrorDetails = $settings->get('displayErrorDetails');
    $logError = $settings->get('logError');
    $logErrorDetails = $settings->get('logErrorDetails');
    // Create Request object from globals
    $serverRequestCreator = ServerRequestCreatorFactory::create();
    $request = $serverRequestCreator->createServerRequestFromGlobals();
    // Create Error Handler
    $responseFactory = $app->getResponseFactory();
    $errorHandler = new HttpErrorHandler($callableResolver, $responseFactory);
    // Create Shutdown Handler
    $shutdownHandler = new ShutdownHandler($request, $errorHandler, $displayErr
    register_shutdown_function($shutdownHandler);
    // Add Routing Middleware
    $app->addRoutingMiddleware();
    // Add Body Parsing Middleware
    $app->addBodyParsingMiddleware();
    // Add Error Middleware
    $errorMiddleware = $app->addErrorMiddleware($displayErrorDetails, $logError
    $errorMiddleware->setDefaultErrorHandler($errorHandler);
    // Run App & Emit Response
    $response = $app->handle($request);
    $responseEmitter = new ResponseEmitter();
    $responseEmitter->emit($response);
    Slim の index.php
    // Register middleware
    $middleware = require __DIR__ . '/../app/middleware.php';
    $middleware($app);
    // Register routes
    $routes = require __DIR__ . '/../app/routes.php';
    $routes($app);
    App Container
    DI コンテナ
    ルーティング、
    ミドルウェア

    View Slide

  73. // Register middleware
    $middleware = require __DIR__ . '/../app/middleware.php';
    $middleware($app);
    // Register routes
    $routes = require __DIR__ . '/../app/routes.php';
    $routes($app);
    /** @var SettingsInterface $settings */
    $settings = $container->get(SettingsInterface::class);
    $displayErrorDetails = $settings->get('displayErrorDetails');
    $logError = $settings->get('logError');
    $logErrorDetails = $settings->get('logErrorDetails');
    // Create Request object from globals
    $serverRequestCreator = ServerRequestCreatorFactory::create();
    $request = $serverRequestCreator->createServerRequestFromGlobals();
    // Create Error Handler
    $responseFactory = $app->getResponseFactory();
    $errorHandler = new HttpErrorHandler($callableResolver, $responseFactory);
    // Create Shutdown Handler
    $shutdownHandler = new ShutdownHandler($request, $errorHandler, $displayErr
    register_shutdown_function($shutdownHandler);
    // Add Routing Middleware
    $app->addRoutingMiddleware();
    // Add Body Parsing Middleware
    $app->addBodyParsingMiddleware();
    // Add Error Middleware
    $errorMiddleware = $app->addErrorMiddleware($displayErrorDetails, $logError
    $errorMiddleware->setDefaultErrorHandler($errorHandler);
    // Run App & Emit Response
    $response = $app->handle($request);
    $responseEmitter = new ResponseEmitter();
    $responseEmitter->emit($response);
    Slim の index.php
    ※エラーログや最低限のミドルウェア等の設定

    View Slide

  74. // Register middleware
    $middleware = require __DIR__ . '/../app/middleware.php';
    $middleware($app);
    // Register routes
    $routes = require __DIR__ . '/../app/routes.php';
    $routes($app);
    /** @var SettingsInterface $settings */
    $settings = $container->get(SettingsInterface::class);
    $displayErrorDetails = $settings->get('displayErrorDetails');
    $logError = $settings->get('logError');
    $logErrorDetails = $settings->get('logErrorDetails');
    // Create Request object from globals
    $serverRequestCreator = ServerRequestCreatorFactory::create();
    $request = $serverRequestCreator->createServerRequestFromGlobals();
    // Create Error Handler
    $responseFactory = $app->getResponseFactory();
    $errorHandler = new HttpErrorHandler($callableResolver, $responseFactory);
    // Create Shutdown Handler
    $shutdownHandler = new ShutdownHandler($request, $errorHandler, $displayErr
    register_shutdown_function($shutdownHandler);
    // Add Routing Middleware
    $app->addRoutingMiddleware();
    // Add Body Parsing Middleware
    $app->addBodyParsingMiddleware();
    // Add Error Middleware
    $errorMiddleware = $app->addErrorMiddleware($displayErrorDetails, $logError
    $errorMiddleware->setDefaultErrorHandler($errorHandler);
    // Run App & Emit Response
    $response = $app->handle($request);
    $responseEmitter = new ResponseEmitter();
    $responseEmitter->emit($response);
    Slim の index.php
    // Create Request object from globals
    $serverRequestCreator = ServerRequestCreatorFactory::create();
    $request = $serverRequestCreator->createServerRequestFromGlobals();
    Request のインスタンスを生成

    View Slide

  75. // Register middleware
    $middleware = require __DIR__ . '/../app/middleware.php';
    $middleware($app);
    // Register routes
    $routes = require __DIR__ . '/../app/routes.php';
    $routes($app);
    /** @var SettingsInterface $settings */
    $settings = $container->get(SettingsInterface::class);
    $displayErrorDetails = $settings->get('displayErrorDetails');
    $logError = $settings->get('logError');
    $logErrorDetails = $settings->get('logErrorDetails');
    // Create Request object from globals
    $serverRequestCreator = ServerRequestCreatorFactory::create();
    $request = $serverRequestCreator->createServerRequestFromGlobals();
    // Create Error Handler
    $responseFactory = $app->getResponseFactory();
    $errorHandler = new HttpErrorHandler($callableResolver, $responseFactory);
    // Create Shutdown Handler
    $shutdownHandler = new ShutdownHandler($request, $errorHandler, $displayErr
    register_shutdown_function($shutdownHandler);
    // Add Routing Middleware
    $app->addRoutingMiddleware();
    // Add Body Parsing Middleware
    $app->addBodyParsingMiddleware();
    // Add Error Middleware
    $errorMiddleware = $app->addErrorMiddleware($displayErrorDetails, $logError
    $errorMiddleware->setDefaultErrorHandler($errorHandler);
    // Run App & Emit Response
    $response = $app->handle($request);
    $responseEmitter = new ResponseEmitter();
    $responseEmitter->emit($response);
    Slim の index.php
    // Run App & Emit Response
    $response = $app->handle($request);
    $responseEmitter = new
    ResponseEmitter();
    $responseEmitter->emit($response);

    View Slide

  76. public function handle(ServerRequestInterface $request): ResponseInterface
    {
    $response = $this->middlewareDispatcher->handle($request);
    /**
    * This is to be in compliance with RFC 2616, Section 9.
    * If the incoming request method is HEAD, we need to ensure that the response body
    * is empty as the request may fall back on a GET route handler due to FastRoute's
    * routing logic which could potentially append content to the response body
    * https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4
    */
    $method = strtoupper($request->getMethod());
    if ($method === 'HEAD') {
    $emptyBody = $this->responseFactory->createResponse()->getBody();
    return $response->withBody($emptyBody);
    }
    return $response;
    }
    \Slim\App::handle()
    https://github.com/slimphp/Slim/blob/4.11.0/Slim/App.php
    Request を受け取って Response を返す

    View Slide

  77. // Register middleware
    $middleware = require __DIR__ . '/../app/middleware.php';
    $middleware($app);
    // Register routes
    $routes = require __DIR__ . '/../app/routes.php';
    $routes($app);
    /** @var SettingsInterface $settings */
    $settings = $container->get(SettingsInterface::class);
    $displayErrorDetails = $settings->get('displayErrorDetails');
    $logError = $settings->get('logError');
    $logErrorDetails = $settings->get('logErrorDetails');
    // Create Request object from globals
    $serverRequestCreator = ServerRequestCreatorFactory::create();
    $request = $serverRequestCreator->createServerRequestFromGlobals();
    // Create Error Handler
    $responseFactory = $app->getResponseFactory();
    $errorHandler = new HttpErrorHandler($callableResolver, $responseFactory);
    // Create Shutdown Handler
    $shutdownHandler = new ShutdownHandler($request, $errorHandler, $displayErr
    register_shutdown_function($shutdownHandler);
    // Add Routing Middleware
    $app->addRoutingMiddleware();
    // Add Body Parsing Middleware
    $app->addBodyParsingMiddleware();
    // Add Error Middleware
    $errorMiddleware = $app->addErrorMiddleware($displayErrorDetails, $logError
    $errorMiddleware->setDefaultErrorHandler($errorHandler);
    // Run App & Emit Response
    $response = $app->handle($request);
    $responseEmitter = new ResponseEmitter();
    $responseEmitter->emit($response);
    Slim の index.php
    // Run App & Emit Response
    $response = $app->handle($request);
    $responseEmitter = new
    ResponseEmitter();
    $responseEmitter->emit($response);

    View Slide

  78. public function emit(ResponseInterface $response): void
    {
    $isEmpty = $this->isResponseEmpty($response);
    if (headers_sent() === false) {
    $this->emitHeaders($response);
    // Set the status _after_ the headers, because of PHP's "helpful" behavior with location headers.
    // See https://github.com/slimphp/Slim/issues/1730
    $this->emitStatusLine($response);
    }
    if (!$isEmpty) {
    $this->emitBody($response);
    }
    }
    \Slim\ResponseEmitter::emit()
    https://github.com/slimphp/Slim/blob/4.11.0/Slim/ResponseEmitter.php

    View Slide

  79. \Slim\ResponseEmitter::emitBody()
    https://github.com/slimphp/Slim/blob/4.11.0/Slim/ResponseEmitter.php
    private function emitBody(ResponseInterface $response): void
    {
    $body = $response->getBody();
    if ($body->isSeekable()) {
    $body->rewind();
    }
    $amountToRead = (int) $response->getHeaderLine('Content-Length');
    if (!$amountToRead) {
    $amountToRead = $body->getSize();
    }
    if ($amountToRead) {
    while ($amountToRead > 0 && !$body->eof()) {
    $length = min($this->responseChunkSize, $amountToRead);
    $data = $body->read($length);
    echo $data;
    $amountToRead -= strlen($data);
    if (connection_status() !== CONNECTION_NORMAL) {
    break;
    }
    }
    } else {
    while (!$body->eof()) {
    echo $body->read($this->responseChunkSize);
    if (connection_status() !== CONNECTION_NORMAL) {
    break;
    }
    }
    }
    }
    echo $data;
    echo $body->read($this->responseChunkSize);
    奥底まで辿ると echo している!

    View Slide

  80. • Composer の autoload.php の読み込み
    • App インスタンスの生成
    • DI コンテナを構築し、受け取る
    • ミドルウェアやルーティングの反映
    • エラーログや最低限のミドルウェア等の設定
    • Request インスタンスの生成
    • 処理を実行する(handle)
    • 生成された Request を受け取り Response を返す
    • レスポンスを送信する(emit)
    • Response のインスタンスをもとに
    実際に HTTP ヘッダ、ボディ(HTML, JSON 等)を出力する
    Slim の index.php の処理の流れまとめ

    View Slide

  81. (参考)Slim 公式ドキュメント
    https://www.slimframework.com/docs/v4/concepts/life-cycle.html

    View Slide

  82. 1. はじめに 01:00 〜 04:00
    2. FW を使った/使ってないコードの比較 04:00 〜 10:00
    3. CakePHP の index.php 10:00 〜 15:00
    4. Laravel の index.php 15:00 〜 20:00
    5. Slim の index.php 20:00 〜 25:00
    6. Symfony の index.php 25:00 〜 30:00
    7. 各 FW の共通点 〜問いへの答え〜 30:00 〜 34:00
    8. 自己紹介 34:00 〜 35:00
    目次(※時間は目安です)

    View Slide

  83. Symfony の index.php
    https://github.com/symfony/recipes/blob/main/symfony/framework-
    bundle/6.2/public/index.php
    use App\Kernel;
    require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
    return function (array $context) {
    return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
    };

    View Slide

  84. autoload_runtime.php
    https://github.com/symfony/runtime/blob/v6.2.5/Internal/autoload_
    runtime.template(←はテンプレート)
    // autoload_runtime.php @generated by Symfony Runtime
    if (true === (require_once __DIR__.'/autoload.php') || empty($_SERVER['SCRIPT_FILENAME'])) {
    return;
    }
    $app = require $_SERVER['SCRIPT_FILENAME'];
    if (!is_object($app)) {
    throw new TypeError(sprintf('Invalid return value: callable object expected, "%s" returned from "%s".', get_debug_type($app),
    $_SERVER['SCRIPT_FILENAME']));
    }
    $runtime = $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? 'Symfony\\Component\\Runtime\\SymfonyRuntime';
    $runtime = new $runtime(($_SERVER['APP_RUNTIME_OPTIONS'] ?? $_ENV['APP_RUNTIME_OPTIONS'] ?? []) + [
    'project_dir' => dirname(__DIR__, 1),
    ]);
    [$app, $args] = $runtime
    ->getResolver($app)
    ->resolve();
    $app = $app(...$args);
    exit(
    $runtime
    ->getRunner($app)
    ->run()
    );

    View Slide

  85. autoload_runtime.php
    // autoload_runtime.php @generated by Symfony Runtime
    if (true === (require_once __DIR__.'/autoload.php') || empty($_SERVER['SCRIPT_FILENAME'])) {
    return;
    }
    $app = require $_SERVER['SCRIPT_FILENAME'];
    if (!is_object($app)) {
    throw new TypeError(sprintf('Invalid return value: callable object expected, "%s" returned from "%s".', get_d
    $_SERVER['SCRIPT_FILENAME']));
    }
    $runtime = $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? 'Symfony\\Component\\Runtime\\SymfonyRuntime';
    $runtime = new $runtime(($_SERVER['APP_RUNTIME_OPTIONS'] ?? $_ENV['APP_RUNTIME_OPTIONS'] ?? []) + [
    'project_dir' => dirname(__DIR__, 1),
    ]);
    [$app, $args] = $runtime
    ->getResolver($app)
    ->resolve();
    $app = $app(...$args);
    exit(
    $runtime
    ->getRunner($app)
    ->run()
    );
    if (true === (require_once __DIR__.'/autoload.php') || empty($_SERVER['SCRIPT_FILENAME'])) {
    return;
    }

    View Slide

  86. autoload_runtime.php
    // autoload_runtime.php @generated by Symfony Runtime
    if (true === (require_once __DIR__.'/autoload.php') || empty($_SERVER['SCRIPT_FILENAME'])) {
    return;
    }
    $app = require $_SERVER['SCRIPT_FILENAME'];
    if (!is_object($app)) {
    throw new TypeError(sprintf('Invalid return value: callable object expected, "%s" returned from "%s".', get_d
    $_SERVER['SCRIPT_FILENAME']));
    }
    $runtime = $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? 'Symfony\\Component\\Runtime\\SymfonyRuntime';
    $runtime = new $runtime(($_SERVER['APP_RUNTIME_OPTIONS'] ?? $_ENV['APP_RUNTIME_OPTIONS'] ?? []) + [
    'project_dir' => dirname(__DIR__, 1),
    ]);
    [$app, $args] = $runtime
    ->getResolver($app)
    ->resolve();
    $app = $app(...$args);
    exit(
    $runtime
    ->getRunner($app)
    ->run()
    );
    $app = require $_SERVER['SCRIPT_FILENAME'];
    = index.php

    View Slide

  87. Symfony の index.php
    https://github.com/symfony/recipes/blob/main/symfony/framework-
    bundle/6.2/public/index.php
    use App\Kernel;
    require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
    return function (array $context) {
    return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
    };
    Kernel 各種設定
    DI、ルーティング、
    ミドルウェア......

    View Slide

  88. autoload_runtime.php
    // autoload_runtime.php @generated by Symfony Runtime
    if (true === (require_once __DIR__.'/autoload.php') || empty($_SERVER['SCRIPT_FILENAME'])) {
    return;
    }
    $app = require $_SERVER['SCRIPT_FILENAME'];
    if (!is_object($app)) {
    throw new TypeError(sprintf('Invalid return value: callable object expected, "%s" returned from "%s".', get_d
    $_SERVER['SCRIPT_FILENAME']));
    }
    $runtime = $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? 'Symfony\\Component\\Runtime\\SymfonyRuntime';
    $runtime = new $runtime(($_SERVER['APP_RUNTIME_OPTIONS'] ?? $_ENV['APP_RUNTIME_OPTIONS'] ?? []) + [
    'project_dir' => dirname(__DIR__, 1),
    ]);
    [$app, $args] = $runtime
    ->getResolver($app)
    ->resolve();
    $app = $app(...$args);
    exit(
    $runtime
    ->getRunner($app)
    ->run()
    );
    $runtime = $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? 'Symfony\\Component\\Runtime\\SymfonyRuntime';
    $runtime = new $runtime(($_SERVER['APP_RUNTIME_OPTIONS'] ?? $_ENV['APP_RUNTIME_OPTIONS'] ?? []) + [
    'project_dir' => dirname(__DIR__, 1),
    ]);
    [$app, $args] = $runtime
    ->getResolver($app)
    ->resolve();
    $app = $app(...$args);
    Kernel 各種設定
    DI、ルーティング、
    ミドルウェア......
    Runtime

    View Slide

  89. autoload_runtime.php
    // autoload_runtime.php @generated by Symfony Runtime
    if (true === (require_once __DIR__.'/autoload.php') || empty($_SERVER['SCRIPT_FILENAME'])) {
    return;
    }
    $app = require $_SERVER['SCRIPT_FILENAME'];
    if (!is_object($app)) {
    throw new TypeError(sprintf('Invalid return value: callable object expected, "%s" returned from "%s".', get_d
    $_SERVER['SCRIPT_FILENAME']));
    }
    $runtime = $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? 'Symfony\\Component\\Runtime\\SymfonyRuntime';
    $runtime = new $runtime(($_SERVER['APP_RUNTIME_OPTIONS'] ?? $_ENV['APP_RUNTIME_OPTIONS'] ?? []) + [
    'project_dir' => dirname(__DIR__, 1),
    ]);
    [$app, $args] = $runtime
    ->getResolver($app)
    ->resolve();
    $app = $app(...$args);
    exit(
    $runtime
    ->getRunner($app)
    ->run()
    );
    exit(
    $runtime
    ->getRunner($app)
    ->run()
    );
    Kernel 各種設定
    DI、ルーティング、
    ミドルウェア......
    Runtime
    Runner

    View Slide

  90. autoload_runtime.php
    // autoload_runtime.php @generated by Symfony Runtime
    if (true === (require_once __DIR__.'/autoload.php') || empty($_SERVER['SCRIPT_FILENAME'])) {
    return;
    }
    $app = require $_SERVER['SCRIPT_FILENAME'];
    if (!is_object($app)) {
    throw new TypeError(sprintf('Invalid return value: callable object expected, "%s" returned from "%s".', get_d
    $_SERVER['SCRIPT_FILENAME']));
    }
    $runtime = $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? 'Symfony\\Component\\Runtime\\SymfonyRuntime';
    $runtime = new $runtime(($_SERVER['APP_RUNTIME_OPTIONS'] ?? $_ENV['APP_RUNTIME_OPTIONS'] ?? []) + [
    'project_dir' => dirname(__DIR__, 1),
    ]);
    [$app, $args] = $runtime
    ->getResolver($app)
    ->resolve();
    $app = $app(...$args);
    exit(
    $runtime
    ->getRunner($app)
    ->run()
    );
    exit(
    $runtime
    ->getRunner($app)
    ->run()
    );

    View Slide

  91. \Symfony\Component\Runtime\SymfonyRuntime::getRunner()
    https://github.com/symfony/runtime/blob/v6.2.5/SymfonyRuntime.php
    public function getRunner(?object $application): RunnerInterface
    {
    if ($application instanceof HttpKernelInterface) {
    return new HttpKernelRunner($application, Request::createFromGlobals());
    }
    :
    Request のインスタンスを生成

    View Slide

  92. autoload_runtime.php
    // autoload_runtime.php @generated by Symfony Runtime
    if (true === (require_once __DIR__.'/autoload.php') || empty($_SERVER['SCRIPT_FILENAME'])) {
    return;
    }
    $app = require $_SERVER['SCRIPT_FILENAME'];
    if (!is_object($app)) {
    throw new TypeError(sprintf('Invalid return value: callable object expected, "%s" returned from "%s".', get_d
    $_SERVER['SCRIPT_FILENAME']));
    }
    $runtime = $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? 'Symfony\\Component\\Runtime\\SymfonyRuntime';
    $runtime = new $runtime(($_SERVER['APP_RUNTIME_OPTIONS'] ?? $_ENV['APP_RUNTIME_OPTIONS'] ?? []) + [
    'project_dir' => dirname(__DIR__, 1),
    ]);
    [$app, $args] = $runtime
    ->getResolver($app)
    ->resolve();
    $app = $app(...$args);
    exit(
    $runtime
    ->getRunner($app)
    ->run()
    );
    exit(
    $runtime
    ->getRunner($app)
    ->run()
    );

    View Slide

  93. \Symfony\Component\Runtime\Resolver\HttpKernelRunner::run()
    https://github.com/symfony/runtime/blob/v6.2.5/Runner/Symfony/H
    ttpKernelRunner.php
    public function run(): int
    {
    $response = $this->kernel->handle($this->request);
    $response->send();
    if ($this->kernel instanceof TerminableInterface) {
    $this->kernel->terminate($this->request, $response);
    }
    return 0;
    }
    Request を受け取って Response を返す

    View Slide

  94. \Symfony\Component\Runtime\Resolver\HttpKernelRunner::run()
    https://github.com/symfony/runtime/blob/v6.2.5/Runner/Symfony/H
    ttpKernelRunner.php
    public function run(): int
    {
    $response = $this->kernel->handle($this->request);
    $response->send();
    if ($this->kernel instanceof TerminableInterface) {
    $this->kernel->terminate($this->request, $response);
    }
    return 0;
    }

    View Slide

  95. public function send(): static
    {
    $this->sendHeaders();
    $this->sendContent();
    if (\function_exists('fastcgi_finish_request')) {
    fastcgi_finish_request();
    } elseif (\function_exists('litespeed_finish_request')) {
    litespeed_finish_request();
    } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
    static::closeOutputBuffers(0, true);
    flush();
    }
    return $this;
    }
    \Symfony\Component\HttpFoundation\Response::send()
    https://github.com/symfony/http-foundation/blob/v6.2.6/Response.php

    View Slide

  96. public function sendContent():
    static
    {
    echo $this->content;
    return $this;
    }
    \Symfony\Component\HttpFoundation\Response::sendContent()
    https://github.com/symfony/http-foundation/blob/v6.2.6/Response.php
    奥底まで辿ると echo している!

    View Slide

  97. • Composer の autoload.php の読み込み
    • Kernel のインスタンス生成
    • 依存関係(DI コンテナ)、ルーティング、ミドルウェア等の反映
    • Runner インスタンスの生成、処理の実行
    • 上記 Kernel を受け取る
    • 同時に Request インスタンスを生成
    • 処理を実行する(handle)
    • 生成された Request を受け取り Response を返す
    • レスポンスを送信する(send)
    • Response のインスタンスをもとに
    実際に HTTP ヘッダ、ボディ(HTML, JSON 等)を出力する
    Symfony の index.php の処理の流れまとめ

    View Slide

  98. (参考)Symfony 公式ドキュメント
    https://symfony.com/doc/current/components/http_kernel.html

    View Slide

  99. 1. はじめに 01:00 〜 04:00
    2. FW を使った/使ってないコードの比較 04:00 〜 10:00
    3. CakePHP の index.php 10:00 〜 15:00
    4. Laravel の index.php 15:00 〜 20:00
    5. Slim の index.php 20:00 〜 25:00
    6. Symfony の index.php 25:00 〜 30:00
    7. 各 FW の共通点 〜問いへの答え〜 30:00 〜 34:00
    8. 自己紹介 34:00 〜 35:00
    目次(※時間は目安です)

    View Slide

  100. 各 FW の index.php の処理の流れ再掲

    View Slide

  101. • URL と対応する PHP ファイルがなくても 404 エラーにならないのか
    • HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
    • $_GET, $_POST を使わずにパラメータにアクセスできるのか
    • echo 等をしていないのに HTTP レスポンスを返せるのか
    問い:FW を使って実装すると、どうして

    View Slide

  102. ✅ URL と対応する PHP ファイルがなくても 404 エラーにならないのか
    • HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
    • $_GET, $_POST を使わずにパラメータにアクセスできるのか
    • echo 等をしていないのに HTTP レスポンスを返せるのか
    → すべてのリクエストを index.php でハンドリングしているから
     (Apache や Nginx 等でそのように設定)
    問い:FW を使って実装すると、どうして

    View Slide

  103. Web アプリケーション(サーバサイド)がやっていること
    サーバ
    GET /search-products HTTP/1.1
    Accept: text/html
    Accept-Encoding: gzip, deflate
    Accept-Language: ja,en-US;q=0.9,en;q=0.8
    Connection: keep-alive
    :
    :
    クライアント
    HTTP リクエスト
    index.php

    View Slide

  104. ✅ URL と対応する PHP ファイルがなくても 404 エラーにならないのか
    • HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
    • $_GET, $_POST を使わずにパラメータにアクセスできるのか
    • echo 等をしていないのに HTTP レスポンスを返せるのか
    問い:FW を使って実装すると、どうして

    View Slide

  105. 各 FW の index.php の処理の流れ再掲

    View Slide

  106. 各 FW の index.php の処理の流れ再掲
    依存関係、ルーティング、
    ミドルウェア等の読み込み、反映

    View Slide

  107. Web アプリケーション(サーバサイド)がやっていること
    クライアント
    サーバ
    アプリケーションの準備
    · autoload
    · 設定等の読み込み
    Application
    index.php

    View Slide

  108. ✅ URL と対応する PHP ファイルがなくても 404 エラーにならないのか
    ✅ HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
    • $_GET, $_POST を使わずにパラメータにアクセスできるのか
    • echo 等をしていないのに HTTP レスポンスを返せるのか
    → 記述したルーティングを読み込んでアプリケーションを構築しているから
     (依存関係やミドルウェアなども)
    問い:FW を使って実装すると、どうして

    View Slide

  109. ✅ URL と対応する PHP ファイルがなくても 404 エラーにならないのか
    ✅ HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
    • $_GET, $_POST を使わずにパラメータにアクセスできるのか
    • echo 等をしていないのに HTTP レスポンスを返せるのか
    問い:FW を使って実装すると、どうして

    View Slide

  110. 各 FW の index.php の処理の流れ再掲
    処理の実行
    (Request → Response)

    View Slide

  111. Web アプリケーション(サーバサイド)がやっていること
    サーバ
    GET /search-products HTTP/1.1
    Accept: text/html
    Accept-Encoding: gzip, deflate
    Accept-Language: ja,en-US;q=0.9,en;q=0.8
    Connection: keep-alive
    :
    :
    クライアント
    HTTP リクエスト
    Request
    Application
    index.php

    View Slide

  112. ✅ URL と対応する PHP ファイルがなくても 404 エラーにならないのか
    ✅ HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
    ✅ $_GET, $_POST を使わずにパラメータにアクセスできるのか
    • echo 等をしていないのに HTTP レスポンスを返せるのか
    → FW 内部で($_GET, $_POST 等をもとに)Request オブジェクトに
      詰めているから
    問い:FW を使って実装すると、どうして

    View Slide

  113. ✅ URL と対応する PHP ファイルがなくても 404 エラーにならないのか
    ✅ HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
    ✅ $_GET, $_POST を使わずにパラメータにアクセスできるのか
    • echo 等をしていないのに HTTP レスポンスを返せるのか
    問い:FW を使って実装すると、どうして

    View Slide

  114. 各 FW の index.php の処理の流れ再掲
    レスポンスの送信

    View Slide

  115. Application
    Web アプリケーション(サーバサイド)がやっていること
    サーバ
    クライアント
    Request
    Response
    リクエスト内容に基づ
    く処理の実行
    index.php

    View Slide

  116. Web アプリケーション(サーバサイド)がやっていること
    サーバ
    クライアント
    HTTP/1.1 200 OK
    Date: Sat, 25 Mar 2023 02:15:00 GMT
    Server: Apache/2.4.54 (Debian)
    X-Powered-By: PHP/8.1.16
    :
    :


    :
    :
    HTTP レスポンス
    処理の結果に基づくレ
    スポンスを生成、送信
    Response
    Application
    index.php

    View Slide

  117. ✅ URL と対応する PHP ファイルがなくても 404 エラーにならないのか
    ✅ HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
    ✅ $_GET, $_POST を使わずにパラメータにアクセスできるのか
    ✅ echo 等をしていないのに HTTP レスポンスを返せるのか
    → Response オブジェクトに詰められた情報をもとに
      FW 内部で echo 等しているから
    問い:FW を使って実装すると、どうして

    View Slide

  118. ✅ URL と対応する PHP ファイルがなくても 404 エラーにならないのか
    ✅ HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
    ✅ $_GET, $_POST を使わずにパラメータにアクセスできるのか
    ✅ echo 等をしていないのに HTTP レスポンスを返せるのか
    → 各 FW の index.php を読み解いたことで答えが見えた!
    問い:FW を使って実装すると、どうして

    View Slide

  119. 各 FW の index.php の処理の流れ再掲

    View Slide

  120. index.php
    Web アプリケーション(サーバサイド)がやっていること
    サーバ
    クライアント
    HTTP リクエスト
    HTTP レスポンス
    リクエスト内容に基づ
    く処理の実行
    処理の結果に基づくレ
    スポンスを生成、送信
    HTTP/1.1 200 OK
    Date: Sat, 25 Mar 2023 02:15:00 GMT
    Server: Apache/2.4.54 (Debian)
    X-Powered-By: PHP/8.1.16
    :
    :


    :
    :
    Application
    Response
    GET /search-products HTTP/1.1
    Accept: text/html
    Accept-Encoding: gzip, deflate
    Accept-Language: ja,en-US;q=0.9,en;q=0.8
    Connection: keep-alive
    :
    :
    Request

    View Slide

  121. index.php
    Web アプリケーション(サーバサイド)がやっていること
    サーバ
    クライアント
    HTTP リクエスト
    HTTP レスポンス
    リクエスト内容に基づ
    く処理の実行
    処理の結果に基づくレ
    スポンスを生成、送信
    HTTP/1.1 200 OK
    Date: Sat, 25 Mar 2023 02:15:00 GMT
    Server: Apache/2.4.54 (Debian)
    X-Powered-By: PHP/8.1.16
    :
    :


    :
    :
    Application
    Response
    GET /search-products HTTP/1.1
    Accept: text/html
    Accept-Encoding: gzip, deflate
    Accept-Language: ja,en-US;q=0.9,en;q=0.8
    Connection: keep-alive
    :
    :
    Request

    View Slide

  122. (参考)
    https://tadsan.fanbox.cc/posts/3674121

    View Slide

  123. (参考)
    https://tadsan.fanbox.cc/posts/3674121

    View Slide

  124. • すべてのリクエストを index.php でハンドリングしている
    • 概ね共通した流れがある
    • FW を使っていてもいなくても、同じ PHP が解釈できるソースコード
    • 根本のところでは同じような処理が書かれている
    まとめ

    View Slide

  125. FW の仕組みを知っているとデバッグや拡張の役に立つ
    https://docs.google.com/presentation/d/1kA4ItJdHamg8tSdxsUhGgOwtpXr-erSocu8GLFkGO
    VE/edit?usp=sharing

    View Slide

  126. FW の仕組みを知っているとデバッグや拡張の役に立つ
    https://docs.google.com/presentation/d/1kA4ItJdHamg8tSdxsUhGgOwtpXr-erSocu8GLFkGO
    VE/edit?usp=sharing

    View Slide

  127. Day 0(前夜祭)の FW に関するセッションふたつ
    https://fortee.jp/phperkaigi-2023/proposal/db787adc-c855-4114-99b6-8c6958d47b42

    View Slide

  128. Day 0(前夜祭)の FW に関するセッションふたつ
    https://fortee.jp/phperkaigi-2023/proposal/2466b395-cc29-479a-a90a-395de6528028

    View Slide

  129. 1. はじめに 01:00 〜 04:00
    2. FW を使った/使ってないコードの比較 04:00 〜 10:00
    3. CakePHP の index.php 10:00 〜 15:00
    4. Laravel の index.php 15:00 〜 20:00
    5. Slim の index.php 20:00 〜 25:00
    6. Symfony の index.php 25:00 〜 30:00
    7. 各 FW の共通点 〜問いへの答え〜 30:00 〜 34:00
    8. 自己紹介 34:00 〜 35:00
    目次(※時間は目安です)

    View Slide

  130. 岡田 正平/おかしょい
    Twitter: @okashoi
    GitHub: @okashoi
    所属:株式会社ウィルゲート
    登壇:
    寄稿:
    PHPerKaigi 2023
    シルバースポンサー

    View Slide

  131. ウィルゲートは PHPerKaigi を応援しています!
    開発組織について
    https://speakerdeck.com/willgate/willgate-engineering-team
    廊下、アンカンファレンス、懇親会等でお話しましょう!!

    View Slide