[PHP]stream_context_create完全解説|HTTPリクエスト・FTP・SSL設定まで実践サンプルで理解する

PHP

stream_context_createとは?

stream_context_create() は、PHPのストリーム操作に対してオプション(設定)やパラメータを付与するコンテキストリソースを生成する関数です。

PHPのストリームとは、ファイル・HTTP・FTP・SSL/TLSなど、あらゆる入出力をひとつの統一されたAPIで扱うための仕組みです。file_get_contents()fopen() など、多くの組み込み関数はストリームを内部で利用しており、stream_context_create() によって作成したコンテキストをこれらの関数に渡すことで、通信方法・タイムアウト・ヘッダー・SSLオプションなどを細かく制御できます。


基本構文

stream_context_create(array $options = [], array $params = []): resource
引数説明
$optionsarrayラッパー(http, ftp, sslなど)ごとのオプション連想配列
$paramsarrayコンテキスト自体のパラメータ(通知コールバックなど)
戻り値resource生成されたストリームコンテキストリソース

$options の基本構造

$options = [
    'ラッパー名' => [
        'オプション名' => 値,
        ...
    ],
];

基本的な使い方

<?php
// 最もシンプルな例:HTTPでPOSTリクエストを送る
$options = [
    'http' => [
        'method'  => 'POST',
        'header'  => "Content-Type: application/x-www-form-urlencoded\r\n",
        'content' => http_build_query(['name' => 'Taro', 'age' => 30]),
    ],
];

$context = stream_context_create($options);
$result  = file_get_contents('https://httpbin.org/post', false, $context);

echo $result;

stream_context_create() が返すのはリソース型の値で、file_get_contents()fopen() の第3引数に渡して使います。


実践クラスサンプル

サンプル1:HTTPクライアントクラス(GETリクエスト)

外部APIにGETリクエストを送り、JSONレスポンスを取得します。

<?php

class SimpleHttpClient
{
    private int $timeout;
    private string $userAgent;

    public function __construct(int $timeout = 10, string $userAgent = 'PHP-Client/1.0')
    {
        $this->timeout   = $timeout;
        $this->userAgent = $userAgent;
    }

    public function get(string $url, array $headers = []): array
    {
        $headerString = "User-Agent: {$this->userAgent}\r\n";
        foreach ($headers as $key => $value) {
            $headerString .= "{$key}: {$value}\r\n";
        }

        $options = [
            'http' => [
                'method'          => 'GET',
                'header'          => $headerString,
                'timeout'         => $this->timeout,
                'ignore_errors'   => true,  // エラー時もレスポンスボディを取得
            ],
        ];

        $context  = stream_context_create($options);
        $body     = file_get_contents($url, false, $context);
        $metaData = stream_get_meta_data($GLOBALS['_last_stream'] ?? false);

        // $http_response_header はfile_get_contents後に自動セットされるグローバル変数
        $statusLine = $http_response_header[0] ?? '';
        preg_match('/HTTP\/\d\.\d (\d{3})/', $statusLine, $matches);
        $statusCode = (int)($matches[1] ?? 0);

        return [
            'status' => $statusCode,
            'body'   => $body,
            'headers'=> $http_response_header ?? [],
        ];
    }
}

// 使用例
$client   = new SimpleHttpClient(timeout: 15);
$response = $client->get('https://jsonplaceholder.typicode.com/todos/1');

echo "ステータス: {$response['status']}\n";
$data = json_decode($response['body'], true);
echo "タイトル: {$data['title']}\n";

サンプル2:REST APIへのPOSTリクエスト(JSONボディ)

<?php

class JsonApiClient
{
    private string $baseUrl;
    private string $apiKey;

    public function __construct(string $baseUrl, string $apiKey)
    {
        $this->baseUrl = rtrim($baseUrl, '/');
        $this->apiKey  = $apiKey;
    }

    public function post(string $endpoint, array $data): ?array
    {
        $url     = $this->baseUrl . $endpoint;
        $body    = json_encode($data, JSON_UNESCAPED_UNICODE);

        $options = [
            'http' => [
                'method'        => 'POST',
                'header'        => implode("\r\n", [
                    'Content-Type: application/json',
                    'Accept: application/json',
                    "Authorization: Bearer {$this->apiKey}",
                    'Content-Length: ' . strlen($body),
                ]),
                'content'       => $body,
                'timeout'       => 30,
                'ignore_errors' => true,
            ],
        ];

        $context  = stream_context_create($options);
        $response = file_get_contents($url, false, $context);

        if ($response === false) {
            return null;
        }

        return json_decode($response, true);
    }
}

// 使用例
$client = new JsonApiClient('https://jsonplaceholder.typicode.com', 'dummy-token');
$result = $client->post('/posts', [
    'title'  => 'PHPストリームの解説',
    'body'   => 'stream_context_createの使い方',
    'userId' => 1,
]);

echo "作成されたID: {$result['id']}\n";
echo "タイトル: {$result['title']}\n";

サンプル3:SSL/TLS証明書の検証制御クラス

