PHP で XML の妥当性検証を行う

PHP を使用して、データの完全性を確実にし、XML スキーマに対して XML 文書の妥当性検証を行う

PHP で開発を行っていると、コードの中で XML (Extensible Markup Language) パーサーのサービスが必要となる場合がよくあります。また、XML 入力の妥当性検証が必要な場合もよくあります。幸いなことに、PHP ではそうしたことを容易に実現することができます。この記事では、PHP の中で XML 文書の妥当性検証をする方法と、妥当性検証が失敗した場合にその原因を判断する方法について説明します。

Brian M. Carey, Senior Systems Engineer, Triangle Information Solutions

Photo of Brian CareyBrian Carey は情報システムのコンサルタントであり、Java、Java Enterprise、PHP、Ajax、そしてそれらの関連技術を専門としています。Twitter で Brian Carey をフォローするためには http://twitter.com/brianmcarey にアクセスしてください。



2009年 11月 10日

なぜ XML の妥当性検証が必要なのか

XML はマークアップ言語であり、開発者は XML を使用することで独自のカスタム言語を作成することができます。このカスタム言語を使用すると、プラットフォームに依存しない形でデータを送信することができます (ただしプラットフォームに依存せずにデータを表示できるとは限りません)。その言語は、HTML (Hypertext Markup Language) によく似たマークアップ・タグを使って定義されます。

最近 XML が一般的に使われるようになった理由は、2 つの世界の良い点を XML が兼ね備えているからです。つまり XML は、人間にも、コンピューターにも、容易に理解することができます。XML 言語はツリーのような構造で表現され、要素や属性を使って重要なデータを表現します。要素名や属性名は通常、単純な英語で作成されます (そのため人間が読み取ることができます)。また XML 言語は非常に構造化されています (そのためコンピューターが構文解析することができます)。

ここで例えば、LuresXML という独自の XML 言語を作成するとします。LuresXML は単純に、皆さんの Web サイトで提供されている各種の商品 (lure) を定義する手段を指定します。まず、この XML 文書の概要を定義する XML スキーマを作成します (リスト 1)。

リスト 1. lures.xsd
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="lures">
 <xs:complexType> 
  <xs:sequence>
   <xs:element name="lure">
    <xs:complexType>
     <xs:sequence>
     <xs:element name="lureName" type="xs:string"/>
     <xs:element name="lureCompany" type="xs:string"/>
     <xs:element name="lureQuantity" type="xs:integer"/>
     </xs:sequence>
    </xs:complexType>
   </xs:element>
  </xs:sequence>
 </xs:complexType>
</xs:element>
</xs:schema>

これは非常に単純な例です (意図的に単純にしてあります)。ルート要素には lures という名前が付けられています。lures は、1 つ以上の lure 要素の親要素であり、それぞれの lure 要素は、次の 3 つの要素の親です。最初の要素は商品の名前 (lureName) です。2 番目の要素は、その商品を製造する会社の名前 (lureCompany) です。そして、最後の要素は、その量 (lureQuantity)、つまり皆さんの会社がその商品の在庫をいくつ持っているかを表します。これらの子要素のうちの最初の 2 つはストリングとして定義され、lureQuantity は整数として定義されています。

ここで、このスキーマに基づいて XML 文書 (インスタンスと呼ばれることがあります) を作成するとします。この XML 文書はリスト 2 のようになります。

リスト 2. lures.xml
<lures>
 <lure>
  <lureName>Silver Spoon</lureName>
  <lureCompany>Clark</lureCompany>
  <lureQuantity>Seven</lureQuantity>
 </lure>
</lures>

リスト 2 は、リスト 1 のスキーマによる単純な XML 文書インスタンスです。この場合、この文書インスタンスには 1 つの商品しか含まれていません。この商品の名前は Silver Spoon です。この商品のメーカーは Clark です。そして在庫の数は Seven です。

