13.

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 も拡張。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. Fatal error: Maximum execution time of 30 seconds exceeded in...
  2. Fatal error: Uncaught Error: Cannot use object of type stdClass as array in ...
  3. Warning: Use of undefined constant ... - assumed '...' (this will throw an Error)
  4. ERROR: Call to undefined method Maatwebsite\Excel\Excel::load()
  5. Maximum execution time of 30 seconds exceeded
  6. Your requirements could not be resolved to an installable set of packages. ... To enable extensions, verify that they are enabled in your .ini files:
  7. could not find driver
  8. the requested PHP extension mbstring is missing from your system.
  9. the requested PHP extension dom is missing from your system.
  10. A non well formed numeric value encountered
  11. Warning: Cannot modify header information - headers already sent by ...
  12. php_network_getaddresses: getaddrinfo failed: Name or service not known
  13. XMLWriter::openUri(): Unable to resolve file path
  14. Object of class stdClass could not be converted to string
  15. Class 'Google_Service_Youtube' not found

最近更新/作成されたページ