Share
Embed
いろいろなフレームワークの仕組みを 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
Transcript
131 slides
-
• 発表内容の性質上コードを示すことが多く文字が小さくて読めない可能 性があります • 資料の URL を共有してありますので、 必要に応じて手元で資料を確認しながら聴講、視聴してください • 対象としたフレームワーク、およびバージョンは以下のとおりです
• CakePHP 4.4 • Laravel 10.0 • Slim 4.11 • Symfony 6.2 • 各公式ドキュメントに則り、通常の Web アプリケーション開発用途で インストールした場合を想定しています おことわり -
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 目次(※時間は目安です) -
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 目次(※時間は目安です) -
• 多くの場合、我々は Web アプリケーションフレームワーク(以下、 FW)を利用して開発している • 一方で単一の PHP ファイルでも Web
ページを表示することはできる • 書き方は全く異なっているのに、どうして同じ PHP で動くのだろう? はじめに -
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 リクエスト -
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 : : <!DOCTYPE html> <html> : : クライアント サーバ 処理の結果に基づくレ スポンスを生成、送信 HTTP レスポンス -
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 : : <!DOCTYPE html> <html> : : HTTP レスポンス リクエスト内容に基づ く処理の実行 処理の結果に基づくレ スポンスを生成、送信 -
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 目次(※時間は目安です) -
• 例として Slim を取り上げます • 他の FW でも大まかな流れは同じようなものだと思います • 説明の目的上、ORM
は使っていません • 使っていようがいまいが説明したいことには関係ありません • 各種設定(依存関係の注入含む)は適切になされているものとします FW を使って実装する場合 - 前提条件 -
FW を使って実装する場合(Slim の例) <?php declare(strict_types=1); use App\Application\Actions\SearchProducts; use Slim\App; return
function (App $app) { $app->get('/search-products', SearchProducts::class); }; -
FW を使って実装する場合(Slim の例) <?php 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')); } } -
<?php 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) { } -
<?php 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'] ?? ''; -
FW を使って実装する場合(Slim の例) <?php 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')); -
FW を使わずに実装する場合 <?php // 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 を使ったりすることで出力 ?><!DOCTYPE html> <html> <body> <form> <input name="q" value="<?php echo $q ?>"> <input type="submit"> </form> <table> <thead> <tr> <th>商品名</th> <th>価格</th> </tr> </thead> <tbody> <?php while ($row = $stmt->fetch()): ?> <tr> <th><?php echo htmlspecialchars($row['name']); ?></th> <th><?php echo $row['price']; ?></th> </tr> <?php endwhile; ?> </tbody> </table> </body> </html> ドキュメントルート -
FW を使わずに実装する場合 <?php // 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 を使ったりすることで出力 ?><!DOCTYPE html> <html> <body> <form> <input name="q" value="<?php echo $q ?>"> <input type="submit"> </form> <table> <thead> <tr> <th>商品名</th> <th>価格</th> </tr> </thead> <tbody> <?php while ($row = $stmt->fetch()): ?> <tr> <th><?php echo htmlspecialchars($row['name']); ?></th> <th><?php echo $row['price']; ?></th> </tr> <?php endwhile; ?> </tbody> </table> </body> </html> // 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 を使わずに実装する場合 <?php // 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 を使ったりすることで出力 ?><!DOCTYPE html> <html> <body> <form> <input name="q" value="<?php echo $q ?>"> <input type="submit"> </form> <table> <thead> <tr> <th>商品名</th> <th>価格</th> </tr> </thead> <tbody> <?php while ($row = $stmt->fetch()): ?> <tr> <th><?php echo htmlspecialchars($row['name']); ?></th> <th><?php echo $row['price']; ?></th> </tr> <?php endwhile; ?> </tbody> </table> </body> </html> // $_GET, $_POST 等で入力を受け取る $q = $_GET['q'] ?? ''; -
FW を使わずに実装する場合 <?php // 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 を使ったりすることで出力 ?><!DOCTYPE html> <html> <body> <form> <input name="q" value="<?php echo $q ?>"> <input type="submit"> </form> <table> <thead> <tr> <th>商品名</th> <th>価格</th> </tr> </thead> <tbody> <?php while ($row = $stmt->fetch()): ?> <tr> <th><?php echo htmlspecialchars($row['name']); ?></th> <th><?php echo $row['price']; ?></th> </tr> <?php endwhile; ?> </tbody> </table> </body> </html> -
FW を使わずに実装する場合 <?php // 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 を使ったりすることで出力 ?><!DOCTYPE html> <html> <body> <form> <input name="q" value="<?php echo $q ?>"> <input type="submit"> </form> <table> <thead> <tr> <th>商品名</th> <th>価格</th> </tr> </thead> <tbody> <?php while ($row = $stmt->fetch()): ?> <tr> <th><?php echo htmlspecialchars($row['name']); ?></th> <th><?php echo $row['price']; ?></th> </tr> <?php endwhile; ?> </tbody> </table> </body> </html> // .php ファイル内に HTML を記述したり、echo を使ったりすることで出力 ?><!DOCTYPE html> <html> <body> <form> <input name="q" value="<?php echo $q ?>"> <input type="submit"> </form> : : -
異なる点 FW あり FW なし URL ルーティングを記述 ファイルパスがそのまま 依存関係 インスタンスが渡される
include, require パラメータの受け取り ServerRequestInterface を介する $_GET, $_POST 出力 ResponseInterface を返す HTML を直接記述, echo 等 -
異なる点 - URL FW あり FW なし return function (App
$app) { $app->get('/search-products', SearchProducts::class); }; ドキュメントルート routes.php に書いたパス ドキュメントルートからのファイルパス -
異なる点 - 依存関係 // 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 なし -
異なる点 - パラメータの受け取り // ServerRequestInterface を介して入力を受け取る $q = $request->getQueryParams()['q'] ??
''; // $_GET, $_POST 等で入力を受け取る $q = $_GET['q'] ?? ''; FW あり FW なし -
異なる点 - 出力 // echo などは使わず、ResponseInterface のインスタンスを返す return Twig::fromRequest($request) ->render($response,
'search-products.html', compact('q', 'products' // .php ファイル内に HTML を記述したり、echo を使ったりすることで出力 ?><!DOCTYPE html> <html> <body> <form> <input name="q" value="<?php echo $q ?>"> <input type="submit"> </form> : : FW あり FW なし -
• PHP-FIG(Framework Interop Group) という団体によって PSR(PHP Standard Recommendation)として定められているもの • 仕様と
interface の定義が提供されている (参考)ServerRequestInterface と ResponseInterface https://www.php-fig.org/ https://github.com/php-fig -
本トークでは以降、(正しい表現とは言えないが) • 「ServerRequestInterface を実装したクラスのインスタンス」のことを 「Request のインスタンス」あるいは単に「Request」 • 「ResponseInterface を実装したクラスのインスタンス」のことを 「Response
のインスタンス」あるいは単に「Response」 と表記する/呼ぶこととします。 (参考)ServerRequestInterface と ResponseInterface -
• URL と対応する PHP ファイルがなくても 404 エラーにならないのか • HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
• $_GET, $_POST を使わずにパラメータにアクセスできるのか • echo 等をしていないのに HTTP レスポンスを返せるのか 問い:FW を使って実装すると、どうして -
• URL と対応する PHP ファイルがなくても 404 エラーにならないのか • HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
• $_GET, $_POST を使わずにパラメータにアクセスできるのか • echo 等をしていないのに HTTP レスポンスを返せるのか → 各 FW の index.php を読み解くことで答えが見えてくる! 問い:FW を使って実装すると、どうして -
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 でハンドリングしている -
すべてのリクエストを 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 の仕組みがわかる! -
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 目次(※時間は目安です) -
CakePHP の index.php https://github.com/cakephp/app/blob/4.4.2/webroot/index.php <?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()); ※スペースの都合でライセンス表記等を省略しています -
CakePHP の index.php <?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'; -
CakePHP の index.php <?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'; -
CakePHP の index.php <?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、ルーティング、 ミドルウェア...... -
CakePHP の index.php <?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()); -
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 のインスタンスを生成 -
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 を返す -
CakePHP の index.php <?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()); -
\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); } -
\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; } -
\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 している! -
• サーバにインストールされている依存関係のチェック • Composer の autoload.php の読み込み • Application インスタンスの生成
• 依存関係(DI コンテナ)、ルーティング、ミドルウェア等の反映 • Server インスタンスの生成 • 上記 Application を受け取る • 処理を実行する(run) • 生成された Request を受け取り Response を返す • レスポンスを送信する(emit) • Response のインスタンスをもとに 実際に HTTP ヘッダ、ボディ(HTML, JSON 等)を出力する CakePHP の index.php の処理の流れまとめ -
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 目次(※時間は目安です) -
Laravel の index.php <?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 -
<?php 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; } -
<?php 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'; -
<?php 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、ルーティング、 ミドルウェア...... -
<?php 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 のインスタンスを生成 -
\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 -
<?php 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(); -
\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 -
<?php 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(); -
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 -
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 している! -
• メンテナンスモードのチェック • Composer の autoload.php の読み込み • Kernel インスタンスの生成
• 依存関係(DI コンテナ)、ルーティング、ミドルウェア等の反映 • 処理を実行する(handle) • 生成された Request を受け取り Response を返す • レスポンスを送る(send) • Response のインスタンスをもとに 実際に HTTP ヘッダ、ボディ(HTML, JSON 等)を出力する Laravel の index.php の処理の流れまとめ -
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 目次(※時間は目安です) -
Slim の index.php : https://github.com/slimphp/Slim-Skeleton/blob/4.5.0/public/index.php <?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); : -
Slim の index.php <?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'; -
Slim の index.php <?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 コンテナ -
// Instantiate PHP-DI ContainerBuilder $containerBuilder = new ContainerBuilder(); Slim の
index.php <?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 コンテナ -
Slim の index.php <?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 コンテナ -
Slim の index.php <?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 コンテナ -
// 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 コンテナ ルーティング、 ミドルウェア -
// 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); /** @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 のインスタンスを生成 -
// 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); -
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 を返す -
// 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); -
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 -
\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 している! -
• Composer の autoload.php の読み込み • App インスタンスの生成 • DI
コンテナを構築し、受け取る • ミドルウェアやルーティングの反映 • エラーログや最低限のミドルウェア等の設定 • Request インスタンスの生成 • 処理を実行する(handle) • 生成された Request を受け取り Response を返す • レスポンスを送信する(emit) • Response のインスタンスをもとに 実際に HTTP ヘッダ、ボディ(HTML, JSON 等)を出力する Slim の index.php の処理の流れまとめ -
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 目次(※時間は目安です) -
Symfony の index.php https://github.com/symfony/recipes/blob/main/symfony/framework- bundle/6.2/public/index.php <?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']); }; -
autoload_runtime.php https://github.com/symfony/runtime/blob/v6.2.5/Internal/autoload_ runtime.template(←はテンプレート) <?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_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() ); -
autoload_runtime.php <?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; } -
autoload_runtime.php <?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 -
Symfony の index.php https://github.com/symfony/recipes/blob/main/symfony/framework- bundle/6.2/public/index.php <?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、ルーティング、 ミドルウェア...... -
autoload_runtime.php <?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 -
autoload_runtime.php <?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 -
autoload_runtime.php <?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() ); -
\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 のインスタンスを生成 -
autoload_runtime.php <?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() ); -
\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 を返す -
\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; } -
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 -
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 している! -
• Composer の autoload.php の読み込み • Kernel のインスタンス生成 • 依存関係(DI
コンテナ)、ルーティング、ミドルウェア等の反映 • Runner インスタンスの生成、処理の実行 • 上記 Kernel を受け取る • 同時に Request インスタンスを生成 • 処理を実行する(handle) • 生成された Request を受け取り Response を返す • レスポンスを送信する(send) • Response のインスタンスをもとに 実際に HTTP ヘッダ、ボディ(HTML, JSON 等)を出力する Symfony の index.php の処理の流れまとめ -
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 目次(※時間は目安です) -
• URL と対応する PHP ファイルがなくても 404 エラーにならないのか • HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
• $_GET, $_POST を使わずにパラメータにアクセスできるのか • echo 等をしていないのに HTTP レスポンスを返せるのか 問い:FW を使って実装すると、どうして -
✅ URL と対応する PHP ファイルがなくても 404 エラーにならないのか • HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
• $_GET, $_POST を使わずにパラメータにアクセスできるのか • echo 等をしていないのに HTTP レスポンスを返せるのか → すべてのリクエストを index.php でハンドリングしているから (Apache や Nginx 等でそのように設定) 問い:FW を使って実装すると、どうして -
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 -
✅ URL と対応する PHP ファイルがなくても 404 エラーにならないのか • HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
• $_GET, $_POST を使わずにパラメータにアクセスできるのか • echo 等をしていないのに HTTP レスポンスを返せるのか 問い:FW を使って実装すると、どうして -
✅ URL と対応する PHP ファイルがなくても 404 エラーにならないのか ✅ HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
• $_GET, $_POST を使わずにパラメータにアクセスできるのか • echo 等をしていないのに HTTP レスポンスを返せるのか → 記述したルーティングを読み込んでアプリケーションを構築しているから (依存関係やミドルウェアなども) 問い:FW を使って実装すると、どうして -
✅ URL と対応する PHP ファイルがなくても 404 エラーにならないのか ✅ HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
• $_GET, $_POST を使わずにパラメータにアクセスできるのか • echo 等をしていないのに HTTP レスポンスを返せるのか 問い:FW を使って実装すると、どうして -
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 -
✅ URL と対応する PHP ファイルがなくても 404 エラーにならないのか ✅ HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
✅ $_GET, $_POST を使わずにパラメータにアクセスできるのか • echo 等をしていないのに HTTP レスポンスを返せるのか → FW 内部で($_GET, $_POST 等をもとに)Request オブジェクトに 詰めているから 問い:FW を使って実装すると、どうして -
✅ URL と対応する PHP ファイルがなくても 404 エラーにならないのか ✅ HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
✅ $_GET, $_POST を使わずにパラメータにアクセスできるのか • echo 等をしていないのに HTTP レスポンスを返せるのか 問い:FW を使って実装すると、どうして -
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 : : <!DOCTYPE html> <html> : : HTTP レスポンス 処理の結果に基づくレ スポンスを生成、送信 Response Application index.php -
✅ URL と対応する PHP ファイルがなくても 404 エラーにならないのか ✅ HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
✅ $_GET, $_POST を使わずにパラメータにアクセスできるのか ✅ echo 等をしていないのに HTTP レスポンスを返せるのか → Response オブジェクトに詰められた情報をもとに FW 内部で echo 等しているから 問い:FW を使って実装すると、どうして -
✅ URL と対応する PHP ファイルがなくても 404 エラーにならないのか ✅ HTTP リクエストを受け取ると指定のクラスのメソッドが呼ばれるのか
✅ $_GET, $_POST を使わずにパラメータにアクセスできるのか ✅ echo 等をしていないのに HTTP レスポンスを返せるのか → 各 FW の index.php を読み解いたことで答えが見えた! 問い:FW を使って実装すると、どうして -
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 : : <!DOCTYPE html> <html> : : 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 -
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 : : <!DOCTYPE html> <html> : : 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 -
• すべてのリクエストを index.php でハンドリングしている • 概ね共通した流れがある • FW を使っていてもいなくても、同じ PHP
が解釈できるソースコード • 根本のところでは同じような処理が書かれている まとめ -
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 目次(※時間は目安です)