我对pdo_odbc和PDO :: FETCH_OBJ(以及PDO :: FETCH_CLASS)有一个奇怪的问题,导致以下错误消息:
PHP Fatal error: Cannot access empty property
以下是代码:
$dbh = new PDO("odbc:FOO");
$sth = $dbh->query("
SELECT rolename
FROM dbc.allrolerights
WHERE databasename = 'BAR'
");
$result = $sth->fetch(PDO::FETCH_OBJ);
作为参考,FOO DSN是使用tdodbc软件包提供的tdata.so驱动程序的Teradata数据源。
我相信这种情况正在发生,因为当PDO调用zend_API.h:object_init_ex()来实例化stdClass对象时,字段名称(从ODBC查询返回)是空白的。如果我切换到PDO :: FETCH_LAZY和var_dump()行,我得到以下内容:
object(PDORow)#3 (2) {
["queryString"]=>
string(95) "
SELECT rolename
FROM dbc.allrolerights
WHERE databasename = 'BAR'
"
[""]=>
string(30) "FNAR "
}
这似乎支持它。我已经尝试了几种不同的PDO属性组合和一堆不同的角度来解决这个问题。一种解决方案是获取关联数组并将其传递给类构造函数。但是,这对于在幕后直接使用PDO :: FETCH_CLASS的某些框架和ORM不起作用。
我想补充说其他fetch方法似乎做对了,例如,PDO :: FETCH_NAMED:
array(1) {
["RoleName"]=>
string(30) "FNAR "
}
我正在寻找可以放入PDO dbh或sth定义的东西,或者在odbc.ini或odbcinst.ini中寻找数据源或驱动程序来解决此问题。先感谢您。
更新: odbc_fetch_object()(即不是PDO)与所有内容完全相同。只是想提一下。显然,PHP,unixODBC或ODBC驱动程序似乎没有任何严重问题。这是PDO代码中的一些东西。是时候打开错误报告了...... opened
$dbh = odbc_connect("FOO", NULL, NULL)
or die(odbc_error_msg());
$sth = odbc_exec($dbh, "
SELECT rolename
FROM dbc.allrolerights
WHERE databasename = 'BAR'
");
$result = odbc_fetch_object($sth);
var_dump($result);
输出:
object(stdClass)#1 (1) {
["RoleName"]=>
string(30) "FNAR "
}
更新2:情况继续变得越来越奇怪。我可以做一个PDO :: FETCH_LAZY并看到上面的var_dump()中看到的空白列名称,但如果我尝试按名称访问该属性(例如$ result-> RoleName),它就可以了!这些获取方法的作用有何不同,以至于其中一些方法有时可以访问字段名称,而其他方法则不能?
ODBC跟踪的并排比较("工作" cf。"不工作")显示没有差异(除了不同的指针地址)。 PDO :: FETCH_BOUND适用于编号和命名列。 PDO :: FETCH_INTO具有RoleName属性的对象不具有。
答案 0 :(得分:3)
您的问题描述了两个问题:
为什么在使用
PDO::FETCH_OBJ
时无法使用具有空字符串名称的属性创建对象,但显然可以在使用其他方法时使用?
正如PHP Internals Book中的Internal structures and implmentation所述," 动态 properties" (即,未在其类定义中声明但在运行时创建的对象的成员变量)实现为哈希表。
如果希望用一堆当前保存在哈希表中的属性填充新实例化的标准对象,可以简单地将对象的properties
变量指向该现有哈希表-PHP& #39; s object_and_properties_init()
功能,called by odbc_fetch_object()
,does exactly that ,不对表格的密钥进行任何健全性检查。因此,可以使用奇怪的属性名称(例如空字符串)实例化一个对象。
另一方面,如果一个人已经有一个实例化的对象并且需要设置一个属性值(同时保留其他已经存在的属性值),那么必须将该值复制到对象的哈希表-PHP&#39中; s zend_std_write_property()
方法,为标准对象处理此操作,does exactly that 首先对属性名称执行完整性检查。因此,无法将具有奇怪名称的属性(例如空字符串)添加到现有对象。
在我看来,这两种方法之间的完整性检查的差异似乎是一个错误:应该强制执行对动态属性名称的任何限制,而不管创建这些属性的方法如何。是否应该允许这种奇怪的名字(因此应该从后一种方法中删除健全性),或者不允许(因此应该在前一种方法中添加一些健全性检查),这是我将留给的一个决定。 PHP开发人员。
PDO如何适应这一切?
PDOStatement::fetch()
首先准备将存储结果的目标,然后iterates over the columns依次存储每个字段:我想这样做是为了简化代码库,因为每个获取样式都可以实现在同一结构内。但是,这确实意味着使用PDO::FETCH_OBJ
样式(以及PDO::FETCH_CLASS
和PDO::FETCH_INTO
同时调用),an object is instantiated first和its properties are populated later。因此,奇怪的属性名称(例如空字符串)会导致观察到的失败。
您尝试过的其他提取方式不会遇到同样的问题,因为:
PDO::FETCH_BOUND
获取前一次调用PDOStatement::bindColumn()
指定的变量,因此PHP从不尝试写入具有空名称的属性;
PDO::FETCH_LAZY
skips the whole shebang并采用与上述odbc_fetch_object()
类似的方式。
同样,基于数组的提取样式也不会遇到类似的问题,因为空字符串键在这些哈希表中完全有效。
为什么PDO认为此ODBC记录集中的列名是空字符串?
对我来说,这个问题的答案就不那么明显了。
我们之前看到,为了填充属性,PDO使用stmt->columns[i].name
作为属性名称。这应该在pdo_stmt_describe_columns()
an earlier point时was called正确填写。这个函数反过来had called结果集中每列的驱动程序describer
方法:在PDO_ODBC的情况下,确实odbc_stmt_describe()
{{1}}确实assign a value to that field }}
所以,PHP方面的一切看起来都很好。知道the call to the driver's SQLDescribeCol()
function是否正确地将列名填充到作为其第三个参数提供的缓冲区中会很有趣:一个想象没有,这表明问题出在ODBC驱动程序本身。你提到你正在使用Teradata:但是你确定你对PDO_ODBC(它不工作)和ext / odbc(这是)使用相同的驱动程序吗?
特别是Extension Level Functions下的Teradata文档:
默认情况下,SQLDescribeCol和SQLColAttribute返回列名而不是Teradata列标题。如果应用程序希望用于Teradata的ODBC驱动程序返回列标题而不是实际列名称,则 Teradata ODBC驱动程序选项对话框中的选项使用列名不得为所使用的DSN选择,或在UNIX OS上设置DontUseTitles = No。
返回列标题而不是实际的列名可能会导致某些应用程序(例如Crystal Reports)出现问题,因为它们希望获取列名而不是列标题。
答案 1 :(得分:-1)
我认为现在的解决方案是使用获取样式PDO :: FETCH_NAMED,然后转换返回的数组并填充动态类:
function arrayToObject(array $array){
$obj = new stdClass();
foreach($array as $k => $v)
$obj->$k = $v;
return $obj;
}