2014-03-24 79 views
22

我在与PDO_ODBC和PDO :: FETCH_OBJ(和PDO :: FETCH_CLASS)一个奇怪的问题导致出现以下错误信息:PDO返回空属性名

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,这不起作用。

我想补充一点,其他的提取方法似乎做了正确的事情,例如,PDO :: FETCH_NAMED:

array(1) { 
    ["RoleName"]=> 
    string(30) "FNAR       " 
} 

我要找的东西,我可以把在PDO胸径或某物的定义,或者在数据源或驱动程序的odbc.ini或odbcinst.ini中解决此问题。先谢谢你。

更新: odbc_fetch_object()(即不是PDO)的工作原理与所有相同。只是想提到它。显然,PHP,unixODBC或ODBC驱动程序似乎没有任何严重问题。这是PDO代码中的东西。时间打开一个bug报告... 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跟踪(“working”cf.“not working”)的并行比较显示没有区别(除了不同的指针地址)。 PDO :: FETCH_BOUND适用于编号列和命名列。 PDO :: FETCH_INTO具有RoleName属性的对象不。

+1

尝试先升级PHP。 –

+1

我试过5.5.9。不用找了。 – mwp

+0

有两件事我可以建议:你的'SELECT rolename'不区分大小写吗? PDO可能会对此有点有趣。另外,如果您加入到具有相同列的表中,除非您在SQL中选择dup作为dup1,否则列可能不会再回来。还有一个最后的建议尝试反驳'''列名.. –

回答

-1

我认为一个“解决方案”,由现在沃尔德是使用fetch风格PDO :: FETCH_NAMED然后转换返回数组并填充动态类:

function arrayToObject(array $array){ 
    $obj = new stdClass(); 
    foreach($array as $k => $v) 
     $obj->$k = $v; 

    return $obj; 
} 
+0

从我原来的问题:“但是,这并不适用于幕后的直接使用PDO :: FETCH_CLASS的某些框架和ORM。” – mwp

2

你的问题介绍了两个问题:

  1. 为什么在使用PDO::FETCH_OBJ时无法使用具有空字符串名称的属性创建对象,但显然在使用其他方法时可以创建对象?

    如PHP内部书Internal structures and implmentation下记载,“动态properties”(即未在其类定义中声明,而是在运行时创建的对象的成员变量)实现为一个哈希表。

    如果希望填充当前在哈希表举行了一堆属性的新实例化的标准对象,可以简单地在现有的哈希表指向对象的properties变量— PHP的object_and_properties_init()功能,这是called byodbc_fetch_object()does exactly that未对表格的密钥进行任何健康检查。因此,可以实例化具有奇怪属性名称的对象(如空字符串)。另一方面,如果已经有一个实例化对象并需要设置一个属性值(同时保留其他已存在的属性值),则必须将该值复制到对象的哈希表— PHP的zend_std_write_property()方法中,该方法处理标准对象的此动作,does exactly that首先对属性名称执行了完整性检查。因此,不能将具有奇怪名称的属性(如空字符串)添加到现有对象。

    在我看来,这两种方法之间的理智检查差异似乎是一个错误:无论创建这些属性的方法如何,动态属性名称的任何限制都应执行。是否应该允许这种奇怪的名字(并且因此应该从后一种方法中去除理智检查),或者不允许(因此一些理智检查应该被添加到前一种方法中),这是我将留给PHP开发人员。

    PDO如何适应这一切?

    PDOStatement::fetch()首先准备结果将被存储到其中的目的地,然后iterates over the columns存储依次在每个字段:我想它这样做为了简化代码库,因为每个取样式可以在同一结构内实现。但是,这确实意味着使用PDO::FETCH_OBJ样式(以及PDO::FETCH_CLASSPDO::FETCH_INTO,如您所观察到的),an object is instantiated firstits properties are populated later进行调用。因此,奇怪的属性名称(如空字符串)会导致观察失败。

    另取你已经试过不会遇到同样的问题,因为风格:

    • PDO::FETCH_BOUND取到由以前的通话中被指定为PDOStatement::bindColumn()的变量,那么PHP从来没有试图写一个财产名称空;

    • PDO::FETCH_LAZYskips the whole shebang并且以类似于上述odbc_fetch_object()的方式进行操作。

    同样,基于数组的提取样式也不会遇到类似的问题,因为空字符串键在这些哈希表中是完全有效的。

  2. 为什么PDO认为此ODBC记录集中的列名是空字符串?

    答案对我来说不太明显。

    我们前面看到,为了填充属性,PDO使用stmt->columns[i].name作为属性名称。当pdo_stmt_describe_columns()was called时,应该已经正确填写了an earlier point。这个函数依次为had called司机的describer方法为结果集中的每一列:在PDO_ODBC的情况下,即odbc_stmt_describe()确实是assign a value to that field

    所以,在PHP方面一切都很好。知道the call to the driver's SQLDescribeCol() function是否将列名正确地填充到作为其第三个参数提供的缓冲区中是很有趣的:我们不会想象,这表明问题在于ODBC驱动程序本身。您提到您使用Teradata:但是您确定您对PDO_ODBC(不工作)和ext/odbc(这是)使用相同的驱动程序吗?

    特别是,Teradata的文件下Extension Level Functions

    默认情况下,并SQLDescribeCol将返回的SQLColAttribute Teradata数据列标题的列名来代替。如果应用程序希望Teradata的ODBC驱动程序返回列标题而不是实际列名称,则选项Teradata ODBC驱动程序选项中使用列名称不得为所用的DSN选择对话框,或者设置DontUseTitles =在UNIX操作系统上没有。

    返回列标题而不是实际列名可能会导致某些应用程序(如Crystal Reports)出现问题,因为他们希望获取列名称而不是列标题。

+0

感谢您提供冗长,翔实的答案。是的,我确定我在两个测试中都使用了相同的驱动程序。如果stmt-> columns [i] .name为空,那么PDO :: FETCH_NAMED如何工作? – mwp

+1

@mwp:呃,这是一个好点(我完全忽略了)。我能看到的最明显的区别是'PDO :: FETCH_NAMED'使用'stmt-> columns [i] .name' [单独](https://github.com/php/php-src/blob/master/ ext/pdo/pdo_stmt.c#L1097),而'PDO :: FETCH_OBJ'也[使用](https://github.com/php/php-src/blob/master/ext/pdo/pdo_stmt.c#L1109 )'stmt-> columns [i] .namelen'。 'namelen'的值来自[call](https://github.com/php/php-src/blob/master/ext/pdo_odbc/odbc_stmt.c#L557)到Teradata的'SQLDescribeCol()'函数。 ..也许它错误地返回0? – eggyal

+0

不错!我会研究它。 (或者更恰当地说,请求我的前同事研究它,因为我不再工作了。)再次感谢您的帮助。 – mwp