この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:4
ページ更新者:パスタ★
更新日時:2026-06-11 07:12:00

タイトル: XMLWriter::openUri(): Unable to resolve file path
SEOタイトル: PHP XMLWriter::openUri Unable to resolve file path の原因と対処

この記事の要点
  • 原因: XMLWriter::openUri() に渡したパスが解決できない
  • 主な原因: 保存先ディレクトリが存在しない / 書き込み権限不足 / 相対パスの基準が想定と違う / open_basedir 制限
  • 対処: __DIR__ や Laravel の storage_path() で絶対パス化、事前に mkdir($dir, 0755, true)
  • 権限: Web サーバユーザ (www-data / apache / nginx) で書込権を持つこと。SELinux / open_basedir の制限も確認
  • 実用例: sitemap.xml 生成 / RSS 出力 / OPC (Office Open XML) パッケージ作成

エラー内容

Warning: XMLWriter::openUri(): Unable to resolve file path in /var/www/html/app/Sitemap.php on line 12

Warning: XMLWriter::openUri(/var/www/storage/sitemap.xml): failed to open stream:
  Permission denied in /var/www/html/...

Warning: XMLWriter::openUri(/some/path/file.xml): failed to open stream:
  No such file or directory in /var/www/html/...

原因の切り分け

原因確認対処
保存先ディレクトリが存在しないis_dir(dirname($path))mkdir(..., 0755, true)
書き込み権限不足is_writable(dirname($path))chmod / オーナー変更
相対パスの基準が想定外getcwd() で確認__DIR__ で絶対化
open_basedir 制限php -i \| grep open_basedir許可パス追加 / 保存先変更
SELinux コンテキストls -Zchcon -t httpd_sys_rw_content_t
ファイルシステム空きなしdf -hディスク容量確保
URL ラッパ未許可http:// / ftp:// 等allow_url_fopen 確認 / ローカルパスに

基本パターン (sitemap.xml 出力)

<?php
// 安全な保存パターン

$dir = __DIR__ . '/public/sitemaps';
$file = $dir . '/sitemap.xml';

// 1. ディレクトリを必ず作成(既存なら何もしない)
if (!is_dir($dir)) {
    mkdir($dir, 0755, true);    // ★ true で多階層作成
}

// 2. 書込権限を確認
if (!is_writable($dir)) {
    throw new RuntimeException("not writable: $dir");
}

// 3. XMLWriter で書き出し
$xw = new XMLWriter();
if (!$xw->openUri($file)) {
    throw new RuntimeException("openUri failed: $file");
}

$xw->setIndent(true);
$xw->startDocument('1.0', 'UTF-8');
$xw->startElement('urlset');
$xw->writeAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9');

foreach ($urls as $url) {
    $xw->startElement('url');
    $xw->writeElement('loc', $url['loc']);
    $xw->writeElement('lastmod', $url['lastmod']);
    $xw->writeElement('changefreq', $url['changefreq']);
    $xw->writeElement('priority', $url['priority']);
    $xw->endElement();
}

$xw->endElement();    // urlset
$xw->endDocument();
$xw->flush();

// 4. 出力確認
clearstatcache();
echo "wrote: $file (" . filesize($file) . " bytes)\n";

対処1: パスを絶対化

// ❌ 相対パスは getcwd() の値次第で不安定
$xw->openUri('sitemap.xml');    // cron / Web で getcwd() が違う

// ✅ __DIR__ で絶対化(このファイルがある場所基準)
$xw->openUri(__DIR__ . '/sitemap.xml');

// ✅ Laravel の場合
$xw->openUri(storage_path('app/sitemap.xml'));
$xw->openUri(public_path('sitemap.xml'));
$xw->openUri(base_path('public/sitemap.xml'));

// ✅ realpath で実体パスを取る(シンボリックリンク解消)
$path = realpath(__DIR__ . '/../public') . '/sitemap.xml';
$xw->openUri($path);

対処2: ディレクトリ自動生成

function ensureDir(string $path, int $mode = 0755): void {
    $dir = dirname($path);
    if (!is_dir($dir)) {
        if (!mkdir($dir, $mode, true) && !is_dir($dir)) {
            throw new RuntimeException("failed to create dir: $dir");
        }
    }
    if (!is_writable($dir)) {
        throw new RuntimeException("dir not writable: $dir");
    }
}

$file = '/var/www/storage/sitemaps/2026/sitemap.xml';
ensureDir($file);

$xw = new XMLWriter();
$xw->openUri($file);

対処3: open_basedir 制限の確認

// 現在の制限を確認
echo ini_get('open_basedir');
// → /var/www:/tmp のように出る場合、その外には書き込めない

// 例: open_basedir = /var/www
$xw->openUri('/tmp/foo.xml');    // ★ open_basedir 制限で失敗

