phpsoap-errorstroubleshooting

PHP SoapClient エラー: WSDL の読み込みに失敗する場合の解決方法

SOAPless Team10 min read

PHP の SoapClient を使って SOAP サービスに接続する際、次のようなエラーに遭遇したことはないでしょうか。

SOAP-ERROR: Parsing WSDL: Couldn't load from 'https://example.com/service?wsdl'

このエラーは「WSDL の URL からドキュメントを取得できなかった」ことを意味しますが、具体的な失敗理由が一切表示されません。ブラウザや SoapUI では問題なく開ける URL なのに、PHP からだけ読み込めないというケースも珍しくありません。

本記事では、このエラーの主な原因を網羅的に取り上げ、それぞれの対処法をコード付きで解説します。

原因 1: SSL 証明書の検証失敗

最も多い原因です。特に開発環境や自己署名証明書を使用するサーバーとの接続で頻繁に発生します。

PHP の SoapClient は内部的に stream wrapper を使って WSDL を取得しますが、デフォルトで SSL 証明書の検証が有効です。リモートサーバーの証明書が自己署名であったり、期限切れであったり、PHP が信頼しない CA による署名であった場合、取得がサイレントに失敗します。

診断方法:

# コマンドラインから証明書を確認
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -dates -issuer

# curl で SSL 情報を確認
curl -v https://example.com/service?wsdl 2>&1 | grep -E "(SSL|certificate|expire)"

修正 -- stream_context で SSL を設定:

$context = stream_context_create([
    'ssl' => [
        'verify_peer' => true,
        'verify_peer_name' => true,
        'cafile' => '/etc/ssl/certs/ca-certificates.crt',
    ],
]);

$client = new SoapClient('https://example.com/service?wsdl', [
    'stream_context' => $context,
]);

開発環境で自己署名証明書を使っている場合(本番環境では絶対に使わないでください):

$context = stream_context_create([
    'ssl' => [
        'verify_peer' => false,
        'verify_peer_name' => false,
        'allow_self_signed' => true,
    ],
]);

$client = new SoapClient('https://example.com/service?wsdl', [
    'stream_context' => $context,
]);

注意: Linux ディストリビューションにより CA バンドルファイルのパスが異なります。

  • Debian/Ubuntu: /etc/ssl/certs/ca-certificates.crt
  • CentOS/RHEL: /etc/pki/tls/certs/ca-bundle.crt
  • Alpine: /etc/ssl/cert.pem

原因 2: WSDL キャッシュの破損

PHP はデフォルトで WSDL ファイルをキャッシュします(soap.wsdl_cache_enabled で制御)。以前の取得が途中で失敗した場合や、サーバー側で WSDL が更新された場合、破損したキャッシュや古いコピーを読み込み続けることがあります。

診断方法:

# キャッシュディレクトリを確認
php -r "echo ini_get('soap.wsdl_cache_dir');"

# キャッシュファイルを一覧表示
ls -la /tmp/wsdl-*

修正 -- キャッシュをクリアまたは無効化:

// 方法 1: このクライアントのみキャッシュ無効化
$client = new SoapClient('https://example.com/service?wsdl', [
    'cache_wsdl' => WSDL_CACHE_NONE,
]);

// 方法 2: キャッシュファイルを全削除
$cacheDir = ini_get('soap.wsdl_cache_dir') ?: '/tmp';
array_map('unlink', glob($cacheDir . '/wsdl-*'));

php.ini で設定する場合:

; WSDL キャッシュを無効化(開発環境のみ)
soap.wsdl_cache_enabled = 0

; または TTL を短く設定(秒)
soap.wsdl_cache_ttl = 60

問題が解消されたら、必ずキャッシュを再度有効化してください。WSDL の解析はリクエストごとに実行すると大きなオーバーヘッドになります。

原因 3: open_basedir 制限

php.iniopen_basedir が設定されている場合、PHP の stream wrapper が WSDL キャッシュディレクトリへの書き込みやネットワークストリームへのアクセスをブロックすることがあります。

診断方法:

php -r "echo ini_get('open_basedir');"

修正:

; php.ini でキャッシュディレクトリを許可パスに追加
open_basedir = /var/www/html:/tmp:/etc/ssl/certs

; または許可パス内にカスタムキャッシュディレクトリを設定
soap.wsdl_cache_dir = /var/www/html/tmp/wsdl-cache

キャッシュディレクトリの作成と権限設定:

mkdir -p /var/www/html/tmp/wsdl-cache
chmod 755 /var/www/html/tmp/wsdl-cache
chown www-data:www-data /var/www/html/tmp/wsdl-cache

原因 4: DNS 解決の失敗

WSDL URL のホスト名を PHP サーバーが解決できない場合、取得がサイレントに失敗します。Docker コンテナや Kubernetes Pod、カスタムの /etc/resolv.conf を使用する環境でよく発生します。

診断方法:

# サーバーから DNS 解決を確認
nslookup example.com

# Docker コンテナ内から確認
docker exec -it your-container nslookup example.com

修正:

// 方法 1: WSDL をローカルにダウンロードしてファイルパスで指定
$wsdlContent = file_get_contents('https://example.com/service?wsdl', false, $sslContext);
$localPath = '/tmp/service.wsdl';
file_put_contents($localPath, $wsdlContent);

