PHP에서 Oracle 스토어드 프로시저 호출하기

PHP에서 Oracle 스토어드 프로시저 호출하기
저자 Harry Fuecks

Oracle/PHP 환경에서의 스토어드 프로시저 활용법을 소개합니다.


아티클 관련 다운로드:
Oracle Database 10g
Zend Core for Oracle
Apache HTTP Server 1.3 and later


게시일: 2005년 11월

스토어드 프로시저(stored procedure)란 오라클에 물리적으로 저장된 프로그램을 의미합니다. 대부분의 스토어드 프로시저는 PL/SQL로 작성되어 있으며, Oracle Database 10g Release 2 및 이후 버전에서는 Java, .NET 또는 다른 언어를 External Procedure로 활용할 수 있습니다.


스토어드 프로시저는 일련의 상호 연관된 작업을 하나의 API로 그룹화하는 역할을 수행합니다. 특히 여러 개의 SQL 구문 또는 PL/SQL 구문을 이용하여 데이터를 조회, 변경하거나 수학 계산을 수행하거나, 값의 검증, 에러의 처리 등을 수행하는 작업을 위해 스토어드 프로시저가 활용됩니다. 스토어드 프로시저를 이용하는 경우, 호출 프로그램과 데이터베이스 간의 “라운드 트립” 횟수를 줄일 수 있을 뿐 아니라, 클라이언트 내부의 데이터 관리 로직을 단순화할 수 있으므로 성능적인 개선을 기대할 수 있습니다.

N:N 관계로 연결된 두 테이블을 변경하기 위해서는, 일반적으로 3 개의 쿼리가 필요합니다. 이러한 프로세스를 하나의 스토어드 프로시저에 인캡슐레이트(encapsulate) 함으로써, 클라이언트와 데이터베이스 간의 트래픽을 줄일 수 있을 뿐 아니라 여러 단계를 거쳐 실행되어야 하는 클라이언트 코드 대신 하나의 데이터베이스 호출만으로 모든 작업을 완료하는 것이 가능합니다.

PHP OCI8 익스텐션은 스토어드 프로시저의 호출을 지원하며, 일반적인 SQL 구문의 경우와 마찬가지로 프로시저 구문에 매개변수를 바인딩하는 것이 가능합니다. 또 결과로 반환된 커서와 Oracle 컬렉션에 접근하는 것 또한 가능합니다. 본 문서에서는 일반적인 예제를 통해 스토어드 프로시저의 활용 방법을 소개하기로 합니다.

스토어드 프로시저 입력 및 출력

오라클 스토어드 프로시저를 호출하는 경우, 모든 입력/출력 데이터는 프로시저의 매개변수로써 전달됩니다. 매개변수를 입력하고 값을 출력으로 반환하는 PHP 함수에 익숙한 개발자라면 이러한 방법이 다소 생소하게 느껴질 수도 있겠지만, 예제를 보면 쉽게 이해가 가실 것입니다. 먼저 간단한 스토어드 프로시저의 예를 들어 보겠습니다:
sayHello (name IN VARCHAR2, greeting OUT VARCHAR2)

위 프로시저에서, 첫 번째 매개 변수는 호출 시점에 제공되는 입력 값을 저장하는데 사용됩니다. 반면 greeting 매개변수에는 프로시저의 실행이 완료된 후 “반환”되는 값이 저장됩니다.

프로그램 구조의 이해

PL/SQL 프로그래밍에 대한 자세한 설명은 본 문서의 논의 범주에서 벗어나는 주제입니다. 여기에서 PL/SQL에 대한 세부적인 지식은 필요하지 않습니다. 다만 스토어드 프로시저에서 사용되는 기본적인 인터페이스를 이해하는 것으로 충분합니다.

스토어드 프로시저의 가장 앞부분에는 사용되는 매개변수(parameter)가 정의됩니다:
PROCEDURE edit_entry(
status_out OUT NUMBER,
status_msg_out OUT VARCHAR2,
id_inout IN OUT INTEGER,
title_in IN VARCHAR2,
text_out OUT CLOB,
categories_in IN list_of_numbers
);

위 프로시저는 edit_entry. 라는 이름을 가지고 있습니다. 괄호 안에는 프로시저에 전달할 수 있는 매개변수의 목록이 정의되며, 각 매개변수는 쉼표로 구분됩니다. 또 각각의 매개변수에 대해서는 프로시저 내부에서 값을 참조하는데 사용되는 이름 (PHP 스크립트에서 이 이름을 그대로 사용할 필요는 없습니다), 매개변수의 모드(아래 설명 참고), 그리고 매개변수 타입이 정의됩니다.

