1. Home
  2. PHP
  3. PSR
  4. 【PHP】PSR-17 HTTP Factories(HTTPファクトリ)

【PHP】PSR-17 HTTP Factories(HTTPファクトリ)

  • 公開日
  • 更新日
  • カテゴリ:PSR
  • タグ:PHP,PSR,PSR-7,PSR-17
【PHP】PSR-17 HTTP Factories(HTTPファクトリ)

PSR( PHP 標準勧告)


Contents

  1. 仕様
  2. インターフェース
    1. RequestFactoryInterface
    2. ResponseFactoryInterface
    3. ServerRequestFactoryInterface
    4. StreamFactoryInterface
    5. UploadedFileFactoryInterface
    6. UriFactoryInterface
  3. 概要
  4. 理由
  5. 範囲
    1. 範囲とするところ
    2. 範囲としないところ
  6. アプローチ
    1. 選択されたアプローチ
    2. 既存の実装
    3. 潜在的な問題
  7. 設計上の決定
    1. 複数のインターフェースが必要な理由
    2. ResponseFactoryInterface への $reasonPhrase 引数が存在するのはなぜか?
    3. ServerRequestFactoryInterface への $serverParams 引数が存在するのはなぜか?
    4. スーパーグローバルから ServerRequestInterface を作成するファクトリーがないのはなぜか?
    5. RequestFactoryInterface::createRequest が文字列 URI を許可するのはなぜか?

仕様

HTTP factory は、PSR-7 で定義されている新しい HTTP オブジェクトを作成するものです。

HTTP factory は、パッケージによって提供される各オブジェクトタイプに対してこれらのインターフェイスを実装する必要があります。

インターフェース

次のインターフェイスは、単一のクラス内または個別のクラス内で一緒に実装できます。

RequestFactoryInterface

クライアントリクエストを作成する機能を持ちます。

namespace Psr\Http\Message;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;

interface RequestFactoryInterface
{
    /**
     * 新しいリクエストを作成する
     *
     * @param string $method リクエストに関連付けられた HTTP メソッド
     * @param UriInterface|string $uri リクエストに関連付けられた URI
     */
    public function createRequest(string $method, $uri): RequestInterface;
}

ResponseFactoryInterface

レスポンスを作成する機能を持ちます。

namespace Psr\Http\Message;

use Psr\Http\Message\ResponseInterface;

interface ResponseFactoryInterface
{
    /**
     * 新しいレスポンスを作成する
     *
     * @param int $code HTTP ステータスコード デフォルト=200
     * @param string $reasonPhrase 
     *      生成された応答のステータスコードに関連付ける理由フレーズ
     *      何も提供されない場合、実装は HTTP 仕様で提案されているデフォルトを使用できる
     */
    public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface;
}

ServerRequestFactoryInterface

サーバーリクエストを作成する機能を持ちます。

<?php
namespace Psr\Http\Message;

use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;

interface ServerRequestFactoryInterface
{
    /**
     * サーバーリクエストを作成する
     *
     * サーバーパラメーターは指定されたとおりに正確に取得されることに注意してください。
     * 指定された値の解析/処理は実行されません。 特に、HTTP メソッドまたは URI を決定する試みは行われません。
     * これらは明示的に提供する必要があります。
     *
     * @param string $method リクエストに関連付けられた HTTP メソッド
     * @param UriInterface|string $uri リクエストに関連付けられた URI
     * @param array $serverParams サーバー API( SAPI)パラメーターの配列
     */
    public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface;
}

StreamFactoryInterface

リクエストとレスポンスのストリームを作成する機能を持ちます。

<?php
namespace Psr\Http\Message;

use Psr\Http\Message\StreamInterface;

interface StreamFactoryInterface
{
    /**
     * 文字列から新しいストリームを作成する
     *
     * ストリームは一時リソースで作成する必要があります( SHOULD)
     *
     * @param string $content ストリームに入力する文字列コンテンツ
     */
    public function createStream(string $content = ''): StreamInterface;

    /**
     * 既存のファイルからストリームを作成する
     *
     * ファイルは指定されたモードを使用して開く必要があります( MUST)
     * これは fopen関数でサポートされている任意のモードです
     *
     * $filename は fopen() でサポートされている任意の文字列( MAY)
     *
     * @param string $filename ストリームのベースとして使用するファイル名またはストリーム URI
     * @param string $mode 基になるファイル名/ストリームを開くモード
     *
     * @throws \RuntimeException ファイルを開けない場合
     * @throws \InvalidArgumentException モードが無効な場合
     */
    public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface;

