| Developer: PHP
PHPfest Tutorial: Oracle Database 10g Express Edition and Zend Core for Oracle
by Christopher Jones and Alison Holloway
A step-by-step guide to installing Oracle Database XE and Zend Core for Oracle, as well as using the PHP OCI8 extension
Published March 2006
Installing Oracle Database XE and Zend Core for Oracle
This section covers the installation of:
We have tested the install and configuration with the following systems:
- 512MB Memory
- 5GB of hard drive space
- Red Hat Enterprise Linux (RHEL) 3
- RHEL 4
- Novell SUSE Linux Enterprise Server (SLES) 9
Oracle Database XE
Oracle Database XE is an entry-level, small footprint starter database. It is free to download, develop, and deploy, and you can freely distribute it with your applications. There are no database licencing costs!
Oracle Database XE is built using the same code base as Oracle Database 10g Release 2 products (Standard Edition and Enterprise Edition), and is available on 32-bit Windows platforms and Linux platforms.
Oracle Database XE is a good choice for:
- Developers working on PHP, Java, .NET and other applications that require a database
- DBAs who need a free starter database for training and deployment
- Independent Software Vendors (ISVs) who want to embed an Oracle database in their application or product
- Educational institutions and students who need a free starter database
Oracle Database XE includes the following programming interfaces:
- SQL, PL/SQL
- Java, C, and PHP
- Windows .NET
- Oracle Application Express
- C++, ODBC, OLE DB
Oracle Database XE has the following limitations:
- Up to 4GB of user data
- A single database instance
- A single CPU used
- 1GB RAM used
Oracle Database XE has a browser-based management interface, Oracle Application Express.
Support is provided through an OTN discussion forum via peers and product experts.
Installing Oracle Database XE
If you do not have a version of libaio over 0.3.96, you will need to install this library before you can install Oracle Database XE.
The installation procedure on the Oracle Database Developer CD for Linux x86 differs slighty to the procedure below. If you are installing from this CD, follow the instructions outlined in the document named Start_Here.html at the root of the CD.
To install Oracle Database XE:
- Log in or su as root
su
Password:
- Copy oracle-xe-10.2.0.1-1.0.i386.rpm to /tmp, then change directory to /tmp.
cp oracle-xe-10.2.0.1-1.0.i386.rpm /tmp
cd /tmp
- Install the RPM
rpm -ivh oracle-xe-10.2.0.1-1.0.i386.rpm
Oracle Database XE installs.
- Configure the database.
/etc/init.d/oracle-xe configure
- Accept the default ports of 8080 for Application Express, and 1521 for the Database Listener.
- Enter and confirm the password for the default users.
- Enter Y or N for whether you want the database to start automatically on reboot.
The database and database listener are configured and started.
Testing the Oracle Database XE Installation
To test the installation of Oracle Database XE:
- Open a web browser and enter:
http://127.0.0.1:8080/apex
- Log in as user system with the password you entered during the installation.
You should be logged into Oracle.
Zend Core for Oracle
In 2005, Oracle and Zend Technologies entered into a partnership to develop an integrated solution designed to help developers build and deploy database-driven Web applications based on the popular PHP scripting language.
In October 2005, Oracle and Zend announced the general availability of Zend Core for Oracle (ZCO). ZCO delivers a fully tested and supported PHP 5 distribution that includes integration with Oracle Database 10g client libraries and enables developers to get up and running in minutes with PHP and Oracle.
ZCO is a pre-built stack for PHP developers that makes it easier for them to develop and deploy applications on a robust, scalable and reliable infrastructure. The product includes tight integration with Oracle Database client libraries, and native support for XML and Web services, while also supporting increased adoption of service-oriented architecture (SOA). It delivers a rapid development and deployment foundation for database-driven applications.
Oracle also offers a PHP Developer Center on Oracle Technology Network dedicated to PHP development with Oracle.
The collaboration between Oracle and Zend reinforces Oracle's commitment to the open source PHP community. Oracle's newly introduced and optimized OCI-8 extension for PHP will be submitted back to the PHP community and integrated into ZCO.
This collaboration expands Oracle's investment in open source by helping developers more effectively create and deploy database-driven applications.
Support for ZCO and for PHP is provided by Zend.
Installing Zend Core for Oracle
To install ZCO:
- Log in or su as root if you are not already.
su
Password:
- Copy ZendCoreForOracle-v1.3.1-Linux-x86.tar.gz to /tmp, then change directory to /tmp.
cp ZendCoreForOracle-v1.3.1-Linux-x86.tar.gz /tmp
cd /tmp
- Extract the contents of the downloaded Zend Core for Oracle software:
tar -zxf ZendCoreForOracle-v1.3.1-Linux-x86.tar.gz
Files are extracted to a subdirectory called ZendCoreForOracle-v1.3.1-Linux-x86.
- Change directory to ZendCoreForOracle-v1.3.1-Linux-x8 and start the Zend Core for Oracle installation:
cd ZendCoreForOracle-v1.3.1-Linux-x86
./install
- In the initial Zend Core for Oracle Installation page, click OK.
- In the Zend Core for Oracle V.1 page, click Exit.
- When prompted to accept the terms of the license, click Yes.
- If you have an existing version of PHP installed, you are prompted to back up and overwrite the existing php.ini file. Click Yes.
- When prompted to specify the location for installing Zend Core for Oracle, accept the default (or enter your preferred location), and click OK. The installer begins extracting the files required for the installation.
- When the progress window indicates that all the software has been installed, you are prompted to "Please enter the GUI password." In the Password field, enter the password you want to use when accessing the Zend Core Console, and click OK.
- When prompted to "Verify the password," enter the same password as specified in the previous step and click OK.
- In the Zend Core support page, you may optionally enter your Zend network user ID and password to be able to use the Zend Core Console to track when updates to Zend Core and PHP components are available. If you have not registered, or do not want to track updates, click No.
- The next page prompts you to select the Web server for Zend Core installation. Select the default Apache installed with Linux. Click OK.
- In the page confirming your Web server selection, at the "Do you wish to proceed?" prompt, click Yes.
- In the next installation page, you are prompted to "Please select an installation method for Apache 2.0.52." Select Apache module as the method, and click OK.
- In the next installation page, when you are prompted to "Please select a virtual server for the Zend Core GUI," select Main Server, and click OK.
- In the next installation page, at the "Would you like to restart the Web Server" prompt, click Yes.
- In the next installation page, a notice is displayed stating that the apachectl script has been updated. Click OK.
- The next installation page (containing "Thank you for installing Zend Core for Oracle") lists useful configuration commands and a Web page for the administration of the Zend Core engine. Take note of the information and click Exit.
- A final confirmation page is displayed. Click OK to finish the installation.
The Zend Core for Oracle installation is now complete.
Configuring Zend Core for Oracle
In this section, you configure environment variables and Zend Core directives that control default error reporting in Web pages.
- Enter the following URL in a Web browser to access the Zend Core Administration page:
http://127.0.0.1/ZendCore
- Enter the GUI password that you provided during Zend Core for Oracle installation. Click the login >>> icon.
- Click the Configuration tab to display the configuration options.
- Click the + icon to expand the Error Handling and Logging configuration entry.
- Set the display_errors directive to On to enable the display of errors in the HTML script output during development.
- Because there are unsaved changes, the "Unsaved configuration" message appears under the page header. Click Save Settings to save the configuration change.
- Because you have made configuration changes, you must restart the Apache Web server. Under the page header notice the "Please Restart Apache" message reminding you to do so. Click Restart Server to restart the Apache server.
- Click Logout to exit the Zend Core for Oracle Administration page.
Testing the Zend Core for Oracle Installation
To test the ZCO installation:
Add info on creating a virtual directory to user's home directory.
- Create a public virtual directory as public_html. Edit APACHE_HOME/conf/httpd.conf and remove the "#" from the following line:
#UserDir public_html
- As your normal user (not root), create a directory called public_html in your home directory, and change directory to the newly created directory, enter the following commands in a command window:
cd $HOME
mkdir public_html
cd public_html
- Create a file called hello.php that contains the following PHP code:
<?php
echo "Hello world!";
?>
- Open a Web browser and enter the following URL in your browser:
http://127.0.0.1/~<username>/hello.php
The line "Hello world!" appears in the browser.
The PHP OCI8 Extension
The PHP oci8 extension provides PHP functions for accessing Oracle databases. It was recently re-factored for stability and performance by Zend and Oracle. This new version can be installed in several ways. It is included in the easy to install Zend Core for Oracle bundle. It is included in the PHP source code and Windows binaries. It is also available by itself as source code from PHP Extension Community Library (PECL) and for Windows from http://pecl4win.php.net/ext.php/php_oci8.dll.
The new extension is designed to work with both PHP 4 and PHP 5.
Querying Oracle is as simple as fetching the data and displaying it.
<?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>';
?>
This example connects as Oracle's demonstration user HR (short for Human Resources) and finds all cities in the LOCATIONS table.
In PHP 5, the oci8 function names were standardized. PHP 4 names like OCILogin() became oci_connect() etc. The old names are still available. One side effect is that user contributed comments in the PHP manual for the same functionality are found in two places. They are either on the page for the old or the new syntax. Where a function has a synonym, check both manual pages for some good, up-to-date user tips.
Connecting to Oracle
To connect to Oracle you can use PHP's oci_connect() call:
$c = oci_connect($username, $password, $dbname)
Each oci_connect() connection is stored in a cache. When a second oci_connect() occurs in the same script, the previously cached connection is returned. The cache is cleared when the script finishes.
Oci_new_connect() gives a totally independent connection. Each connection is separate from any other. This lets you do more than one database transaction at the same time:
$c = oci_new_connect($username, $password, $dbname)
Persistent connections are not automatically closed at the end of PHP scripts. They remain open for reuse in other scripts:
$c = oci_pconnect($username, $password, $dbname)
This is good if the cost of opening a connection is high. What you consider high depends on your application requirements, and on implementation issues such as whether the web server and database are on the same host.
The drawback is a persistent connection uses Oracle resources even when no one is accessing the application or database. And if Apache spawns a number of server processes each of them may have its own set of connections to the database. You can tune the resource usage of persistent connections with php.ini parameters.
- oci8.max_persistent: this limits the number of persistent connections cached. When the limit is reached, all oci_pconnect() calls are treated like oci_connect() calls. Setting it to -1 (the default) means there is no limit.
- oci8.persistent_timeout: the length in seconds an Apache process maintains an idle persistent connection. The expiry check happens whenever a PHP script finishes regardless of whether the script calls oci8 functions. Setting it to -1 (the default) means there is no timeout. If a connection has been expired, oci_pconnect() will create a new connection.
- oci8.ping_interval: the number of seconds that pass before oci8 does a ping during oci_pconnect(). When set to 0, PHP pings the database each time oci_pconnect() is called. To disable pinging, set the value to -1. The default value is 60 seconds. If the ping determines the connection is no longer usable, a new connection is created.
Good application design would transparently recover from most failures. Although systems like Oracle are stable, in any application there are a number of potential points of failure including the network, the hardware and user actions such as shutting down the database. Oracle itself may be configured to close idle connections. The DBA may have installed user profiles with CREATE PROFILE IDLE_TIMEOUT. Or the Oracle Net layer may time out the network.
Oci_pconnect() will always check an Oracle client-side setting to see if the server was known to be available last time anything was received from the server. This is a quick operation. Setting oci8.ping_interval additionally physically pings the server, causing what is called a "round-trip" over the network and is a "bad thing" (tm) for scalability. Since there still is the possibility the database or some data becomes unavailable between the connection check and when you actually use the connection, for highest availability and scalability we generally recommend you don't use oci8.ping_interval but do error recovery in your application code.
Of course many smaller applications can benefit from the increased ease of use of the ping functionality.
Make sure that you understand the life time of your applications connections. Reuse connections where possible, but don't be afraid to create new connections and close them as needed. Each connection will take some Oracle memory, so overall load can be reduced if idle connections are closed.
Connection Strings
The connection string seems to cause the most confusion. The common generic Oracle error "ORA-12514 TNS:listener does not currently know of service requested in connect descriptor" leads to hours of frustration.
The DB name can be one of:
- Easy Connect string
- Full connection string
- tnsnames.ora alias
Easy Connect String
The Easy Connect string is JDBC-like. It specifies the hostname, the port number and the Oracle database service name:
//hostname:port/service_name
For Oracle Database XE using the default port of 1521, simply use '//localhost/XE'.
$c = oci_connect('hr', 'hrpw', '//localhost/XE');
You must have Oracle 10g client-side libraries to use the Easy Connect syntax. ZCO has the appropriate Oracle libraries.
Full Hard Coded Connection String
The full Oracle Net connection string gives total flexibility over the connection.
$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);
When in doubt, copy the connection string used by other Oracle tools and users.
By using the full syntax you can enable Oracle Net features like load balancing and tweak packet sizes. The Easy Connect syntax doesn't allow this flexibility.
Network Alias from a Tnsnames.ora File
You can store the full connection string in a file called tnsnames.ora and refer to it in PHP via an alias
# tnsnames.ora
MYDB2 = (DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)
(HOST = mymachine.mydomain)(PORT = 1521))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = MYDB.AU.ORACLE.COM)))
In PHP you can then connect with:
$c = oci_connect($un, $pw, 'MYDB2');
PHP needs to be able to find the tnsnames.ora file to resolve the alias 'MYDB2'. This is a common problem.
In an "ORACLE_HOME"-style install (e.g. XE), the default tnsnames.ora is in:
$ORACLE_HOME/network/admin/tnsnames.ora
i.e.
/usr/lib/oracle/xe/app/oracle/product/10.2.0/server/network/admin/tnsnames.ora
Whether you use the default location or another, Apache must be able to find the directory when it starts. Typically (including in ZCO) this is done by setting the TNS_ADMIN environment variable.
Oracle Environment Variables
The oci8 extension always needs to find Oracle libraries and message files. Not finding the correct files can lead to PHP returning an Oracle "ORA-12705: Cannot access NLS data files or invalid environment specified". If you have multiple installations of Oracle you can get conflicts. ZCO contains its own local copy of Oracle Instant Client which has the Oracle libraries and messages so you shouldn't see this error.
The NLS_LANG (globalization) and TNS_ADMIN (network) variables are most likely to be wanted for ZCO.
Environment variables must be set in the environment that starts Apache so when the oci8 extension is first loaded it has access to the values. Do NOT set environment variables in a PHP script with putenv(). The resulting behavior is not consistent for all oci8 functions. It is definitely not portable.
ZCO modifies apachectl and adds LD_LIBRARY_PATH. (This may not be required in a future version of ZCO if Oracle ship Instant Client linked differently). This allows ZCO GUI Console to be reused to start Apache.
You may need to do something similar with TNS_ADMIN and NLS_LANG for ZCO, or if you start Apache manually, set the environment in a calling script:
#!/bin/sh
TNS_ADMIN=/usr/local/apache/conf
export TNS_ADMIN
echo Starting Apache
#export > /tmp/envvars
/usr/local/apache/bin/apachectl start
This example assumes /usr/local/apache/conf/tnsnames.ora exists. TNS_ADMIN points to the directory containing the tnsnames.ora file.
Closing a Connection
At the end of each script, connections opened with oci_connect() or oci_new_connect() are automatically closed. You can also explicitly close them by calling:
oci_close($c);
Any uncommitted data is rolled back.
If a long running script only spends a small amount of time interacting with the DB, you may want to close connections to free resources for other DB users.
Connections opened with oci_pconnect() will not be closed by oci_close(). This is similar to the way persistent resources work in other PHP extensions.
Oci_close() works by reference counting. Only when all references to the connection are finished will the connection actually be closed. In this example $c1 and $c2 are the one connection, but only at the end of script is the database connection closed.
$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);
The oci_close() function was a no-op prior to the re-factoring of oci8. You could not explicitly close connections even if you wanted to. This has changed but you can revert to the old behavior if necessary by setting oci8.old_oci_close_semantics in php.ini. Set this to 1 to make oci_close() a no-op again. The default value 0 means oci_close() will close a connection.
Executing Statements
Queries in oci8 follow a model familiar in the Oracle world: parse, execute and fetch. Statements like CREATE and INSERT require only parsing and executing. Nowadays parsing is really just a "prepare" step, since Oracle's actual parsing can occur at the execute stage.
You can optionally "bind" local values into a statement similar to the way you use "%s" print format specifiers in strings. This improves performance and security.
You can also "define" where you want the results to be stored. Most users let the oci8 fetch functions do this for them.
The possible steps are:
- Parse - Prepare a statement for execution
- Bind - Optionally lets you bind data values e.g. in the WHERE clause, for better performance and security
- Define - Optional step allowing you to specify which PHP variables will hold the results. (Uncommon?)
- Execute - The database processes the command and buffers the results.
- Fetch - Get query results back from the database. Oci8 has a bunch of fetch statements to choose from.
There is no one-stop function to do all this in one PHP function call, but it is trivial to create on in your application and lets you add custom error handling requirements.
Queries
A basic query in oci8 is:
$stid = oci_parse($c, 'select * from locations');
oci_execute($stid, OCI_DEFAULT);
oci_fetch_all($stid, $res);
Make sure to use double quotes where necessary:
$stid = oci_parse($c, "select * from locations where city = 'Sydney'");
There are a number of fetch functions, all carefully documented in the PHP oci8 reference manual.
- oci_fetch_all(): get all results at once
- oci_fetch_array(): get next row as an array of your choice
- oci_fetch_assoc(): get next row as an associative array
- oci_fetch_object(): get new row as an object
- oci_fetch_row(): get next row as an integer indexed array
- oci_fetch(): used with oci_result(), which returns the result of a given field
Functions that fetch a single row need to be called repeatedly:
$stid = oci_parse($c, "select city from locations");
oci_execute($stid, OCI_DEFAULT);
while ($res = oci_fetch_row($stid)) {
echo $res[0] . "<br>\n";
}
Some of the functions have optional parameters that alter their behavior, for example oci_fetch_array() has options to specify whether results are returned as an associative array or indexed numerically (or both!). Associative arrays are keyed by the uppercase column name.
For unknown, historical reasons some of the PHP 4 fetch functions don't return NULL data by default. For these, specify you want NULLs returned otherwise your results may not appear to have all the columns you selected.
You can tune PHP's overall query performance with two php.ini parameters:
- oci8.default_prefetch: the number of records to be returned by Oracle when each database fetch occurs. The default value is 10. Tuning this setting can significantly improve performance of queries that return a large number of rows. It minimizes database server "round-trips" by returning as much data as possible each time. Oracle will cache the data in its client buffers and give PHP only the rows PHP itself requests.
- oci8.statement_cache_size: enables OCI client statement caching. The default is 20 statements. Caching can be disabled by setting the value to 0. The client-side statement cache means even the text of the statement does not need to be transmitted to the database, further reducing network traffic and database server load. The cache is per-Oracle session so this feature is most likely to be useful when persistent connections are used.
Insert/Update/Delete/Create/Drop
Executing statements like CREATE and INSERT simply require a parse and execute:
$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);
Only one-off configuration sections of applications should be using CREATE TABLE. Some users think applications need to create temporary tables but Oracle doesn't have the same limitations as other database.
Create temporary tables once before your application is run. Tom Kyte talks about global temporary tables on asktom.oracle.com.
In the Oracle database, creating and dropping tables will automatically do a commit of all uncommitted data. This cannot be changed.
Transactions
Using transactions to protect the integrity of data is as important in PHP as any other relational application. Except in rare cases you want either all your data to be committed, or none of it.
In previous examples we specified OCI_EXECUTE flag at execution.
$r = oci_execute($s, OCI_DEFAULT);
The default mode of oci_execute() is OCI_COMMIT_ON_SUCCESS, which does as it suggests. But unnecessarily committing impacts database performance as it causes unnecessary network traffic and wasteful I/O to the database files. This is why OCI_DEFAULT is preferred.
The PHP manual for oci_execute() puts it succinctly:
When using OCI_DEFAULT mode, you're creating a transaction. Transactions are automatically rolled back when you close the connection, or when the script ends, whichever is soonest. You need to explicitly call oci_commit() to commit the transaction, or oci_rollback() to abort it.
In this example, if we don't explicitly commit, the second row will be rolled back at the conclusion of the PHP script. To insert precisely these two rows, we should not commit the first one but should commit after the second - exactly the opposite of this example.
$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);
Error Handling
The error handing of any solid application adds complexity and requires careful design. Expect the unexpected. Check all return codes.
Passing in the correct resource to oci_error() is necessary to get the Oracle error message:
$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);
}
Bind Variables
Bind variables are just like "%s" print format specifiers. They let you re-execute a statement with different values for the variables and get different results. Binding is highly recommended.
They can improve overall database throughput. Oracle can reuse any cached execution plan for the statement, even if someone else originally executed it.
Bind variables are also an important way to prevent SQL Injection security attacks. User data is always treated as data and never as part of the SQL statement.
$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";
The bind data needs to be accessible when oci_execute() is called. Using a local variable in a sub function may cause a scope problem.
As well as "IN" binds which pass data into Oracle, there are also "OUT" binds that return values. These are mostly used to return values from PL/SQL procedures and functions.
The oci_bind_by_name() function takes optional size and data type parameters.
PHP numbers are converted to and from strings when they are bound. This means the length parameter must generally be given to OCIBindByName() when returning a numeric data value in a bind variable. The length is the number of digits that will be returned.
There is one case where you might decide not to use bind variables. When statements contain bind variable the optimizer does not have any information about the value you may eventually use. If your data is highly skewed you might want to hard code values. But if the data is derived from user input be sure to sanitize it.
A lot of the older documentation uses "&" in calls to oci_bind_by_name(). Don't do this. Since the recent PHP call-by-reference clean up this syntax has been deprecated. I've also see it cause problems.
PL/SQL Stored Procedures
PL/SQL is Oracle's procedural language. Originally modeled on ADA, it is a strong and complete language. You can embed SQL statements right in it. It executes in the database server, which has some implications for I/O.
When deciding whether to write PHP on the client or PL/SQL in the server, consider your skill levels in the languages, the cost of data transfer across the network and the re-usability of the code. If you write in PL/SQL all your Oracle applications in any tool or client language can reuse the functionality. Some functionality should only ever be in the database, such as event triggers. In Oracle you can create these to be fired when event such as a data insert or a user logon occurs.
There are a number of pre-supplied PL/SQL packages to make coding easier and utilities like DBMS_JOB for scheduling. You can create stored procedures, functions and packages so your applications can be extended infinitely. PL/SQL allows highly functional applications to be created.
To call a previously created PL/SQL procedure, use an "anonymous block". This is just a BEGIN/END pair with PL/SQL statements inside. In this example it is a single procedure call. You can use any PL/SQL statements you want inside the block.
$stid = oci_parse($c, "begin myproc('mydata', 123); end;");
oci_execute($stid, OCI_DEFAULT);
Note there is a semi-colon after "end" which is different to the way SQL statements are written.
PL/SQL REF CURSORS
REF CURSORS are another important feature of PL/SQL. You can store a set of query results in a single variable of REF CURSOR type and pass it like a normal variable. In PHP you bind a OCI_B_CURSOR variable to a PL/SQL procedure call and retrieve the rows of the result set in a normal fetch loop.
As an example we create a PL/SQL package with a procedure that accepts a job identifier and queries the employees who perform that job. The procedure returns a REF CURSOR containing the employee identifiers and salaries.
In the package specification the a ref cursor type and signature of the procedure is given. The package body contains the code of the function.
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;
/
In PHP we would use this PL/SQL procedure like:
$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>';
Large Objects (LOBS)
Oracle Character Large Object (CLOB) and Binary Large Object (BLOB) columns (and PL/SQL variables) can contain very large amounts of data. There are various ways of creating them to optimize Oracle storage. There is also a pre-supplied package DBMS_LOB that makes manipulating them in PL/SQL easy.
Similar to using REF CURSORS, we tell PHP that we want a variable of LOB type, and then bind it into our SQL or PL/SQL statement. From then, we have some special oci8 functions that can be used to access the data.
To upload LOB data into a table:
$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);
We manipulate LOBs via a PHP descriptor. These map to an Oracle LOB locator.
If the lob had been uploaded via a web form, it could be uploaded directly from the upload-directory file with $lob->savefile($filename). The maximum allowed size for uploaded files can be set in php.ini with upload_max_filesize.
When fetching a LOB, oci8 returns the LOB descriptor and the data is retrieved by using a load() or read() method:
$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();
A number of other methods on the LOB descriptor allow seeking to a specified offset, exporting data directly to file, erasing data, and copying or comparing a LOB.
This code snippet show seeking to the 10th position in the result descriptor, and then storing the next 50 bytes in $result
$arr['BLOBDATA']->seek(10, OCI_SEEK_SET);
$result = $arr['BLOBDATA']->read(50);
Using CLOBs is almost exactly identical to BLOBs. The bind type becomes OCI_B_CLOB and the table must obviously contain a CLOB column.
Oracle Collections
The Oracle manual says: "A collection is an ordered group of elements, all of the same type." They are effectively arrays.
Similar to LOBs, collections are manipulated by methods on a collection resource which is allocated with oci_new_collection().
In a simple email address book demonstration (created by Charles Poulsen from Oracle), two VARRAYs are created, one for an array of people's names, and one for an array of email addresses. VARRAYs (short for variable-size arrays, the Oracle manual tells us), use sequential numbers as subscripts to access a fixed number of elements.
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;
/
The update_address_book() procedure loops over all elements of the address collection and inserts each one.
In PHP we create collection variables and use the append() method to add elements to each array. By binding as OCI_B_NTY ("Named Type") we can pass collections to the PL/SQL procedure arguments.
$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);
Other PHP collection methods allow accessing or copying data in the collection.
PHP 5.1.2 improved collection support with a new function oci_bind_array_by_name(). Coupled with a helper PL/SQL function, this can be very efficient for insertion. We can bind a PHP array containing all data and send it to the database with a 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;
/
To insert a PHP array into MYTAB, use:
$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();
The oci_bind_array_by_name() function is similar to oci_bind_by_name(). It takes not just the upper data length but also the number of elements in the array. In this example the number of elements is 5 and the data length is given as -1 meaning use the actual length of the character data.
The inserted data is:
SQL> select * from mytab;
NAME
--------------------
abc
def
ghi
jkl
mno
A number of other Oracle types can be bound. The PHP manual mirrors are only just getting the oci_bind_array_by_name() documentation. A source of information is from reading the automated tests under the oci8 tree in CVS. These are also bundled with the PHP source code.
Globalization
Globalization of Web applications requires careful planning. Data must be in the correct character set, HTML pages must be translated and encoded correctly, and conventions that the user expects should be observed. The Oracle Database Express Edition 2 Day Plus PHP Developer Guide has a comprehensive overview on globalization.
Oracle databases are created with a specified character set. PHP can choose its own character set by setting the NLS_LANG environment variable. The Oracle client (aka PHP) globalization setting also specifies default values such as the appropriate date format for the locale, what language Oracle error messages use, and the linguistic sorting order. NLS_LANG should be set in the environment that starts the web server. It has the form:
<language>_<territory>.<character set>
For example, a German user in Germany running an application in Unicode, should have NLS_LANG set to:
GERMAN_GERMANY.AL32UTF8
The character set may also be set per-connection with an optional fourth parameter to oci_connect(). The character set is a string containing an Oracle character set name, e.g. 'ja16euc'.
$c = oci_connect("hr", "hr", "//localhost/XE", 'ja16euc');
When not specified or NULL, the NLS_LANG environment variable setting is used.
The client character set determines how Oracle translates data when it is transferred from the database to PHP. If the characters sets are not equivalent some data may get converted abnormally.
It is up to your application to handle returned data correctly, perhaps by using PHP's mb_string functionality.
Using XML
Do yourself a service and explore SQL and PL/SQL. Make maximum reuse of functionality that already exists. Tom Kyte's popular asktom.oracle.com has a lot of very useful information.
Oracle's general guideline is to let the database manage data and to transfer the minimum amount across the network. Avoid shipping data from the database to PHP for unnecessary post processing. Data is a core asset of your business. It should be treated consistently across your applications. Keeping a thin interface between your application layer and the database is also good programming practice.
Some examples of useful database features are regular expressions, XML functions, analytic functions, autonomous transactions and spatial functionality.
Both Oracle and PHP 5 have excellent XML capabilities allowing lots of scope for processing information. All editions of Oracle contain what is known as Oracle XML DB (or XDB), the XML capabilities of the database.
When tables are created, XML can be stored in linear LOB format or according to the structure of your XML schema.
One useful feature is that relational SQL tables can automatically be retrieved as 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)." ";
Watch out for the quoting of XML queries.
The PL/SQL package DBMS_XMLGEN() is another way to create XML from relational data. Queries that use DBMS_XMLGEN() return a CLOB column, so the initial result needs to be treated as a LOB descriptor:
$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
The value in $mylob is:
<?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
Thanks for reading this guide; we hope it was helpful!
Christopher Jones and Alison Holloway are members of Oracle's PHP development team. You may provide feedback about this guide at their blogs.
|