Developer: PHP
 
 

PHPfestチュートリアル:Oracle Database 10g Express EditionおよびZend Core for Oracle


Christopher Jones、Alison Holloway著

PHP OCI8拡張モジュールの使用とOracle Database XEおよびZend Core for Oracleのインストールに関するステップ・バイ・ステップのガイド

2006年3月公開

Oracle Database XEおよびZend Core for Oracleのインストール

この項では次のインストールについて説明します。

下記のシステム構成において、インストールおよび構成のテストが完了しています。
  • 512MBのメモリ
  • 5GBのハードディスク・ドライブ領域
  • Red Hat Enterprise Linux(RHEL)3
  • RHEL 4
  • Novell SUSE Linux Enterprise Server(SLES)9

Oracle Database XE

Oracle Database XEは、使用するディスク領域の少ない、エントリ・レベルのデータベースです。 無料でダウンロード、開発、および配置でき、アプリケーションとともに配布することも可能です。 また、データベースのライセンス・コストもかかりません。

Oracle Database XEは、Oracle Database 10g Release 2の製品版(Standard EditionおよびEnterprise Edition)と同じコードを使用して構築されており、32ビットのWindowsプラットフォームおよびLinuxプラットフォーム上で使用できます。

Oracle Database XEは、次のユーザーに最適です。
  • データベースを必要とするPHP、Java、.NET、およびその他アプリケーションに取り組む開発者
  • トレーニングまたは配置のために無料のスタータ・データベースを必要とするDBA
  • 販売するアプリケーションおよび製品へのOracleデータベースの組込みを希望する独立系ソフトウェア・ベンダー(ISV)
  • 無料のスタータ・データベースを必要とする教育機関および研究者
Oracle Database XEには、次のプログラミング・インタフェースが含まれています。
  • SQL、PL/SQL
  • Java、C、PHP
  • Windows .NET
  • Oracle Application Express
  • C++、ODBC、OLE DB
Oracle Database XEには、次の制限があります。
  • 4GBまでのユーザー・データ領域
  • 単一のデータベース・インスタンス
  • 単一のCPU
  • 1GBまでのRAM
Oracle Database XEはブラウザ・ベースの管理インタフェースであるOracle Application Expressを備えています。

Oracle Technology Network(OTN)ディスカッション・フォーラムを通じて他のユーザーや製品の専門家からサポートを受けられます。

Oracle Database XEのインストール

0.3.96バージョン以降のlibaioを所有していない場合は、Oracle Database XEのインストールの前にこのライブラリをインストールする必要があります。

Oracle Database Developer CDにあるLinux x86のインストール手順は、下記の手順とは多少異なります。 CDからインストールしている場合は、CDのルートにあるStart_Here.htmlで説明されている手順に従ってください。

Oracle Database XEのインストール手順
  1. rootとしてログインするかsuコマンドを実行します。
    su
    Password:
                              
  2. oracle-xe-10.2.0.1-1.0.i386.rpmを/tmpへコピーし、ディレクトリを/tmpに変更します。
    cp oracle-xe-10.2.0.1-1.0.i386.rpm /tmp
    cd /tmp
                              
  3. RPMをインストールします。
    rpm -ivh oracle-xe-10.2.0.1-1.0.i386.rpm
                              
    Oracle Database XEがインストールされます。

  4. データベースを設定します。
    /etc/init.d/oracle-xe configure
                              
  5. Oracle Application Expressのポートをデフォルト(8080)、Oracle Database Listenerのポートを1521に設定します。

  6. デフォルト・ユーザーのパスワードを入力し、確認します。

  7. リブート時にデータベースを自動的に開始するかどうかにより、YまたはNを入力します。 データベースおよびデータベース・リスナーが設定され、起動します。

Oracle Database XEインストールのテスト

Oracle Database XEインストールのテスト手順
  1. Webブラウザを開き、以下を入力します。
    http://127.0.0.1:8080/apex
                              
  2. インストール時に入力したパスワードを使用して、ユーザーsystemとしてログインします。
正しくインストールされている場合、Oracleデータベースにログインします。

Zend Core for Oracle

2005年、オラクルとZend Technologiesは、開発者による一般的なPHPスクリプト言語に基づいたデータベース・ドリブンのWebアプリケーションの開発および配置をサポートする統合ソリューションを開発するためのパートナーシップを結びました。

2005年10月、オラクルとZendはZend Core for Oracle(ZCO)を発表しました。 ZCOは、完全に検査済みで、サポートされるPHP 5の配信を可能にします。これは、Oracle Database 10gクライアント・ライブラリとの統合を含み、開発者によるPHPおよびOracleの迅速な稼動を可能にします。

ZCOは、PHP開発者のためのビルトイン・スタックであり、強固、スケーラブルかつ信頼性の高いインフラストラクチャでのアプリケーションの開発および配置を容易にします。 この製品には、Oracle Databaseクライアント・ライブラリとの密接な統合、およびXMLとWebサービスのネイティブ・サポートが含まれます。また、増加傾向にあるサービス指向アーキテクチャ(SOA)の導入をサポートします。 さらに、データベース・ドリブンのアプリケーションを迅速に開発および配置するための基盤を提供します。

オラクルは、OTNのPHP Developer CenterからOracleデータベースとのPHPの開発に関する情報を提供しています。

オラクルとZendのコラボレーションにより、オープン・ソースのPHPコミュニティへのオラクルの協力体制が強化されました。 オラクルがPHPのために新しく導入および最適化したOCI-8拡張は、PHPコミュニティに還元されるとともにZCOに統合されます。

このコラボレーションは、開発者の効率的なデータベース・ドリブンのアプリケーションの開発およびデプロイをサポートすることで、オープン・ソースにおけるオラクルの投資を拡大します。

ZCOおよびPHPのサポートはZendから提供されます。

Zend Core for Oracleのインストール

