[PHP]stream_context_set_option完全解説|既存コンテキストへのオプション追加・変更を実践サンプルで理解する

PHP

stream_context_set_optionとは?

stream_context_set_option() は、既存のストリームコンテキストリソースに対してオプションを追加・変更する関数です。

stream_context_create() で作成済みのコンテキストに後からオプションを足したり、特定の値だけを上書きしたりする際に使います。コンテキストを作り直さずに部分的な変更ができるため、段階的に設定を組み立てる処理や、条件によってオプションを動的に変更するシナリオで力を発揮します。


基本構文

stream_context_set_option() には2つの呼び出し形式があります。

形式1:配列でまとめて設定

stream_context_set_option(resource $context, array $options): bool

形式2:ラッパー・キー・値を個別に指定

stream_context_set_option(
    resource $context,
    string   $wrapper,
    string   $option,
    mixed    $value
): bool
引数説明
$contextresource変更対象のコンテキストリソース
$optionsarrayラッパー名をキーとするネストした連想配列
$wrapperstringラッパー名('http''ssl''ftp' など)
$optionstringオプション名('timeout''method' など)
$valuemixed設定する値
戻り値bool成功時 true、失敗時 false

2形式の使い分け

<?php
$ctx = stream_context_create([]);

// 形式1:複数オプションをまとめて設定
stream_context_set_option($ctx, [
    'http' => ['method' => 'POST', 'timeout' => 30],
]);

// 形式2:1つのオプションだけピンポイントで変更
stream_context_set_option($ctx, 'http', 'timeout', 60);

基本的な使い方

<?php
// コンテキストを作成
$ctx = stream_context_create([
    'http' => ['method' => 'GET', 'timeout' => 10],
]);

// タイムアウトだけ変更(形式2)
stream_context_set_option($ctx, 'http', 'timeout', 30);

// SSLオプションを追加(形式1)
stream_context_set_option($ctx, [
    'ssl' => ['verify_peer' => true, 'verify_peer_name' => true],
]);

// 確認
$opts = stream_context_get_options($ctx);
echo $opts['http']['timeout'];    // 30
echo $opts['ssl']['verify_peer']; // 1

実践クラスサンプル

サンプル1:HTTPリクエストビルダークラス

メソッドチェーンでオプションを段階的に組み立て、最後に実行します。

<?php

class HttpRequestBuilder
{
    private resource $context;
    private string   $url = '';

    public function __construct()
    {
        $this->context = stream_context_create([]);
    }

    public function url(string $url): static
    {
        $this->url = $url;
        return $this;
    }

    public function method(string $method): static
    {
        stream_context_set_option($this->context, 'http', 'method', strtoupper($method));
        return $this;
    }

    public function timeout(int $seconds): static
    {
        stream_context_set_option($this->context, 'http', 'timeout', $seconds);
        return $this;
    }

    public function header(string $name, string $value): static
    {
        $opts    = stream_context_get_options($this->context);
        $current = $opts['http']['header'] ?? '';
        $current .= "{$name}: {$value}\r\n";
        stream_context_set_option($this->context, 'http', 'header', $current);
        return $this;
    }

    public function body(string $content, string $contentType = 'application/json'): static
    {
        stream_context_set_option($this->context, 'http', 'content', $content);
        $this->header('Content-Type',   $contentType);
        $this->header('Content-Length', (string)strlen($content));
        return $this;
    }

    public function bearerToken(string $token): static
    {
        return $this->header('Authorization', "Bearer {$token}");
    }

    public function ignoreSslErrors(): static
    {
        stream_context_set_option($this->context, [
            'ssl' => [
                'verify_peer'       => false,
                'verify_peer_name'  => false,
                'allow_self_signed' => true,
            ],
        ]);
        return $this;
    }

    public function send(): string|false
    {
        if (empty($this->url)) {
            throw new LogicException('URLが設定されていません');
        }
        // ignore_errors を自動付与してレスポンスボディを必ず取得
        stream_context_set_option($this->context, 'http', 'ignore_errors', true);
        return file_get_contents($this->url, false, $this->context);
    }

    public function getContext(): resource
    {
        return $this->context;
    }
}

// 使用例
$response = (new HttpRequestBuilder())
    ->url('https://jsonplaceholder.typicode.com/posts')
    ->method('POST')
    ->timeout(20)
    ->bearerToken('my-api-token')
    ->body(json_encode(['title' => 'テスト投稿', 'userId' => 1]))
    ->send();

$result = json_decode($response, true);
echo "作成ID: {$result['id']}\n";
echo "タイトル: {$result['title']}\n";

サンプル2:条件付きSSLオプション設定クラス

接続先ホストの種別に応じてSSL設定を動的に切り替えます。

<?php

class ConditionalSslConfigurator
{
    private array $trustedHosts;
    private array $selfSignedHosts;

