PHPのお勉強!

PHP TOP

oci_bind_by_name

(PHP 5, PHP 7, PHP 8, PECL OCI8 >= 1.1.0)

oci_bind_by_nameOracle プレースホルダに PHP 変数をバインドする

説明

oci_bind_by_name(
    resource $statement,
    string $param,
    mixed &$var,
    int $max_length = -1,
    int $type = 0
): bool

PHP の変数 var を Oracle プレースホルダ param にバインドします。 バインドは Oracle データベースのパフォーマンスに影響を及ぼす重要な仕組みで、 SQL インジェクション攻撃を防ぐための方法にもなります。

バインドを使うとデータベースでステートメントのコンテキストを (たとえ別のユーザーやプロセスが実行したものであっても) 再利用することができるようになります。 バインドによって SQL インジェクションの心配を軽減できる理由は、 バインド変数で代入したデータが決して SQL 文の一部と見なされることがないからです。 クォートやエスケープは不要です。

バインドする PHP 変数の中身を変更してステートメントを再実行するときにも、 ステートメントをパースしなおしたり変数をバインドしなおしたりする必要はありません。

Oracle のバインド変数は、データベースに渡す値に使う IN 変数と PHP に返される値に使う OUT 変数のふたつに大別できます。 バインド変数は IN あるいは OUT のどちらかの形式になります。バインド変数を入力用に使うか出力用に使うかは、 実行時に決まります。

OUT 変数を使う場合は max_length を指定しなければなりません。 これを用いて、返される値を格納するのに必要なメモリを PHP が確保します。

IN 変数の場合も、もし PHP の変数にさまざまな値を格納してステートメントを何度も実行するのであれば max_length を設定しておくことを推奨します。 そうしないと、最初に渡した PHP の変数の値にあわせて Oracle がデータの長さを切り詰めてしまう可能性があります。 最大長がどの程度になるかわからない場合は現在のデータサイズで oci_bind_by_name() を再コールしてから oci_execute() を実行するようにしましょう。 不必要に大きなサイズでバインドすると、 データベースのプロセスのメモリ使用量に影響を及ぼします。

バインドは、Oracle にデータをどのメモリアドレスから読み込むのかを指示します。 IN 変数の場合、oci_execute() がコールされたときにそのメモリアドレスに正しいデータがなければなりません。 つまり、バインドされる変数は実行時までスコープ内に残っていなければならないということです。 もしそうでなければ、 "ORA-01460: unimplemented or unreasonable conversion requested" のような予期せぬエラーが発生します。 OUT 変数の場合は、PHP の変数に何も値が格納されないといったことになるでしょう。

繰り返し実行されるステートメントで決して値が変わることのないバインド変数を使っていると、 Oracle のオプティマイザが最適な実行プランを選びにくくなる可能性があります。 実行に長い時間がかかり、再実行されることもめったにないようなステートメントは、 バインドの恩恵を受けられないでしょう。 しかし、どちらの場合であっても、文字列を連結して SQL 文を作るよりはバインドを使ったほうが安全です。 ユーザーの入力をフィルタリングせずに埋め込んでしまうというリスクをなくせるからです。

パラメータ

statement

有効な OCI8 ステートメント識別子。

param

コロンを先頭につけたバインド変数プレースホルダをステートメント内で使います。 コロンは param では必須ではありません。 Oracle はクエスチョンマークをプレースホルダとして使いません。

var

param に関連づける PHP の変数。

max_length

バインド時の最大長。-1 に設定した場合、 var の現在の長さを最大長として設定します。 この場合は、oci_bind_by_name() がコールされたときに var が存在してデータが格納されている必要があります。

type

Oracle がデータを扱うときのデータ型。デフォルトの typeSQLT_CHR です。 Oracle は、可能な場合はこの型とデータベースのカラム (あるいは PL/SQL の変数) の型の間で変換を行います。