그럼, 가장 먼저 정의된 매개변수를 살펴봅시다:
status_out OUT NUMBER,

이 매개변수의 내부 이름(internal name)은 status_out, 모드는 OUT, 타입은 네이티브 오라클 데이터 타입인 NUMBER로 정의되어 있습니다.

또 id_inout 매개변수가 정의된 부분을 살펴봅시다.
id_inout IN OUT INTEGER,

이 매개변수는 IN OUT 모드와 INTEGER 타입으로 정의됩니다.

리스트의 마지막 부분에는 categories_in 매개변수가 정의되어 있습니다:
categories_in IN list_of_numbers

여기에서는 사용자 정의 타입이 사용되고 있습니다 (사용자 정의 타입에 대해서는 뒷부분에서 자세히 설명합니다).

매개변수 모드

매개변수의 모드(mode)란 호출 프로그램 및 프로시저 간의 데이터 “흐름”의 방향을 기술하는 용도로 사용됩니다:
IN—매개변수가 호출 프로그램에 의해 입력됩니다.
OUT—매개변수의 값이 스토어드 프로시저에 할당되어 호출 프로그램에 반환됩니다.
IN OUT—매개변수가 “양방향”으로 사용됩니다. 다시 말해, 호출 프로그램이 이 매개변수를 통해 값을 전달할 수도 있고, 스토어드 프로시저가 매개변수의 값을 수정할 수도 있습니다.
매개변수는 옵션이 아닙니다. PHP를 통해 프로시저를 호출하는 경우, 정의된 모든 매개변수에 대해 PHP 변수를 바인딩해야 합니다. 다만 입력 매개변수의 경우라 하더라도, 정의된 모든 PHP 변수에 값을 할당할 필요는 없습니다. 스칼라 타입의 매개변수에 값이 할당되지 않은 경우, 오라클은 이 값을 NULL로 간주합니다.

오라클에 의해 스토어드 프로시저의 “오버로드(overload)”가 가능하다는 점도 참고해 둘 필요가 있습니다. 다시 말해, 프로시저 이름이 같으나 매개변수 시그니처가 다른 두 개의 프로시저가 존재하는 것이 가능합니다. 둘 중 어떤 프로시저가 호출될 것인지의 여부는, PHP에서 바인딩되는 매개변수의 숫자와 타입에 따라 결정됩니다.

컴플렉스 타입

스토어드 프로시저에 의해 사용되는 매개변수는 VARCHAR2, INTEGER와 같은 스칼라 타입으로 제한되는 것은 아닙니다. 매개변수를 통해 값의 리스트, 또는 결과값 커서와 같은 컴플렉스 데이터 타입을 전달하는 것 또한 가능합니다.

일반적으로 순환적으로 다수의 로우를 반환해야 하는 경우에는 커서를, 값의 목록을 전달해야 하는 경우에는 컬렉션을 사용하게 됩니다. 아래 예제는 PHP에서 커서와 컬렉션을 활용하는 방법을 설명하고 있습니다.

Invoker / Definer 권한. 오라클 “invoker” (스토어드 프로시저를 실행하는 사용자)와 “definer” (CREATE PROCEDURE 구문을 실행하는 사용자)를 엄격하게 구분하고 있습니다).

디폴트 환경에서, 스토어드 프로시저는 definer의 권한을 통해 실행됩니다 (invoker와 definer가 다른 경우에도 마찬가지입니다). 따라서 invoker가 테이블 접근 권한은 없고 프로시저 실행 권한만을 갖는 경우에도 프로시저의 모든 테이블 액세스는 definer의 권한을 가지게 됩니다.

이러한 실행 모델은 프로시저 정의 부분에 AUTHID CURRENT_USER 키워드를 적용하여 변경할 수 있습니다. 이 키워드가 설정된 경우, 접근 권한은 스토어드 프로시저가 실행되는 시점에 (프로시저를 실행하는 현재 사용자를 기준으로) 런타임으로 결정됩니다.

이 키워드를 활용할만한 한 가지 예로, 운영 데이터를 실제로 수정하지 않는 상태에서 테이블의 데이터를 변경하는 테스트 프로시저를 작성하는 경우를 생각해 볼 수 있습니다. 이 경우 사용자는 사용자의 스키마 내부에 프로시저가 액세스하는 테이블과 동일한 이름을 갖는 테이블을 정의하고, definer가 접근할 수 있는 테이블과는 별개의 로컬 테이블에 대해 프로시저를 실행하게 됩니다.