ここで質問です。リスト 2 の XML 文書が、リスト 1 で定義されたスキーマのインスタンスとして適切かどうかは、どうすればわかるのでしょう。実際、リスト 2 は適切ではありません (これも、意図的にそうしてあります)。

リスト 1 で定義された lureQuantity 要素に注目してください。この要素は xs:integer 型です。しかしリスト 2lureQuantity 要素には、整数ではない Seven という単語が含まれています。

XML の妥当性検証の目的は、まさにこうした種類の誤りを見つけることにあります。適切な妥当性検証を行うことによって、XML 文書を、その文書のスキーマで定義された規則に合致させることができるのです。

この例を使って続けると、リスト 2 の XML 文書を妥当性検証してみるとエラーが発生します。この文書をソフトウェア・アプリケーションの中で使用する前に、(Seven7 に変更して) このエラーを修正する必要があります。

エラーは、情報交換プロセスのできるだけ早い段階で見つける必要があるため、XML の妥当性検証は重要です。妥当性検証を行わないと、XML 文書を構文解析する際に無効なデータ型や想定外の構造が文書に含まれていた場合に、予期せぬ結果になる可能性があります。

PHP による単純な XML 構文解析

この記事では、PHP で XML 文書を構文解析する方法の概要を延々と説明することはしません。ただし PHP で XML 文書をロードするための基本については説明しておきます。

先ほどと同じく単純にするために、この場合もリスト 1 のスキーマとリスト 2 の XML 文書を使うことにします。リスト 3 は XML 文書をロードするための基本的な PHP コードを示しています。

リスト 3. testxml.php
<?php

$xml = new DOMDocument(); 
$xml->load('./lures.xml'); 

?>

この場合も、何も複雑なものはありません。DOMDocument クラスを使用して (lures.xml という) XML 文書をロードしています。この PHP コードが PHP サーバー上で動作するためには、lures.xml ファイルが実際の PHP コードと同じパス上に存在している必要があります。

この時点で、この XML 文書を構文解析したくなりますが、先ほど見たとおり、まずこの XML 文書の妥当性検証を行い、先ほどのスキーマで定義された言語仕様に確実に合致するようにするのが最も確実な方法です。

PHP による単純な XML 妥当性検証

引き続きリスト 3 の PHP コードを使い、単純な妥当性検証コードを少し挿入します (リスト 4)。

リスト 4. 妥当性検証コードを追加した testxml.php
<?php

$xml = new DOMDocument(); 
$xml->load('./lures.xml');

if (!$xml->schemaValidate('./lures.xsd')) { 
   echo "invalid<p/>";
} 
else { 
   echo "validated<p/>"; 
} 

?>

この場合も、リスト 2 のスキーマ・ファイルが PHP コードと同じディレクトリーになければならないことに注意してください。そうでないと、PHP はエラーを返します。

この新しいコードは XML をロードした DOMDocument オブジェクトに対して schemaValidate メソッドを呼び出します。このメソッドは、XML 文書の妥当性検証に使われる XML スキーマの場所という、1 つのパラメーターを受け付けます。またこのメソッドはブール値を返し、true は妥当性検証の成功、false は妥当性検証の失敗を示します。

ここで、リスト 3 の PHP コードを PHP サーバーにデプロイします。デプロイするコードは、リスト 3 とリスト 4 で指定されているとおり、testxml.php という名前にします。XML 文書 (リスト 2) と XML スキーマ (リスト 1) が両方とも同じディレクトリーにあることを確認します。この場合も、XML 文書と XML スキーマが同じディレクトリーにない場合には、PHP がエラーを返します。

ブラウザーで testxml.php にアクセスします。すると、「invalid (無効)」という単純な単語が画面上に表示されるはずです。

この結果が得られて良かった点は、スキーマによる妥当性検証が機能していることが確認されたことです。妥当性検証によってエラーが返されるはずであり、実際にエラーが返されました。