ZCOのインストール手順

  1. まだ済んでいない場合は、rootとしてログインするかsuコマンドを実行します。
    su
    Password:
                              
  2. ZendCoreForOracle-v1.3.1-Linux-x86.tar.gzを/tmpへコピーし、ディレクトリを/tmpに変更します。
    cp ZendCoreForOracle-v1.3.1-Linux-x86.tar.gz /tmp
    cd /tmp
                              
  3. ダウンロードしたZend Core for Oracleソフトウェアのコンテンツを抽出します。
    tar -zxf ZendCoreForOracle-v1.3.1-Linux-x86.tar.gz
                              
    ファイルは、ZendCoreForOracle-v1.3.1-Linux-x86というサブディレクトリに抽出されます。

  4. ZendCoreForOracle-v1.3.1-Linux-x8にディレクトリを変更し、Zend Core for Oracleのインストールを開始します。
    cd ZendCoreForOracle-v1.3.1-Linux-x86
    ./install
                              
  5. 最初のZend Core for Oracle Installationページで「OK」をクリックします。
  6. Zend Core for Oracle V.1ページで「Exit」をクリックします。
  7. ライセンス規約の受諾が指示されたら、「Yes」をクリックします。
  8. 既存のPHPがインストールされている場合、既存のphp.iniファイルをバックアップし、上書きするように指示されます。 「Yes」をクリックします。
  9. Zend Core for Oracleをインストールする場所を指定するよう指示されたら、デフォルトのまま(または、希望する場所を入力し)「OK」をクリックします。 インストーラがインストールに必要なファイルの抽出を開始します。
  10. ウィンドウの進捗状況がすべてのソフトウェアのインストール完了を示したら、"Please enter the GUI password"と表示されます。 PasswordフィールドにZend Core Consoleにアクセスする際に使用する任意のパスワードを入力し、「OK」をクリックします。
  11. "Verify the password"と表示されたら、前述の手順で指定したパスワードと同じパスワードを入力し、「OK」をクリックします。
  12. Zend Coreサポート・ページで、Zend CoreおよびPHPコンポーネントの更新が可能な場合に、Zend Core Consoleを利用して通知するようZendネットワーク・ユーザーIDおよびパスワードをオプションとして入力する場合があります。 登録していないか、更新の通知を希望しない場合は、「No」をクリックします。
  13. 次のページでは、Zend CoreインストールのためのWebサーバーを選択するよう指示されます。 LinuxとともにインストールされたデフォルトのApacheを選択します。 「OK」をクリックします。
  14. Webサーバー選択の確認ページで、"Do you wish to proceed?"と表示されたら、「Yes」をクリックします。
  15. 次のインストール・ページでは、"Please select an installation method for Apache 2.0.52"と表示されます。 Apacheモジュールを選択し、「OK」をクリックします。
  16. 次のページで"Please select a virtual server for the Zend Core GUI"と表示されたら、Main Serverを選択し「OK」をクリックします。
  17. 次のページで"Would you like to restart the Web Server"と表示されたら、「Yes」をクリックします。
  18. 次のページで、apachectlスクリプトが更新されたことを示す通知が表示されます。 「OK」をクリックします。
  19. 次のページでは、"Thank you for installing Zend Core for Oracle"と表示され、有用な設定コマンドおよびZend Coreエンジンの管理のためのWebページの一覧を得られます。 情報に注意し、「Exit」をクリックします。
  20. 最後の確認ページが表示されます。 「OK」をクリックして、インストールを完了します。
Zend Core for Oracleのインストールが完了しました。

Zend Core for Oracleの設定

この項では、Webページにおいてデフォルトのエラー・レポートを制御する環境変数およびZend Coreディレクティブを設定します。
  1. Zend Core Administrationページにアクセスするため、Webブラウザに次のURLを入力します。 http://127.0.0.1/ZendCore
  2. Zend Core for Oracleのインストール時に提供されたGUIパスワードを入力します。 「login >>>」アイコンをクリックします。
  3. Configuration」タブをクリックし、設定オプションを表示します。
  4. +」アイコンをクリックし、Error HandlingおよびLogging構成設定エントリを展開します。
  5. display_errorsディレクティブをOnに設定し、開発時のHTMLスクリプト出力のエラー表示を有効化します。
  6. 未保存の変更があるため、"Unsaved configuration"メッセージがページ・ヘッダーの下に表示されます。 「Save Settings」をクリックして、設定を保存します。
  7. 設定に変更を加えたため、Apache Webサーバーを再起動する必要があります。 ページ・ヘッダー下に"Please Restart Apache"という通知メッセージが表示されます。 「Restart Server」をクリックして、Apacheサーバーを再起動します。
  8. Logout」をクリックして、Zend Core for Oracle Administrationを終了します。

Zend Core for Oracleインストールのテスト

ZCOインストールのテスト手順

仮想ディレクトリの作成に関する情報をユーザーのホーム・ディレクトリに追加します。
  1. パブリック仮想ディレクトリをpublic_htmlとして作成します。 APACHE_HOME/conf/httpd.confを編集し、次の行から先頭の"#"を削除します。
    #UserDir public_html
                              
  2. rootではない通常のユーザーとして、ホーム・ディレクトリにpublic_htmlというディレクトリを作成し、ディレクトリを変更します。その後、コマンド・ウィンドウに次のコマンドを入力します。
    cd $HOME
    mkdir public_html
    cd public_html
                              
  3. 次のPHPコードを含むhello.phpファイルを作成します。
    <?php
    echo "Hello world!";
    ?>
                              
  4. Webブラウザを開き、次のURLを入力します。
    http://127.0.0.1/~<username>/hello.php
                              
    ブラウザに"Hello world!"と表示されます。

PHPのOCI8拡張

PHPのOCI8拡張は、OracleデータベースへアクセスするためのPHP関数を提供します。 OCI8拡張は、安定性とパフォーマンスを向上させるため、最近になってZendとオラクルによりリファクタリングされました。 この新しいバージョンのインストールはいくつかの方法で実行できます。 これは、簡単にインストールするために用意されたZend Core for Oracleバンドルに含まれています。 また、PHPソース・コードおよびWindowsバイナリにも含まれています。 そして、PHP Extension Community Library(PECL)からソース・コードとしても取得できます。Window用にはhttp://pecl4win.php.net/ext.php/php_oci8.dllから取得できます。