本番環境・開発環境でSSL検証を切り替えます。

<?php

class SecureHttpFetcher
{
    private bool $verifyPeer;
    private ?string $caFile;

    public function __construct(bool $verifyPeer = true, ?string $caFile = null)
    {
        $this->verifyPeer = $verifyPeer;
        $this->caFile     = $caFile;
    }

    public function fetch(string $url): string|false
    {
        $sslOptions = [
            'verify_peer'       => $this->verifyPeer,
            'verify_peer_name'  => $this->verifyPeer,
            'allow_self_signed' => !$this->verifyPeer,
        ];

        if ($this->caFile !== null) {
            $sslOptions['cafile'] = $this->caFile;
        }

        $options = [
            'http' => [
                'method'  => 'GET',
                'timeout' => 10,
            ],
            'ssl' => $sslOptions,
        ];

        $context = stream_context_create($options);
        return file_get_contents($url, false, $context);
    }
}

// 本番環境:SSL検証あり
$fetcher = new SecureHttpFetcher(verifyPeer: true);
$html    = $fetcher->fetch('https://www.php.net/');

// 開発環境(自己署名証明書など):SSL検証なし ※本番では使用しないこと
// $fetcher = new SecureHttpFetcher(verifyPeer: false);

echo mb_strlen($html) . " バイト取得\n";

⚠️ セキュリティ注意verify_peer: false は自己署名証明書のローカル開発環境専用です。本番環境では必ず true に設定してください。中間者攻撃(MITM)のリスクがあります。


サンプル4:FTPファイルアップロードクラス

<?php

class FtpStreamUploader
{
    private string $host;
    private string $user;
    private string $password;

    public function __construct(string $host, string $user, string $password)
    {
        $this->host     = $host;
        $this->user     = $user;
        $this->password = $password;
    }

    public function upload(string $localPath, string $remotePath): bool
    {
        if (!file_exists($localPath)) {
            throw new RuntimeException("ローカルファイルが見つかりません: {$localPath}");
        }

        $options = [
            'ftp' => [
                'overwrite' => true,  // 既存ファイルを上書き
            ],
        ];

        $context = stream_context_create($options);
        $url     = "ftp://{$this->user}:{$this->password}@{$this->host}/{$remotePath}";

        $localContent = file_get_contents($localPath);
        $result       = file_put_contents($url, $localContent, 0, $context);

        return $result !== false;
    }

    public function download(string $remotePath): string|false
    {
        $options = [
            'ftp' => [
                'overwrite' => false,
            ],
        ];

        $context = stream_context_create($options);
        $url     = "ftp://{$this->user}:{$this->password}@{$this->host}/{$remotePath}";

        return file_get_contents($url, false, $context);
    }
}

// 使用例
// $uploader = new FtpStreamUploader('ftp.example.com', 'ftpuser', 'secret');
// $uploader->upload('/tmp/report.csv', 'uploads/report.csv');

サンプル5:リダイレクト制御・プロキシ設定クラス

<?php

class ProxyAwareFetcher
{
    private ?string $proxyUrl;
    private int $maxRedirects;

    public function __construct(?string $proxyUrl = null, int $maxRedirects = 5)
    {
        $this->proxyUrl     = $proxyUrl;
        $this->maxRedirects = $maxRedirects;
    }

    public function fetch(string $url): string|false
    {
        $httpOptions = [
            'method'          => 'GET',
            'timeout'         => 20,
            'follow_location' => 1,               // リダイレクトを追跡
            'max_redirects'   => $this->maxRedirects,
            'ignore_errors'   => true,
        ];

        if ($this->proxyUrl !== null) {
            $httpOptions['proxy']           = $this->proxyUrl;
            $httpOptions['request_fulluri'] = true;  // プロキシ用にフルURIを使用
        }

        $options = ['http' => $httpOptions];
        $context = stream_context_create($options);

        return file_get_contents($url, false, $context);
    }

    public function getLastResponseHeaders(): array
    {
        return $http_response_header ?? [];
    }
}

// 使用例:プロキシなし、最大3回リダイレクトを許可
$fetcher = new ProxyAwareFetcher(maxRedirects: 3);
$content = $fetcher->fetch('https://httpbin.org/redirect/2');

// プロキシ経由の場合
// $fetcher = new ProxyAwareFetcher(proxyUrl: 'tcp://proxy.example.com:8080');

サンプル6:通知コールバック付きの進捗モニタリングクラス

stream_context_create() の第2引数 $params を使い、ストリームイベントをフックします。

<?php

class StreamProgressMonitor
{
    private array $log = [];

    public function download(string $url): string|false
    {
        $params = [
            'notification' => [$this, 'onNotification'],
        ];

        $options = [
            'http' => [
                'method'        => 'GET',
                'timeout'        => 30,
                'ignore_errors' => true,
            ],
        ];

        $context = stream_context_create($options, $params);
        $result  = file_get_contents($url, false, $context);

        return $result;
    }