一方、良くなかった点は、XML 文書のどこにエラーがあるのかがわからないことです。もちろん、この記事の前の方で既にエラーの原因を説明しているので、読者の皆さんはわかっているかもしれませんが、ここでは原因を知らなかったことにしておきます。

エラーはどこに

繰り返しますが、問題は XML 文書のどこにエラーがあるのかわからないことです (わからないふりをしていてください)。できれば、エラーの修正を行えるように、PHP コードが実際にエラーの場所と性質をレポートしてくれると便利です (例えば「lureQuantity はストリングを受け付けません」のように表示してくれると助かります)。

検出されたエラーを表示するためには、libxml_get_errors() 関数を使うことができますが、残念なことに、この関数のテキスト出力では、XML 文書のどこでエラーが検出されたのか、具体的に特定されません。代わりにこの関数は、PHP コードのどこでエラーが検出されたのかを特定しますが、これではほとんど役に立たないため、別の方法を探す必要があります。

libxml_use_internal_errors() という別の関数があります。この関数は、唯一の引数としてブール値を受け付けます。この引数を true に設定すると、libxml によるエラー・レポートを無効にして独自の方法でエラーを取得することを意味します。これを使えばよいのです。

もちろん、これは少し余分にコードを作成する必要があるということです。その代わり、より具体的なエラー・レポートが得られます。長い目で見れば、この方法によって多くの時間を節約することができます。

リスト 5 は最終的なコードを示しています。

リスト 5. 最終的な testxml.php
<?php
function libxml_display_error($error) 
{ 
$return = "<br/>\n"; 
switch ($error->level) { 
case LIBXML_ERR_WARNING: 
$return .= "<b>Warning $error->code</b>: "; 
break; 
case LIBXML_ERR_ERROR: 
$return .= "<b>Error $error->code</b>: "; 
break; 
case LIBXML_ERR_FATAL: 
$return .= "<b>Fatal Error $error->code</b>: "; 
break; 
} 
$return .= trim($error->message); 
if ($error->file) { 
$return .= " in <b>$error->file</b>"; 
} 
$return .= " on line <b>$error->line</b>\n"; 

return $return; 
} 

function libxml_display_errors() { 
$errors = libxml_get_errors(); 
foreach ($errors as $error) { 
print libxml_display_error($error); 
} 
libxml_clear_errors(); 
} 

// Enable user error handling 
libxml_use_internal_errors(true); 

$xml = new DOMDocument(); 
$xml->load('./lures.xml'); 

if (!$xml->schemaValidate('./lures.xsd')) { 
print '<b>Errors Found!</b>'; 
libxml_display_errors(); 
} 
else { 
echo "validated<p/>"; 
} 

?>

まず、コード・リストの先頭にある関数に注目してください。この libxml_display_error() という関数は、唯一の引数として LibXMLError オブジェクトを受け付けます。この関数では、おなじみの switch 文を使ってエラー・レベルを判定し、そのレベルに合ったエラー・メッセージを作成します。エラー・レベルが判定されると、このコードは該当するエラー・レベルをレポートするストリングを生成します。

続いて、次の 2 つのことを行います。まず、エラー・オブジェクトを検証し、file プロパティーに値が含まれているかどうかを判断します。値が含まれている場合には、file のその値がエラー・メッセージに追加され、ファイルの場所がレポートされます。次に、line プロパティーをエラー・メッセージに追加します。これにより、ユーザーは XML ファイルのどこでエラーが発生したのかを正確に知ることができます。言うまでもないことですが、これはデバッグをする上で非常に重要です。

また、libxml_display_error() はエラーを説明するストリングを生成するのみであることにも注意してください。実際にエラーを画面に出力する作業は、呼び出し側 (この場合は libxml_display_errors()) に委ねられます。

その下にある関数が、上で触れた libxml_display_errors() であり、この関数は引数を取りません。この関数はまず、libxml_get_errors() を呼び出します。この呼び出しによって LibXMLError オブジェクトの配列が返されます。これらの LibXMLError オブジェクトは、XML 文書に対して schemaValidate() メソッドを呼び出した際に検出されたすべてのエラーを表しています。