新しい拡張はPHP 4およびPHP 5の両方で動作するように設計されています。
Oracleの問合せは、データのフェッチや表示のようにシンプルです。
<?php
  $conn = oci_connect('hr', 'hrpw', '//localhost/XE');

  $stid = oci_parse($conn, 'select city from locations');
  oci_execute($stid);

  print '<table border="1">';
  while ($row = oci_fetch_array($stid, OCI_RETURN_NULLS)) {
    print '<tr>';
    foreach ($row as $item) {
      print '<td>'.($item?htmlentities($item):' ').'</td>';
    }
    print '</tr>';
  }
  print '</table>';
?> 
                        
この例では、Oracleのデモ・ユーザーHR(Human Resources)として接続し、LOCATIONS表のすべての街を検索します。

PHP 5では、OCI8関数の名前は標準化されています。 OCILogin()のようなPHP 4での名称は、oci_connect()などに変更されましたが、現在も旧称は使用可能です。 しかし、PHPマニュアル内のユーザーによる同じ関数に対するコメントが2箇所に存在するという側面もあります。 その2つとは古い構文または新しい構文のためのコメントであり、関数に同意語が存在する際には、両方のマニュアル・ページを確認し、最新の情報を入手します。

Oracleへの接続

PHPのoci_connect()コールを使用したOracleへの接続手順
$c = oci_connect($username, $password, $dbname)
                        
それぞれのoci_connect()接続は、キャッシュに保存されます。 同じスクリプトで2回目のoci_connect()接続が発生した場合、事前にキャッシュされた接続が返されます。 スクリプトが終了すると、キャッシュは消去されます。

Oci_new_connect()は、完全に独立した接続を提供します。 それぞれの接続は、その他の接続とは切り離されています。 この接続では、複数のデータベース・トランザクションの同時実行が可能になります。
$c = oci_new_connect($username, $password, $dbname)
                        
永続的接続は、PHPスクリプトの終了時に自動的に閉じられません。 それらの接続は開かれた状態で維持され、その他のスクリプトで再利用されます。
$c = oci_pconnect($username, $password, $dbname)
                        
これは接続を開くコストが高い場合に有効な手段です。 アプリケーション要件への高い依存性や同じホスト上のWebサーバーやデータベースなどの実装の問題を考慮する必要があります。

永続的接続の欠点は、アプリケーションまたはデータベースへのアクセスが発生していない場合もOracleのリソースを使用することです。 Apacheがいくつものサーバー・プロセスを発生させている場合、それぞれのプロセスはデータベースへの独自の接続を持つ場合があります。 php.iniのパラメータにより永久的接続のリソース使用率を調整できます。
  • oci8.max_persistent: キャッシュされる永久的接続の数を制限します。 制限に達した場合、すべてのoci_pconnect()コールはoci_connect()コールのように扱われます。 -1(デフォルト)に設定されている場合、制限がないことを意味します。
  • oci8.persistent_timeout: Apacheプロセスがアイドル状態で永久的接続を維持する時間(秒)です。 有効期間の確認は、スクリプトによるOCI8関数の呼出しの有無に関わらず、PHPスクリプトの終了時に実行されます。 -1(デフォルト)に設定されていることは、タイムアウトがないことを意味します。 接続の有効期限が切れると、oci_pconnect()は新しい接続を作成します。
  • oci8.ping_interval: oci_pconnect()の間に、Pingを発行するまでの時間(秒)です。 0に設定すると、oci_pconnect()が呼び出されるたびにPHPはデータベースにPingを発行します。 Pingを無効化するには-1と設定します。デフォルトの値は60秒です。 Pingが接続の切断を特定すると新しい接続が作成されます。
優れたアプリケーションの設計は、ほとんどの障害から透過的に回復します。 Oracleのようなシステムは安定性が高いですが、どのようなアプリケーションにもネットワーク、ハードウェア、およびデータベースの停止といったユーザー操作を含めた潜在的な障害の可能性が存在します。 Oracle自体は、アイドル接続を閉じるように設定されている場合があります。 DBAは、CREATE PROFILE IDLE_TIMEOUTとともにユーザー・プロファイルをインストールできます。 または、Oracle Netレイヤーがネットワークをタイムアウトします。

Oci_pconnect()は、常にOracleのクライアント側の設定を確認し、サーバーからの受信時にサーバーが実際に使用可能かどうかを監視します。 この操作は迅速に実行されます。 物理的にサーバーへPingを発行するように、oci8.ping_intervalへ追加の設定を実行すると、ネットワークで"ラウンド・トリップ"が発生し、スケーラビリティに"悪影響"を与えます。 データベースまたはデータは接続確認の間に使用不能になる可能性があるため、高い可用性とスケーラビリティの保持に通常推奨されることは、oci8.ping_intervalを使用せずにアプリケーション・コードでエラー回復を行うことです。

また、多くの小さなアプリケーションにおいても、より簡単なPing機能による利点があります。

アプリケーション接続のライフタイムを再確認してください。 利用可能な接続を再利用するべきですが、必要な場合は接続を閉じ、新しい接続を作成することも重要です。 それぞれの接続はOracleのメモリを消費しますが、総合的な負荷はアイドル接続を閉じることで削減されます。

接続文字列

接続文字列がほとんどの混乱を引き起こします。 一般的なOracleのエラー"ORA-12514 TNS:listener does not currently know of service requested in connect descriptor"からの回復には多くの時間が必要です。

DBの名前は次のうちの1つになります。
  1. 簡易接続文字列
  2. 完全な接続文字列
  3. tnsnames.oraエイリアス
簡易接続文字列

簡易接続文字列はJDBCのようになります。 ホスト名、ポート番号、およびOracleデータベースのサービス名を指定します。
//hostname:port/service_name
                        
デフォルトのポート(1521)を使用するOracle Database XEのためには、'//localhost/XE'を使用します。
$c = oci_connect('hr', 'hrpw', '//localhost/XE');
                        
簡易接続構文を使用するには、Oracle 10gのクライアント側のライブラリが必要です。 ZCOは適切なOracleライブラリを保持しています。

完全にハードコードされた接続文字列

完全なOracle Net接続文字列は、接続において完全な柔軟性を提供します。
$db = MYDB2 = (DESCRIPTION =
                (ADDRESS = (PROTOCOL = TCP)
                  (HOST = mymachine.mydomain)(PORT = 1521))
                  (CONNECT_DATA =
                    (SERVER = DEDICATED)
                    (SERVICE_NAME = MYDB.AU.ORACLE.COM)))';

