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.ini で open_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/xml や application/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 を直接扱う必要はありません。