次に、検出された各エラーに対して順番に処理を行い、各エラー・オブジェクトに対して libxml_display_error() 関数を呼び出します。この libxml_display_error() 関数によって返されるストリングは、すべて画面に出力されます。このエラー処理の大きなメリットは、すべてのエラーが 1 度に出力されることです。これはつまり、1 度だけコードを実行すれば、その特定の XML 文書に特有のエラーがすべて表示されるということです。

最後に、libxml_clear_errors() は、schemaValidate() メソッドによって最近検出されたエラーをすべてクリアーします。これはつまり、同じコード・シーケンスの中で schemaValidate() を再度実行すると、クリーンな状態で開始され、新しいエラーのみがレポートされるということです。これをせずに schemaValidate() を再度実行すると、最初に schemaValidate() を呼び出した際のエラーがすべて配列に残り、libxml_get_errors() によって返されます。当然ですが、これでは新たなエラーのレポートが必要な場合には問題です。

また、同じく重要な注意点として、リスト 5 のコードの一番下にある if-then 文を少し変更しています。この変更によって、エラーが検出されると「Errors Found! (エラー発見)」という文字が太字で出力されます。そして先ほどの libxml_display_errors() 関数が呼び出され、検出されたすべてのエラーを表示した後にエラー配列をクリアーします。ここでは、リスト 4 の場合のように単純に「invalid」を出力する代わりに、このようにしました。

2 番目のテスト

ここで、もう 1 度テストします。リスト 5 の PHP ファイルを PHP サーバーに移動します (ファイル名は同じままにしておきます (testxml.php))。先ほどと同じように、XSD (XML Schema Definition) ファイルと XML ファイルが両方とも PHP ファイルと同じディレクトリーにあることを確認します。ブラウザーで再度 testxml.php にアクセスすると、今度は以下のような出力が表示されるはずです。

Errors Found! (エラー検出!)
Error 1824: Element 'lureQuantity': 'Seven' is not a valid value of the atomic type 'xs:integer'. in /home/thehope1/public_html/example.xml on line 5

(エラー 1824: 要素「lureQuantity」:「Seven」はアトミック型「xs:integer」の値として有効ではありません。場所は /home/thehope1/public_html/example.xml の 5 行目です)

これは非常によく表現されていると思いませんか。このエラー・メッセージから、何行目でエラーが発生したのかがわかります。また、(皆さんがファイルの場所を知らなかった場合には) エラーが発生したファイルの場所もわかります。そして、なぜエラーが発生したのかも正確にわかります。この情報であれば十分役に立ちます。

問題を修正する

これで PHP ファイルの処理は終わり、XML 文書の中にある問題の修正を始めることができます。

レポートによれば、XML 文書の 5 行目でエラーが発生していたので、5 行目がどうなっているのかを調べるのが妥当な考えです。驚くには当たりませんが、5 行目は lureQuantity 要素がある場所です。そして、注意深く見るとわかるように、Seven がストリングであり、数字ではないことに「ハッ」と気付くというわけです。そこで、Seven というストリングを数字の 7 に変更します。最終的な XML 文書のコピーはリスト 6 のようになるはずです。

リスト 6. 更新された XML ファイル
<lures>
 <lure>
  <lureName>Silver Spoon</lureName>
  <lureCompany>Clark</lureCompany>
  <lureQuantity>7</lureQuantity>
 </lure>
</lures>

ここで、この新しい XML ファイルを PHP サーバーにコピーして、再度ブラウザーで testxml.php にアクセスします。すると、「validated (検証ずみ)」という 1 つの単語のみが表示されるはずです。これは 2 つの理由から素晴らしいことです。第 1 に、これは妥当性検証コードが適切に動作しているということです (なぜなら、XML 文書が実際に妥当だからです)。第 2 に、これはおそらく皆さんが初めて PHP で XML 文書の妥当性検証を行ったケースであるからです。おめでとうございます!