    /**
     * 既存のリソースから新しいストリームを作成する
     *
     * ストリームは読み取り可能でなければならない。書き込み可能の場合もある。
     *
     * @param resource $resource ストリームのベースとして使用する PHP リソース
     */
    public function createStreamFromResource($resource): StreamInterface;
}

このインターフェイスの実装では、文字列からリソースを作成するときに一時的なストリームを使用する必要があります。推奨される方法は次のとおりです。

$resource = fopen('php://temp', 'r+');

UploadedFileFactoryInterface

アップロードされたファイルのストリームを作成する機能を持ちます。

<?php
namespace Psr\Http\Message;

use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;

interface UploadedFileFactoryInterface
{
    /**
     * 新しいアップロードファイルを作成する
     *
     * サイズが指定されていない場合はストリームのサイズを確認して決定される
     *
     * @link http://php.net/manual/features.file-upload.post-method.php
     * @link http://php.net/manual/features.file-upload.errors.php
     *
     * @param StreamInterface $stream アップロードされたファイルコンテンツを表す基になるストリーム
     *
     * @param int $size ファイルサイズ( bytes)
     * @param int $error PHP アップロードエラーコード
     * @param string $clientFilename クライアントが提供するファイル名 etc
     * @param string $clientMediaType クライアントが提供するメディアタイプ etc
     *
     * @throws \InvalidArgumentException ファイルリソースが読めない場合
     */
    public function createUploadedFile(
        StreamInterface $stream,
        int $size = null,
        int $error = \UPLOAD_ERR_OK,
        string $clientFilename = null,
        string $clientMediaType = null
    ): UploadedFileInterface;
}

UriFactoryInterface

クライアントおよびサーバーリクエストの URI を作成する機能を持ちます。

namespace Psr\Http\Message;

use Psr\Http\Message\UriInterface;

interface UriFactoryInterface
{
    /**
     * 新しい URI を作成する
     *
     * @param string $uri パースする URI
     *
     * @throws \InvalidArgumentException 指定された URI をパースできない場合
     */
    public function createUri(string $uri = '') : UriInterface;
}

概要

この PSR の目的は、PSR-7 オブジェクトを作成するメソッドを定義する Factory インターフェースを提供することです。

理由

PSR-7 の現在の仕様では、不変のコピーを作成することにより、ほとんどのオブジェクトを変更できます。ただし、2 つの注目すべき例外があります。

  • StreamInterface は、リソースが書き込み可能な場合にのみリソースへの書き込みを許可する、リソースに基づく可変オブジェクトである。
  • UploadedFileInterface は、変更機能を提供しないリソースに基づく読み取り専用オブジェクトである。

前者は、レスポンスを不完全な状態のままにする可能性があるため、PSR-7 ミドルウェアの重大な問題点です。レスポンス本文に添付されたストリームがシーク可能でも書き込み可能でもない場合、本文が既に書き込まれているエラー状態から回復する方法はありません。

このシナリオは、新しいストリームを作成する Factory を提供することで回避できます。 HTTP Object Factory の正式な標準がないため、開発者はこれらのオブジェクトを作成するために特定のベンダーの実装に依存する必要があります。

別の問題点は、再利用可能なミドルウェアまたはリクエストハンドラを作成するときです。そのような場合、パッケージの作成者はレスポンスを作成して返す必要があります。

ただし、個別のインスタンスを作成すると、パッケージが特定の PSR-7 実装に結び付けられます。これらのパッケージが代わりにリクエストファクトリインターフェイスに依存している場合、PSR-7 実装にとらわれないままにすることができます。

Factory の正式な標準を作成すると、開発者は特定の実装への依存を回避しながら、必要に応じて新しいオブジェクトを作成できます。

範囲

以下、PSR-17 が提供するところ、しないところ。

範囲とするところ

PSR-7 互換オブジェクトを作成するメソッドを定義するインターフェースのセットを提供します。

範囲としないところ

PSR-7 ファクトリーの特定の実装を提供すること。

アプローチ

以下、策定に関するアプローチについて

選択されたアプローチ