    public function __construct(array $trustedHosts = [], array $selfSignedHosts = [])
    {
        $this->trustedHosts    = $trustedHosts;
        $this->selfSignedHosts = $selfSignedHosts;
    }

    public function configure(resource $context, string $url): void
    {
        $host = parse_url($url, PHP_URL_HOST) ?? '';

        if ($this->isInList($host, $this->selfSignedHosts)) {
            // 自己署名証明書のホスト:検証を緩和
            stream_context_set_option($context, [
                'ssl' => [
                    'verify_peer'       => true,
                    'verify_peer_name'  => false,
                    'allow_self_signed' => true,
                ],
            ]);
            echo "[SSL] {$host}: 自己署名を許可\n";

        } elseif ($this->isInList($host, $this->trustedHosts)) {
            // 信頼済みホスト:フル検証
            stream_context_set_option($context, [
                'ssl' => [
                    'verify_peer'       => true,
                    'verify_peer_name'  => true,
                    'allow_self_signed' => false,
                ],
            ]);
            echo "[SSL] {$host}: フル検証\n";

        } else {
            // デフォルト:標準検証
            stream_context_set_option($context, [
                'ssl' => ['verify_peer' => true, 'verify_peer_name' => true],
            ]);
            echo "[SSL] {$host}: 標準検証\n";
        }
    }

    private function isInList(string $host, array $list): bool
    {
        foreach ($list as $pattern) {
            if (fnmatch($pattern, $host)) {
                return true;
            }
        }
        return false;
    }
}

// 使用例
$configurator = new ConditionalSslConfigurator(
    trustedHosts:    ['*.trusted-partner.com', 'api.myservice.com'],
    selfSignedHosts: ['*.internal.local', 'dev.example.com']
);

$urls = [
    'https://api.myservice.com/data',
    'https://dev.example.com/api',
    'https://unknown-host.net/resource',
];

foreach ($urls as $url) {
    $ctx = stream_context_create([]);
    $configurator->configure($ctx, $url);
    $opts = stream_context_get_options($ctx);
    echo "  allow_self_signed: " . var_export($opts['ssl']['allow_self_signed'] ?? false, true) . "\n\n";
}

サンプル3:リクエストヘッダー管理クラス

ヘッダーの追加・削除・取得を安全に行います。

<?php

class ContextHeaderManager
{
    private resource $context;

    public function __construct(resource $context)
    {
        $this->context = $context;
    }

    public function set(string $name, string $value): void
    {
        $this->remove($name); // 重複を防ぐため先に削除
        $opts    = stream_context_get_options($this->context);
        $current = rtrim($opts['http']['header'] ?? '', "\r\n");
        $current .= ($current !== '' ? "\r\n" : '') . "{$name}: {$value}";
        stream_context_set_option($this->context, 'http', 'header', $current . "\r\n");
    }

    public function remove(string $name): void
    {
        $opts  = stream_context_get_options($this->context);
        $lines = explode("\r\n", trim($opts['http']['header'] ?? ''));
        $lines = array_filter($lines, function (string $line) use ($name): bool {
            return !str_starts_with(strtolower($line), strtolower($name) . ':');
        });
        stream_context_set_option(
            $this->context, 'http', 'header',
            implode("\r\n", array_values($lines)) . "\r\n"
        );
    }

    public function get(string $name): ?string
    {
        $opts  = stream_context_get_options($this->context);
        $lines = explode("\r\n", $opts['http']['header'] ?? '');
        foreach ($lines as $line) {
            if (str_starts_with(strtolower($line), strtolower($name) . ':')) {
                return trim(substr($line, strlen($name) + 1));
            }
        }
        return null;
    }

    public function all(): array
    {
        $opts   = stream_context_get_options($this->context);
        $lines  = explode("\r\n", trim($opts['http']['header'] ?? ''));
        $result = [];
        foreach (array_filter($lines) as $line) {
            [$k, $v]   = explode(':', $line, 2) + ['', ''];
            $result[trim($k)] = trim($v);
        }
        return $result;
    }
}

// 使用例
$ctx     = stream_context_create(['http' => ['method' => 'GET']]);
$manager = new ContextHeaderManager($ctx);

$manager->set('Content-Type',  'application/json');
$manager->set('Accept',        'application/json');
$manager->set('Authorization', 'Bearer old-token');

echo "Authorization: " . $manager->get('Authorization') . "\n";

// トークンを更新
$manager->set('Authorization', 'Bearer new-token');
echo "更新後Authorization: " . $manager->get('Authorization') . "\n";

// Acceptを削除
$manager->remove('Accept');

echo "\n全ヘッダー:\n";
foreach ($manager->all() as $name => $value) {
    echo "  {$name}: {$value}\n";
}

サンプル4:タイムアウトをリトライごとに段階的に延長するクラス

