Microsoft Office 2003 (Microsoft Windows® 版) によって、Microsoft 以外のエンジニアの人達にも、これまで実現できなかったまったく新しい可能性が開けてきました。もちろん、Microsoft Office 2003 には、いつも通りの新しい機能がありますが、新たに大きく進化した点は、XML ファイル・フォーマットが追加されたことです。Microsoft Office 2003 では、Microsoft Excel のスプレッドシートを XML として保存することができ、そのファイルをバイナリー・ファイルとまったく同じように使用することができます。同じことは Microsoft Word にも言えます。
なぜ XML ファイル・フォーマットがそれほど重要なのかというと、これまで長い間、Excel や Word の真の強力さはバイナリー・ファイル・フォーマットの中に閉じ込められており、それを利用するためには複雑な変換プログラムが必要だったからです。しかし Microsoft Office 2003 では、Excel や Word のファイルの読み書きを、PHP プログラミング言語に組み込まれた XSLT (Extensible Stylesheet Language Transformation) や XML DOM (Document Object Model) 関数などの XML ツールを使って行うことができます。
この記事では、PHP による Web アプリケーションを作成する方法を説明します。この Web アプリケーションは XML フォーマットを使用して Excel スプレッドシートからデータベースにデータを読み込み、またデータベース・テーブルの内容を Excel スプレッドシートにエクスポートします。
この記事では、単純な Web アプリケーションを用いて Excel の XML メカニズムがはっきりわかるようにします。このアプリケーションでは、名前と E メール・アドレスで構成されるテーブルを扱います。
MySQL の構文によるスキーマのコードをリスト 1 に示します。
リスト 1. データベースに対して実行される SQL
DROP TABLE IF EXISTS names; CREATE TABLE names ( id INT NOT NULL AUTO_INCREMENT, first TEXT, middle TEXT, last TEXT, email TEXT, PRIMARY KEY( id ) ); |
このスキーマ・ファイルは、1 つのテーブルからなるデータベースになっており、この、names という名前のテーブルには、5つのフィールドがあります。自動的にインクリメントされる ID フィールドが先頭にあり、続いて first (名前)、middle (ミドルネーム)、last (苗字)、email (E メール) のフィールドがあります。
このデータベースをセットアップするために、まずは Mysqladmin コマンドライン・ツールを使ってデータベースを作成します (mysqladmin --user=root create names)。次にスキーマ・ファイルからデータベースをロードします (mysql --user=root names < schema.sql)。ユーザー名とパスワードによる認証に何を使用するかはインストールに依存しますが、考え方は同じです。まずデータベースを作成します。次に、SQL ファイルを使用して、必要なフィールドを持つテーブルを作成します。
次のステップではインポート用のデータを作成します。まず新しい Excel ファイルを作成します。最初のワークブックでは、各列の先頭行に First、Middle、Last、Email という列名を指定します。続いて、このリストに数行のデータを追加します (図 1)。
図 1. インポート用のデータ
リストには好きなだけデータを追加することができ、またフィールドの値も好きなように変更することができます。この記事で使用する PHP のインポート・スクリプトは、最初の行を見出し行とみなして無条件に無視します。本番のアプリケーションではおそらく、見出し行を読み取って解析する必要があります。それによって、どのフィールドがどの列にあるのかを判断し、必要に応じてインポート・ロジックを変更します。
最後のステップでは、このファイルを XML として保存します。そのために、「File (ファイル)」 > 「Save As (名前を付けて保存)」の順にクリックし、「Save As (名前を付けて保存)」のウィンドウで、「Save as type (ファイルの種類)」のドロップダウン・リストから「XML Spreadsheet (XML スプレッドシート)」を選択します (図 2)
図 2. XML スプレッドシートとしてファイルを保存する
XML ファイルが用意できると、PHP アプリケーションを作り始めることができます。
データをインポートするシステムは簡単に起動することができ、システムが起動されると入力 Excel XML ファイルを指定するページが表示されます (図 3)。
図 3. 入力 Excel XML ファイルを指定する
このページのロジックはリスト 2 に示すように単純です。
リスト 2. アップロード・ページのコード
<html> <body> <form enctype="multipart/form-data" action="import.php" method="post"> <input type="hidden" name="MAX_FILE_SIZE" value="2000000" /> <table width="600"> <tr> <td>Names file:</td> <td><input type="file" name="file" /></td> <td><input type="submit" value="Upload" /></td> </tr> </table> </form> </body> </html> |
ここではファイルに .php という拡張子を付けましたが、実際にはこのファイルは PHP ではありません。このファイルは単なる HTML ファイルであり、この HTML ファイルによってユーザーがファイルを指定すると、そのファイルが import.php ページに送信されます。実際の動作は import.php ページで行われます。
少し理解しやすくするために、ここでは import.php ページを 2 つのフェーズに分けて作成しました。第 1 のフェーズでは、単純に XML データの構文解析を行い、そのデータをテーブルとして出力します。第 2 のフェーズでは、データベースにレコードを挿入するロジックを追加しています。
リスト 3 は Excel 2003 の XML ファイルの例を示しています。
リスト 3. Excel 2003 の XML ファイルの例
<?xml version="1.0"?> <?mso-application progid="Excel.Sheet"?> <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40"> <DocumentProperties xmlns="urn:schemas-microsoft-com:office:office"> <Author>Jack Herrington</Author> <LastAuthor>Jack Herrington</LastAuthor> <Created>2005-08-02T04:06:26Z</Created> <LastSaved>2005-08-02T04:30:11Z</LastSaved> <Company>My Software Company, Inc.</Company> <Version>11.6360</Version> </DocumentProperties> <ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel"> <WindowHeight>8535</WindowHeight> <WindowWidth>12345</WindowWidth> <WindowTopX>480</WindowTopX> <WindowTopY>90</WindowTopY> <ProtectStructure>False</ProtectStructure> <ProtectWindows>False</ProtectWindows> </ExcelWorkbook> <Styles> <Style ss:ID="Default" ss:Name="Normal"> <Alignment ss:Vertical="Bottom"/> <Borders/> <Font/> <Interior/> <NumberFormat/> <Protection/> </Style> <Style ss:ID="s21" ss:Name="Hyperlink"> <Font ss:Color="#0000FF" ss:Underline="Single"/> </Style> <Style ss:ID="s23"> <Font x:Family="Swiss" ss:Bold="1"/> </Style> </Styles> <Worksheet ss:Name="Sheet1"> <Table ss:ExpandedColumnCount="4" ss:ExpandedRowCount="5" x:FullColumns="1" x:FullRows="1"> <Column ss:Index="4" ss:AutoFitWidth="0" ss:Width="154.5"/> <Row ss:StyleID="s23"> <Cell><Data ss:Type="String">First</Data></Cell> <Cell><Data ss:Type="String">Middle</Data></Cell> <Cell><Data ss:Type="String">Last</Data></Cell> <Cell><Data ss:Type="String">Email</Data></Cell> </Row> <Row> <Cell><Data ss:Type="String">Molly</Data></Cell> <Cell ss:Index="3"><Data ss:Type="String">Katzen</Data></Cell> <Cell ss:StyleID="s21" ss:HRef="mailto:molly@katzen.com"> <Data ss:Type="String">molly@katzen.com</Data></Cell> </Row> ... </Table> <WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel"> <Print> <ValidPrinterInfo/> <HorizontalResolution>300</HorizontalResolution> <VerticalResolution>300</VerticalResolution> </Print> <Selected/> <Panes> <Pane> <Number>3</Number> <ActiveRow>5</ActiveRow> </Pane> </Panes> <ProtectObjects>False</ProtectObjects> <ProtectScenarios>False</ProtectScenarios> </WorksheetOptions> </Worksheet> <Worksheet ss:Name="Sheet2"> <WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel"> <ProtectObjects>False</ProtectObjects> <ProtectScenarios>False</ProtectScenarios> </WorksheetOptions> </Worksheet> <Worksheet ss:Name="Sheet3"> <WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel"> <ProtectObjects>False</ProtectObjects> <ProtectScenarios>False</ProtectScenarios> </WorksheetOptions> </Worksheet> </Workbook> |
ここでは途中の 2、3 行を省略してありますが、それを除けば、このファイルは Excel からの出力そのままです。このファイルは比較的すっきりとした XML です。この文書の先頭にあるヘッダー部分には文書の説明と作成者の名前が含まれていますが、この部分に視覚的な情報やスタイルなどが記述されていることに注意してください。それに続いて、メインの Workbook オブジェクト内の一連のワークシートに含まれる形でデータが記述されています。
最初の Worksheet オブジェクトには実際のデータが含まれています。このオブジェクトの中で、データは Table タグの中の一連の Row タグと Cell タグの中にあります。各 Cell タグには、そのセルのデータを保持する Data タグが関連付けられています。ここでは、データには必ず String 型のフォーマットが指定されます。
新しい文書を作成する場合、Excel はデフォルトで、Sheet1、Sheet2、Sheet3 という 3 つのシートを作成します。ここでは 2 番目と 3 番目のシートを削除しなかったため、文書の最後にこの 2 つの空のシートが含まれています。
リスト 4 に、import.php スクリプトの最初のバージョンを示します。リスト 4. インポート・スクリプトの最初のバージョン
<?php
$data = array();
function add_person( $first, $middle, $last, $email )
{
global $data;
$data []= array(
'first' => $first,
'middle' => $middle,
'last' => $last,
'email' => $email
);
}
if ( $_FILES['file']['tmp_name'] )
{
$dom = DOMDocument::load( $_FILES['file']['tmp_name'] );
$rows = $dom->getElementsByTagName( 'Row' );
$first_row = true;
foreach ($rows as $row)
{
if ( !$first_row )
{
$first = "";
$middle = "";
$last = "";
$email = "";
$index = 1;
$cells = $row->getElementsByTagName( 'Cell' );
foreach( $cells as $cell )
{
$ind = $cell->getAttribute( 'Index' );
if ( $ind != null ) $index = $ind;
if ( $index == 1 ) $first = $cell->nodeValue;
if ( $index == 2 ) $middle = $cell->nodeValue;
if ( $index == 3 ) $last = $cell->nodeValue;
if ( $index == 4 ) $email = $cell->nodeValue;
$index += 1;
}
add_person( $first, $middle, $last, $email );
}
$first_row = false;
}
}
?>
<html>
<body>
<table>
<tr>
<th>First</th>
<th>Middle</th>
<th>Last</th>
<th>Email</th>
</tr>
<?php foreach( $data as $row ) { ?>
<tr>
<td><?php echo( $row['first'] ); ?></td>
<td><?php echo( $row['middle'] ); ?></td>
<td><?php echo( $row['last'] ); ?></td>
<td><?php echo( $row['email'] ); ?></td>
</tr>
<?php } ?>
</table>
</body>
</html>
|
このスクリプトはまず、アップロードされた一時ファイルを読み込んで DOMDocument オブジェクトに格納します。次に、スクリプトは各 Row タグを見つけます。最初の行は、$first_row 変数に関連付けられたロジックを使って無視されます。最初の行の後、内部ループにより、各行の各 Cell タグが解析されます。
次の巧妙な処理では、現在どの列を扱っているのかを判断します。この XML を見るとわかるように、Cell タグには行や列の番号が指定されていないため、このスクリプトによって行や列の番号を追跡する必要があります。実際にはただ単に追跡するよりも、もう少し複雑です。実は Cell タグには ss:Index 属性があるため、そのセルの行に空白の列がある場合でも、そのセルがどの列にあるのかを ss:Index 属性を使って判断することができるのです。これを利用して、getAttribute('index') というコードによって、この ss:Index 属性を探します。
インデックスの値が判断できれば、あとのコードは単純です。セルの値を、そのセルのフィールドに関連付けられたローカル値へと格納します。そして、行の最後まで来たら、add_person 関数を呼び出し、その行に記載されている人のデータをデータ・セットに追加します。
ページの最後で、この PHP コードはおなじみの PHP のメカニズムを使用して、見つかったデータを HTML の表に出力します (図 4)。
図 4. データを HTML の表に出力する
次のステップでは、このデータをデータベースにロードします。
このスクリプトによって PHP のデータ構造で行のデータが得られたら、そのデータをデータベースに追加する必要があります。そのために、ここでは Pear DB モジュールを使用するコードを追加しました (リスト 5)。
リスト 5. インポート・スクリプトの 2 番目のバージョン
<?php
require_once( "db.php" );
$data = array();
$db =& DB::connect("mysql://root@localhost/names", array());
if (PEAR::isError($db)) { die($db->getMessage()); }
function add_person( $first, $middle, $last, $email )
{
global $data, $db;
$sth = $db->prepare( "INSERT INTO names VALUES( 0, ?, ?, ?, ? )" );
$db->execute( $sth, array( $first, $middle, $last, $email ) );
$data []= array(
'first' => $first,
'middle' => $middle,
'last' => $last,
'email' => $email
);
}
if ( $_FILES['file']['tmp_name'] )
{
$dom = DOMDocument::load( $_FILES['file']['tmp_name'] );
$rows = $dom->getElementsByTagName( 'Row' );
$first_row = true;
foreach ($rows as $row)
{
if ( !$first_row )
{
$first = "";
$middle = "";
$last = "";
$email = "";
$index = 1;
$cells = $row->getElementsByTagName( 'Cell' );
foreach( $cells as $cell )
{
$ind = $cell->getAttribute( 'Index' );
if ( $ind != null ) $index = $ind;
if ( $index == 1 ) $first = $cell->nodeValue;
if ( $index == 2 ) $middle = $cell->nodeValue;
if ( $index == 3 ) $last = $cell->nodeValue;
if ( $index == 4 ) $email = $cell->nodeValue;
$index += 1;
}
add_person( $first, $middle, $last, $email );
}
$first_row = false;
}
}
?>
<html>
<body>
These records have been added to the database:
<table>
<tr>
<th>First</th>
<th>Middle</th>
<th>Last</th>
<th>Email</th>
</tr>
<?php foreach( $data as $row ) { ?>
<tr>
<td><?php echo( $row['first'] ); ?></td><
<td><?php echo( $row['middle'] ); ?></td><
<td><?php echo( $row['last'] ); ?></td><
<td><?php echo( $row['email'] ); ?></td><
</tr>
<?php } ?>
</table>
Click <a href="list.php">here</a> for the entire table.
</body>
</html>
|
図 5 は出力を Firefox で表示した様子を示しています。
図 5. データベース
この画面は見栄えがよくありませんが、ここではそれは重要ではありません。ここでのポイントは、データベース・オブジェクトの prepare 文と execute 文を使うことでデータベースにデータを追加することができる、という点です。それを証明するために、ここでは list.php という別のページを作成し、このページにデータベースのデータを表示しています (リスト 6)。
リスト 6. List.php
<?php
// Install the DB module using 'pear install DB'
require_once( "db.php" );
$data = array();
$db =& DB::connect("mysql://root@localhost/names", array());
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query( "SELECT * FROM names ORDER BY last" );
?>
<html>
<body>
<table>
<tr>
<th>ID</th>
<th>First</th>
<th>Middle</th>
<th>Last</th>
<th>Email</th>
</tr>
<?php while( $res->fetchInto( $row,
DB_FETCHMODE_ASSOC ) ) { ?>
<tr>
<td><?php echo( $row['id'] ); ?></td>
<td><?php echo( $row['first'] ); ?></td>
<td><?php echo( $row['middle'] ); ?></td>
<td><?php echo( $row['last'] ); ?></td>
<td><?php echo( $row['email'] ); ?></td>
</tr>
<?php } ?>
</table>
Download as an
<a href="listxl.php">Excel spreadsheet</a>.
</body>
</html>
|
この単純なページではまず、names テーブルに対して SQL の select 操作を実行します。次に HTML で表を作成し、fetchInto メソッドを使って取得した names テーブルのすべての行のデータをこの表に追加します。
図 6 は、このページの出力を示しています。
図 6. list.php の出力
このページも見栄えはよくありませんが、このページを例としてデータベースのデータを取得する方法を説明しました。このページはエクスポート用の Excel XML ファイルを生成するスクリプトの基本になります。
最後のステップでは Excel の XML ファイルを生成します。この記事の場合には、まず Excel の XML を PHP スクリプトにコピーします (リスト 7)。これが不精な方法であることは私も承知していますが、適切に解析可能な Excel XML ファイルを得るには、この方法が最も簡単なのです。(Excel では XML を厳密に扱います。)
リスト 7. XML をエクスポートするページ
<?php
header( "content-type: text/xml" );
// Install the DB module using 'pear install DB'
require_once( "db.php" );
$data = array();
$db =& DB::connect("mysql://root@localhost/names", array());
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query( "SELECT * FROM names ORDER BY last" );
$rows = array();
while( $res->fetchInto( $row, DB_FETCHMODE_ASSOC ) )
{ $rows []= $row; }
print "<?xml version=\"1.0\"?>\n";
print "<?mso-application progid=\"Excel.Sheet\"?>\n";
?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:html="http://www.w3.org/TR/REC-html40">
<DocumentProperties
xmlns="urn:schemas-microsoft-com:office:office">
<Author>Jack Herrington</Author>
<LastAuthor>Jack Herrington</LastAuthor>
<Created>2005-08-02T04:06:26Z</Created>
<LastSaved>2005-08-02T04:30:11Z</LastSaved>
<Company>My Company, Inc.</Company>
<Version>11.6360</Version>
</DocumentProperties>
<ExcelWorkbook
xmlns="urn:schemas-microsoft-com:office:excel">
<WindowHeight>8535</WindowHeight>
<WindowWidth>12345</WindowWidth>
<WindowTopX>480</WindowTopX>
<WindowTopY>90</WindowTopY>
<ProtectStructure>False</ProtectStructure>
<ProtectWindows>False</ProtectWindows>
</ExcelWorkbook>
<Styles>
<Style ss:ID="Default" ss:Name="Normal">
<Alignment ss:Vertical="Bottom"/>
<Borders/>
<Font/>
<Interior/>
<NumberFormat/>
<Protection/>
</Style>
<Style ss:ID="s21" ss:Name="Hyperlink">
<Font ss:Color="#0000FF" ss:Underline="Single"/>
</Style>
<Style ss:ID="s23">
<Font x:Family="Swiss" ss:Bold="1"/>
</Style>
</Styles>
<Worksheet ss:Name="Names">
<Table ss:ExpandedColumnCount="4"
ss:ExpandedRowCount="<?php echo( count( $rows ) + 1 ); ?>"
x:FullColumns="1" x:FullRows="1">
<Column ss:Index="4" ss:AutoFitWidth="0" ss:Width="154.5"/>
<Row ss:StyleID="s23">
<Cell><Data
ss:Type="String">First</Data></Cell>
<Cell><Data
ss:Type="String">Middle</Data></Cell>
<Cell><Data
ss:Type="String">Last</Data></Cell>
<Cell><Data
ss:Type="String">Email</Data></Cell>
</Row>
<?php foreach( $rows as $row ) { ?>
<Row>
<Cell><Data
ss:Type="String"><?php echo( $row['first'] ); ?>
</Data></Cell>
<Cell><Data
ss:Type="String"><?php echo( $row['middle'] ); ?>
</Data></Cell>
<Cell><Data
ss:Type="String"><?php echo( $row['last'] ); ?>
</Data></Cell>
<Cell ss:StyleID="s21"><Data ss:Type="String">
<?php echo( $row['email'] ); ?></Data></Cell>
</Row>
<?php } ?>
</Table>
<WorksheetOptions
xmlns="urn:schemas-microsoft-com:office:excel">
<Print>
<ValidPrinterInfo/>
<HorizontalResolution>300</HorizontalResolution>
<VerticalResolution>300</VerticalResolution>
</Print>
<Selected/>
<Panes>
<Pane>
<Number>3</Number>
<ActiveRow>1</ActiveRow>
</Pane>
</Panes>
<ProtectObjects>False</ProtectObjects>
<ProtectScenarios>False</ProtectScenarios>
</WorksheetOptions>
</Worksheet>
</Workbook>
|
このスクリプトはまず、出力のコンテンツ・タイプを XML に設定しています。コンテンツ・タイプの設定は重要です。コンテンツ・タイプを設定しないと、ブラウザーはこのコードが単に不適切な HTML だとみなしてしまうからです。
ここでは、コード内の SQL クエリーの部分を変更し、クエリーの結果を配列に保存しています。この種のレポート・ページでは、私は通常そうした方法は使いませんが、ここでは行の数に 1 を加えた値を ss:ExpandedRowCount 属性に設定する必要があります。1 を加えるのは、見出し行を考慮に入れる必要があるためです。
図 7 はリンクをクリックした結果を示しています。
図 7. エクスポートされた XML を Firefox で表示する
あまり驚くような表示内容ではありません。しかし、同じリンクを Internet Explorer でクリックした場合にどうなるかを見てください (図 8)。
図 8. エクスポートされた XML を Internet Explorer で表示する
何という違いでしょう。フォーマット設定されていて申し分のない、完全なスプレッドシートがブラウザーに表示されています (もちろん、Firefox の場合はリンクを右クリックして XML をファイルに保存し、そのファイルを開きます)。
開発最先端のものには落とし穴が付き物ですが、この記事で紹介した手法にも落とし穴があります。例えば、この手法は Macintosh ではまだ使えません。最新版の Office for Mac は XML ファイルをサポートしていないからです。
もう 1 つの問題点は、これらのファイルのデバッグが簡単ではないことです。XML が少しでも不適切な場合、埋め込まれた Excel オブジェクトはおかしな状態になります。すると Excel はそのオブジェクトが既に実行されているとみなし、そのオブジェクトを起動しようとしません。この問題を修正するにはアプリケーションを再起動するしかありません。
とはいえ、この手法によって、これまでにはないような統合の可能性が PHP プログラムにもたらされます。皆さんも何度も経験していると思いますが、データのソースが Excel や Word の形式のように見えながら、それを Web アプリケーションで使おうとすると、セルごとにあるいは段落ごとに、手動でマイグレーション作業を行わざるを得ない場合があります。この問題は、この記事で紹介したインポート手法を使うことで解決することができます。ワークシートや文書から直接データを読み取ることができるのです。
エクスポートの場合にも同じことが言えます。HTML は記事や論文のためには優れたフォーマットですが、スプレッドシートの情報を適切に描画するようには設計されていません。この記事で紹介した手法を使うことで、数式、書式設定、その他すべてがユーザーの期待通りのスプレッドシートを生成することができます。
学ぶために
- PHP.net は PHP に関する最新ニュースについて学ぶことができる場であり、またダウンロードを見つけ、他のユーザーから学ぶこともできる場です。
- Microsoft Office Online は、サポート含め、Office に関する情報を得るために最適の場所です。
- XML 標準に関して最も信頼できるソースは World Wide Web Consortium (W3C) です。
- Charles F. Goldfarb と Priscilla Walmsley の共著による『XML in Office 2003: Information Sharing with Desktop XML』(2003年 Prentice Hall 刊) は、幅広い領域をカバーした完全な案内書です。
- 2003年11月の developerWorks の記事、「Convert Excel data to XML」は、Excel ファイルのデータの秘密を解き、XML で Excel データを処理する方法を説明しています。また、さまざまなソリューションの長所短所についても検証しています。この記事は Excel 2002 や Excel XP のクライアント・ソフトウェアにとって適切です。
- developerWorks の XML ゾーンには、XML 開発者のための資料が豊富に用意されています。
- developerWorks の Open source ゾーンをご覧ください。オープンソース技術を使った開発や、IBM 製品でオープンソース技術を使用するためのハウ・ツー情報やツール、プロジェクトの更新情報など、豊富な情報が用意されています。
製品や技術を入手するために
- 皆さんの次期オープンソース開発プロジェクトを IBM ソフトウェアの試用版を使って革新してください。ダウンロード、あるいは DVD で入手することができます。
議論するために
- developerWorks blogs から developerWorks のコミュニティーに加わってください。
Jack D. Herringtonは、20 年以上の経験を持つシニア・ソフトウェア・エンジニアです。彼の著書には、『Code Generation in Action』、『Podcasting Hacks ―構成、録音、発信の必須テクニック』、『PHP Hacks』の 3 冊があります。また、30 を超える記事も執筆しています。