Welcome, Reviewer!
Reviewer's Guide: Oracle Application Server 10g Standard Edition One
Home Instant Information Sharing Complete Web Site Easy Development Simplified Management
Site Map
Key Features
Demonstrations
Changing the Home Page
Collateral
PHP Extension
Python Extension
White Paper
Technical FAQs
Oracle HTTP Server
OracleAS Web Cache
Oracle and PHP
Oracle PHP Troubleshooting
Oracle PHP Troubleshooting Frequently Asked Questions
 

Topics

  • Oracle not installed or not found
  • Setting Oracle environment variables prior to starting the Web server
  • Connecting to a database
  • Managing Connections
  • Echoing SQL statements to check they are well formed
  • Testing return codes from database function calls
  • Inserting strings containing quotes
  • Fetching results using associative arrays
  • Array fetching in PHP to improve performance
  • Using Oracle bind variables in PHP
  • Uploading LOBS
  • Upgrading to PHP 5
  • Oracle error messages in PEAR DB
  • OCI thread safety
  • External and operating system authentication
  • Authentication with AS SYSDBA or AS SYSOPER
  • NCHAR and NCLOB support in PHP
  • Oracle not installed or not found

    If Oracle support is enabled in PHP, but the Oracle client libraries can not be found, you will get an error when trying to start Apache. For example, on Windows if php.ini has extension=php_oci8.dll, but the Oracle home cannot be found, the alert "The dynamic link library OCI.dll could not be found in the specified path" is displayed.

    Make sure the Oracle directory is readable by the OS user starting the Web server.

    Make sure the Oracle environment variables are set correctly before starting Web server (see the next topic).

    Linux users might see an Apache error about being unable to load libclntsh.so, but are more likely to notice the problem earlier when compiling PHP. The compiler will fail with the error "Cannot find file ocidfn.h" or "Cannot find file oci.h." If you have Oracle installed but are missing the Oracle header files, do a "Client" install of Oracle. In Oracle 9i, download the Oracle9i Database Release 2, run the installer, and choose the Client option. In Oracle 10g, download the Oracle Database 10g Client Release, run the installer, and choose the Administrator option.

    Also see the OTN articles Installing Oracle, PHP, and Apache on Windows 2000/XP and Installing Oracle, PHP, and Apache on Linux.

    Setting Oracle environment variables prior to starting the Web server

    Setting all Oracle environment variables before the Web server starts is the only safe way to get PHP talking to Oracle. Setting the variables in PHP scripts or the httpd.conf file mostly does not work. There are numerous mail list and forum posts because of general confusion about the environment. The behavior on Windows and Linux also differs.

    The environment needs to be set so PHP can find Oracle's libraries and message files. Environment variables may also need to be set to identify the default database or to locate configuration files for remote database access.

    An example shell script for starting Apache is start_apache :

            #!/bin/sh
    
            ORACLE_HOME=/u01/app/oracle/product/10.1.2
            ORACLE_SID=orcl
            export ORACLE_HOME ORACLE_SID
            export LD_LIBRARY_PATH=${ORACLE_HOME}/lib:${LD_LIBRARY_PATH}
            echo "Oracle Home: $ORACLE_HOME"
            echo "Oracle SID:  $ORACLE_SID"
            echo Starting Apache
            #export > /tmp/envvars
            ./apachectl start

    Depending on where your database server is and how you connect to it, you may also want set TWO_TASK or TNS_ADMIN (see the next topic).

    Apache, PHP, and Oracle error messages that use phrases like "could not load" or "could not resolve" all point to environment setup problems. If you are having problems with the environment, try uncommenting the "export" line in start_apache. After the script is run, the file /tmp/envvars will contain the environment variables Apache knows about. This is useful to solve problems starting Apache at machine boot time—the environment may be very different from starting Apache in your user shell.

    If Apache starts but gives errors about Oracle libraries, and/or OCI8 function calls fail, try looking at PHP's environment. Create the following script "phpinfo.php" where your Web server can read it and load it in a browser:

            <?php
              phpinfo();
            ?>

    Triple-check the environment and path configuration. On UNIX, check that LD_LIBRARY_PATH (or equivalent) contains $ORACLE_HOME/lib. On Windows, the PATH variable may need to contain %ORACLE_HOME%\bin.

    Some messages such as the run-time "Warning: ocilogon(): _oci_open_session: OCIHandleAlloc? OCI_HTYPE_SVCCTX: OCI_INVALID_HANDLE" are also symptoms of an incorrect ORACLE_HOME.

    If you have compiled PHP with an ORACLE_HOME that is different from the one being used at run time, be sure to follow the steps carefully as described in Using PHP with OHS. If OCILogon() gives "ORA-12705 The NLS parameter value specified in an alter session statement is not valid or not implemented", try setting ORA_NLS33 to the NLS data file directory in the ORACLE_HOME used to compile PHP. In Using PHP with OHS, this is called $RDBMS_ORACLE_HOME so set ORA_NLS33 to $RDBMS_ORACLE_HOME/ocommon/nls/admin/data.

    The author shares his experience:

    I did some tests to see what effect the basic environment in a standard installation had on a PHP login call:

          $mycon = OCILogon("myusername", "mypassword", "MYDB");

    I used Red Hat Linux AS 2.1, Apache 1.3, and PHP 4.3.3.

    1. With no ORACLE_HOME set prior to "apachectl start" and no "putenv('ORACLE_HOME=/usr/oracle/MYDB')" in the PHP script I got:
          Warning: ocilogon(): _oci_open_server: Error while trying to retrieve text for error ORA-12154

    This shows the connection failed, and the message files (which are located under the Oracle home directory) could not be found.

    1. With "putenv('ORACLE_HOME=/usr/oracle/MYDB')" in the PHP script but no ORACLE_HOME set, I got:

      Warning: ocilogon(): _oci_open_server: ORA-12154: TNS:could not resolve service name

      The connection still failed, but now the correct message text could be read from the message file.

    2. With ORACLE_HOME set prior to "apachectl start" but without "putenv()" the connection succeeded. This is the recommended configuration.

    3. With ORACLE_HOME set correctly prior to "apachectl start" and with "putenv()" using an invalid ORACLE_HOME directory, the connection succeeded. I also tried this on Windows. This time the connection failed with the same message as my first test above.

    I had a similar set of results when I replaced the PHP putenv() call with an Apache httpd.conf directive "setenv ORACLE_HOME /usr/oracle/MYDB".

    However some variables can be set in your PHP scripts. After setting ORACLE_HOME correctly before starting Apache the following code changed my default connection and connected to MYDB:

          putenv("TWO_TASK=MYDB");
          $mycon = OCILogon("myusername", "mypassword");

    The environment variables TNS_ADMIN, NLS_DATE_FORMAT (and there may be others) can be set this way too.

    Back to Top

    Connecting to a database

    1. A user-chosen net service name is often used to identify which database to connect to. By default, it is read from the environment variable ORACLE_SID or it can be explicitly given in the connection call. The net service name MYDB could be used in Oracle's command-line SQL*Plus utility, for example:

             sqlplus myusername/mypassword@MYDB

      or used in PHP like:

             $mycon = OCILogon("myusername", "mypassword", "MYDB");

      The net service name is commonly mapped to an actual database by an entry in a tnsnames.ora file :

             MYDB =
                (DESCRIPTION =
                  (ADDRESS = (PROTOCOL = TCP)(HOST = mymachine.mydomain)(PORT = 1521))
                  (CONNECT_DATA =
                    (SERVER = DEDICATED)
                    (SERVICE_NAME = MYDB)
                  )
                )

      The default file location is $ORACLE_HOME/network/admin/tnsnames.ora. On some operating systems other locations will be checked if this does not exist. If your tnsnames.ora is in a non-standard location, you can set the environment variable TNS_ADMIN to the directory containing it. For example, if you are using /home/myuser/tnsnames.ora, add these lines to start_apache (see the previous topic):

              TNS_ADMIN=/home/myuser
              export TNS_ADMIN

      If the net service name used in OCILogon() cannot be found in the tnsnames.ora, or the tnsnames.ora is not found at all by PHP, you may get an error when logging in:

              Warning: ocilogon(): _oci_open_server: ORA-12154: TNS:could not resolve service name

      The error ORA-12154 can also occur if you have a file $ORACLE_HOME/network/admin/sqlnet.ora and it specifies a NAMES.DEFAULT_DOMAIN value. Unqualified net service names in OCILogon() calls will have sqlnet.ora's value for NAMES.DEFAULT_DOMAIN appended to the alias. For example if sqlnet.ora had:

              NAMES.DEFAULT_DOMAIN = au.oracle.com

      then 'OCILogon("myusername", "mypassword", "mydb")' causes Oracle to look for the alias "MYDB.AU.ORACLE.COM = . . ." in tnsnames.ora. A quick solution is to change the tnsnames.ora entry to:

              MYDB.AU.ORACLE.COM =
                (DESCRIPTION =
                  (ADDRESS = (PROTOCOL = TCP)(HOST = mymachine.mydomain)(PORT = 1521))
                  (CONNECT_DATA =
                    (SERVER = DEDICATED)
                    (SERVICE_NAME = MYDB)
                  )
                )
    2. If Oracle 10g libraries are used by PHP, try the new Easy Connect syntax. No tnsnames.ora file is required. To connect to the MYDB database service running on mymachine:
         $c = OCILogon('myusername', 'mypassword', '//mymachine.mydomain/MYDB');

      This will only work if the PHP-side libraries are from 10g. If the database server is 10g but PHP is using Oracle 9i libraries, it will not work. See Oracle's Using the Easy Connect Naming Method documentation for the Easy Connect syntax.

    3. If Easy Connect has been disabled or you are using Oracle 8i or 9i libraries, another alternative is to use the full connection string:

               $db = "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)
                            (HOST = mymachine.mydomain)(PORT=1521))
                            (CONNECT_DATA=(SERVER=DEDICATED)
                            (SERVICE_NAME=MYDB)))";
      
               $mycon = OCILogon("myusername", "mypassword", $db);

      Back to Top

    Managing Connections

    Optimizing database connections in a frequently used script can improve performance and prevent resource problems. Some tips are:

    • Avoid multiple connections to Oracle in a single script.
    • If you have to make multiple connections, use different tnsnames.ora aliases, even when you want to connect to the same Oracle instance with a different user. This will avoid PHP context conflicts since each connection will use a distinct TCP link to the database.
    • Using the persistent OCIPLogon() call minimizes the number of physical connection requests to the database. It has some limitations. If the DBA terminates the session with ALTER SYSTEM KILL SESSION or (worse) terminates the Oracle shadow process with an operating system kill command, OCIPLogon() may succeed, but subsequent OCI8 calls may still fail. This can be timing dependent. When reusing a persistent connection, always check its validity at the start of the script with an explicit query. If you receive an Oracle error then the connection is no longer valid and you may need to logoff and login again. Some developers give the user a message asking them to retry later and call PHP's apache_child_terminate() to clean up the session.
    • Use Oracle Database Resource Manager (which replaces User Profiles) to limit the resources used by any one connection.
    • Use Oracle Net configuration parameters such as SQLNET.EXPIRE_TIME to free up unused database resources.
    • Refresh the Apache processes after they have managed a certain number of requests, depending on site load. An Apache graceful restart nightly will clean up connections.

    Back to Top

    Echoing SQL statements to check they are well formed

    Getting no, or incorrect, results is surprisingly often due to executing the wrong statement. During development, echo each complete SQL statement from PHP to check it is well formed and all variables are correctly expanded. Quoting errors or misunderstandings with PHP's variable syntax in strings can cause incorrect statements to be executed.

    Testing SQL statements in SQL*Plus before executing them in PHP also helps confirm correctness.

    When statements are entered in tools like SQL*Plus, a semi-colon is often used to tell the tool that the statement is complete and can now be executed. However the semi-colon is not considered part of the statement and is not sent to the database. In PHP, do not add a semi-colon to a SQL statement—otherwise an Oracle error will occur. This example is a valid query:

            $sql = "SELECT * FROM EMP_DETAILS_VIEW";

    Oracle's inbuilt scripting language PL/SQL has syntax that is different from SQL and requires a final semi-colon:

            $plsql = "BEGIN DBMS_OUTPUT.PUT_LINE('hi'); END;";

    Back to Top

    Testing return codes from database function calls

    Test all return values from OCI8 functions to prevent hidden problems and misleading results.

    During development, set php.ini's error_reporting directive to E_ALL. Also set display_errors on, or configure the error log file (and remember to check it!). This will show Oracle errors as they occur, unless you have explicitly prefixed Oracle functions calls with http://www.php.net/manual/en/language.operators.errorcontrol.php.

    Back to Top

    Inserting strings containing quotes

    Inserting strings containing single quotes can be handled in several ways:

    1. Use bind variables. This also protects against SQL Injection security issues:
              $name = "O'Reilly";
              $stmt = 'INSERT INTO CUSTOMERS (NAME) VALUES (:nm)';
              $stid = OCIParse($mycon, $stmt);
              OCIBindByName($stid, ':nm', $name, -1);
              OCIExecute($stid);

      This is the recommended method.

    2. Double every single quote:
              $name = "O'Reilly";
              $name = str_replace("'", "''", $name);
              $stmt = "INSERT INTO CUSTOMERS (NAME) VALUES ('".$name."')";
    3. Turn on magic_quotes_sybase in php.ini and use addslashes():
              $name = addslashes("O'Reilly");
              $stmt = "INSERT INTO CUSTOMERS (NAME) VALUES ('".$name."')";

      This is not recommended for portability reasons.

    Back to Top

    Fetching results using associative arrays

    Oracle column names are cases insensitive by default, but when fetching into associative arrays, specify the names in upper case:

            $query = 'select dname from dept';
    
            $stid = OCIParse($conn, $query);
            OCIExecute($stid);
            while (OCIFetchInto($stid, $row, OCI_ASSOC)) {
              echo $row['DNAME']."<br>\n";
            }

    It is possible to create a table with case-sensitive column names. Use var_dump($row) to see the column names and data structure actually returned by OCIFetchInto(). PHP has an array_change_key_case() function to change the case of array keys if needed.

    Table name prefixes are not included in associative array keys. If a column name is used more than once in a query, use column aliases to distinguish the columns. Otherwis, there will only be one associative array entry. If the aliases T1N and T2N were not used in the example below, only results from one of the NAME columns would be returned:

            $query = 'select myt1.name as t1n, myt2.name as t2n from myt1, myt2';
    
            $stid = OCIParse($conn, $query);
            OCIExecute($stid);
            while (OCIFetchInto($stid, $row, OCI_ASSOC)) {
              echo $row['T1N']." ".$row['T2N']."<br>\n";

    Back to Top

    Array fetching in PHP to improve performance

    There is no array fetch capability in PHP, but setting the prefetch row count with OCISetPrefetch() is similar and can dramatically improve performance. The advantage of setting the prefetch row count instead of the array size is that Oracle does the caching for you. Your data structures only have to handle one row at a time, and your code can often be simpler.

    Here's an example that sets the prefetch row count to 100:

            <?php
    
            $conn = OCILogon('myusername', 'mypassword', 'mydb');
    
            $query = 'SELECT * FROM EMP_DETAILS_VIEW';
    
            $stid = OCIParse($conn, $query);
            OCIExecute($stid);
            OCISetPrefetch($stid, 100);
            while ($succ = OCIFetchInto($stid, $row)) {
              foreach ($row as $item) {
                echo $item." ";
              }
              echo "<br>\n";
            }
    
            OCILogoff($conn);
    
            ?>

    Back to Top

    Using Oracle bind variables in PHP

    Bind variables are generally recommended because they help database performance.

    1. Problems with binding are often caused because the data value needs to be accessible at the time when OCIExecute() is called, not just when OCIBindByName() is executed.

      If OCIBindByName() is called inside a wrapper function or method, and the PHP variable passed to OCIBindByName() is local to the wrapper function, there may be a problem. The variable needs to be in scope when OCIExecute() is later called. Otherwise an Oracle error such as "OCIStmtExecute: ORA-01460: unimplemented or unreasonable conversion requested" may occur or, confusingly, it can appear as if no value was set for an OUT variable.

      The sample below is a variation on this. The variable $val is local to the "foreach" command. The code returns no records:

              <?php
      
              $qs = 'SELECT * FROM DEPARTMENTS WHERE DEPARTMENT_NAME = :dname AND LOCATION_ID = :loc';
      
              $dn = 'IT Support';
              $lc = '1700';
              $ba = array(':dname' => $dn, ':loc' => $lc);
      
              $conn = OCILogon('myusername', 'mypassword', 'mydb');
              $stmt = OCIParse($conn, $qs);
      
              foreach ($ba as $key => $val)
              {
                OCIBindByName($stmt, $key, $val, -1);
              }
      
              OCIExecute($stmt);
      
              while ($succ = OCIFetchInto($stmt, $o)) {
                foreach ($o as $mv) {
                  echo $mv." ";
                }
                echo "<br>\n";
              }
      
              ?>
      

      Changing the OCIBindByName() call fixes the problem:

             . . .
             foreach ($ba as $key => $val)
             {
               OCIBindByName($stmt, $key, $ba[$key], -1);
      
             }
             . . .
    2. 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.

    Back to Top

    Uploading LOBS

    Some sample code to upload a BLOB is given in the OCINewDescriptor() manual entry.

    Watch out for size limitations either in the HTML script:

            <input type="hidden" name="MAX_FILE_SIZE" value="3000">

    or in the LimitRequestBody directive in httpd.conf.

    An example that uploads into a CLOB is:

            <?php
    
            //
            // Sample form to upload and insert data into an ORACLE CLOB column
            // using PHP's Oracle 8 API.
            //
            // Based on http://www.php.net/manual/en/function.oci-new-descriptor.php
            // modified to work on CLOBs and use register_globals = Off.
            //
            // Before running this script, execute these statements in SQL*Plus:
            //   drop table myclobtab;
            //   create table myclobtab (c1 number, c2 clob);
            //
            // Tested with PHP 4.3.3 against Oracle 9.2
            //
    
            if (!isset($_FILES['lob_upload'])) {
            ?>
    
            <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="POST" enctype="multipart/form-data">
            Upload file: <input type="file" name="lob_upload">
            <input type="submit" value="Upload">
    
            </form>
    
            <?php
            }
            else {
    
              $myid = 1; // should really be a unique id e.g. a sequence number
    
              $conn = OCILogon('myusername', 'mypassword', 'mydb');
    
              // Delete any existing CLOB so the query at the bottom
              // displays the new data
    
              $query = 'DELETE FROM MYCLOBTAB';
              $stmt = OCIParse ($conn, $query);
              OCIExecute($stmt, OCI_COMMIT_ON_SUCCESS);
              OCIFreeStatement($stmt);
    
              // Insert the CLOB from PHP's temporary upload area
    
              $lob = OCINewDescriptor($conn, OCI_D_LOB);
              $stmt = OCIParse($conn, 'INSERT INTO MYCLOBTAB (C1, C2) VALUES('.
                               $myid . ', EMPTY_CLOB()) RETURNING C2 INTO :C2');
              OCIBindByName($stmt, ':C2', $lob, -1, OCI_B_CLOB);
              OCIExecute($stmt, OCI_DEFAULT);
    
              // The function $lob->savefile(...) reads from the uploaded file.
              // If the data was already in a PHP variable $myv, the
              // $lob->save($myv) function could be used instead.
              if ($lob->savefile($_FILES['lob_upload']['tmp_name'])) {
                OCICommit($conn);
                echo "CLOB successfully uploaded\n";
              }
              else {
                echo "Could not upload CLOB\n";
              }
              $lob->free();
              OCIFreeStatement($stmt);
    
              // Now query the uploaded CLOB and display it
    
              $query = 'SELECT C2 FROM MYCLOBTAB WHERE C1 = '.$myid;
    
              $stmt = OCIParse ($conn, $query);
              OCIExecute($stmt, OCI_DEFAULT);
              OCIFetchInto($stmt, $arr, OCI_ASSOC);
              $result = $arr['C2']->load();
    
              echo '<pre>';
              echo $result;
              echo '</pre>';
    
              OCIFreeStatement($stmt);
    
              OCILogoff($conn);
            }
            ?>

    Back to Top

    Upgrading to PHP 5

    1. Despite ongoing debate in the PHP community about how and if function names should be standardized, the OCI8 extension function names have been changed in PHP 5. Both old and new names can be used. Most changes were the addition of underscores; for example OCIBindByName() is now oci_bind_by_name(). A couple of function names are very different: OCILogon() is now an alias for oci_connect(), and the preferred name for OCILogoff() is now oci_close(). All names are still case insensitive but are documented in lowercase by convention.

      The OCI8 Documentation was updated in March 2004 and shows only the new names. If documentation on a particular function does not seem to exist, look at the PHP_FALIAS macros in PHP's oci8.c to see if the name you are using is an alias for another function.

      Note: This FAQ uses the old function names so examples work with PHP 4 and PHP 5.

    2. Another name that has changed in PHP 5 on Windows is the name of the directory containing the extension DLLs. If starting the Web server gives you the error "Unable to load dynamic library 'c:\PHP\extension\php_oci8.dll'" edit your php.ini file and change the value of the extension_dir directive from "extension" to "ext".

    Back to Top

    Oracle error messages in PEAR DB

    The PEAR DB interface is a database abstraction layer that uses the same syntax for different database brands. The standard PEAR DB error function $db->getMessage() returns a simple PEAR error description. For example, if connection fails for any reason, the message is always:

            DB Error: connect failed

    You can get the exact Oracle error number and message by using:

            $db->getDebugInfo()

    It includes the Oracle error and the complete statement, for example:

            [nativecode=ORA-01017: invalid username/password; logon denied ] ** oci8://myusername:wrongpassword@mydb

    The Oracle message text can be extracted from this string with a wrapper function, for example:

            require_once('DB.php');
    
            PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'PrintOCIError');
    
            // Display PEAR DB error
            function PrintOCIError($e)
            {
              if (is_object($e)) {
                $s = preg_match('/.*\[nativecode=(.*)/', $e->getDebugInfo(), $r);
                $etxt = $s ? $r[1] : $e->getDebugInfo();
              }
              else {
                $etxt = "Unknown Error";
              }
              echo "<p><b>Error</b>:</p>\n<pre>" .
                htmlspecialchars($etxt)."</pre>\n";
            }
    
            $db = DB::connect("oci8://myusername:wrongpassword@mydb");
    
            if (!DB::isError($db)) {
              .
              .
              .
            } 

    The output from PrintPEARDBError() is:

            Error:

            ORA-01017: invalid username/password; logon denied
    

    Back to Top

    OCI thread safety

    The PHP libraries were made OCI Thread-safe in a bug fixed in PHP 4.3.5. In practice, it appears that only loaded servers had encountered the problem, which exhibited random behavior including crashing. See PHP bug 26558 and PHP Bug 26393

    Back to Top

    External and operating system authentication

    Allowing externally authenticated database connections over the Web would be a potential security risk for most configurations. Luckily PHP's OCI8 extension will not allow external authentication where the username is "/" and the password an empty string. The call in PHP's oci8.c to Oracle's OCISessionBegin() always sets the credential flag to OCI_CRED_RDBMS. To support operating system authentication, the PHP source code would have to be changed to pass Oracle the OCI_CRED_EXT flag when appropriate.

    Back to Top

    Authentication with AS SYSDBA or AS SYSOPER

    It is not possible to connect AS SYSDBA or AS SYSOPER in PHP.

    The call in PHP's oci8.c to Oracle's OCISessionBegin() function always sets the mode flag to OCI_DEFAULT. To allow privileged connections, this would need to be changed to OCI_SYSDBA or OCI_SYSOPER. But this simple solution opens a potential security hole, see Re: suggestion about php ocilogon() oracle OCI FUNCTION

    Back to Top

    NCHAR and NCLOB support in PHP

    There is no NCHAR or NCLOB support in PHP.

    The current PHP implementations of oci8.c always use SQLCS_IMPLICIT for the character set form when it calls Oracle's OCI. The character set form would need to be SQLCS_NCHAR to support NCHAR and NCLOB, and other data-handling changes may be needed in PHP's code.

    Back to Top

    Copyright © 2005, Oracle. All rights reserved.
    http://www.oracle.com/