PHP에서 스토어드 프로시저 호출하기

PHP에서 프로시저를 실행하는 경우, 일반적으로 오라클의 BEGIN ... END; 블록(이를 anonymous block이라 부릅니다)을 이용하여 호출되는 부분을 구분하게 됩니다. 그 예가 아래와 같습니다:
<?php
// etc.

$sql = 'BEGIN sayHello(:name, :message); END;';

그런 다음 oci_bind_by_name()를 호출하여 PHP 변수를 매개변수에 바인딩합니다.
sayHello 프로시저가 아래 DDL 구문에 의해 정의되었다고 가정해 봅시다:

CREATE OR REPLACE PROCEDURE
sayHello (name IN VARCHAR2, greeting OUT VARCHAR2)
AS
BEGIN
greeting := 'Hello ' || name;
END;
/

위 구분은 SQL*Plus 커맨드 라인을 통해서도 실행이 가능합니다. (SAYHELLO.SQL) 파일에 구문을 저장합시다. 그런 다음, SQL*Plus에 로그인합니다:
$ sqlplus username@SID

그런 다음 START 커맨드를 이용하여 프로시저를 생성합니다:
SQL> START /home/username/SAYHELLO.SQL

프로시저를 호출하기 위한 PHP 스크립트가 아래와 같습니다:
<?php
$conn = oci_connect('SCOTT','TIGER') or die;


$sql = 'BEGIN sayHello(:name, :message); END;';

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

// Bind the input parameter
oci_bind_by_name($stmt,':name',$name,32);

// Bind the output parameter
oci_bind_by_name($stmt,':message',$message,32);

// Assign a value to the input
$name = 'Harry';

oci_execute($stmt);

// $message is now populated with the output value
print "$message\n";
?>

Blog 패키지의 활용. 스토어드 프로시저의 호출 과정에서 다소 까다로울 수 있는 부분을 처리하기 위해, 여기에서는 blog라는 패키지를 사용하기로 합니다. blog는 블로그 애플리케이션의 게시물을 조회하고 수정하기 위한 API를 제공합니다. 패키지는 네임스페이스 내부의 프로시저, 함수, 데이터를 해당 네임스페이스의 스코프에 인캡슐레이트하고, 글로벌 데이터베이스 네임스페이스의 다른 프로시저로부터 격리하는 역할을 담당합니다. 패키지에서 프로시저를 호출하는 경우, 프로시저명과 패키지명을 구분하기 위해 마침표를 사용합니다.

blog 패키지는 다음과 같이 선언됩니다:
CREATE OR REPLACE PACKAGE blog AS

TYPE cursorType IS REF CURSOR RETURN blogs%ROWTYPE;

/*
Fetch the latest num_entries_in from the blogs table, populating
entries_cursor_out with the result
*/
PROCEDURE latest(
num_entries_in IN NUMBER,
entries_cursor_out OUT cursorType
);

/*
Edit a blog entry. If id_inout is NULL, results in an INSERT, otherwise
attempts to UPDATE the existing blog entry. status_out will have the value
1 on success, otherwise a negative number on failure with status_msg_out
containing a description
categories_in is a collection where list_of_numbers is described by
TYPE list_of_numbers AS VARRAY(50) OF NUMBER;
*/
PROCEDURE edit_entry(
status_out OUT NUMBER,
status_msg_out OUT VARCHAR2,
id_inout IN OUT INTEGER,
title_in IN VARCHAR2,
text_out OUT CLOB,
categories_in IN list_of_numbers
);

END blog;
/

위 패키지는 두 가지 프로시저를 포함하고 있습니다: blog.latest는 가장 최근의 num_entries 블로그 게시물을 포함하는 결과 커서를 반환합니다. blog.edit_entry는 새로운 블로그 게시물을 INSERT 하거나 기존 블로그 게시물을 수정하는데 사용됩니다. id_inout 매개변수에 값이 입력되는 경우, 프로시저는 해당 id의 블로그 게시물을 수정하려 시도합니다. 그렇지 않은 경우에는 새로운 blog 게시물이 INSERT 되고, 새로운 로우의 프라이머리 키 값이 id_inout 매개변수로 반환됩니다. 이 프로시저는 CLOB 오브젝트의 처리를 지원하며, 이 경우 블로그 게시물의 본문과 카테고리 리스트에 해당하는 컬렉션 오브젝트가 사용됩니다. 위 패키지에서 참조되는 list_of_numbers 컬렉션 타입은 아래와 같이 정의됩니다:
CREATE OR REPLACE TYPE list_of_numbers AS VARRAY(50) OF NUMBER;