$c = oci_connect($un, $pw, $db);
                        
判断が付かない場合は、他のOracleツールやユーザーが使用している接続文字列をコピーしてください。

完全な構文を使用することで、ロード・バランシングやパケット・サイズの調整などのOracle Netの特性を有効化できます。 簡易接続構文はこのような柔軟性を提供しません。

tnsnames.oraファイルからのNetwork Alias

完全な接続文字列はtnsnames.oraファイルに保存できます。また、PHPからエイリアスを介して参照できます。
# tnsnames.ora
    MYDB2 = (DESCRIPTION =
              (ADDRESS = (PROTOCOL = TCP)
                (HOST = mymachine.mydomain)(PORT = 1521))
                (CONNECT_DATA =
                  (SERVER = DEDICATED)
                  (SERVICE_NAME = MYDB.AU.ORACLE.COM)))
                        
PHPではその後、次と接続できます。
$c = oci_connect($un, $pw, 'MYDB2');
                        
PHPは、エイリアス'MYDB2を解決するためにtnsnames.oraファイルを検索できるようになる必要があります。 これは一般的な課題です。

"ORACLE_HOME"へのインストール(XEなど)の場合には、デフォルトのtnsnames.oraファイルは、次の場所にあります。
$ORACLE_HOME/network/admin/tnsnames.ora
                        
たとえば、次のようになります。
/usr/lib/oracle/xe/app/oracle/product/10.2.0/server/network/admin/tnsnames.ora
                        
デフォルトの場所を使用しているかどうかにかかわらず、Apacheは実行時に、ディレクトリを特定できる必要があります。 通常は、(ZCOを含め)TNS_ADMIN環境変数を設定することで実行されます。

Oracleの環境変数

OCI8拡張は、常にOracleライブラリおよびメッセージ・ファイルを特定できる必要があります。 正しいファイルを特定できない場合、PHPはOracleに"ORA-12705: Cannot access NLS data files or invalid environment specified"を返します。 複数のOracleをインストールした場合、競合が発生することがあります。 ZCOには、Oracleライブラリおよびメッセージを保持するOracle Instant Clientの独自のローカル・コピーを含んでいるため、このエラーは発生しません。

NLS_LANG(グローバリゼーション)およびTNS_ADMIN(ネットワーク)変数は、おもにZCOにより使用されます。

環境変数は、Apacheを開始する環境に設定する必要があります。そのため、OCI8拡張が最初にロードされた際にアクセスできます。 putenv()を使用して、PHPスクリプトに環境変数は設定しないでください。 これは、設定後の動作がすべてのOCI8関数に対して一定ではないためです。 そのため、この関数の使用は適切ではありません。

ZCOはapachectlを修正し、LD_LIBRARY_PATHを追加します (オラクルが別々にリンクされたInstant Clientを出荷する場合、将来的にZCOでは必要がなくなる可能性もあります)。 これによりZCO GUI ConsoleはApacheを起動するために再利用されます。

ZCOのためにTNS_ADMINおよびNLS_LANGで同じような作業を実行する必要があります。または、Apacheを手動で起動して、呼び出すスクリプトで環境を設定する必要があります。
#!/bin/sh

TNS_ADMIN=/usr/local/apache/conf
export TNS_ADMIN
echo Starting Apache
#export > /tmp/envvars
/usr/local/apache/bin/apachectl start
                        
この例では、/usr/local/apache/conf/tnsnames.oraファイルが存在すると仮定しています。 TNS_ADMINはtnsnames.oraファイルのあるディレクトリを指します。

接続の切断

それぞれのスクリプトの最後で、oci_connect()またはoci_new_connect()で開かれた接続は自動的に閉じられます。 また、次を呼び出すことで明示的に切断できます。
oci_close($c);
                        
コミットされていないデータはロールバックされます。

長いスクリプトの場合もデータベースにアクセスする時間が少ない場合は、接続を閉じ、他のデータベース・ユーザーのためにリソースを解放できます。

oci_pconnect()で開かれた接続は、oci_close()では閉じられません。 これは、他のPHP拡張における永続リソースの動作と類似しています。

oci_close()は参照カウントにより動作します。 また、すべての接続への参照が終了した時にのみ実際に接続が閉じます。 また、例$c1および$c2は1つの接続ですが、スクリプトの終了時にのみデータベース接続は閉じられます。
$c1 = oci_connect('hr', 'hrpw', '//localhost/XE');
$c2 = oci_connect('hr', 'hrpw', '//localhost/XE');

do_query($c1, 'select user from dual');
oci_close($c1);
do_query($c1, 'select user from dual');
do_query($c2, 'select user from dual');
oci_close($c2);
                        
oci_close()関数は、OCI8のリファクタリング前には操作不能です。 閉じたい場合でも、接続を明示的に閉じることはできません。 これはすでに変更済みではありますが、必要な場合には、php.iniのoci8.old_oci_close_semanticsを設定することで以前の動作に戻せます。1に設定するとoci_close()は再び操作不能になります。 デフォルト値が0の場合、oci_close()は接続を閉じます。

ステートメントの実行

OCI8における問合せは、Oracleに使用されているモデル(解析、実行、フェッチ)に準拠します。 CREATEINSERTのようなステートメントは、解析や実行のみを必要とします。 Oracleの実際の解析は実行段階で行われるため、解析は"準備"段階に過ぎません。

オプションで、ローカルの値をステートメントに"バインド"できます。これは、文字列でプリント・フォーマット指定子の"%s"を使用する方法と似ています。 また、パフォーマンスとセキュリティを改善します。

同様に、どこに結果を格納するかを"定義"できます。 ほとんどのユーザーはOCI8フェッチ関数をこのような方法で使用しています。

手順の一例は次のとおりです。
  1. Parse - 実行のためにステートメントを準備します。
  2. Bind - パフォーマンスとセキュリティの向上を図るため、オプションでWHERE句などのデータ値をバインドします。
  3. Define - どのPHP変数が結果を格納するかを指定するオプションの手順です。
  4. Execute - データベースでコマンドを実行し、結果をバッファします。
  5. Fetch - 問合せの結果をデータベースから取得します。 OCI8には多くのフェッチ・ステートメントが用意されています。