抽象データ型 (LOB/ROWID/BFILE) をバインドする必要がある場合、まず oci_new_descriptor() 関数を使用してこれを確保する必要があります。 length は抽象データ型用には 使用されず、-1 を設定する必要があります。

type に設定できる値は以下のとおりです。

戻り値

成功した場合に true を、失敗した場合に false を返します。

例1 oci_bind_by_name() によるデータの挿入

<?php

// このようなテーブルを作ります
// CREATE TABLE mytab (id NUMBER, text VARCHAR2(40));

$conn = oci_connect('hr', 'welcome', 'localhost/XE');
if (!
$conn) {
$m = oci_error();
trigger_error(htmlentities($m['message']), E_USER_ERROR);
}

$stid = oci_parse($conn,"INSERT INTO mytab (id, text) VALUES(:id_bv, :text_bv)");

$id = 1;
$text = "Data to insert ";
oci_bind_by_name($stid, ":id_bv", $id);
oci_bind_by_name($stid, ":text_bv", $text);
oci_execute($stid);

// テーブルに入る内容: 1, 'Data to insert '

?>

例2 一度組み立てたものを複数回実行

<?php

// このようなテーブルを作ります
// CREATE TABLE mytab (id NUMBER);

$conn = oci_connect('hr', 'welcome', 'localhost/XE');
if (!
$conn) {
$m = oci_error();
trigger_error(htmlentities($m['message']), E_USER_ERROR);
}

$a = array(1,3,5,7,11); // 挿入するデータ

$stid = oci_parse($conn, 'INSERT INTO mytab (id) VALUES (:bv)');
oci_bind_by_name($stid, ':bv', $v, 20);
foreach (
$a as $v) {
$r = oci_execute($stid, OCI_DEFAULT); // 自動コミットはしません
}
oci_commit($conn); // すべてをここでコミットします

// テーブルには次の 5 行が含まれます: 1, 3, 5, 7, 11

oci_free_statement($stid);
oci_close($conn);

?>

例3 foreach ループでのバインド

<?php

$conn
= oci_connect('hr', 'welcome', 'localhost/XE');
if (!
$conn) {
$m = oci_error();
trigger_error(htmlentities($m['message']), E_USER_ERROR);
}

$sql = 'SELECT * FROM departments WHERE department_name = :dname AND location_id = :loc';
$stid = oci_parse($conn, $sql);

$ba = array(':dname' => 'IT Support', ':loc' => 1700);

foreach (
$ba as $key => $val) {

// oci_bind_by_name($stid, $key, $val) ではうまくいきません。
// これは、すべてのプレースホルダを同じ内容にバインドしてしまうからです。
// $val ではなく、データの実際の位置を表す $ba[$key] を使いましょう。
oci_bind_by_name($stid, $key, $ba[$key]);
}

oci_execute($stid);
$row = oci_fetch_array($stid, OCI_ASSOC+OCI_RETURN_NULLS);
foreach (
$row as $item) {
print
$item."<br>\n";
}

oci_free_statement($stid);
oci_close($conn);

?>

例4 WHERE 句でのバインド

<?php

$conn
= oci_connect("hr", "hrpwd", "localhost/XE");
if (!
$conn) {
$m = oci_error();
trigger_error(htmlentities($m['message']), E_USER_ERROR);
}

$sql = 'SELECT last_name FROM employees WHERE department_id = :didbv ORDER BY last_name';
$stid = oci_parse($conn, $sql);
$didbv = 60;
oci_bind_by_name($stid, ':didbv', $didbv);
oci_execute($stid);
while ((
$row = oci_fetch_array($stid, OCI_ASSOC)) != false) {
echo
$row['LAST_NAME'] ."<br>\n";
}

// 出力は
// Austin
// Ernst
// Hunold
// Lorentz
// Pataballa

oci_free_statement($stid);
oci_close($conn);

?>

例5 LIKE 句でのバインド

<?php

$conn
= oci_connect('hr', 'welcome', 'localhost/XE');
if (!
$conn) {
$m = oci_error();
trigger_error(htmlentities($m['message']), E_USER_ERROR);
}