ファクトリメソッド定義は、インスタンス化後にオブジェクトを変更できるかどうかに基づいて選択されています。変更できないインターフェイスの場合、インスタンス化時にすべてのオブジェクトプロパティを定義する必要があります。

UriInterface の場合、便宜上完全な URI を渡すことができます。

使用されるメソッド名は競合しません。これにより、適切な場合に単一のクラスで複数のインターフェイスを実装できます。

既存の実装

PSR-7 の現在の実装はすべて、独自の要件を定義しています。ほとんどの場合、必要なパラメーターは、提案されているファクトリーメソッドと同じかそれよりも厳密ではありません。

Diactoros

Diactoros は、サーバー使用のための最初の HTTP メッセージ実装の 1 つであり、PSR-7 仕様と並行して開発されました。

Request
必須パラメーター、メソッド、および URI のデフォルトはヌルではありません。
Response
必須パラメーターはありません。ステータスコードのデフォルトは 200 です。
ServerRequest
必須パラメーターはありません。グローバルからリクエストを作成するための個別の ServerRequestFactory が含まれています。
Stream
body には **string|resource \$stream** が必要です。
UploadedFile
string|resource \$streamOrFile, int \$size, int \$errorStatus が必要です。エラーステータスは、PHP アップロード定数でなければなりません。
Uri
必須パラメーターはありません。文字列 \$uri はデフォルトでは空です。

全体的にこのアプローチは提案された Factory に非常に似ています。場合によっては、有効なオブジェクトには不要なオプションが Diactoros によって提供されます。

PSR-17 アップロードファイルファクトリでは、サイズとエラーステータスをオプションにすることができます。

Guzzle

Guzzle は、クライアントの使用に焦点を当てた HTTP メッセージ実装です。

Request
文字列 $method と string|UriInterface \$uri の両方が必要です。
Response
必須パラメーターはありません。ステータスコードのデフォルトは 200 です。
Stream
body にリソース \$stream が必要です。
Uri
必須パラメーターはありません。文字列 \$uri はデフォルトでは空です。

Guzzle には、クライアントの使用を対象とするため、ServerRequest または UploadedFile の実装は含まれていません。

全体的にこのアプローチは提案された( PSR-17) Factory と非常に似ています。注目すべき違いの 1 つは、Guzzle ではリソースでストリームを構築する必要があり、文字列を許可しないことです。

ただし、コンテンツの文字列からストリームを作成するヘルパー関数 stream_for と、ファイルパスからリソースを作成する try_fopen関数が含まれています。

Slim

Slim は、バージョン 3.0 以降の HTTP メッセージを利用する PHP マイクロフレームワークです。

Request
文字列 \$method / UriInterface \$uri / HeadersInterface \$headers / 配列 \$cookies / 配列 \$serverParams / StreamInterface \$body が必要です。
ファクトリメソッド createFromEnvironment(Environment \$environment)が含まれます。このメソッドはフレームワーク固有ですが、提案されている createServerRequestFromArray に類似しています。
Response
必須パラメーターはありません。ステータスコードのデフォルトは 200 です。
Stream
body にリソース \$stream が必要です。
UploadedFile
ソースファイルに文字列 \$file が必要です。
\$_FILES または同様の形式から UploadedFile インスタンスの配列を作成するためのファクトリメソッド parseUploadedFiles(array \$uploadedFiles)が含まれています。
フレームワーク固有で、parseUploadedFiles を使用するファクトリメソッド createFromEnvironment(Environment \$env)が含まれています。
Uri
文字列 \$scheme および、文字列 \$host が必要です。
文字列から Uri を作成するために使用できるファクトリメソッド createFromString(\$uri)が含まれています。

サーバーの使用のみを対象としているため、Slim には Request の実装は含まれていません。上記の実装は、ServerRequest の実装です。

比較されたアプローチの中で、Slim は提案された Factory と最も異なります。最も注目すべきは、リクエストの実装には HTTP メッセージ仕様で定義されていないフレームワーク固有の要件が含まれていることです。

潜在的な問題

この標準を確立する上で最も難しいタスクは、インターフェイスのメソッドシグネチャを定義することです。 PSR-7 には明示的に必要な値について明確な宣言がないため、読み取り専用のプロパティはインターフェイスにオブジェクトをコピーおよび変更するメソッドがあるかどうかに基づいて推測する必要があります。