これらのすべて1つのPHP関数の呼出しで実行するワンストップの関数はありませんが、アプリケーションにそのような関数を作成し、カスタムのエラー処理要件を追加することは難しくありません。

問合せ

OCI8における基本的な問合せは次のとおりです。
$stid = oci_parse($c, 'select * from locations');
oci_execute($stid, OCI_DEFAULT);
oci_fetch_all($stid, $res);
                        
必要な場所への二重引用符の使用を確認します。
$stid = oci_parse($c, "select * from locations where city = 'Sydney'");
                        
フェッチ関数は複数存在し、そのすべてはPHP OCI8リファレンス・マニュアルに記載されています。
  • oci_fetch_all(): すべての結果を一度に取得します。
  • oci_fetch_array(): 任意の配列として次の行を取得します。
  • oci_fetch_all(): 連想配列として次の行を取得します。
  • oci_fetch_object(): オブジェクトとして新しい行を取得します。
  • oci_fetch_row(): 整数の索引配列として次の行を取得します。
  • oci_fetch(): 特定のフィールドの結果を返すoci_result()と共に使用されます。
繰り返し呼び出される必要のある単一の行をフェッチする関数を次に示します。
$stid = oci_parse($c, "select city from locations");
oci_execute($stid, OCI_DEFAULT);
while ($res = oci_fetch_row($stid)) {
   echo $res[0] . "<br>\n";
}
                        
いくつかの関数は、それらの動作を変更するオプションのパラメータを持ちます。たとえば、oci_fetch_array()では、連想配列か索引配列(または両方)として結果を返すように指定します。 連想配列は、大文字の列名で入力されています。

原因不明の過去の経緯により、いくつかのPHP 4のフェッチ関数は、デフォルトではNULLデータを返しません。 このため、NULLを返すように指定しない限り、選択した列の結果は表示されない場合があります。

PHPの総合的な問合せパフォーマンスは、2つのphp.iniパラメータで調整することができます。
  • oci8.default_prefetch: それぞれのデータベースでフェッチが実行された際に、Oracleから返されるレコードの数です。 デフォルトの値は10です。この設定を調整することで、大きな数の行を返す問合せのパフォーマンスを大幅に改善できます。 毎回可能な限りのデータを返すことで、データベース・サーバーの"ラウンド・トリップ"を最小化します。 Oracleはクライアントのバッファにあるデータをキャッシュし、PHPが要求した行のみをPHPにわたします。
  • oci8.statement_cache_size: OCIクライアントのステートメント・キャッシュを有効化します。 デフォルトのステートメントは20です。 キャッシュは0を設定することで、無効化できます。クライアント側のステートメント・キャッシュにより、ステートメントのテキストでさえもデータベースに送られる必要がなくなり、ネットワーク・トラフィックとデータベースのサーバー・ロードがさらに削減されます。 Oracleセッションごとにキャッシュされるので、この機能は永久的接続が使用される場合に特に有用です。

Insert/Update/Delete/Create/Drop

CREATEINSERTのようなステートメントの実行は、解析や実行を必要とします。
$s = oci_parse($c1, "create table i1test (col1 number)");
$r = oci_execute($s, OCI_DEFAULT);

$s = oci_parse($c1, "insert into i1test values (1)");
$r = oci_execute($s, OCI_DEFAULT);
                        
1回限りのアプリケーションの設定では、CREATE TABLEを使用する必要があります。 一部のユーザーは、アプリケーションが一時表を作成する必要があると考えていますが、Oracleには他のデータベースのような制限はありません。

アプリケーションが実行される前に、一時表を作成します。 Tom Kyteは、asktom.oracle.comでグローバルの一時表について言及しています。

Oracleデータベースでは、表の作成および削除により自動的にコミットされていないデータをコミットします。 この設定は変更できません。

トランザクション

トランザクションを使用し、データの完全性を保護することは、その他のリレーショナル・アプリケーションと同様にPHPにおいても重要です。 まれなケースを除外すると、すべてのデータをコミットするか、すべてをコミットしないかを選択する必要があります。

前述の例では、実行時にOCI_EXECUTEフラグを指定しています。
$r = oci_execute($s, OCI_DEFAULT);
                        
デフォルト・モードのoci_execute()は、OCI_COMMIT_ON_SUCCESSであり、成功時にコミットします。 しかし、必要のないコミットは、データベースのパフォーマンスに影響を与え、不要なネットワーク・トラフィックおよびデータベース・ファイルへのI/Oの浪費を引き起こします。 このことは、OCI_DEFAULTが優先的に使用される理由となっています。

PHPマニュアルでは、oci_execute()について簡潔に説明しています。

OCI_DEFAULTモードを使用している場合、トランザクションを作成しています。 接続の切断時またはスクリプトの終了時のどちらか早い時点で、トランザクションは自動的にロールバックされます。 トランザクションをコミットするためにoci_commit()を明示的に呼び出すか、中断するためにoci_rollback()を明示的に呼び出す必要があります。

この例では、明示的にコミットしなかった場合、2番目の行はPHPスクリプトの終了時にロールバックされます。 これらの2つの行を正確に挿入するには、1つ目はコミットせず、2つ目以降はコミットする必要があります(この例とは完全に逆となります)。
$s = oci_parse($c, "insert into i2test values ('row 1')");
$r = oci_execute($s);

$s = oci_parse($c, "insert into i2test values ('row 2')");
$r = oci_execute($s, OCI_DEFAULT);
                        

エラー処理

任意の安定したアプリケーションのエラー処理には複雑さが加わり、注意深い設計が必要とされます。 不測の事態を予測し、すべてのリターン・コードを確認する必要があります。

正しいリソースをoci_error()にわたすことは、Oracleのエラー・メッセージを取得するために必要です。
$c = oci_connect("hr", "hr", "//localhost/XE");
if (!$c) {
  $e = oci_error();  // no resource passed
  var_dump($e);
}

$stid = oci_parse($c, "select city from locations");
if (!$stid) {
  $e = oci_error($c);   // connection resource passed
  var_dump($e);
}

$rc = oci_execute($stid, OCI_DEFAULT);
if (!$rc) {
  $e = oci_error($stid);  // statement resource passed
  var_dump($e);
}

$rc = oci_fetch_all($stid, $results);
if (!$rc) {
  $e = oci_error($stid);  // statement resource passed
  var_dump($e);
}
                        