stream_context_set_option() でタイムアウトを動的に更新しながらリトライします。

<?php

class RetryableFetcher
{
    private resource $context;
    private int      $baseTimeout;
    private int      $maxAttempts;

    public function __construct(int $baseTimeout = 5, int $maxAttempts = 4)
    {
        $this->context     = stream_context_create(['http' => ['ignore_errors' => true]]);
        $this->baseTimeout = $baseTimeout;
        $this->maxAttempts = $maxAttempts;
    }

    public function fetch(string $url): string|false
    {
        for ($attempt = 0; $attempt < $this->maxAttempts; $attempt++) {
            $timeout = $this->baseTimeout * (2 ** $attempt); // 指数バックオフ

            // 既存コンテキストのタイムアウトだけ更新
            stream_context_set_option($this->context, 'http', 'timeout', $timeout);

            echo "試行 " . ($attempt + 1) . "/{$this->maxAttempts}"
               . "(タイムアウト: {$timeout}秒): {$url}\n";

            $result = @file_get_contents($url, false, $this->context);

            if ($result !== false) {
                echo "  → 成功\n";
                return $result;
            }

            echo "  → 失敗\n";

            if ($attempt < $this->maxAttempts - 1) {
                sleep(1);
            }
        }

        return false;
    }

    public function setHeader(string $name, string $value): void
    {
        $opts    = stream_context_get_options($this->context);
        $current = $opts['http']['header'] ?? '';
        stream_context_set_option($this->context, 'http', 'header', $current . "{$name}: {$value}\r\n");
    }
}

// 使用例
$fetcher = new RetryableFetcher(baseTimeout: 3, maxAttempts: 3);
$fetcher->setHeader('Accept', 'application/json');

$body = $fetcher->fetch('https://jsonplaceholder.typicode.com/posts/1');

if ($body !== false) {
    $data = json_decode($body, true);
    echo "\nタイトル: " . $data['title'] . "\n";
} else {
    echo "\nすべての試行が失敗しました\n";
}

サンプル5:FTPアップロードオプションを動的に切り替えるクラス

FTPコンテキストのオプションをファイルごとに調整します。

<?php

class FtpContextManager
{
    private resource $context;
    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;
        $this->context  = stream_context_create(['ftp' => ['overwrite' => false]]);
    }

    public function allowOverwrite(bool $allow = true): static
    {
        stream_context_set_option($this->context, 'ftp', 'overwrite', $allow);
        return $this;
    }

    public function usePassiveMode(): static
    {
        // FTPラッパーはパッシブモードがデフォルトだが、明示的に設定
        stream_context_set_option($this->context, 'ftp', 'overwrite', false);
        return $this;
    }

    public function upload(string $localContent, string $remotePath): bool
    {
        $url    = "ftp://{$this->user}:{$this->password}@{$this->host}/{$remotePath}";
        $result = file_put_contents($url, $localContent, 0, $this->context);
        return $result !== false;
    }

    public function getCurrentOptions(): array
    {
        return stream_context_get_options($this->context)['ftp'] ?? [];
    }
}

// 使用例
// $ftp = new FtpContextManager('ftp.example.com', 'user', 'pass');

// 上書きを許可してアップロード
// $ftp->allowOverwrite(true)->upload('CSV内容,データ', 'reports/daily.csv');

// 上書きを禁止に戻す
// $ftp->allowOverwrite(false);

// デモ:オプション変化の確認
$ctx = stream_context_create(['ftp' => ['overwrite' => false]]);
echo "初期 overwrite: " . var_export(stream_context_get_options($ctx)['ftp']['overwrite'], true) . "\n";

stream_context_set_option($ctx, 'ftp', 'overwrite', true);
echo "変更後 overwrite: " . var_export(stream_context_get_options($ctx)['ftp']['overwrite'], true) . "\n";

サンプル6:コンテキストのオプションをパイプライン的に適用するクラス

複数の「オプション適用プロセッサ」を順番に実行します。

<?php

interface ContextProcessor
{
    public function process(resource $context): void;
}

class TimeoutProcessor implements ContextProcessor
{
    public function __construct(private int $seconds) {}

    public function process(resource $context): void
    {
        stream_context_set_option($context, 'http', 'timeout', $this->seconds);
    }
}

class UserAgentProcessor implements ContextProcessor
{
    public function __construct(private string $userAgent) {}

    public function process(resource $context): void
    {
        stream_context_set_option($context, 'http', 'user_agent', $this->userAgent);
    }
}

class SslStrictProcessor implements ContextProcessor
{
    public function process(resource $context): void
    {
        stream_context_set_option($context, [
            'ssl' => [
                'verify_peer'      => true,
                'verify_peer_name' => true,
            ],
        ]);
    }
}

class AuthHeaderProcessor implements ContextProcessor
{
    public function __construct(private string $token) {}