패키지의 본문이 아래와 같습니다. PL/SQL에 대한 깊은 지식이 없는 개발자라도 커멘트를 통해 대략적인 개념을 이해하실 수 있을 것입니다:
CREATE OR REPLACE PACKAGE BODY blog AS

/*------------------------------------------------*/
PROCEDURE latest(
num_entries_in IN NUMBER,
entries_cursor_out OUT cursorType
) AS

BEGIN

OPEN entries_cursor_out FOR
SELECT * FROM blogs WHERE rownum < num_entries_in
ORDER BY date_published DESC;

END latest;

/*------------------------------------------------*/
PROCEDURE edit_entry(
status_out OUT NUMBER,
status_msg_out OUT VARCHAR2,
id_inout IN OUT INTEGER,
title_in IN VARCHAR2,
text_out OUT CLOB,
categories_in IN list_of_numbers
) AS

ENTRY_NOT_FOUND EXCEPTION;
entry_found INTEGER := 0;

BEGIN

/* Default status to success */
status_out := 1;

/* If id_inout has a value then attempt to UPDATE */
IF id_inout IS NOT NULL THEN

/* Check the id exists - raise ENTRY_NOT_FOUND if not */
SELECT COUNT(*) INTO entry_found
FROM blogs b WHERE b.id = id_inout;
IF entry_found != 1 THEN RAISE ENTRY_NOT_FOUND; END IF;

/* Update the blogs table returning the CLOB field */
UPDATE blogs b SET b.title = title_in, b.text = EMPTY_CLOB()
WHERE b.id = id_inout RETURNING b.text INTO text_out;

/* Remove any existing relationships to categories
- new categories inserted below */
DELETE FROM blogs_to_categories WHERE blog_id = id_inout;

status_msg_out := 'Blog entry ' || id_inout || ' updated';

/* id_inout was null so INSERT new record */
ELSE

INSERT INTO blogs b ( b.id, b.title, b.date_published, b.text )
VALUES ( blog_id_seq.nextval, title_in, SYSDATE, EMPTY_CLOB() )
RETURNING b.id, b.text INTO id_inout, text_out;

status_msg_out := 'Blog entry ' || id_inout || ' inserted';

END IF;

/* Now handle assignment to categories.
Loop over the categories_in collection,
inserting the new category assignments */
FOR i IN 1 .. categories_in.count
LOOP
INSERT INTO blogs_to_categories (blog_id,category_id)
VALUES (id_inout,categories_in(i));
END LOOP;

status_msg_out := status_msg_out || ' - added to '
|| categories_in.count || ' categories';

EXCEPTION
/* Catch the exception when id_inout not found */
WHEN ENTRY_NOT_FOUND THEN
status_out := -1001;
status_msg_out := 'No entry found in table blogs with id = '
|| id_inout;
/* Catch any other exceptions raised by Oracle */
WHEN OTHERS THEN
status_out := -1;
status_msg_out := 'Error: ' || TO_CHAR (SQLCODE) || SQLERRM;

END edit_entry;

END blog;
/

프로시저가 사용하는 하부 테이블 구조가 다음과 같습니다:
CREATE SEQUENCE blog_id_seq
INCREMENT BY 1;
/
CREATE TABLE blogs (
id NUMBER PRIMARY KEY,
title VARCHAR2(200),
date_published DATE,
text CLOB
);
/
CREATE SEQUENCE category_id_seq
INCREMENT BY 1;

CREATE TABLE categories (
id NUMBER PRIMARY KEY,
name VARCHAR2(30) UNIQUE
);
/
CREATE TABLE blogs_to_categories (
blog_id INTEGER NOT NULL
REFERENCES blogs(id),
category_id INTEGER NOT NULL
REFERENCES categories(id),
PRIMARY KEY (blog_id, category_id)
);
/

스토어드 프로시저와 레퍼런스 커서
blog.latest 프로시저에서, blogs 테이블의 여러 로우를 반복적으로 조회하기 위해 레퍼런스 커서(reference cursor)가 반환됨을 확인할 수 있습니다.