// 'South' からはじまるすべての都市を探します
$stid = oci_parse($conn, "SELECT city FROM locations WHERE city LIKE :bv");
$city = 'South%'; // '%' は SQL でのワイルドカードです
oci_bind_by_name($stid, ":bv", $city);
oci_execute($stid);
oci_fetch_all($stid, $res);

foreach (
$res['CITY'] as $c) {
print
$c . "<br>\n";
}
// 出力は
// South Brunswick
// South San Francisco
// Southlake

oci_free_statement($stid);
oci_close($conn);

?>

例6 REGEXP_LIKE でのバインド

<?php

$conn
= oci_connect('hr', 'welcome', 'localhost/XE');
if (!
$conn) {
$m = oci_error();
trigger_error(htmlentities($m['message']), E_USER_ERROR);
}

// 'ing' を含むすべての都市を探します
$stid = oci_parse($conn, "SELECT city FROM locations WHERE REGEXP_LIKE(city, :bv)");
$city = '.*ing.*';
oci_bind_by_name($stid, ":bv", $city);
oci_execute($stid);
oci_fetch_all($stid, $res);

foreach (
$res['CITY'] as $c) {
print
$c . "<br>\n";
}
// 出力は
// Beijing
// Singapore

oci_free_statement($stid);
oci_close($conn);

?>

IN 句の中の条件数が少なくて固定なのであれば、個々にバインド変数を割り当てればいいでしょう。 実行時に不明な値は NULL が設定されます。 こうすればひとつのステートメントをすべてのユーザーで使い回すことができ、 Oracle の DB キャッシュの効率を最大化させることができます。

例7 IN 句での複数の値のバインド

<?php

$conn
= oci_connect('hr', 'welcome', 'localhost/XE');
if (!
$conn) {
$m = oci_error();
trigger_error(htmlentities($m['message']), E_USER_ERROR);
}

$sql = 'SELECT last_name FROM employees WHERE employee_id in (:e1, :e2, :e3)';
$stid = oci_parse($conn, $sql);
$mye1 = 103;
$mye2 = 104;
$mye3 = NULL; // pretend we were not given this value
oci_bind_by_name($stid, ':e1', $mye1);
oci_bind_by_name($stid, ':e2', $mye2);
oci_bind_by_name($stid, ':e3', $mye3);
oci_execute($stid);
oci_fetch_all($stid, $res);
foreach (
$res['LAST_NAME'] as $name) {
print
$name ."<br>\n";
}

// 出力は
// Ernst
// Hunold

oci_free_statement($stid);
oci_close($conn);

?>

例8 クエリが返す ROWID のバインド

<?php

// このようなテーブルを作ります
// CREATE TABLE mytab (id NUMBER, salary NUMBER, name VARCHAR2(40));
// INSERT INTO mytab (id, salary, name) VALUES (1, 100, 'Chris');
// COMMIT;

$conn = oci_connect('hr', 'welcome', 'localhost/XE');
if (!
$conn) {
$m = oci_error();
trigger_error(htmlentities($m['message']), E_USER_ERROR);
}

$stid = oci_parse($conn, 'SELECT ROWID, name FROM mytab WHERE id = :id_bv FOR UPDATE');
$id = 1;
oci_bind_by_name($stid, ':id_bv', $id);
oci_execute($stid);
$row = oci_fetch_array($stid, OCI_ASSOC+OCI_RETURN_NULLS);
$rid = $row['ROWID'];
$name = $row['NAME'];

// 名前を大文字に変換してそれを保存します
$name = strtoupper($name);
$stid = oci_parse($conn, 'UPDATE mytab SET name = :n_bv WHERE ROWID = :r_bv');
oci_bind_by_name($stid, ':n_bv', $name);
oci_bind_by_name($stid, ':r_bv', $rid, -1, OCI_B_ROWID);
oci_execute($stid);