設計上の決定

以下、設計上の決定について

複数のインターフェースが必要な理由

提案された各インターフェイスは、(主に) 1 つの PSR-7 タイプの生成を担当します。これにより、消費者は必要なものを正確に入力できます。レスポンスが必要な場合は、ResponseFactoryInterface で入力します。

URI が必要な場合は、UriFactoryInterface でタイプヒントします。このようにして、ユーザーは必要なものについてきめ細かく設定できます。

これによりアプリケーション開発者は、使用している PSR-7 実装に基づいて匿名の実装を提供し、特定のコンテキストに必要なインスタンスのみを生成できます。結果、定型文が削減されます。開発者は、未使用のメソッドのスタブを作成する必要はありません。

ResponseFactoryInterface への $reasonPhrase 引数が存在するのはなぜか?

ResponseFactoryInterface::createResponse() には、オプションの文字列引数 $reasonPhrase が含まれています。

PSR-7 仕様では、ステータスコードを提供すると同時に理由フレーズを提供できます。これは、2 つが関連するデータであるためです。

この仕様の作成者は、PSR-7 ResponseInterface::withStatus() 署名を模倣して、作成された応答に両方のデータセットが存在することを保証することを選択しました。

ServerRequestFactoryInterface への $serverParams 引数が存在するのはなぜか?

ServerRequestFactoryInterface::createServerRequest() には、オプションの $serverParams 配列引数が含まれます。

これが提供される理由は、サーバーのパラメーターが設定された状態でインスタンスを作成できるようにするためです。

ServerRequestInterface を介してアクセス可能なデータのうち、ミューテーターメソッドを持たないデータは、サーバーのパラメーターに対応するデータのみです。

そのためこのデータは最初の作成時に提供する必要があり、ファクトリメソッドへの引数として存在します。

スーパーグローバルから ServerRequestInterface を作成するファクトリーがないのはなぜか?

ServerRequestFactoryInterface の主な使用例は、既知のデータから新しい ServerRequestInterface インスタンスを作成することです。

スーパーグローバルからのデータのマーシャリングに関するソリューションは、次のことを前提としています。

  • スーパーグローバルが存在する
  • スーパーグローバルは特定の構造に従う

これらの 2 つの仮定は常に当てはまるわけではありません。 Swoole, ReactPHP などの非同期システムを使用する場合

  • _POST/_FILES などの標準的なスーパーグローバルを設定しません
  • $_SERVER に標準の SAPI( mod_php, mod-cgi, mod-fpm など)と同じ要素を追加しません

さらに、さまざまな標準 SAPI がさまざまな情報を $_SERVER に提供しリクエストヘッダーにアクセスするため、リクエストの初期設定にはさまざまなアプローチが必要です。

そのため、スーパーグローバルからインスタンスを作成するためのインターフェイスの設計はこの仕様の範囲外であり、主に実装固有のものである必要があります。

RequestFactoryInterface::createRequest が文字列 URI を許可するのはなぜか?

RequestFactoryInterface の主な使用例は、リクエストを作成することです。リクエストに必要な値は、リクエストメソッドと URI のみです。

RequestFactoryInterface::createRequest() は UriInterface インスタンスを受け入れることができますが、文字列も許可します。

理論的根拠は 2 つあります。まず、大部分のユースケースはリクエストインスタンスを作成することです。

URI インスタンスの作成は二次的です。 UriInterface を必要とするということは、ユーザーが UriFactoryInterface にアクセスする必要があるか、RequestFactoryInterface が UriFactoryInterface に厳しい要件を持つことを意味します。

1 つ目は Factory の消費者の使用を複雑にし、2 つ目は Factory の開発者または Factory インスタンスを作成する開発者の使用を複雑にします。

第二に、UriFactoryInterface は UriInterface インスタンスを作成する 1 つの方法を提供します。これは文字列 URI からのものです。

URI の作成が文字列に基づいている場合、RequestFactoryInterface が同じセマンティクスを許可しない理由はありません。さらに、この提案が開発された時点で調査されたすべての PSR-7 実装では、RequestInterface インスタンスを作成するときに、提供された UriInterface 実装に値が渡されるため、文字列 URI が許可されました。そのため、文字列を受け入れることは適切であり、既存のセマンティクスに従います。

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級