バインド変数

バインド変数は、"%s"プリント・フォーマット指定子のように使用されます。 これにより、その変数の異なる値でのステートメントの再実行が可能となり、異なる結果を得られます。 また、この場合バインドが強く推奨されます。

バインド変数は、総合的なデータベースの効率を向上させます。 Oracleはステートメント用にキャッシュされた任意の実行プランを再利用できます。これは、当初は第三者が実行していたプランにおいても同様です。

また、バインド変数は、SQLインジェクション・セキュリティ攻撃を防止するための有効な手段です。 ユーザー・データは常にデータとして扱われ、SQL文としては扱われません。
$stid = oci_parse($c,
             "select last_name from employees where employee_id = :eidbv");
$myeid = 101;
oci_bind_by_name($stid, ":EIDBV", $myeid);
oci_execute($stid, OCI_DEFAULT);
oci_fetch_all($stid, $res);
echo "Last name is: ". $res['LAST_NAME'][0] ."\n";

// No need to re-parse
$myeid = 102;
oci_execute($stid, OCI_DEFAULT);
oci_fetch_all($stid, $res);
echo "Last name is: ". $res['LAST_NAME'][0] ."\n";
                        
バインド・データはoci_execute()が呼び出される際にアクセス可能であることが要求されます。 サブ・ファンクションでのローカル変数の使用は、スコープの問題を引き起こす可能性があります。

データをOracleにわたす"IN"バインドと同様に、値を返す"OUT"バインドも存在します。 これらはおもにPL/SQLプロシージャおよび関数から値を返すために使用されます。

oci_bind_by_name()関数は、オプションのサイズおよびデータ型パラメータを取得します。

バインドされた状態において、PHP番号は文字列へ、または文字列から相互変換されます。 これは、数値をバインド変数で返す際に、lengthパラメータが通常OCIBindByName()へわたされることを意味します。 lengthは返される値の桁数を意味します。

バインド変数を使用しないことが決定している場合があります。 ステートメントがバインド変数を含む場合、オプティマイザは最終的に使用する可能性がある値に関するいかなる情報も所持しません。 データに大きな偏りがある場合は、値をハードコードする必要があります。 データがユーザーの入力から生成されている場合、整理する必要があります。

多くの古いドキュメントは、oci_bind_by_name()の呼出しに"&"を使用しています。 最新のPHPにおける参照渡しでは、廃止予定の構文は削除されているため、これは不適切です。 また、結果として他の問題を引き起こす可能性もあります。

PL/SQLストアド・プロシージャ

PL/SQLはOracleの手続き型言語です。 本来はADAをモデルとした、強力かつ完全な言語です。 SQL文の組込みも実行できます。 また、データベース・サーバーで実行され、I/Oにも関連しています。

サーバーのクライアントまたはPL/SQLのどちらにPHPを書き込むかを決定する場合、言語におけるスキル・レベル、ネットワーク間のデータ転送コスト、およびコードの再利用性を考慮します。 PL/SQLで書き込む場合は、任意のツールまたはクライアントの言語のすべてのOracleアプリケーションで機能を再利用できます。 イベント・トリガーなどのいくつかの機能は、データベースでのみ使用されます。 Oracleでは、データの挿入やユーザーのログオンといったイベントが発生した際に、これらの機能を作成できます。

事前に供給された多くのPL/SQLパッケージによりコーディングのサポートが行われ、また、スケジューリングのためDBMS_JOBのようなユーティリティも存在します。 ストアド・プロシージャ、関数、およびパッケージの作成によりアプリケーションの永続的な拡張が可能です。 PL/SQLは高度に機能的なアプリケーションの構築をサポートします。

事前に作成されたPL/SQLプロシージャを呼び出すには、"無名ブロック"を使用します。 これは、PL/SQL文に存在するBEGIN/ENDペアです。 また、この例では、単一のプロシージャ・コールです。 ブロック内では任意のPL/SQL文を利用できます。
$stid = oci_parse($c, "begin myproc('mydata', 123); end;");
oci_execute($stid, OCI_DEFAULT);
                        
"end"の後にセミコロンが存在することに注意してください。この点がSQL文とは異なる箇所です。

PL/SQL REF CURSORS

REF CURSORSは、PL/SQLにおけるもう一つの重要な機能です。 問合せ結果セットをREF CURSOR型の単一の変数に保存でき、通常の変数のようにわたすことができます。 PHPでは、OCI_B_CURSOR変数をPL/SQLプロシージャ・コールにバインドし、通常のフェッチ・ループにおいて結果セットの行を取得できます。

例として、ジョブ識別子を受け取り、そのジョブを実行する従業員を問い合わせるプロシージャとPL/SQLパッケージを作成します。 このプロシージャは、従業員識別子とサラリーを含むREF CURSORを返します。

パッケージの仕様において、REF CURSOR型およびプロシージャのシグネチャが提供されます。 パッケージ本体は関数のコードを含みます。
CREATE OR REPLACE PACKAGE cv_types AS
  TYPE EmpInfoTyp is REF CURSOR;
  PROCEDURE EmpInfoRpt (jid IN VARCHAR, emp_cv IN OUT EmpInfoTyp);
END cv_types;
/

CREATE OR REPLACE PACKAGE BODY cv_types AS
PROCEDURE EmpInfoRpt (jid IN VARCHAR, emp_cv IN OUT EmpInfoTyp) AS
  BEGIN
    OPEN emp_cv FOR SELECT EMPLOYEE_ID, SALARY
                    FROM EMP_DETAILS_VIEW WHERE JOB_ID=jid;
   END;
END;
/
                        
PHPでは、次のようなPL/SQLプロシージャを使用します。
$job_id = 'SA_MAN';
$stmt = "BEGIN cv_types.EmpInfoRpt(:jid, :rc); END;";

$stid = oci_parse($c, $stmt);
oci_bind_by_name($stid, ':jid', $job_id);

$refcur = oci_new_cursor($c); // pass the connection
oci_bind_by_name($stid, ':rc', $refcur, -1, OCI_B_CURSOR);

// Execute the call to EmpInfoRpt()
oci_execute($stid);