$client = new SoapClient($localPath, [
    'location' => 'https://example.com/service',
]);

原因 5: 接続タイムアウト

PHP のデフォルトソケットタイムアウト(default_socket_timeout)は通常 60 秒ですが、VPN 経由のアクセスやレスポンスの遅いエンタープライズ SOAP サービスでは不十分な場合があります。

修正:

$context = stream_context_create([
    'http' => [
        'timeout' => 120,
    ],
    'ssl' => [
        'verify_peer' => true,
    ],
]);

$client = new SoapClient('https://example.com/service?wsdl', [
    'stream_context' => $context,
    'connection_timeout' => 120,
]);

php.ini でグローバルに設定する場合:

default_socket_timeout = 120

原因 6: WSDL レスポンスが有効な XML ではない

一部のサーバーは特定の HTTP ヘッダーがないと WSDL を返さず、代わりに HTML のエラーページやリダイレクトレスポンスを返します。

診断方法:

# サーバーが実際に返す内容を確認
curl -s -D - https://example.com/service?wsdl | head -30

# Content-Type ヘッダーを確認
curl -s -I https://example.com/service?wsdl | grep -i content-type

text/xmlapplication/wsdl+xml ではなく text/html が返される場合、サーバーは WSDL を返していません。

修正:

$context = stream_context_create([
    'http' => [
        'header' => "Accept: text/xml, application/xml\r\n" .
                     "User-Agent: PHP-SoapClient\r\n",
    ],
]);

$client = new SoapClient('https://example.com/service?wsdl', [
    'stream_context' => $context,
]);

原因 7: PHP 拡張モジュールが未ロード

SoapClient クラスには soap 拡張が必要で、HTTPS 接続には openssl 拡張が必要です。

診断方法:

php -m | grep -E "(soap|openssl)"

修正:

# Debian/Ubuntu
sudo apt-get install php-soap php-xml
sudo phpenmod soap
sudo systemctl restart apache2

# Docker (Dockerfile)
# RUN docker-php-ext-install soap

デバッグチェックリスト

このエラーに遭遇した場合、次の手順で切り分けを行ってください。

1. 同じサーバーから curl で WSDL URL にアクセスできるか
   $ curl -s https://example.com/service?wsdl | head -5

2. レスポンスが有効な XML か(<?xml で始まるか)

3. PHP から file_get_contents で取得できるか
   $ php -r "var_dump(file_get_contents('https://example.com/service?wsdl'));"

4. WSDL キャッシュが古くないか
   $ ls -la /tmp/wsdl-* && rm /tmp/wsdl-*

5. soap と openssl 拡張がロードされているか
   $ php -m | grep -E 'soap|openssl'

6. open_basedir 制限がないか
   $ php -r "echo ini_get('open_basedir');"

堅牢な SoapClient 初期化の例

よくある失敗パターンに対応した SoapClient の初期化コードです。

function createSoapClient(string $wsdlUrl, array $options = []): SoapClient
{
    $defaults = [
        'cache_wsdl' => WSDL_CACHE_DISK,
        'connection_timeout' => 30,
        'trace' => true,
        'exceptions' => true,
    ];

    $sslContext = stream_context_create([
        'ssl' => [
            'verify_peer' => true,
            'verify_peer_name' => true,
            'cafile' => '/etc/ssl/certs/ca-certificates.crt',
            'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT |
                               STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT,
        ],
        'http' => [
            'timeout' => 30,
            'header' => "Accept: text/xml\r\nUser-Agent: PHP-SoapClient\r\n",
        ],
    ]);

    $defaults['stream_context'] = $sslContext;

    try {
        return new SoapClient($wsdlUrl, array_merge($defaults, $options));
    } catch (SoapFault $e) {
        throw new RuntimeException(
            "Failed to load WSDL from {$wsdlUrl}: {$e->getMessage()}",
            0,
            $e
        );
    }
}

SOAPless による解決

PHP の SoapClient と格闘するということは、stream_context の設定、SSL の構成、WSDL キャッシュの問題、php.ini の調整 — これらすべてを API コールを始める前に解決しなければならないということです。デプロイ環境ごとに異なる組み合わせの問題が発生します。

SOAPless はこの問題を根本的に解消します。SOAPless のダッシュボードに WSDL URL を貼り付けるだけで、標準的な REST JSON API が自動生成されます。PHP アプリケーションからはシンプルな HTTP リクエストを送るだけです。

// Before: SoapClient との格闘
$context = stream_context_create([/* SSL, ヘッダー, タイムアウト... */]);
$client = new SoapClient($wsdl, ['stream_context' => $context, /* その他オプション */]);
$result = $client->__soapCall('GetUser', [$params]);

// After: SOAPless 経由の標準 HTTP コール
$response = file_get_contents('https://api.soapless.com/v1/your-service/GetUser', false,
    stream_context_create([
        'http' => [
            'method' => 'POST',
            'header' => "Content-Type: application/json\r\nX-API-Key: your-key\r\n",
            'content' => json_encode(['userId' => '12345']),
        ],
    ])
);
$data = json_decode($response, true);

SOAPless が WSDL の解析、SSL 接続、SOAP エンベロープの構築、XML から JSON への変換をすべて処理するため、アプリケーションが SOAP を直接扱う必要はありません。