如果您的 Oracle 数据库位于本地网络中的远程系统上,并且您不想担心 tnsnames 文件,您可以尝试以下方法。
$db = "(DESCRIPTION=(ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.XX.XXX)(PORT = 1521)))(CONNECT_DATA=(SID=XXXX)))";
$c1 = ocilogon("name","password",$db);
希望这对某些人有所帮助。
(PHP 5, PHP 7, PHP 8, PECL OCI8 >= 1.1.0)
oci_connect — 连接到 Oracle 数据库
$username
,$password
,$connection_string
= null
,$encoding
= "",$session_mode
= OCI_DEFAULT
返回大多数其他 OCI8 操作所需的连接标识符。
为了提高性能,大多数应用程序应该使用 oci_pconnect() 而不是 oci_connect() 来进行持久连接。有关连接管理和连接池的常规信息,请参阅 连接处理。
使用相同参数对 oci_connect() 的第二次及后续调用将返回第一次调用返回的连接句柄。这意味着一个句柄中的事务也在其他句柄中,因为它们使用的是相同的底层数据库连接。如果两个句柄需要彼此之间进行事务隔离,请改用 oci_new_connect()。
username
Oracle 用户名。
password
username
的密码。
connection_string
包含要连接到的 Oracle 实例
。它可以是 » Easy Connect 字符串、tnsnames.ora 文件中的连接名称或本地 Oracle 实例的名称。
如果未指定或为 null
,则 PHP 使用环境变量(例如 Linux 上的 TWO_TASK
或 Windows 上的 LOCAL
和 ORACLE_SID
)来确定要连接到的 Oracle 实例
。
要使用 Easy Connect 命名方法,PHP 必须与 Oracle 10g 或更高版本的客户端库链接。Oracle 10g 的 Easy Connect 字符串格式为:[//]host_name[:port][/service_name]。从 Oracle 11g 开始,语法为:[//]host_name[:port][/service_name][:server_type][/instance_name]。Oracle 19c 引入了更多选项,包括超时和保持活动设置。请参阅 Oracle 文档。可以通过在数据库服务器机器上运行 Oracle 实用程序 lsnrctl status
来查找服务名称。
tnsnames.ora 文件可以位于 Oracle Net 搜索路径中,其中包括 /your/path/to/instantclient/network/admin、$ORACLE_HOME/network/admin 和 /etc。或者设置 TNS_ADMIN
,以便读取 $TNS_ADMIN/tnsnames.ora。确保 Web 守护程序具有对该文件的读取访问权限。
encoding
确定 Oracle 客户端库使用的字符集。字符集不需要与数据库使用的字符集匹配。如果它们不匹配,Oracle 将尽最大努力将数据转换为数据库字符集并从中转换。根据字符集,这可能不会产生可用的结果。转换还会增加一些时间开销。
如果未指定,则 Oracle 客户端库从 NLS_LANG
环境变量确定字符集。
传递此参数可以减少连接所需的时间。
session_mode
此参数自 PHP 5 版(PECL OCI8 1.1)起可用,并接受以下值:OCI_DEFAULT
、OCI_SYSOPER
和 OCI_SYSDBA
。如果指定了 OCI_SYSOPER
或 OCI_SYSDBA
,则此函数将尝试使用外部凭据建立特权连接。默认情况下,特权连接处于禁用状态。要启用它们,您需要将 oci8.privileged_connect 设置为 On
。
PHP 5.3(PECL OCI8 1.3.4)引入了 OCI_CRED_EXT
模式值。这告诉 Oracle 使用外部或操作系统身份验证,这必须在数据库中进行配置。OCI_CRED_EXT
标志只能与用户名“/”和空密码一起使用。oci8.privileged_connect 可以为 On
或 Off
。
OCI_CRED_EXT
可以与 OCI_SYSOPER
或 OCI_SYSDBA
模式组合。
出于安全原因,Windows 上不支持 OCI_CRED_EXT
。
返回连接标识符或错误时的 false
。
版本 | 描述 |
---|---|
8.0.0、PECL OCI8 3.0.0 |
connection_string 现在可以为空。 |
示例 #1 使用 Easy Connect 语法的基本 oci_connect()
<?php
// 连接到“localhost”机器上的 XE 服务(即数据库)
$conn = oci_connect('hr', 'welcome', 'localhost/XE');
if (!$conn) {
$e = oci_error();
trigger_error(htmlentities($e['message'], ENT_QUOTES), E_USER_ERROR);
}
$stid = oci_parse($conn, 'SELECT * FROM employees');
oci_execute($stid);
echo "<table border='1'>\n";
while ($row = oci_fetch_array($stid, OCI_ASSOC+OCI_RETURN_NULLS)) {
echo "<tr>\n";
foreach ($row as $item) {
echo " <td>" . ($item !== null ? htmlentities($item, ENT_QUOTES) : " ") . "</td>\n";
}
echo "</tr>\n";
}
echo "</table>\n";
?>
示例 #2 使用网络连接名称的基本 oci_connect()
<?php
// 连接到 tnsnames.ora 文件中描述的 MYDB 数据库,
// MYDB 的一个 tnsnames.ora 条目示例可能如下:
// MYDB =
// (DESCRIPTION =
// (ADDRESS = (PROTOCOL = TCP)(HOST = mymachine.oracle.com)(PORT = 1521))
// (CONNECT_DATA =
// (SERVER = DEDICATED)
// (SERVICE_NAME = XE)
// )
// )
$conn = oci_connect('hr', 'welcome', 'MYDB');
if (!$conn) {
$e = oci_error();
trigger_error(htmlentities($e['message'], ENT_QUOTES), E_USER_ERROR);
}
$stid = oci_parse($conn, 'SELECT * FROM employees');
oci_execute($stid);
echo "<table border='1'>\n";
while ($row = oci_fetch_array($stid, OCI_ASSOC+OCI_RETURN_NULLS)) {
echo "<tr>\n";
foreach ($row as $item) {
echo " <td>" . ($item !== null ? htmlentities($item, ENT_QUOTES) : " ") . "</td>\n";
}
echo "</tr>\n";
}
echo "</table>\n";
?>
示例 #3 带有显式字符集的 oci_connect()
<?php
$conn = oci_connect('hr', 'welcome', 'localhost/XE', 'AL32UTF8');
if (!$conn) {
$e = oci_error();
trigger_error(htmlentities($e['message'], ENT_QUOTES), E_USER_ERROR);
}
$stid = oci_parse($conn, 'SELECT * FROM employees');
oci_execute($stid);
echo "<table border='1'>\n";
while ($row = oci_fetch_array($stid, OCI_ASSOC+OCI_RETURN_NULLS)) {
echo "<tr>\n";
foreach ($row as $item) {
echo " <td>" . ($item !== null ? htmlentities($item, ENT_QUOTES) : " ") . "</td>\n";
}
echo "</tr>\n";
}
echo "</table>\n";
?>
示例 #4 使用多次调用 oci_connect()
<?php
$c1 = oci_connect("hr", "welcome", 'localhost/XE');
$c2 = oci_connect("hr", "welcome", 'localhost/XE');
// $c1 和 $c2 显示相同的 PHP 资源 ID,这意味着它们使用相同的底层数据库连接
// same underlying database connection
echo "c1 is $c1<br>\n";
echo "c2 is $c2<br>\n";
function create_table($conn)
{
$stmt = oci_parse($conn, "create table hallo (test varchar2(64))");
oci_execute($stmt);
echo "创建表<br>\n";
}
function drop_table($conn)
{
$stmt = oci_parse($conn, "drop table hallo");
oci_execute($stmt);
echo "删除表<br>\n";
}
function insert_data($connname, $conn)
{
$stmt = oci_parse($conn, "insert into hallo
values(to_char(sysdate,'DD-MON-YY HH24:MI:SS'))");
oci_execute($stmt, OCI_DEFAULT);
echo "$connname 插入行但不提交<br>\n";
}
function rollback($connname, $conn)
{
oci_rollback($conn);
echo "$connname 回滚<br>\n";
}
function select_data($connname, $conn)
{
$stmt = oci_parse($conn, "select * from hallo");
oci_execute($stmt, OCI_DEFAULT);
echo "$connname ----选择<br>\n";
while (oci_fetch($stmt)) {
echo " " . oci_result($stmt, "TEST") . "<br>\n";
}
echo "$connname ----完成<br>\n";
}
create_table($c1);
insert_data('c1', $c1); // 使用 c1 插入一行
sleep(2); // 休眠以显示第二行的不同时间戳
insert_data('c2', $c2); // 使用 c2 插入一行
select_data('c1', $c1); // 返回两个插入的结果
select_data('c2', $c2); // 返回两个插入的结果
rollback('c1', $c1); // 使用 c1 回滚
select_data('c1', $c1); // 两个插入都已回滚
select_data('c2', $c2);
drop_table($c1);
// 关闭其中一个连接会使 PHP 变量无法使用,但另一个可以继续使用
// the other could be used
oci_close($c1);
echo "c1 is $c1<br>\n";
echo "c2 is $c2<br>\n";
// 输出为:
// c1 is Resource id #5
// c2 is Resource id #5
// 创建表
// c1 插入行但不提交
// c2 插入行但不提交
// c1 ----选择
// 09-DEC-09 12:14:43
// 09-DEC-09 12:14:45
// c1 ----完成
// c2 ----选择
// 09-DEC-09 12:14:43
// 09-DEC-09 12:14:45
// c2 ----完成
// c1 回滚
// c1 ----选择
// c1 ----完成
// c2 ----选择
// c2 ----完成
// 删除表
// c1 is
// c2 is Resource id #5
?>
注意:
OCI8 扩展安装或配置不正确通常会导致连接问题或错误。请参阅 安装/配置 以获取故障排除信息。
如果您的 Oracle 数据库位于本地网络中的远程系统上,并且您不想担心 tnsnames 文件,您可以尝试以下方法。
$db = "(DESCRIPTION=(ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.XX.XXX)(PORT = 1521)))(CONNECT_DATA=(SID=XXXX)))";
$c1 = ocilogon("name","password",$db);
希望这对某些人有所帮助。
在 Oracle RAC“Real Application Clusters”中连接的一种替代方法
<?php
$dbstr ="(DESCRIPTION =(ADDRESS = (PROTOCOL = TCP)(HOST =ip1)(PORT = 1521))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = banco)
(INSTANCE_NAME = banco1)))";
$dbstr1 ="(DESCRIPTION =(ADDRESS = (PROTOCOL = TCP)(HOST =ip2)(PORT = 1521))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = banco)
(INSTANCE_NAME = banco2)))";
if(!@($conn = oci_connect('user','password',$dbstr1)))
{ $conn = oci_connect('user','password',$dbstr) or die (ocierror()); }
?>
如果想要在网络出现问题时指定连接超时时间,可以编辑客户端(例如 PHP 端)的 sqlnet.ora 文件并设置 SQLNET.OUTBOUND_CONNECT_TIMEOUT。这将设置从建立连接到数据库的整个过程中的最长时间限制,包括连接到其他服务的尝试时间。该参数从 Oracle 10.2.0.3 开始可用。
在 Oracle 11.1 中,引入了一个稍微轻量级的解决方案 TCP.CONNECT_TIMEOUT。它也是一个 sqlnet.ora 参数。它只限制 TCP 连接建立时间,这通常是连接问题出现的地方。
客户端 sqlnet.ora 文件应该放在与 tnsnames.ora 文件相同的目录下。
当您使用 Oracle 9.2 及以上版本时,我建议您**必须**使用 CHARSET 参数。
当然,除非遇到带重音符号的字符,否则您不会注意到它... 所以只需指定它,您就可以避免很多麻烦。
例如,这是我们 Oracle 内部配置
select * from nls_database_parameters;
PARAMETER VALUE
------------------------------ ----------------------------------------
…
NLS_LANGUAGE AMERICAN
NLS_TERRITORY AMERICA
NLS_ISO_CURRENCY AMERICA
NLS_CHARACTERSET WE8ISO8859P15
…
以及我们的 oci_connect 调用
$dbch=ocilogon($user,$pass,$connectString,"WE8ISO8859P15");
如果没有它,您将得到问号(反向)、方块... 而不是大多数带重音符号的字符。
不要忘记在写入和读取时都使用它。
使用 LDAP 进行 Oracle 名称解析
Web 服务器将需要环境变量 TNS_ADMIN='tnsname.ora 文件所在的目录',除非使用默认位置。我使用 '/etc/tns_admin'。可以使用 phpinfo() 函数确认。
在 TNS_ADMIN 位置需要三个文件:tnsnames.ora、sqlnet.ora 和 ldap.ora。如果您只使用 LDAP,则不需要 tnsnames.ora。
在 sqlnet.ora 中添加
NAMES.DIRECTORY_PATH=(TNSNAMES,LDAP)
在 ldap.ora 中添加
DIRECTORY_SERVERS=(ldap_server_fqdn:port)
DEFAULT_ADMIN_CONTEXT=""
DIRECTORY_SERVER_TYPE=OID
对于一个快速简便的 LDAP tnsnames 服务器,可以使用 Dave Berry 的 tnsManager。可以使用 Oracle OID 或 OpenLDAP,但设置起来比较复杂。tnsManager 非常简单易用。快速部分:提供一个 tnsnames.ora 文件并启动它。麻烦的部分:我无法让 Toad 和 SQLDeveloper 与它一起工作,它忽略了域名,并且不再维护了。
sqlnet.ora 中 NAMES.DIRECTORY_PATH 中值的顺序决定了使用哪个查找“适配器”,在本例中是 tnsnames.ora 文件,然后是 LDAP。我使用 LDAP 进行一般使用,并使用 tnsnames.ora 文件覆盖 LDAP 或不供一般使用的条目。
如果您有完整的 Oracle 客户端,则可以使用 tnsping。'tnsping ORACLE_SID' 将告诉您正在使用哪个适配器:“使用 LDAP 适配器解析别名”。
<?php
echo system("/PATH/tnsping ".$ORACLE_SID." 2>&1")."<br />";
echo 'TNS_ADMIN='.getenv('TNS_ADMIN');
?>
问题
如果只使用 ORACLE_SID 而未使用 ORACLE_SID.DB_DOMAIN 连接,则会追加 sqlnet.ora 中 NAMES.DEFAULT_DOMAIN 的值,然后由于某种原因,PHP 会尝试 HOSTNAME 适配器,如果数据库名称在 DNS 中解析成功,则它将无法使用数据库名称作为主机名连接,因为 SID 和 SERVICE_NAME 均未定义。
如果使用 tnsManager,请将 '.ANY_DOMAIN' 附加到 $ORACLE_SID 以解决上述问题。
我已使用以下版本测试过
11.1.0.7 完整客户端和 PHP 5.1.6
11.2.0.2 完整客户端和 PHP 5.4.11
我听说 LDAP 查找不适用于较旧的 instantclient。
在 PHP 中使用 OCI_CRED_EXT 时
如果设置了 ENV $ORACLE_SID,则无需显式指定数据库,并且连接将失败,除非在创建连接时提供 NULL 数据库值。
$ORACLE_SID 优先于 TNS 名称查找连接。因此,即使在数据库参数中使用手动连接字符串也会失败。
因此,当设置了 $ORACLE_SID Env 时,传递 NULL 而不是数据库名称可以成功连接。
希望这可以节省一些在迁移到 %.3 和操作系统身份验证时的麻烦。
关于文档中的以下语句
“使用相同参数对 oci_connect() 的第二次及后续调用将返回第一次调用返回的连接句柄。”
这里有一个需要注意的地方。只有在持有对原始句柄的引用时,对 oci_connect() 的后续调用才会返回与第一次调用相同的连接句柄。
例如,以下代码将生成*一个*连接句柄
<?php
$dbh = oci_connect($username, $password, $conn_info);
// 执行某些操作
$dbh = oci_connect$username, $password, $conn_info);
// 执行更多操作
以下代码将生成 *两个* 连接句柄:
getData();
// 执行某些操作
getData();
// 执行更多操作
getData() {
$dbh = oci_connect($username, $password, $conn_info);
// 执行某些操作
}
?>
这是 PHP 在方法作用域结束时垃圾回收句柄的结果。
如果您想通过函数调用隔离您的数据库层,但仍想利用 oci_connect 可以返回相同句柄的事实,只需保留对句柄的引用,如下所示
<?php
getData($username, $password, $conn_info) {
$dbh = oci_connect($username, $password, $conn_info);
$key = hash('md5', "$username|$password|$conn_info");
$GLOBALS[$key] = $dbh;
// 执行某些操作
}
?>
我最初将其记录为错误,但显然这是预期的行为,可能是因为 oci_close($dbh) 只调用 unset($dbh)。
在 David Sklar 和 Adam Trachtenberg 编写的 PHP Cookbook(O'Reilly)中,有一个关于保护连接信息的有用解决方案。他们建议在 Apache 配置中使用“SetEnv”,然后使用 $_SERVER 从脚本中访问这些值。
不幸的是,使用“SetEnv”解决方案会将您的连接信息暴露给该虚拟主机的所有用户。如果他们运行 phpinfo.php 或显示 $_SERVER,我发现他们将看到该虚拟主机根目录下任何文件的密码。
要将暴露限制在特定目录或特定文件
1. 首先在 httpd.conf 中将“Include”指向秘密文件。例如
Include "/web/private/secret.txt"
2. 在密码文件中,使用“SetEnvIf”指令仅通过目录或在特定文件中启用环境变量。例如
- 对于目录中的所有文件
SetEnvIf Request_URI "/path/to/my/directory" ORACLE_PASS=5gHj790j
- 对于目录中的特定文件
SetEnvIf Request_URI "/path/to/my/directory/connection.oracle.php" ORACLE_PASS=5gHj790j
使用 LDAP 进行 Oracle 名称解析
Web 服务器需要环境变量 TNS_ADMIN='tnsname.ora 文件所在的目录'。我使用 '/etc/tns_admin'。可以使用 phpinfo() 函数确认。
在 TNS_ADMIN 位置需要三个文件:tnsnames.ora、sqlnet.ora 和 ldap.ora。如果您只使用 LDAP,则不需要 tnsnames.ora。
在 sqlnet.ora 中添加
NAMES.DIRECTORY_PATH=(TNSNAMES,LDAP)
在 ldap.ora 中添加
DIRECTORY_SERVERS=(ldap_fqdn_hostname:1575)
DEFAULT_ADMIN_CONTEXT=""
DIRECTORY_SERVER_TYPE=OID
对于一个快速简便的 LDAP tnsnames 服务器,可以使用 Dave Berry 的 tnsManager。可以使用 Oracle OID 或 OpenLDAP,但设置起来比较复杂。tnsManager 非常简单易用。默认端口为 1575。
sqlnet.ora 中 NAMES.DIRECTORY_PATH 中值的顺序决定了首先使用哪个查找“适配器”,在本例中是 tnsnames.ora 文件,然后是 LDAP。我使用 LDAP 进行一般使用,并使用 tnsnames.ora 文件覆盖 LDAP 或不供一般使用的条目。
如果您有完整的 Oracle 客户端,则可以使用 tnsping。'tnsping ORACLE_SID' 将告诉您正在使用哪个适配器:“使用 LDAP 适配器解析别名”。
<?php
echo system("/PATH/tnsping ".$ORACLE_SID." 2>&1")."<br />";
echo 'TNS_ADMIN='.getenv('TNS_ADMIN');
?>
问题
由于某种原因,PHP 首先尝试使用 HOSTNAME 适配器,如果数据库名称在 DNS 中解析成功,它将尝试使用数据库名称作为主机名进行连接,而没有定义 SID 或 SERVICE_NAME。我使用过的所有其他 Oracle 客户端,除非在 NAMES.DIRECTORY_PATH 中列出,否则不会尝试使用 HOSTNAME 适配器。
我听说 LDAP 查找不适用于旧版本的 instantclients。