    public function onNotification(
        int $notificationCode,
        int $severity,
        ?string $message,
        int $messageCode,
        int $bytesTransferred,
        int $bytesMax
    ): void {
        switch ($notificationCode) {
            case STREAM_NOTIFY_CONNECT:
                $this->log[] = "接続を確立しました";
                break;
            case STREAM_NOTIFY_AUTH_REQUIRED:
                $this->log[] = "認証が必要です";
                break;
            case STREAM_NOTIFY_MIME_TYPE_IS:
                $this->log[] = "MIMEタイプ: {$message}";
                break;
            case STREAM_NOTIFY_FILE_SIZE_IS:
                $this->log[] = "ファイルサイズ: {$bytesMax} バイト";
                break;
            case STREAM_NOTIFY_PROGRESS:
                if ($bytesMax > 0) {
                    $percent     = round($bytesTransferred / $bytesMax * 100, 1);
                    $this->log[] = "進捗: {$bytesTransferred}/{$bytesMax} バイト ({$percent}%)";
                }
                break;
            case STREAM_NOTIFY_COMPLETED:
                $this->log[] = "ダウンロード完了";
                break;
            case STREAM_NOTIFY_FAILURE:
                $this->log[] = "エラー: {$message} (コード: {$messageCode})";
                break;
        }
    }

    public function getLog(): array
    {
        return $this->log;
    }
}

// 使用例
$monitor = new StreamProgressMonitor();
$content = $monitor->download('https://www.php.net/');

foreach ($monitor->getLog() as $entry) {
    echo $entry . "\n";
}

echo "\n取得サイズ: " . strlen($content) . " バイト\n";

サンプル7:Webhookリスナーのテスト送信クラス

POSTで署名ヘッダー付きのWebhookペイロードを送信します。

<?php

class WebhookSender
{
    private string $secret;

    public function __construct(string $secret)
    {
        $this->secret = $secret;
    }

    public function send(string $url, array $payload): array
    {
        $body      = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
        $signature = 'sha256=' . hash_hmac('sha256', $body, $this->secret);
        $timestamp = time();

        $options = [
            'http' => [
                'method'        => 'POST',
                'header'        => implode("\r\n", [
                    'Content-Type: application/json',
                    "X-Hub-Signature-256: {$signature}",
                    "X-Timestamp: {$timestamp}",
                    'Content-Length: ' . strlen($body),
                ]),
                'content'       => $body,
                'timeout'       => 15,
                'ignore_errors' => true,
            ],
        ];

        $context    = stream_context_create($options);
        $response   = file_get_contents($url, false, $context);
        $statusLine = $http_response_header[0] ?? '';
        preg_match('/HTTP\/\d\.\d (\d{3})/', $statusLine, $matches);

        return [
            'status'    => (int)($matches[1] ?? 0),
            'response'  => $response,
            'signature' => $signature,
        ];
    }
}

// 使用例
$sender = new WebhookSender(secret: 'my-webhook-secret');
$result = $sender->send('https://httpbin.org/post', [
    'event'   => 'order.created',
    'orderId' => 12345,
    'amount'  => 9800,
]);

echo "ステータス: {$result['status']}\n";
echo "署名: {$result['signature']}\n";

関連関数との比較

関数用途
stream_context_create()コンテキストリソースを新規作成
stream_context_set_option()既存コンテキストにオプションを追加・変更
stream_context_get_options()コンテキストのオプションを取得
stream_context_get_params()コンテキストのパラメータを取得
stream_context_get_default()デフォルトコンテキストを取得
stream_context_set_default()デフォルトコンテキストを設定(全ストリーム関数に影響)

よく使うオプション一覧

httpラッパー

オプション説明
methodstringGET, POST, PUT, DELETE など
headerstringHTTPリクエストヘッダー(\r\n 区切り)
contentstringリクエストボディ
timeoutfloatタイムアウト秒数
follow_locationintリダイレクトを追跡するか(1=はい)
max_redirectsint最大リダイレクト回数(デフォルト20)
ignore_errorsbool4xx・5xxでもボディを返すか
proxystringプロキシURL(例:tcp://proxy:8080
user_agentstringUser-Agentヘッダー

sslラッパー

オプション説明
verify_peerboolサーバー証明書を検証するか
verify_peer_nameboolホスト名を検証するか
allow_self_signedbool自己署名証明書を許可するか
cafilestringCA証明書ファイルのパス
local_certstringクライアント証明書のパス

まとめ

項目内容
関数名stream_context_create()
分類ストリーム制御関数
PHP バージョンPHP 4.3.0以上
戻り値ストリームコンテキストリソース
主な用途HTTP/FTP/SSLの細かい通信制御

stream_context_create() を使いこなすことで、file_get_contents()fopen() だけでHTTPヘッダー操作・認証・SSL設定・プロキシ・タイムアウト制御まで幅広く対応できます。cURLを使わなくても高度な通信処理が実現でき、シンプルなスクリプトからクラスベースの設計まで柔軟に活用できる関数です。

外部APIとの連携やWebhook送信など、現代のWeb開発に欠かせない場面で活躍しますので、ぜひ積極的に活用してみてください。

タイトルとURLをコピーしました