// テーブルに入る内容: 1, 100, CHRIS

oci_free_statement($stid);
oci_close($conn);

?>

例9 INSERT での ROWID のバインド

<?php

// この例では id と name を挿入し、それから salary を更新します
// このようなテーブルを作ります
// CREATE TABLE mytab (id NUMBER, salary NUMBER, name VARCHAR2(40));
//
// Based on original ROWID example by thies at thieso dot net (980221)

$conn = oci_connect('hr', 'welcome', 'localhost/XE');
if (!
$conn) {
$m = oci_error();
trigger_error(htmlentities($m['message']), E_USER_ERROR);
}

$sql = "INSERT INTO mytab (id, name) VALUES(:id_bv, :name_bv)
RETURNING ROWID INTO :rid"
;

$ins_stid = oci_parse($conn, $sql);

$rowid = oci_new_descriptor($conn, OCI_D_ROWID);
oci_bind_by_name($ins_stid, ":id_bv", $id, 10);
oci_bind_by_name($ins_stid, ":name_bv", $name, 32);
oci_bind_by_name($ins_stid, ":rid", $rowid, -1, OCI_B_ROWID);

$sql = "UPDATE mytab SET salary = :salary WHERE ROWID = :rid";
$upd_stid = oci_parse($conn, $sql);
oci_bind_by_name($upd_stid, ":rid", $rowid, -1, OCI_B_ROWID);
oci_bind_by_name($upd_stid, ":salary", $salary, 32);

// 挿入する id と name
$data = array(1111 => "Larry",
2222 => "Bill",
3333 => "Jim");

// それぞれの salary
$salary = 10000;

// 挿入し、その後すぐに各行を更新します
foreach ($data as $id => $name) {
oci_execute($ins_stid);
oci_execute($upd_stid);
}

$rowid->free();
oci_free_statement($upd_stid);
oci_free_statement($ins_stid);

// 新しい行を表示します
$stid = oci_parse($conn, "SELECT * FROM mytab");
oci_execute($stid);
while (
$row = oci_fetch_array($stid, OCI_ASSOC+OCI_RETURN_NULLS)) {
var_dump($row);
}

oci_free_statement($stid);
oci_close($conn);

?>

例10 PL/SQL ストアドファンクションでのバインド

<?php

// PHP プログラムを実行する前に、ストアドファンクションを
// SQL*Plus あるいは SQL Developer で作ります
//
// CREATE OR REPLACE FUNCTION myfunc(p IN NUMBER) RETURN NUMBER AS
// BEGIN
// RETURN p * 3;
// END;

$conn = oci_connect('hr', 'welcome', 'localhost/XE');
if (!
$conn) {
$e = oci_error();
trigger_error(htmlentities($e['message']), E_USER_ERROR);
}

$p = 8;

$stid = oci_parse($conn, 'begin :r := myfunc(:p); end;');
oci_bind_by_name($stid, ':p', $p);

// 戻り値は OUT 変数に格納されます。デフォルトの型は文字列型なので、
// length が 40 ということは最大で 40 まで返される可能性があるということです
oci_bind_by_name($stid, ':r', $r, 40);

oci_execute($stid);

print
"$r\n"; // 24 と表示します

oci_free_statement($stid);
oci_close($conn);

?>

例11 PL/SQL ストアドプロシージャでのパラメータのバインド

<?php

// PHP プログラムを実行する前に、ストアドプロシージャを
// SQL*Plus あるいは SQL Developer で作ります
//
// CREATE OR REPLACE PROCEDURE myproc(p1 IN NUMBER, p2 OUT NUMBER) AS
// BEGIN
// p2 := p1 * 2;
// END;

$conn = oci_connect('hr', 'welcome', 'localhost/XE');
if (!
$conn) {
$e = oci_error();
trigger_error(htmlentities($e['message']), E_USER_ERROR);
}

$p1 = 8;

$stid = oci_parse($conn, 'begin myproc(:p1, :p2); end;');
oci_bind_by_name($stid, ':p1', $p1);