私がいつも助言しているように、今度は少し手直ししてみましょう。lures.xsd を変更し、もっと複雑なスキーマにします。そして lures.xml も変更し、そのスキーマのもっと複雑なインスタンスにします。これらのファイルを PHP サーバーにコピーし、もう 1 度、testxml.php を実行します。その結果どうなるかを調べてください。数ヶ所に誤りがある無効な文書を意図的に作成し、何が起こるかを調べてください。

また、修正を行う際に PHP コードをまったく変更する必要がないことにも注意してください。ファイル名 (lures.xml と lures.xsd) が同じであることを確認するだけで、これらのファイルを気のすむまで変更することができます。

まとめ

PHP を利用すると、XML 文書の妥当性検証を容易に行うことができます。DOMDocument クラスを schemaValidate() メソッドと組みあわせて使うことで、対応するスキーマの仕様に XML 文書を準拠させることができます。これはソフトウェア・アプリケーションのデータの完全性を確実にする上で重要なことです。

参考文献

学ぶために

  • PHP マニュアルで DOMDocument クラスの項目を調べてください。DOMDocument クラスは HTML または XML 文書全体を表し、文書ツリーのルートとして機能します。
  • ウィキペディアで XML の妥当性検証の項目を調べ、XML で作成された文書をチェックするプロセスについて学んでください。
  • Parsing XML using PHP」(Burhan Khalid、DevPapers、2003年12月) は、組み込みの PHP パーサーを使った XML 構文解析に関する優れたチュートリアルです (このパーサーは James Clark が作成した expat ライブラリーがベースとなっています)。
  • 3 回シリーズの記事、「PHP 開発者のための XML」(Cliff Morgan 著、developerWorks、2007年3月) を読み、XML と PHP の組み合わせについて学んでください。
    • 第 1 回 PHP での XML を 15 分で学ぶ」は PHP5 の XML 実装を紹介し、まだ PHP で XML を使うことに慣れていない人達のために、PHP 環境の DOM と SimpleXML を使用して、短く簡単な XML ファイルを読み取り、構文解析し、操作し、そして作成する方法について説明しています。
    • 第 2 回 高度な XML 構文解析方法」は、大きな XML 文書、あるいは複雑な XML 文書の構文解析に焦点を当てながら、PHP5 での XML 構文解析方法について説明しています。構文解析の拡張モジュールについて背景を少し紹介し、また具体的に、どのタイプの XML 文書にどの構文解析方法が最適なのか、その理由は何かについても説明しています。
    • 第 3 回 XML を読み取り、操作し、作成する高度な方法」は、PHP5 で XML を読み取り、操作し、作成するための、その他の方法について解説しています。また、今やおなじみとなった API である DOM と SimpleXML について、より高度な環境での使い方に焦点を当て、またこの 3 回シリーズでは初めて、XSL エクステンションについても説明しています。
  • チュートリアル「Validating XML」(Nicholas Chase 著、developerWorks、2003年8月) を読み、妥当性検証とは何か、また DTD (Document Type Definition) または XML Schema 文書に対して文書をチェックする方法について学んでください。
  • IBM developerWorks に用意された、XML 開発者のためのリソースの素晴らしい出発点、New to XML を訪れてください。
  • XML および関連技術において IBM 認定技術者になる方法については、IBM XML certification を参照してください。
  • developerWorks の XML ゾーンを XML の技術ライブラリーとして利用してください。広範な話題を網羅した技術記事やヒント、チュートリアル、技術標準、IBM Redbooks などが用意されています。
  • developerWorks の Technical events and webcasts で最新情報を入手してください。
  • developerWorks podcasts ではソフトウェア開発者のための興味深いインタビューや議論を聞くことができます。

製品や技術を入手するために

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Open source
ArticleID=454287
ArticleTitle=PHP で XML の妥当性検証を行う
publish-date=11102009