PHP에서 커서를 사용하기 위해서는 SELECT 구문을 통해 로우에 직접 접근하는 경우와 비교했을 때 두 단계의 추가 작업이 필요합니다. 먼저 oci_new_cursor() 함수를 이용하여 PHP에서 커서 리소스를 준비하는 과정이 필요합니다. 이렇게 준비된 커서 리소스는 매개변수를 바인딩하는데 활용됩니다. 두 번째로, SQL 구문을 실행한 다음 커서 리소스에 대해 oci_execute()를 호출해야 합니다.

아래 PHP 스크립트에서 절차를 확인할 수 있습니다:
<?php
$conn = oci_connect('SCOTT','TIGER') or die;


$sql = 'BEGIN blog.latest(:num_entries, :blog_entries); END;';

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

// Bind the input num_entries argument to the $max_entries PHP variable
$max_entries = 5;
oci_bind_by_name($stmt,":num_entries",$max_entries,32);

// Create a new cursor resource
$blog_entries = oci_new_cursor($conn);

// Bind the cursor resource to the Oracle argument
oci_bind_by_name($stmt,":blog_entries",$blog_entries,-1,OCI_B_CURSOR);

// Execute the statement
oci_execute($stmt);

// Execute the cursor
oci_execute($blog_entries);

print "The $max_entries most recent blog entries\n";

// Use OCIFetchinto in the same way as you would with SELECT
while ($entry = oci_fetch_assoc($blog_entries, OCI_RETURN_LOBS )) {
print_r($entry);
}
?>

스토어드 프로시저와 LOB

오라클 long 오브젝트를 스토어드 프로시저를 통해 전달하는 방법은 네이티브 SQL의 경우와 크게 다르지 않습니다.
아래 예제는 CLOB를 이용하여 blog.edit_entry프로시저를 호출하는 방법을 보여주고 있습니다. 이 예제에서는 id 매개변수에 값이 할당되지 않았으며, 따라서 새로운 blog 게시물의 INSERT 작업이 수행됩니다:

<?php
$conn = oci_connect('SCOTT','TIGER') or die;

$sql = 'BEGIN blog.edit_entry(:status, :status_msg, :id, :title, :text, :categories); END;';

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

$title = 'This is a test entry';

oci_bind_by_name($stmt,":status",$status,32);
oci_bind_by_name($stmt,":status_msg",$status_msg,500);
oci_bind_by_name($stmt,":id",$id,32);
oci_bind_by_name($stmt,":title",$title,200);

// Explained in the next example... (use an empty value for now)
$Categories = oci_new_collection($conn,'LIST_OF_NUMBERS');
oci_bind_by_name($stmt,':categories',$Categories,32,OCI_B_SQLT_NTY);

// Create a new lob descriptor object
$textLob = oci_new_descriptor($conn, OCI_D_LOB);

// Bind it to the parameter
oci_bind_by_name($stmt, ":text", $textLob, -1, OCI_B_CLOB);

// Execute the statement but do not commit
oci_execute($stmt, OCI_DEFAULT);

// The status parameter will be negative if the procedure encountered a problem
if ( !$status ) {
// Rollback the procedure
oci_rollback($conn);
die ("$status_msg\n");
}

// Save the body of the blog entry to the CLOB
if ( !$textLob->save('This is the body of the test entry') ) {
// Rollback the procedure
oci_rollback($conn);
die ("Error saving lob\n");
}

// Everything OK so commit
oci_commit($conn);
print $status_msg."\n";
?>

위 스크립트에서 확인할 수 있는 것처럼, LOB를 사용하는 과정에서 트랜잭션을 어떻게 처리할 것인지의 문제가 중요한 관건이 됩니다. 여기에서는 LOB 업데이트가 두 단계로 진행되는 프로세스임을 감안하여, PHP 스크립트가 모든 트랜잭션 처리를 담당하도록 하였습니다.

디폴트 환경에서, 오라클은 주어진 세션에서 동시에 하나의 트랜잭션 실행만을 허용한다는 점을 참고하시기 바랍니다. 따라서 PHP를 통해 프로시저 내부에서 커밋/롤백 구문을 실행하는 경우, oci_commit() 또는 oci_rollback()의 호출이 무시(override)됩니다. 프로시저 정의 부분에 PRAGMA AUTONOMOUS_TRANSACTION을 추가함으로써 “autonomous” 트랜잭션을 사용하고 이러한 디폴트 모드를 해제할 수도 있습니다. 다른 프로시저에서 로깅(logging) 패키지를 호출하는 경우 autonomous 트랜잭션을 사용할 수 잇을 것입니다. 이와 같이 하는 경우 세션 내부에서 실행 중인 트랜잭션의 간섭을 받지 않는 상태에서 스토어드 프로시저에 관련된 정보를 로그에 저장할 수 있습니다.