// Execute and fetch from the cursor
oci_execute($refcur);
echo '<table border="1">';
while($row = oci_fetch_assoc($refcur)) {
  echo '<tr>';
  foreach ($row as $c) {
    echo "<td>$c</td>";
  }
  echo '</tr>';
}
echo '</table>';
                        

ラージ・オブジェクト(LOBS)

Oracleキャラクタ・ラージ・オブジェクト(CLOB)およびバイナリ・ラージ・オブジェクト(BLOB)列(およびPL/SQL変数)は大量のデータを含みます。 Oracleストレージを最適化するためにラージ・オブジェクトを作成する方法は複数あります。 また、事前に提供されているDBMS_LOBは、PL/SQLにおけるそれらの操作を容易にします。

REF CURSORSの使用と同様、LOB型の変数が必要であることをPHPに指示し、SQLまたはPL/SQL文にバインドします。 それ以降は、データへのアクセスに使用するいくつかの特別なOCI8関数を所持できます。

LOBデータの表へのアップロード手順
$myv = 'a very large amount of binary data';

$lob = oci_new_descriptor($c, OCI_D_LOB);
$stid = oci_parse($c,
         'INSERT INTO mybtab (blobid, blobdata) '
       . 'VALUES(123, EMPTY_BLOB()) RETURNING blobdata INTO :blobdata');
oci_bind_by_name($stid, ':BLOBDATA', $lob, -1, OCI_B_BLOB);
oci_execute($stid, OCI_DEFAULT);

$lob->save($myv);
                        
PHP記述子を経由しLOBを操作します。 これらのデータは、Oracle LOBロケータへマッピングされます。

もし、Webフォーム経由でLOBがアップロードされた場合、$lob->savefile($filename)によりupload-directoryファイルから直接アップロードできます。 アップロードされたファイルに許可される最大サイズは、php.iniのupload_max_filesizeで設定できます。

LOBをフェッチする場合、OCI8はLOB記述子を返し、load()またはread()メソッドを使用することでデータが取得されます。
$query = 'SELECT blobdata FROM mybtab WHERE blobid = 123';

$stmt = oci_parse ($conn, $query);
oci_execute($stmt, OCI_DEFAULT);
$arr = oci_fetch_assoc($stmt);
$result = $arr['BLOBDATA']->load();
                        
LOB記述子における他の多くのメソッドにより、特定のオフセットの検索、データのファイルへの直接エクスポート、データの削除、およびLOBのコピーまたは比較が実行できます。

このコードの断片は、記述子の結果から10番目までを検索し、その後、$resultで50バイトまでを保存します。
$arr['BLOBDATA']->seek(10, OCI_SEEK_SET);
$result = $arr['BLOBDATA']->read(50);
                        
バインド型はOCI_B_CLOBになり、表はCLOB列を含むものに変わりますが、CLOBの使用法はBLOBの使用法とほぼ同一です。

Oracle Collections

Oracleマニュアルには、"コレクションはすべて同型の要素の順序体である"と記載されています。 また、それらは効率的に配列されています。

LOBと同じように、oci_new_collection()により割り当てられたコレクション・リソース上のコレクションはメソッドにより操作されます。

簡単な電子メールのアドレス・ブックのデモ(オラクル、Charles Poulsen作成)において、2つのVARRAYが作成され、一方は人名の配列で、もう一方は電子メール・アドレスの配列です。 VARRAY(可変サイズ配列)はサブスクリプトとして連番を使用し、要素の定数にアクセスします。
SQL> drop table emails;

SQL> create table emails (
       user_id       varchar2(10),
       friend_name   varchar2(20),
       email_address varchar2(20));

SQL> create or replace type email_array as
       varray(100) of varchar2(20);
     /

SQL> create or replace type friend_array as
       varray(100) of varchar2(20);
     /

SQL> create or replace procedure update_address_book(
       p_user_id         in varchar2,
       p_friend_name        friend_array,
       p_email_addresses    email_array)
     is
     begin
       delete from emails where user_id = p_user_id;
       for i in 1 .. p_email_addresses.count loop
         insert into emails (user_id, friend_name, email_address)
                     values (p_user_id, p_friend_name(i),
                             p_email_addresses(i));
       end loop;
     end update_address_book;
     /
                        
update_address_book()プロシージャは、アドレス・コレクションのすべての要素をループ・オーバーし、それぞれを挿入します。

PHPでは、コレクション変数を作成し、append()メソッドを使用して要素をそれぞれの配列に追加します。 OCI_B_NTY("名前付き型")としてバインドすることで、PL/SQLプロシージャ引数にコレクションをわたすことができます。
$user_name      = 'cjones';
$friends_names  = array('alison', 'aslam');
$friends_emails = array('alison@example.com', 'aslam@example.com');

$friend_coll = oci_new_collection($c, 'FRIEND_ARRAY');
$email_coll  = oci_new_collection($c, 'EMAIL_ARRAY');

for ($i=0; $i < count($friends_names); $i++) {
  $friend_coll->append($friends_names[$i]);
  $email_coll->append($friends_emails[$i]);
}

$stid = oci_parse($c,
      "begin update_address_book(:name, :friends, :emails); end;");

oci_bind_by_name($stid, ':name',    $user_name);
oci_bind_by_name($stid, ':friends', $friend_coll, -1, OCI_B_NTY);
oci_bind_by_name($stid, ':emails',  $email_coll,  -1, OCI_B_NTY);

oci_execute($stid);
                        
他のPHPコレクション・メソッドにより、コレクションにおけるデータへのアクセスおよびコピーが可能になります。

PHP 5.1.2では、新しい関数oci_bind_array_by_name()によりコレクションのサポートが改善されています。 PL/SQL関数とともに使用することで、挿入の効率を大幅に上げます。 すべてのデータを含むPHP配列をバインドし、single oci_execute()を使用することでデータベースに送ることが可能です。
SQL> drop table mytab;
SQL> create table mytab(name varchar2(20));

SQL> create or replace package mypkg as
       type arrtype is table of varchar2(20) index by binary_integer;
       procedure myproc(p1 in out arrtype);
     end mypkg;
     /

SQL> create or replace package body mypkg as
       cursor cur is select name from mytab;
       procedure myproc(p1 in out arrtype) is
         begin
         for i in 1 .. p1.count loop
           insert into mytab values (p1(i));
         end loop;
       end myproc;
     end mypkg;
     /
                        