// プロシージャの 2 番目のパラメータは OUT 変数です。デフォルトの型は文字列型なので、
// length が 40 ということは最大で 40 まで返される可能性があるということです
oci_bind_by_name($stid, ':p2', $p2, 40);

oci_execute($stid);

print
"$p2\n"; // 16 と表示します

oci_free_statement($stid);
oci_close($conn);

?>

例12 CLOB 列のバインド

<?php

// 実行する前にテーブルを作成します。
// CREATE TABLE mytab (mykey NUMBER, myclob CLOB);

$conn = oci_connect('hr', 'welcome', 'localhost/XE');
if (!
$conn) {
$e = oci_error();
trigger_error(htmlentities($e['message']), E_USER_ERROR);
}

$mykey = 12343; // この例のための任意のキー

$sql = "INSERT INTO mytab (mykey, myclob)
VALUES (:mykey, EMPTY_CLOB())
RETURNING myclob INTO :myclob"
;

$stid = oci_parse($conn, $sql);
$clob = oci_new_descriptor($conn, OCI_D_LOB);
oci_bind_by_name($stid, ":mykey", $mykey, 5);
oci_bind_by_name($stid, ":myclob", $clob, -1, OCI_B_CLOB);
oci_execute($stid, OCI_DEFAULT);
$clob->save("A very long string");

oci_commit($conn);

// CLOB データを取得します

$query = 'SELECT myclob FROM mytab WHERE mykey = :mykey';

$stid = oci_parse ($conn, $query);
oci_bind_by_name($stid, ":mykey", $mykey, 5);
oci_execute($stid);

print
'<table border="1">';
while (
$row = oci_fetch_array($stid, OCI_ASSOC+OCI_RETURN_LOBS)) {
print
'<tr><td>'.$row['MYCLOB'].'</td></tr>';
// ループ内で、大きなサイズの変数を解放してから次のフェッチに進めます。
// これで、PHP のピークメモリ利用量を抑えます。
unset($row);
}
print
'</table>';

?>

例13 PL/SQL の BOOLEAN のバインド

<?php

$conn
= oci_connect('hr', 'welcome', 'localhost/XE');
if (!
$conn) {
$e = oci_error();
trigger_error(htmlentities($e['message']), E_USER_ERROR);
}

$plsql =
"begin
:output1 := true;
:output2 := false;
end;"
;

$s = oci_parse($c, $plsql);
oci_bind_by_name($s, ':output1', $output1, -1, OCI_B_BOL);
oci_bind_by_name($s, ':output2', $output2, -1, OCI_B_BOL);
oci_execute($s);
var_dump($output1); // true
var_dump($output2); // false

?>

注意

警告

クォートは不要なので、 addslashes()oci_bind_by_name() と同時に使わないでください。 自動的に追加されたクォートはデータベースの中にそのまま書き込まれてしまいます。 oci_bind_by_name() はデータをそのままの形で追加し、クォートやエスケープ文字の除去は行わないからです。

注意:

WHERE 句の中の CHAR 型のカラムに文字列をバインドするときには、 Oracle における CHAR 型のカラムの比較が スペースで埋めた (固定長形式で) 行われることを覚えておきましょう。 WHERE 句を期待通りに動作させるには、 PHP の変数側でもスペースを追加してカラムの幅と同じにしておく必要があります。

注意:

PHP の var 引数は参照渡しです。 ループの形式によっては期待通りの動きをしないこともあります。

<?php
foreach ($myarray as $key => $value) {
oci_bind_by_name($stid, $key, $value);
}
?>

これは、それぞれのキーに対して $value の指す場所をバインドします。 つまり、すべてのバインド変数の値は ループの最後の処理で $value が指す値となります。 期待通りに動かすには、次のようにします。

<?php
foreach ($myarray as $key => $value) {
oci_bind_by_name($stid, $key, $myarray[$key]);
}
?>

参考