스토어드 프로시저와 컬렉션

컬렉션(collection)은 스토어드 프로시저에 컴플렉스 데이터 타입을 전달하기 위한 메커니즘으로 활용됩니다. 위의 블로그 애플리케이션 예제에서 하나의 블로그 엔트리를 여러 개의 카테고리에 포함시키려면 “blogs” 테이블과 “categories” 테이블 간의 N:N 관계가 필요합니다.

오라클의 컬렉션 타입은 데이터베이스에서 글로벌하게 선언되어야 합니다. 이 예제에서는 다음과 같은 정의를 사용하기로 합니다:
CREATE OR REPLACE TYPE list_of_numbers AS VARRAY(50) OF NUMBER;

위 구문은 blog.edit_entry 프로시저에 컬렉션 인스턴스를 전달함으로써, 하나의 블로그 엔트리를 최대 50 개의 카테고리에 포함시킬 수 있음을 의미합니다.

PHP에서는 사전 정의된 OCI-Collection PHP 클래스를 이용하여 컬렉션을 처리합니다. 이 클래스의 인스턴스는 oci_new_collection() 오브젝트가 제공하는 메소드가 다음과 같습니다:
append: 엘리먼트를 컬렉션 마지막 부분에 추가합니다
assign: 기존 컬렉션의 엘리먼트를 해당 컬렉션에 추가합니다
assignElem: 컬렉션에 값을 할당하고, 컬렉션에서의 엘리먼트 위치를 지정하기 위해 인덱스를 정의합니다
free: 컬렉션 핸들(collection handle)에 설정된 리소스를 해제합니다
getElem: 컬렉션의 특정 인덱스 위치로부터 엘리먼트를 가져옵니다
max: 컬렉션의 최대 엘리먼트 수를 반환합니다
size: 컬렉션의 현재 크기를 반환합니다
trim: 컬렉션의 마지막 부분에서부터 일정한 수의 엘리먼트를 제거합니다
H본 문서에서는 append 메소드를 사용하여 프로시저 호출 과정에서 카테고리 ID의 목록을 전달하는 방법만을 예시하기로 합니다. 아래 예제는 앞에서 생성한 블로그 게시물을 업데이트하기 위해 blog.edit_entry 프로시저에 ID와 카테고리 ID 리스트를 전달하는 방법을 보여주고 있습니다:
<?php
$conn = oci_connect('SCOTT','TIGER') or die;


$sql = 'BEGIN blog.edit_entry(:status, :status_msg, :id, :title, :text, :categories); END;';

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

$id = 1; // ID of the new entry
$title = 'This is a test entry (v2)';

oci_bind_by_name($stmt,":status",$status,32);
oci_bind_by_name($stmt,":status_msg",$status_msg,500);
oci_bind_by_name($stmt,":id",$id,32);
oci_bind_by_name($stmt,":title",$title,200);
$textLob = oci_new_descriptor($conn, OCI_D_LOB);
oci_bind_by_name($stmt, ":text", $textLob, -1, OCI_B_CLOB);

// Create an OCI-Collection object
$Categories = oci_new_collection($conn,'LIST_OF_NUMBERS');

// Append some category IDs to the collection;
$Categories->append(2);
$Categories->append(4);
$Categories->append(5);

// Bind the collection to the parameter
oci_bind_by_name($stmt,':categories',$Categories,-1,OCI_B_SQLT_NTY);

oci_execute($stmt, OCI_DEFAULT);

if ( !$status ) {
oci_rollback($conn);
die ("$status_msg\n");
}

if ( !$textLob->save('This is the body of the test entry [v2]') ) {
oci_rollback($conn);
die ("Error saving lob\n");
}

oci_commit($conn);
print $status_msg."\n";
?>

결론

지금까지 PHP에서 스토어드 프로시저를 호출하는 방법을 예제를 통해 설명하고, 스칼라 데이터 타입만을 이용한 단순한 프로시저와 LOB를 이용하는 보다 복잡한 형태의 프로시저를 사용하는 방법을 알아보았습니다. 또 PL/SQL 인터페이스를 사용하여 스토어드 프로시저를 정의하고, PHP에서 스토어드 프로시저를 호출한 후 타입 바인딩을 수행하는 방법을 이해하셨으리라 믿습니다.