PHP配列をMYTABに挿入するには、次を使用します。
$s = "BEGIN mypkg.myproc(:c1); END;";
$stid = oci_parse($c, $s);

$array = array("abc", "def", "ghi", "jkl", "mno");
oci_bind_array_by_name($stid, ":c1", $array, 5, -1, SQLT_CHR);
oci_execute($stid, OCI_DEFAULT);
oci_commit();
                        
oci_bind_array_by_name()関数は、oci_bind_by_name()と同じように動作します。 しかし、データ長の最大値だけでなく、配列内の要素数も取得します。 この例では、要素の数は5であり、データ長は-1に設定されています。-1は文字データの実際の長さを使用するという意味です。

挿入されるデータは次のとおりです。
SQL> select * from mytab;

NAME
--------------------
abc
def
ghi
jkl
mno
                        
その他の多くのOracle型はバインド可能です。 PHPマニュアルには、oci_bind_array_by_name()ドキュメントの情報のみ記載されています。 CVSのOCI8ツリー下での自動化されたテストが情報源となっています。 また、これらはPHPのソース・コードとともにバンドルされています。

グローバリゼーション

Webアプリケーションのグローバリゼーションには綿密な計画が必要とされます。 データは正しい文字セットとして存在する必要があり、HTMLページは正しく変換、エンコードされる必要があります。また、ユーザーの期待する規則は遵守されなければなりません。 『Oracle Database Express Edition 2 Day Plus PHP Developer Guide』にはグローバリゼーションの概要が記載されています。

Oracleデータベースは指定された文字セットで作成されます。 PHPでは、NLS_LANG環境変数を設定することで独自の文字セットを選択できます。 Oracleクライアント(PHP)のグローバリゼーション設定では、ロケールのための適切なデータ形式、Oracleのエラー・メッセージに使用される言語、言語のソートの順番などのデフォルト値を指定できます。 NLS_LANGは、Webサーバーを起動する環境で設定する必要があります。 形式は次のとおりです。
<language>_<territory>.<character set>
                        
たとえば、Unicodeでアプリケーションを稼動させているドイツのユーザーは、次のようにNLS_LANGを設定する必要があります。
GERMAN_GERMANY.AL32UTF8
                        
また、文字セットは、oci_connect()へのオプションの4つ目のパラメータとともに接続ごとに設定される必要があります。 文字セットは、たとえば'ja16euc'といったOracleの文字セット名を含む文字列です。
$c = oci_connect("hr", "hr", "//localhost/XE", 'ja16euc');
                        
指定されていないか、またはNULLの場合、NLS_LANG環境変数の設定が使用されます。

クライアントの文字セットは、データベースからPHPへ転送される際に、Oracleがどのようにデータを変換するかを決定します。 文字セットが等しくない場合、いくつかのデータは基準からはずれて変換される可能性があります。

そのため、PHPのmb_string機能を使用する方法などで返されたデータが正しく扱われるかどうかは、アプリケーションに依存しています。

XMLの使用

SQLおよびPL/SQLの補修および確認を行います。 存在する機能を可能な限り再利用するようにします。 Tom Kyteのasktom.oracle.comには多くの有用な情報が存在します。

オラクルの一般的なガイドラインにより、データベースのデータの管理およびネットワーク間の最小容量の転送が可能になります。 不要な後処理のためのデータベースからPHPへのデータの転送を回避します。 データは、データベースの中核的な資産です。 アプリケーションの全域で一貫した処理が実行される必要があります。 アプリケーション・レイヤーとデータベースの間でシン・インタフェースを維持することも、同様に優れたプログラムの練習方法となります。

いくつかの有用な機能の例には、正規表現、XML関数、分析関数、自立型トランザクション、および空間機能が挙げられます。

OracleおよびPHP 5は優れたXML機能を持ち、情報を処理するために多くのスコープが提供されます。 Oracleのすべてのエディションは、Oracle XML DB(XDB)として知られるデータベースのXML機能を含みます。

表が作成された場合、XMLは線形LOB形式で保存されるか、またはXMLスキーマの構造に準じます。

利点の1つは、リレーショナルSQL表が自動的にXMLとして取得されることです。
$query =
  'SELECT XMLELEMENT("Employees",
     XMLELEMENT("Name", employees.last_name), XMLELEMENT("Id", employees.employee_id)) as result
    FROM employees
    WHERE employee_id > 200';

$stid = oci_parse($c, $query);
oci_execute($stid, OCI_DEFAULT);
while ($row = oci_fetch_row($stid))
  foreach ($row as $item)
    echo htmlentities($item)." ";
                        
XML問合せの引用に注意してください。

PL/SQLパッケージのDBMS_XMLGEN()は、リレーショナル・データからXMLを作成する代わりの方法です。 DBMS_XMLGEN()を使用する問合せはCLOB列を返します。そのため、最初の結果はLOB記述子として扱われる必要があります。
$query = "select dbms_xmlgen.getxml('
            select first_name
            from employees
            where department_id = 30') xml
          from dual";

$stid = oci_parse($c, $query);
oci_execute($stid, OCI_DEFAULT);
$res = oci_fetch_row($stid);
$mylob = $res[0]->load();       // treat result as a LOB descriptor
                        
$mylobの値は次のとおりです。
<?xml version="1.0"?>
<ROWSET>
 <ROW>
  <FIRST_NAME>Den</FIRST_NAME>
 </ROW>
 <ROW>
  <FIRST_NAME>Alexander</FIRST_NAME>
 </ROW>
 <ROW>
  <FIRST_NAME>Shelli</FIRST_NAME>
 </ROW>
 <ROW>
  <FIRST_NAME>Sigal</FIRST_NAME>
 </ROW>
 <ROW>
  <FIRST_NAME>Guy</FIRST_NAME>
 </ROW>
 <ROW>
  <FIRST_NAME>Karen</FIRST_NAME>
 </ROW>
</ROWSET>
                        

Onward and Upward

このガイドをお読みいただきありがとうございました。このガイドがお役に立てば幸いです。
Christopher JonesAlison HollowayはオラクルのPHP開発チームのメンバーです。 このガイドについてのフィードバックは、それぞれのBlogにお願い致します。