    public function process(resource $context): void
    {
        $opts    = stream_context_get_options($context);
        $current = $opts['http']['header'] ?? '';
        stream_context_set_option(
            $context, 'http', 'header',
            $current . "Authorization: Bearer {$this->token}\r\n"
        );
    }
}

class ContextPipeline
{
    /** @var ContextProcessor[] */
    private array $processors = [];

    public function pipe(ContextProcessor $processor): static
    {
        $this->processors[] = $processor;
        return $this;
    }

    public function run(resource $context): void
    {
        foreach ($this->processors as $processor) {
            $processor->process($context);
        }
    }
}

// 使用例
$ctx = stream_context_create([]);

(new ContextPipeline())
    ->pipe(new TimeoutProcessor(20))
    ->pipe(new UserAgentProcessor('PipelineApp/1.0'))
    ->pipe(new SslStrictProcessor())
    ->pipe(new AuthHeaderProcessor('eyJhbGciOiJIUzI1NiJ9'))
    ->run($ctx);

$opts = stream_context_get_options($ctx);
echo "timeout: "    . $opts['http']['timeout']    . "\n";
echo "user_agent: " . $opts['http']['user_agent'] . "\n";
echo "ssl.verify: " . var_export($opts['ssl']['verify_peer'], true) . "\n";
echo "header:\n"    . $opts['http']['header'];

サンプル7:デフォルトコンテキストを取得して直接 set_option するクラス

stream_context_get_default() で取得したコンテキストに stream_context_set_option() を組み合わせ、グローバル設定を細かく変更します。

<?php

class GlobalContextTuner
{
    /**
     * デフォルトコンテキストの特定オプションだけをピンポイントで変更する
     */
    public static function tune(string $wrapper, string $key, mixed $value): void
    {
        $ctx = stream_context_get_default();
        stream_context_set_option($ctx, $wrapper, $key, $value);
    }

    /**
     * 複数のオプションをまとめてデフォルトコンテキストに適用する
     */
    public static function tuneMany(array $options): void
    {
        $ctx = stream_context_get_default();
        stream_context_set_option($ctx, $options);
    }

    /**
     * 現在のデフォルトコンテキストのオプションを表示する
     */
    public static function dump(): void
    {
        $opts = stream_context_get_options(stream_context_get_default());
        foreach ($opts as $wrapper => $settings) {
            echo "[{$wrapper}]\n";
            foreach ($settings as $k => $v) {
                echo "  {$k}: " . var_export($v, true) . "\n";
            }
        }
    }
}

// 使用例
// ベースとなるデフォルト設定
stream_context_set_default([
    'http' => ['timeout' => 10, 'user_agent' => 'Base/1.0', 'method' => 'GET'],
    'ssl'  => ['verify_peer' => true],
]);

echo "=== 初期設定 ===\n";
GlobalContextTuner::dump();

// タイムアウトだけ変更
GlobalContextTuner::tune('http', 'timeout', 30);

// SSLオプションをまとめて変更
GlobalContextTuner::tuneMany([
    'ssl' => ['verify_peer' => true, 'verify_peer_name' => true, 'allow_self_signed' => false],
]);

echo "\n=== 変更後 ===\n";
GlobalContextTuner::dump();

関連関数との比較

関数用途
stream_context_set_option()既存コンテキストにオプションを追加・変更
stream_context_create()オプション付きでコンテキストを新規作成
stream_context_get_options()コンテキストのオプションを取得
stream_context_set_default()デフォルトコンテキストを設定(引数必須)
stream_context_get_default()デフォルトコンテキストを取得(引数付きで設定も可)

注意点

  • stream_context_set_option() はオプションをマージします。渡したキー以外の既存オプションはそのまま保持されます。stream_context_set_default() のように全体を上書きする挙動とは異なるため注意してください。
  • header オプションは文字列として蓄積されます。同じヘッダー名を複数回 set_option で追加すると重複します。サンプル3のように「削除してから再追加」するパターンを使うと安全です。
  • デフォルトコンテキスト(stream_context_get_default() で取得したリソース)にも stream_context_set_option() は適用できます(サンプル7参照)。

まとめ

項目内容
関数名stream_context_set_option()
分類ストリームコンテキスト関数
PHP バージョンPHP 4.3.0以上
戻り値bool(成功: true / 失敗: false
主な用途既存コンテキストへのオプションの追加・部分変更

stream_context_set_option() は「作成済みのコンテキストを後から育てる」関数です。ビルダーパターンでメソッドチェーンしながらオプションを積み上げたり、リトライのたびにタイムアウトを変更したり、パイプライン的に複数のプロセッサを通したりと、柔軟な設計を可能にします。stream_context_create() で全オプションをあらかじめ決める方法と使い分けることで、より可読性の高いHTTP通信処理を実装できます。

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