// 対処1: 保存先を許可された場所に
$xw->openUri('/var/www/storage/foo.xml');    // ✅

// 対処2: php.ini で open_basedir 緩和
// open_basedir = /var/www:/var/log:/tmp
// → サーバ再起動 / FPM リロード

対処4: 権限とオーナー

# Apache / Nginx のユーザを確認
ps aux | grep -E 'apache|nginx|php-fpm' | head -3
# www-data / apache / nginx などが見つかる

# ディレクトリの所有とパーミッション
ls -la /var/www/storage
# drwxr-xr-x  www-data  www-data  ...

# Laravel 標準
sudo chown -R www-data:www-data storage bootstrap/cache
sudo chmod -R 775 storage bootstrap/cache

# SELinux (RHEL / CentOS) のコンテキスト
ls -Z storage
sudo chcon -R -t httpd_sys_rw_content_t storage
sudo restorecon -Rv storage

応用: 大規模 sitemap (チャンク分割)

sitemap.xml は1 ファイルあたり 5 万 URL / 50MB まで。それ以上は sitemap index で分割。

function writeSitemapChunk(array $urls, string $file): void {
    $xw = new XMLWriter();
    $xw->openUri($file);
    $xw->setIndent(true);
    $xw->startDocument('1.0', 'UTF-8');
    $xw->startElement('urlset');
    $xw->writeAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9');

    foreach ($urls as $u) {
        $xw->startElement('url');
        $xw->writeElement('loc', $u['loc']);
        $xw->writeElement('lastmod', $u['lastmod']);
        $xw->endElement();
    }

    $xw->endElement();
    $xw->endDocument();
    $xw->flush();
}

function writeSitemapIndex(array $sitemaps, string $file): void {
    $xw = new XMLWriter();
    $xw->openUri($file);
    $xw->setIndent(true);
    $xw->startDocument('1.0', 'UTF-8');
    $xw->startElement('sitemapindex');
    $xw->writeAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9');

    foreach ($sitemaps as $sm) {
        $xw->startElement('sitemap');
        $xw->writeElement('loc', $sm['loc']);
        $xw->writeElement('lastmod', $sm['lastmod']);
        $xw->endElement();
    }

    $xw->endElement();
    $xw->endDocument();
    $xw->flush();
}

// 50,000 URL ごとに分割
$chunks = array_chunk($allUrls, 50000);
$baseDir = public_path('sitemaps');
ensureDir($baseDir . '/dummy');

$sitemapEntries = [];
foreach ($chunks as $i => $chunk) {
    $name = "sitemap-{$i}.xml";
    writeSitemapChunk($chunk, "$baseDir/$name");
    $sitemapEntries[] = [
        'loc' => "https://example.com/sitemaps/$name",
        'lastmod' => date('Y-m-d'),
    ];
}

writeSitemapIndex($sitemapEntries, public_path('sitemap.xml'));

openMemory / openUri / outputMemory

メソッド用途
openUri($filename)ファイル / ストリームに直接書込(メモリ効率良)
openMemory()メモリ上に蓄積、後で outputMemory() で取得
outputMemory(bool $flush)蓄積した XML を返す(true でリセット)
flush(bool $empty)バッファを書き出す
// メモリで作って、後で出力(小規模・テスト向き)
$xw = new XMLWriter();
$xw->openMemory();
$xw->startDocument('1.0', 'UTF-8');
$xw->startElement('root');
$xw->writeElement('hello', 'world');
$xw->endElement();
$xw->endDocument();

$xml = $xw->outputMemory();    // ★ 文字列で取得
echo $xml;
// ファイル書込
file_put_contents('/tmp/out.xml', $xml);

// HTTP レスポンスとして返す
header('Content-Type: application/xml; charset=UTF-8');
echo $xml;

PHP 7.4 → 8.x の変更

  • PHP 8.0 から多くの関数で「失敗時に warning + false」 から「false 返却のみ」に変更(XMLWriter 系も含む)
  • PHP 8.0 から libxml_use_internal_errors(true) を使うとパース系エラーが例外でなくバッファに溜まる
  • 厳密モード declare(strict_types=1) で第 1 引数が string でないとき TypeError

FAQ

Q: PHP 内蔵サーバ (php -S) で動くのに本番で失敗する
A: 実行ユーザの差です。php -S は自分のユーザ権限ですが、本番は www-data / apache 等。chownchmod を確認。

Q: Docker 内で書込失敗
A: ホストとコンテナで UID/GID が違うとマウントしたボリュームに書けません。Dockerfile で USER 1000:1000 を合わせる、または docker run -u $(id -u):$(id -g)

Q: 巨大 XML でメモリ不足
A: openMemory() でなく openUri() で直接ファイル書込 + flush() をループ内で呼ぶ。memory_limit も拡張。